From bb99c30edcd49273cb03ab8aab8993aba0cc977a Mon Sep 17 00:00:00 2001 From: Clint Rutkas Date: Thu, 30 Jul 2026 22:42:05 -0700 Subject: [PATCH] New module: AltWindowCycle (#48281) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary of the Pull Request Introduces a new utility: AltWindowCycle to quickly switch between windows from the same process using Alt + `. In release notes give @wzhudev coauthor credits as he also had an earlier PR It works like Alt + Tab, but scoped to the app you’re already in. Perfect for juggling multiple browser windows, terminals, or editor instances. https://github.com/user-attachments/assets/cd42f6af-fa5d-4f08-8f68-3c4e75c16d94 image ## PR Checklist - [x] Closes: https://github.com/microsoft/PowerToys/issues/278 - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [ ] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx ## Detailed Description of the Pull Request / Additional comments This PR adds AltWindowCycle (in-proc module + Settings integration), then addresses follow-up check-spelling feedback without changing runtime behavior: - allow-list update for `ROOTOWNER` - comment text adjustment for forbidden-pattern compliance - local identifier rename (`wpx` → `whitePx`) for spelling compliance ## Validation Steps Performed - Verified `ROOTOWNER` is present in `.github/actions/spell-check/allow/code.txt` - Verified `wpx` is removed and updated occurrences in `src/modules/AltWindowCycle/AltWindowCycle.cpp` - Ran targeted diff/verification for both updated files - Ran final validation (code review + CodeQL trivial-change path) - Ran secret scan for changed files --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Niels Laute Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Clint Rutkas Copilot-Session: dd5080ea-5001-4efb-87f8-1e7218e10a4e --- .github/actions/spell-check/allow/code.txt | 1 + .github/actions/spell-check/expect.txt | 9 +- .pipelines/ESRPSigning_core.json | 1 + PowerToys.slnx | 4 + doc/images/icons/WindowHopper.png | Bin 0 -> 25178 bytes doc/images/overview/Original/WindowHopper.png | Bin 0 -> 63005 bytes doc/images/overview/WindowHopper_large.png | Bin 0 -> 23130 bytes doc/images/overview/WindowHopper_small.png | Bin 0 -> 8392 bytes src/common/GPOWrapper/GPOWrapper.cpp | 4 + src/common/GPOWrapper/GPOWrapper.h | 1 + src/common/GPOWrapper/GPOWrapper.idl | 1 + src/common/ManagedCommon/ModuleType.cs | 1 + src/common/utils/gpo.h | 6 + src/gpo/assets/PowerToys.admx | 10 + src/gpo/assets/en-US/PowerToys.adml | 1 + src/modules/AltWindowCycle/AltWindowCycle.cpp | 1694 +++++++++++++++++ src/modules/AltWindowCycle/AltWindowCycle.h | 21 + src/modules/AltWindowCycle/AltWindowCycle.rc | 40 + .../AltWindowCycle/AltWindowCycle.vcxproj | 130 ++ .../AltWindowCycle.vcxproj.filters | 56 + .../AltWindowCycle/AltWindowCycleLogic.h | 295 +++ .../UnitTests/AltWindowCycleLogicTests.cpp | 424 +++++ .../UnitTests/AltWindowCycleUnitTests.vcxproj | 49 + .../AltWindowCycleUnitTests.vcxproj.filters | 23 + src/modules/AltWindowCycle/dllmain.cpp | 305 +++ src/modules/AltWindowCycle/packages.config | 4 + src/modules/AltWindowCycle/pch.cpp | 1 + src/modules/AltWindowCycle/pch.h | 15 + src/modules/AltWindowCycle/resource.h | 13 + src/modules/AltWindowCycle/trace.cpp | 33 + src/modules/AltWindowCycle/trace.h | 13 + src/runner/main.cpp | 1 + .../QuickAccess.UI/Helpers/ModuleGpoHelper.cs | 1 + .../AltWindowCycleProperties.cs | 47 + .../AltWindowCycleSettings.cs | 57 + .../Settings.UI.Library/EnabledModules.cs | 16 + .../Helpers/ModuleHelper.cs | 4 + .../SettingsSerializationContext.cs | 1 + .../AltWindowCycleSettingsTests.cs | 116 ++ .../ViewModelTests/AltWindowCycle.cs | 141 ++ .../ViewModelTests/OobeShellViewModelTests.cs | 29 + .../Assets/Settings/Icons/WindowHopper.png | Bin 0 -> 1398 bytes .../Settings/Modules/OOBE/WindowHopper.png | Bin 0 -> 67554 bytes .../Assets/Settings/Modules/WindowHopper.png | Bin 0 -> 23130 bytes .../Settings.UI/Helpers/ModuleGpoHelper.cs | 2 + .../OOBE/Enums/PowerToysModules.cs | 1 + .../OOBE/ViewModel/OobeShellViewModel.cs | 1 + .../SourceGenerationContextContext.cs | 1 + .../Settings.UI/SettingsXAML/App.xaml.cs | 1 + .../OOBE/Views/OobeAltWindowCycle.xaml | 34 + .../OOBE/Views/OobeAltWindowCycle.xaml.cs | 49 + .../Settings.UI/SettingsXAML/OobeWindow.xaml | 5 + .../SettingsXAML/OobeWindow.xaml.cs | 1 + .../Views/AltWindowCyclePage.xaml | 39 + .../Views/AltWindowCyclePage.xaml.cs | 31 + .../SettingsXAML/Views/ShellPage.xaml | 6 + .../Settings.UI/Strings/en-us/Resources.resw | 48 + .../ViewModels/AltWindowCycleViewModel.cs | 174 ++ .../ViewModels/DashboardViewModel.cs | 13 + .../verify-installation-script.ps1 | 1 + 60 files changed, 3974 insertions(+), 1 deletion(-) create mode 100644 doc/images/icons/WindowHopper.png create mode 100644 doc/images/overview/Original/WindowHopper.png create mode 100644 doc/images/overview/WindowHopper_large.png create mode 100644 doc/images/overview/WindowHopper_small.png create mode 100644 src/modules/AltWindowCycle/AltWindowCycle.cpp create mode 100644 src/modules/AltWindowCycle/AltWindowCycle.h create mode 100644 src/modules/AltWindowCycle/AltWindowCycle.rc create mode 100644 src/modules/AltWindowCycle/AltWindowCycle.vcxproj create mode 100644 src/modules/AltWindowCycle/AltWindowCycle.vcxproj.filters create mode 100644 src/modules/AltWindowCycle/AltWindowCycleLogic.h create mode 100644 src/modules/AltWindowCycle/UnitTests/AltWindowCycleLogicTests.cpp create mode 100644 src/modules/AltWindowCycle/UnitTests/AltWindowCycleUnitTests.vcxproj create mode 100644 src/modules/AltWindowCycle/UnitTests/AltWindowCycleUnitTests.vcxproj.filters create mode 100644 src/modules/AltWindowCycle/dllmain.cpp create mode 100644 src/modules/AltWindowCycle/packages.config create mode 100644 src/modules/AltWindowCycle/pch.cpp create mode 100644 src/modules/AltWindowCycle/pch.h create mode 100644 src/modules/AltWindowCycle/resource.h create mode 100644 src/modules/AltWindowCycle/trace.cpp create mode 100644 src/modules/AltWindowCycle/trace.h create mode 100644 src/settings-ui/Settings.UI.Library/AltWindowCycleProperties.cs create mode 100644 src/settings-ui/Settings.UI.Library/AltWindowCycleSettings.cs create mode 100644 src/settings-ui/Settings.UI.UnitTests/ModelsTests/AltWindowCycleSettingsTests.cs create mode 100644 src/settings-ui/Settings.UI.UnitTests/ViewModelTests/AltWindowCycle.cs create mode 100644 src/settings-ui/Settings.UI.UnitTests/ViewModelTests/OobeShellViewModelTests.cs create mode 100644 src/settings-ui/Settings.UI/Assets/Settings/Icons/WindowHopper.png create mode 100644 src/settings-ui/Settings.UI/Assets/Settings/Modules/OOBE/WindowHopper.png create mode 100644 src/settings-ui/Settings.UI/Assets/Settings/Modules/WindowHopper.png create mode 100644 src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAltWindowCycle.xaml create mode 100644 src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAltWindowCycle.xaml.cs create mode 100644 src/settings-ui/Settings.UI/SettingsXAML/Views/AltWindowCyclePage.xaml create mode 100644 src/settings-ui/Settings.UI/SettingsXAML/Views/AltWindowCyclePage.xaml.cs create mode 100644 src/settings-ui/Settings.UI/ViewModels/AltWindowCycleViewModel.cs diff --git a/.github/actions/spell-check/allow/code.txt b/.github/actions/spell-check/allow/code.txt index 5b80ff7d36..7e895c4df2 100644 --- a/.github/actions/spell-check/allow/code.txt +++ b/.github/actions/spell-check/allow/code.txt @@ -330,6 +330,7 @@ xes PACKAGEVERSIONNUMBER APPXMANIFESTVERSION PROGMAN +ROOTOWNER # MRU lists CACHEWRITE diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index b79f362d73..68c1e2dcf1 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -10,6 +10,7 @@ ACCESSDENIED ACCESSTOKEN acfs ACIE +ACRYLICBLURBEHIND acrt ACTIVATEAPP ACTIVATEOPTIONS @@ -93,6 +94,7 @@ ASSOCSTR ASYNCWINDOWPLACEMENT ASYNCWINDOWPOS atl +AWC ATRIOX aumid AUO @@ -465,6 +467,7 @@ dwm dwmapi DWMCOLORIZATIONCOLORCHANGED DWMCOMPOSITIONCHANGED +DWMSBT DWMNCRENDERINGCHANGED Dwmp DWMSENDICONICLIVEPREVIEWBITMAP @@ -1391,6 +1394,7 @@ pnid PNMLINK Poc Podcasts +PARGB POINTERID POINTERUPDATE Pokedex @@ -1857,6 +1861,7 @@ SYSLIB sysmenu systemai SYSTEMAPPS +SYSTEMBACKDROP SYSTEMMODAL systemroot SYSTEMTIME @@ -1931,6 +1936,7 @@ tracerpt trackbar trafficmanager transicc +TRANSIENTWINDOW transitioning TRAYMOUSEMESSAGE triaging @@ -2075,7 +2081,7 @@ WANTNUKEWARNING WANTPALM WASDK wbem -Wca +wca WCE wcex WCRAPI @@ -2103,6 +2109,7 @@ winappcli winappsdk windir WINDOWCREATED +WINDOWCOMPOSITIONATTRIBDATA WINDOWDESTROYED windowedge WINDOWINFO diff --git a/.pipelines/ESRPSigning_core.json b/.pipelines/ESRPSigning_core.json index 8bca6ee4ae..b045420879 100644 --- a/.pipelines/ESRPSigning_core.json +++ b/.pipelines/ESRPSigning_core.json @@ -33,6 +33,7 @@ "PowerToys.AlwaysOnTop.exe", "PowerToys.AlwaysOnTopModuleInterface.dll", + "PowerToys.AltWindowCycle.dll", "PowerToys.CmdNotFoundModuleInterface.dll", diff --git a/PowerToys.slnx b/PowerToys.slnx index 164a5d28e5..c553485a74 100644 --- a/PowerToys.slnx +++ b/PowerToys.slnx @@ -158,6 +158,10 @@ + + + + diff --git a/doc/images/icons/WindowHopper.png b/doc/images/icons/WindowHopper.png new file mode 100644 index 0000000000000000000000000000000000000000..2981f506181e06cebf32da28250d40d3bec0c4d8 GIT binary patch literal 25178 zcmV)TK(W7xP)(d9{_wpvtSt6f$1-jmTg zbH(HPBJr1xck?iK7IQ3hYufqbzb}Y`Lo^cci%r>s-M^Q zPkiB+WwOnydfXrPKWTZPZhqa%`Qw+}yKbD{zyGl4d7OTH$0cnq(Gn*-D%jrgeYHh& zt+Jo%PxbI9^No-jUX)y#g4>*tlaL}l}D7B~7%^z!+g3JX?~9`36;8WbG7 zMITK=H$CN@<>x3I^4UDAALGDeWY^h;`?i1o?xW5he*E~u-}%`16L=#A@V)PS?>8Q9^A8^G4iEH48o)SSL}>iO zJAUf+YY@pOPHHX6VY{kXlqrzS?$`OVL9bSvV0_ksD!xOMZ8;h?3{w1dH7DAw`8m<# zLk@?Dn<_T{zLHfobo5b9#}6aee>{u;pFTZ;D}Vmi|Cw)n>kr?G0sQTU@_(BRV1_mS zs2!mrWgx?2T%aL2vQ)0Nh(`B}b>a&J3dcD?bl^JVVfenH{am4ydc_J9gw+i`B6FMa;}jE|o_e)sv)$A9?=ul)FvKlzjIJkb9;yW+oq z{x6~M&w~CxH;)lIU>u>K5Vy_uI;;s?H(XF-6qnBliuI3igkdbNFu0Upg|4|HRUYj$ zPP!_!z7!U|FgFq)kPt}bRrSrfnu1AU%eFl~{%v1Byu#nT;guf`A^i3(h%X?%{gmhP z@QdF+7v*mkUVq~E2D{xB0DBJDc}|e$j&_EY^|@4(_0Qzmpr>MXw^AfO_gd$i6z+)1 z%?IMw-_phx(4NDY2#fM}k?-N{tfnUGcqs8{;wy3p5}fHJhxlmg-+A>0@Hl?`C86SS zxWDAwUp%Jr|153P1h!EQBb+}@5KOOs_03hMvfSWy47GWAg*fR|(EH=kom4R78Cz&K ziy)7Xqba88cJRzfNzJz`7L81R!Nl*CqDau{B&^V50{_=tt`b7l)Wjy1G{@bVO$2z9y+QcgD)6?Nlye})k?5e-G9dP%Gu zVa0{=R&nxT+14ZLV|n&ckR$_8f9xIIeF(Z-B6&xR>sF=?%G>gm*3}oun{Qvg1rKn7 zgkcarm$2ohJT$xEQw{QT54uzA;qH-sJpTAZ=Ze$gSAKAaLlarglpxwzoD>+UsGjjb zw?ziVhG5pH-Q=IAr%rNkaGw*(X^y~#;p9nNT1WvojcZvIWrp2krU@JapWdjuLae$5PJZOAPl6CMW6wvG;O%A zD#JZ6;=39xqkSPeFZ9_Q<3MqypPYS@q3)Ht&i3Af0pO=sXi}=5?;+}3p}`uUL}9;j z2eWGu&eJ=1QaK{@s{Jh8`m+`Y_g7DMSOQkqry3kifaMCxP zyC;f~&f4s&wl!Gx>*Z_AJI>#7;s>-M4TRp<1WDJpe0y>-dfd- z_DpzMUpdU97NvX|uTYW#v^1WYk)v6U7VJ$Jz>hh8RwchWE{%!f((63;m3zizIhicS zALqGaHA3xrY&FOr*LxzI3TI$+oO^643nSjbdNnu0wYB0*D|qP{OoVeG@&yRhHIS-VPo>R@Ef>rq7dW};#PM#op&4$+rZ*MZ_4zEI!Wxm+$IhSV|N_ns35#?lk zV=hjSv9&@~WA<8n z$s(;Z;6dde!9nq}QtJbK#akH1N})25{~p%mOt5>Y^6)W-mWg!F-j6+n)STs=NJD6+ zbFblJ!Wnf8AKXSrhFU;OOrYe#iKa7c%Hf-w1EiwKqEN}5w+L^@Xg}+jbAxegx~)50Pz5&?@7KjipT{J%TDD`NJ+eHl zp&`mraSM-#F>#i2owX|036_HmS^6>YXPUhIo#ojH1aBn`DuS&X?+@NepZm zlg%QYvOZI2O`|suJ*bE+Ntez=v3{j!Z~c2MqEz9ctBSt6lRI;ixHWI143k-bbuGf41VnqtY@63 z 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 literal 0 HcmV?d00001 diff --git a/doc/images/overview/WindowHopper_large.png b/doc/images/overview/WindowHopper_large.png new file mode 100644 index 0000000000000000000000000000000000000000..5195ea0780547c8c8417164be853ad3aca395931 GIT binary patch literal 23130 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 P=YN#sRAsAWOrie;eKuun literal 0 HcmV?d00001 diff --git a/doc/images/overview/WindowHopper_small.png b/doc/images/overview/WindowHopper_small.png new file mode 100644 index 0000000000000000000000000000000000000000..2b7cc4dee9b1f26acc25bb1ca8c7181f1a4b6f6d GIT binary patch literal 8392 zcmdT~RZ|=Qj7FE@6j_)mfT zwcvO0F9_?T_E{39YJ%kGpMzj7p(FtVQyY!?Vubk5$8eU>cK&8(=ImzVXbK~1X>Mbb zRhwiC14AJtFD;?&4tw@PKAL&aMU>aUHcq6Y_BLxH-da8uxXQWHPfY26ClSLEKm+B9 zfPt+WVe6t0qiCZLheP-fQyD9R#|J`4idw1xE!P6*<5+YiXVSe4`P&5X?Izs9W$=d6 z{+Ru4dpy7IUNpJXtUd2I$nYA}8%!7TGKdWck!4VvnVJd?far>dtn+ zrW%#ygkoEA&{DQ(Ry#+%O~%!UDm+JssgC5TVG;)*3l#IwiNSGQv_TKuc8(TB?lgiH ziHFAwiJ)+_cXJ$X*n!KqTrNg+kbtJ7am#1ZrF7AD`YzFBW=dP;)0&9Mehq;61B5{G zS?ZSgl!gZjU<6bUne_}FEDKzgP-ltWFYY4e`jaqS?!7RDmRH*YUp;ez^po>p za|Kz%vc7b->xFtJ1@ME}~)l#Z2GK$WSteC!A{S4 zdHT5JW%-hra!C+_L33u}7G!x97O!<5`B%QW@mTJ8{ z{-V!*ZzM61P!8tR2fEc0BTvQwyIcWTV>CT_x;-&jEvL4e9umF3Wh>XgBw}qn^WFOM} zB?xna^jcOk;{IMB@(H5hn3w|p$AqhnX(BKzR<0{tE`1lX)a`|{lIUs8t%AaM2rNc7 z{}ibSG(L7bLz=TXn`M$Z6b|V>Gs-XJMmp9(krH{n$#y4Q-J{(DxKzKMi-w9WnXsHWE%ct|x@42BzK?D)naW z=*1p?vAoWM6Ujuq84kX;xhm1X7Z`QuCp%rNwKx^=scsK%e$(3fGg9^KWN>pzV!g0w z!!x3FovpIETH)P(Q%_It=BPM(ro6iI3q3u(v%5QkPvt!LW|%&Ein+SI_v81_EKwgO zLIm)%@)Dmje*y_V>uXczWeOdpVV2nkw+wUgv^>pa@g+;itaW8SgYVr@Y(6M&a+E-xERW>${zz zMODkDy&FlxpAGCUD9o@#Qt;wWanLq)H+rNW$b4kPx*i7D!@J#IZx=%qF+hSsLV5ff z?ov;XNy8UA-|KWLjt$>u8^I8(9+>sb&1BBUmBmikhyWu~hTlEX8xjT=-+b&zt`P%d z$zg8L6)}7L-e2!cJ9|zKQB#P0`Z>ph&j}@`@X@e5u^5n$khq#Q-|m`Bzfyjms*Zr! z9nbElE<$&r&4i70BXU`3=_K{MI$Le(aKiBIhu4V*4r#w*6E2r?abQQ8BO@Wh!G+ww zQyO)Pb68wo4v|Ojz;@%pK%^x71;s54S6g)FiVI?HE=aLIxUO13t zz=(33Q&4drclM#$P|Eo}&1TPNbzT82;kSegzf?&6UXiC*d4Kr(agQa?*Qt$ah$D-_ zzG;!l`2G1)iw~YB-}0ibawpREZYReA%sILbRO_R6==04%PqqTd5W%ApGVFG>uc2ps zUxOjM(JA!4-iZ+5(0E3QaftllTcb)|WS?tzGju2(Pl~W#L0xf@qCAKOphMfw`KOE$ zBEjWh78BWr239t`g%BR?MoJ2)l+<3yT}cd73{TRz1qrg`V2Cm^5>BcaeEtxf2k)xA zTR-nP75_>*EQWNUYGRrea+p$sxy8ooKq5xsi1UDIj45QHzCEIohxBZeM=l{g-|yz@ z7npMA6-j>o%y_q)Lm6hT-}Peok>s93ir|Wfd(P41J7K5tQ6mi0o0L>}%L-@Fjo2Fqe~KnN zx3ZdA#Ne-g6j0`#EGN1v9Huu>q{Sd4xJ|JmIDpw;r#=k7TrVe$J@()Mq64o4^9;e7 zxL1NtZ|uT1Bf6QMidX{y{D+1Z6xPkyQ!Io;$`VwEWu(lYBHA-qzY5pE zv#K3EI3d9Cf?KfV(&cLh^~y>WM844YF5oZLB?ea71+6?)=a;OmNv)8BAlUe0a}2|{ z;Anuq^uWPSdwaH&?)Pyb;zv5S&hL!hTq-$m(L`$>76V8;>$H+Pg;+S53VWe#Px2AL zn5Yy(L}bDh*($L^8xE(nK-fh{V#1*nq&lAYvv2eX&=KQxvXAU+$y=H3UijpXLntAc z-|v^U2UZTGg5USYvEu<%{X8{hURB`w+g+|VpGIBM6)~_JsjPps9z!HSgR8sZ{D?b= zxm&etI&tFmv8aMUxfQ_ysKh~Z!CcMwoSc3VaLny5W1Ju%b> zZ_TdJ!mX(zaS`D+OTR$O?$Z|y6ddD79P?2N8FvVf()S_il1T6s2(41a4lFTdnvhz z-gBPLn(oy++}}_dEr@bbzqawt+_U~wY8aeYU8%sBM2D=i2#f>BYg@DvVxoe{&zZSWi-2o8cN)|BcG*zlG0J&HNGby);N)qN z1fQ6gR0m_0hdx1ZG|Y^QtEt= z9H%fR+9*z5da+l=UJ@=DQ}I+!8A3Y`vU6isv++l`u|pcH91yBVw7-rqaBunR@pEho z+!Hs#g*)EhNHpd=ki1Oy9#i((lzP=#s{D%P;bo{um&36Sqk9Iy-vdnFrzu)jm3Y){ z`3eBqEh`R;3z2E$KxkYZnnoJgW+B6}j_0ONCKsG1U29s$1w!afcbm$`_4hZ7qziY{ z2QG?D=6O77Ca}aTG`W1jgzQp67Byb38yUR(UsE$iSnIy}D(+AFUBbGB#2-{LWVqvp z=$XpRXDz|Uls(4aKKMexrxHiPC%J%W0w}3E|6V0sm7=UG6nK(%jfFPRu%vmkwo7cp zTeO+enV3;`p)f(5%2ytK8XJ)dKicogwb4OII$$KyS&dDHItG~sdhs}Pp_N!#FAi%_ z2rao>ySFq~pA4Ws1-7keHA#ny6(=(CyX!}j^XZhW&RIh@dwI6`be+n0%wbFuOOqc- z?pru1mT!*T)>4)(>1h;>0z)MDuVgOk1p?(Wg9hR+V$s4nhlCisxLmVm1{Yje;=7}) z2@KgID#n^ODxZ8y{A2^iN8(rBzNH}%FTQQC6r0ZKyMvGHE?mB*=UoF zxTu#oROIJWfc2bzBACbF2bjZF-f+^8qA`>_Yjq1h35#*1LZ3s2z%yn7>9jMPZTDYl$0(W6-mP4g zDocpw&eIdtp_*9Dk*ZohezuyibHbS7X*Z1g2Ioqz(C02Tf5s$hI0}09-CeyoOS6CU*w8t)Mqg-?T5pxEvxwW)>eFp@q<9oZlBw%b!oeD z_T*mnIpywz1i{#H$K3sE-Yn_iV~EV%3dWCeYly~qu>K;}ta_xrd*Ile+-C4lWJd~3^!t6MACCUX3iDRH65ie0D^UjYs@HYaFn>=4=Q3i87N6Tn4a zq3W1Bcd2w>A$^gG{C&rZQ5jEt%w;YN*mU!yZdZDKQLoT>B3fh{(MtE74MC{91)qN; zUE-&G#s@HcQjV{|B9FD$_D5Hzpk)5dsuZ$bGqsZiC$h@lLtuq}nz)ZR~=c5O1)0r*z-JLT~!raU;$ zW`w-Rn+2qqR#jsd&?UUKuMCJ+_U!WG7(JMj)G?@CR=6oU`K3-iEcQH8#>aik{Z;{l z5qHR=EWn*GY#fi8^oS7;p&?;FK zQ)xG!T%&p}D;)v%PDW1bSKIin0w})F2$5FDS0RmvZ8pW&hbSRNVP!%NKXKYa#{xn) z8Mt^xNraL?DG?+Dm?tF06RoR9+B!??R9;WHyE~yhg~UJhlD`gK9-6-%AgXi1`JzlR zaL!d*era4CH1Q1k*%W_=`enh#i;8#G5Uq=!=EH*JEzYa*pJ#{Vz0}XT0dXD8p_{2o zvBpq~5E{MZ$$jZZcA7F}9)eH9?4STZe(nvo>P)&IvKiuU=b@j!j-nUmZc=sXyyKCw zYezu8v9=4l&*~3@Cp$R6p$Wu|s<;G!&+`J7PF*7Y#%WphI?RQu5&)kUXdk9&1KRmu z!0y{QSxk-myeIT$Y6uRo)inv2#GK&rkBIS z4}tx1+TcWeiF^;PP8Nm7gAb|G zFCptRk?1w7vF1TnDfV%$8A`(agzy8>ZLT9wLv+}iBJ5^Ab1c zd7+@PE{ZA8Ra(ax3O#NMb+%v1IXU%5m1>J;gSu;WlSl>!p`aU*<=P^~OUzO=pmUX< zOq`xwOGE2AJAlgo97M*dnBsxB(ikdTFsMO-uwZBS;*R045>9phO2^?>QKxdziec2l zr^6;Pm0b4-S4^;?b9x>~v3K!)PlW2cedq6@z*pRO$?a&5tYC5Sa?|e0aBSFud1?{( z@qlGI{l7vPEw4mwCEu^bc`s^h?OEdle@cofe_p>y{a_wk;CQIcRG-F)&+M@(fXViJ zo~uE!7#p;NEetzDsz8!gowvZ@kP>LZbL>{~OZUFfyy7$aEz40V=Mz+tknMIkUj|;{ z*RIXmdPO{PiJduS{w7XwPi5wTT7%z6kz6Wr$@=lsa1W(MQ%s(n($T;ntfo#aCY|PQ zHteURhy)q%pgoC5vtf#8!NX3Iv_`w+s(%rhCOKS5kaYYDIs;iA5aRZ6qb(m#y5Kq`lsDKVi{kAnhu%mGWC^z z33LrfKfR?W{UmuPanCze$Qf#PQ`)fD|C}~r^XF{UM)TuG8q;3Mt?HhhZjdO=YB#j* z=RC!$STlo((^}_bWDj4s2%o;(&fm#FP++K3^bgmFNLi$|1J%;+coKNHW{>HMrX;c+ zuF1p_O#BJ-^-!S(d{kN5yb-}w1@Q>PnQfq#hxQ_ytpiCxN_2;Dlbu|hL9~k(J%1jN zbaG$wD+^_n(V`)~b^95PGx9|O%XvBDu_2!ve$2_6WEzU#0tIQ02 z9ML2=US+_cvR5}1F-;eY@SaC0gwH&1Ov&1cq}XkBN+@rPkOwzea65kMzAvtD*Ckcx zkcyx(an!hOBr2lLy~kAgmpe(l45jJlHA#xtnWlVYVPDpQO%p5Unhhsy_}+I%8x$QR zp{aHghzNJg_5^!@HFy#>ueDkyLoDu)Hr}XHy%?*p%94S8+3V%{-EL}dA5h7sZumTn z#cwxeiR(M^VaJQ@EvSC)e6cZ~9Wt{fs`*fbFzN`a9JT7t z3bRV@W`^yIZI9SxA(9)PPej^SUfDd{}h086Xti|2M5$IU^we}2? z79s+;SRX=P8Fn^NE1&;TGQw>_R$?sYdoa0&0AEdST3HfXV_GN<1o4S!OQLZ=8s;)3 z3O=R03XL$T(rV#?G_4iZTb$A%kmWEwRZHz+P4<{y340o&?+5giseXe?w4ch2|pRH)H9d-1WKK`!Prm)!^5B>2Z*}`1Prg66vY#C~4 zT{{{Ged!BQkDq=wg7NQcoCi|bHYf>w`b4BY*P=f_&MnOuGZhLJ?E5oycHoA6tNXop zmUxce_&$Mx8_IcFSIhDY55xJ%QnS>Ac{%)d%n$~x{&!<1AL+an|8)GU4sw#J>FQNp zx@N!``N9z&N&w10jFs03ui9a()4tsljCeyJN|L!(z6x4_3Pmy6g16nJK1)SX^+o+* z!EzyBRprBA?gUamRp^TQTT_c#nbwH-wnaI4qC3@=O>1|j7xidA=u0ov4mw6&FAJhz z3@09zbO;*=UOD?*US>PdN{itgUFG^9s7>ir@x@5dI#}rCe9BmTt2+mTNw%=n5$JOl0u)TU z;4F|S5&wSk#Hp6)=<&vSr%0tU{kNqbPZ_W(3E}*MeK@Yx;3ko4QSmkmwUPvw0BsA( zfBF*kQO^{;27z5PsXQB#{XR%>WnVk`FWHtbSNCgzqm(|Yan?kQJ;}BsaG}{r@i#j@ zlO{)LDTdH9wwOWc6K6z!)PSnLltx4T+)PqEi$ON}bZ`FFNViEtCq$oQTP z6462_LDmlkjfW)*IS`%w`x5)Hy5gC<-33aoE0#(0=pQB)h*(m)jOrEcFNYGb`V4w3 zM=ZL`)tJV_Jq0LY7y+n~J|(}a*4y7xt>s*wTQZ@WX6q||_u4BJJ(!r+qC|5-1E-T( zwuuP5+eQKW)h0G;em>bu>5FmKO>C4kx}6Tuhcf!5XpOm5KbDCg*SeKZ9{p4woq z9lGP(@oj8oR`zoioZi3XN_VJjPSwM%xSxO>uZogy5fLz0IM_38K*oD?ZwJwoii9m= z-%E{)Dk`Z>Uq2krjl==VI&_R5Dd3&a>pl}#rz$a&4# zhuz=$`1!oc%P;#a^4VP3+Vt42^M?+x4FMVAr@+g)KIWgO?`#7ob=Z*h7Kco&STVm# zqSr(Ds5iTJZ`2?>#JLsgebT3hWLuSG4_BOzyW2Xav(f(g{x4}V`G3eVt2e)Q9Nbwx z?1-i9Cnug1=Q?mwwr>NkJ&-$g$3U`KycXu4cA1g}!BVaWh0t~>52A&?{@cHHh`3AV zU5+l#$$)h|KX}#uwEdQynZU(|!Iu#hp-(kq$r;Eqk|yaUXjy zJe>(;nempGX}JgMpZTLOvZQk9#Wm`v4g)W~iJp{3BhNL%fHce{F3o$Sou<|$dEkv3 z0rIta)G;&!uIP+`O6_tpcAg@k`Ea!@-fZkfu*T4Jn$yb&Lf@%%uNxSuWxl(@D?Y0` z7oI{hnsdS~gE`)>v!XjxJj_Y8B?VNsh$eVrX_+x|qAym|`#8ZI-DvkF?(dIi0dXi( zO^gk%)Ohl1^*vrZLq8>A^bN}&4^(#1s67Vc9(lY3O)te!_ICI|zZK;y8h`@|<-hv#- z$;?s7$2@Z1LSmr}rLs|qPB$4(4*LXWZidf;_lFlH{wxxBT%b#qo1Q?%Z_`tVJUIu= zf0?+)9M{^NzB}%lnm67h5oJ>KiglCBhIp3Xk7C_^u~l!}4K15b9{M>R5j^FUSA1Hx z6@VZ)0Ta@MM*1KXIdRCM_cZJ#|VTful)4l2O9 zhgWpbqk@AJq7+nRptKN zD2tZ=#h^#c|I}^wMEpmnYa?eMI>Dq#@bG|g2&H#tb}KY7#ks=<_kyz%+(Uqb|C6}_ zAvbsn>8S3`^nQm6JOoNhQ3L)v`G4U`M4>~eA)7}!Uq^t2MgP`2EQ(LIxIyMO#($3n OF!C~ArK==EU literal 0 HcmV?d00001 diff --git a/src/common/GPOWrapper/GPOWrapper.cpp b/src/common/GPOWrapper/GPOWrapper.cpp index bcef3ff995..0f4ad1f9e9 100644 --- a/src/common/GPOWrapper/GPOWrapper.cpp +++ b/src/common/GPOWrapper/GPOWrapper.cpp @@ -8,6 +8,10 @@ namespace winrt::PowerToys::GPOWrapper::implementation { return static_cast(powertoys_gpo::getConfiguredAlwaysOnTopEnabledValue()); } + GpoRuleConfigured GPOWrapper::GetConfiguredAltWindowCycleEnabledValue() + { + return static_cast(powertoys_gpo::getConfiguredAltWindowCycleEnabledValue()); + } GpoRuleConfigured GPOWrapper::GetConfiguredAwakeEnabledValue() { return static_cast(powertoys_gpo::getConfiguredAwakeEnabledValue()); diff --git a/src/common/GPOWrapper/GPOWrapper.h b/src/common/GPOWrapper/GPOWrapper.h index 6a361e2750..b1af9a00d1 100644 --- a/src/common/GPOWrapper/GPOWrapper.h +++ b/src/common/GPOWrapper/GPOWrapper.h @@ -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(); diff --git a/src/common/GPOWrapper/GPOWrapper.idl b/src/common/GPOWrapper/GPOWrapper.idl index d9f2c8f14e..157f57f859 100644 --- a/src/common/GPOWrapper/GPOWrapper.idl +++ b/src/common/GPOWrapper/GPOWrapper.idl @@ -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(); diff --git a/src/common/ManagedCommon/ModuleType.cs b/src/common/ManagedCommon/ModuleType.cs index eb9c18fc26..815a4b75f7 100644 --- a/src/common/ManagedCommon/ModuleType.cs +++ b/src/common/ManagedCommon/ModuleType.cs @@ -7,6 +7,7 @@ namespace ManagedCommon public enum ModuleType { AdvancedPaste, + AltWindowCycle, AlwaysOnTop, Awake, ColorPicker, diff --git a/src/common/utils/gpo.h b/src/common/utils/gpo.h index 700c6d5241..1d420596c4 100644 --- a/src/common/utils/gpo.h +++ b/src/common/utils/gpo.h @@ -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); diff --git a/src/gpo/assets/PowerToys.admx b/src/gpo/assets/PowerToys.admx index a19a267bbb..eabd7cac97 100644 --- a/src/gpo/assets/PowerToys.admx +++ b/src/gpo/assets/PowerToys.admx @@ -91,6 +91,16 @@ + + + + + + + + + + diff --git a/src/gpo/assets/en-US/PowerToys.adml b/src/gpo/assets/en-US/PowerToys.adml index 77f0e439ad..30442270f5 100644 --- a/src/gpo/assets/en-US/PowerToys.adml +++ b/src/gpo/assets/en-US/PowerToys.adml @@ -244,6 +244,7 @@ If you don't configure this policy, the user will be able to control the setting Configure global utility enabled state Advanced Paste: Configure enabled state Always On Top: Configure enabled state + Window Hopper: Configure enabled state Awake: Configure enabled state Color Picker: Configure enabled state Command Not Found: Configure enabled state diff --git a/src/modules/AltWindowCycle/AltWindowCycle.cpp b/src/modules/AltWindowCycle/AltWindowCycle.cpp new file mode 100644 index 0000000000..4beb20cfb8 --- /dev/null +++ b/src/modules/AltWindowCycle/AltWindowCycle.cpp @@ -0,0 +1,1694 @@ +// AltWindowCycle.cpp +// Adapts an Alt+` window-cycling overlay proof-of-concept for the PowerToys in-proc module. +// All overlay windows and the Switcher state machine live on a dedicated UI thread; +// the runner's on_hotkey callback only posts a message. +// Thumbnail previews default to DWM compositor thumbnails for visual fidelity. + +#include "pch.h" + +#include "AltWindowCycle.h" +#include "AltWindowCycleLogic.h" + +#include +#include +#include +#include +#include + +// Win11 system backdrop / corner attributes (in case SDK is older). +#ifndef DWMWA_SYSTEMBACKDROP_TYPE +#define DWMWA_SYSTEMBACKDROP_TYPE 38 +#endif +#ifndef DWMSBT_TRANSIENTWINDOW +#define DWMSBT_TRANSIENTWINDOW 3 +#endif +#ifndef DWMWA_CLOAKED +#define DWMWA_CLOAKED 14 +#endif +#ifndef DWMWA_WINDOW_CORNER_PREFERENCE +#define DWMWA_WINDOW_CORNER_PREFERENCE 33 +#endif +#ifndef DWMWCP_ROUND +#define DWMWCP_ROUND 2 +#endif +#ifndef DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 +#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT)-4) +#endif +#ifndef DWMWA_SYSTEMBACKDROP_TYPE +#define DWMWA_SYSTEMBACKDROP_TYPE 38 +#endif +#ifndef DWMSBT_TRANSIENTWINDOW +#define DWMSBT_TRANSIENTWINDOW 3 +#endif + +namespace AltTabStyle +{ + // Alt-Tab In-App Acrylic Thin tints (ABGR, alpha byte 0 = fully blurred tint). + // Dark: TintColor=#545454. Light: a soft #F3F3F3 mica-like tint. + constexpr DWORD AcrylicThinGradientDarkABGR = 0x00545454; + constexpr DWORD AcrylicThinGradientLightABGR = 0x00F3F3F3; + + // Reads HKCU Personalize\AppsUseLightTheme; defaults to dark when unset. + inline bool IsLightTheme() + { + DWORD value = 0; + DWORD size = sizeof(value); + HKEY key = nullptr; + if (RegOpenKeyExW(HKEY_CURRENT_USER, + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", + 0, KEY_READ, &key) == ERROR_SUCCESS) + { + DWORD type = 0; + if (RegQueryValueExW(key, L"AppsUseLightTheme", nullptr, &type, + reinterpret_cast(&value), &size) != ERROR_SUCCESS || + type != REG_DWORD) + { + value = 0; + } + RegCloseKey(key); + } + return value != 0; + } + + // Opaque approximation of the acrylic tint (alpha dropped), used as the + // backdrop fill on OS builds without the public system-backdrop API. + constexpr COLORREF AbgrToRef(DWORD abgr) + { + return RGB(abgr & 0xFF, (abgr >> 8) & 0xFF, (abgr >> 16) & 0xFF); + } + constexpr COLORREF BackdropSolidRef(bool light) + { + return AbgrToRef(light ? AcrylicThinGradientLightABGR : AcrylicThinGradientDarkABGR); + } + + inline COLORREF AccentFallbackRef() { return RGB(0, 120, 215); } + constexpr COLORREF HeaderTextRef(bool light) + { + return light ? RGB(26, 26, 26) : RGB(235, 235, 235); + } + + inline Gdiplus::Color Transparent() { return Gdiplus::Color(0, 0, 0, 0); } + + // WinUI CardBackgroundFillColorDefault. Same for every tile; selection is shown + // only by the accent focus outline. Semi-transparent so the acrylic shows through. + inline Gdiplus::Color Card(bool light) + { + return light ? Gdiplus::Color(179, 255, 255, 255) // #B3FFFFFF + : Gdiplus::Color(210, 18, 18, 18); // dark, mostly-opaque tile + } + constexpr COLORREF CardRef(bool light) + { + // Opaque approximation of the card-over-acrylic color, available for any + // surface that needs a solid blend background. + return light ? RGB(248, 248, 248) : RGB(20, 20, 20); + } + // WinUI CardStrokeColorDefault: a subtle 1px edge around each card. + inline Gdiplus::Color CardStroke(bool light) + { + return light ? Gdiplus::Color(15, 0, 0, 0) + : Gdiplus::Color(25, 0, 0, 0); + } + inline Gdiplus::Color Accent(COLORREF accent) + { + return Gdiplus::Color(255, GetRValue(accent), GetGValue(accent), GetBValue(accent)); + } + inline Gdiplus::Color SurfaceStrokeDefault(bool light) + { + return light ? Gdiplus::Color(24, 0, 0, 0) + : Gdiplus::Color(64, 255, 255, 255); + } + inline Gdiplus::Color PreviewStroke(bool light) + { + return light ? Gdiplus::Color(28, 0, 0, 0) + : Gdiplus::Color(60, 255, 255, 255); + } + inline Gdiplus::Color FocusShadow(bool light) + { + // Contrast hairline just inside the accent ring: white in light mode, + // black in dark mode. + return light ? Gdiplus::Color(120, 255, 255, 255) + : Gdiplus::Color(150, 0, 0, 0); + } +} + +// True on Windows 11 22H2+ (build 22621), where DWMWA_SYSTEMBACKDROP_TYPE is a +// public, documented way to get acrylic. Below that (Windows 10, Windows 11 +// 21H2) we fall back to an opaque solid fill instead of the private composition +// API. RtlGetVersion is used because GetVersionEx is shimmed by app compat. +static bool SupportsSystemBackdrop() +{ + using RtlGetVersionPtr = LONG(WINAPI*)(PRTL_OSVERSIONINFOW); + static const DWORD build = []() -> DWORD { + if (HMODULE nt = GetModuleHandleW(L"ntdll.dll")) + { + auto fn = reinterpret_cast(GetProcAddress(nt, "RtlGetVersion")); + RTL_OSVERSIONINFOW vi{}; + vi.dwOSVersionInfoSize = sizeof(vi); + if (fn && fn(&vi) == 0) + return vi.dwBuildNumber; + } + return 0; + }(); + return build >= 22621; +} + +// thumbHost fallback-fill state. On the acrylic path thumbHost paints nothing so +// the system backdrop shows through; on the fallback path ThumbHostProc fills it +// opaque with this tinted brush. +static HBRUSH g_thumbSolidBrush = nullptr; +static bool g_thumbSolidMode = false; + +static LRESULT CALLBACK ThumbHostProc(HWND h, UINT msg, WPARAM w, LPARAM l) +{ + if (msg == WM_ERASEBKGND) + { + if (g_thumbSolidMode && g_thumbSolidBrush) + { + RECT rc{}; + GetClientRect(h, &rc); + FillRect(reinterpret_cast(w), &rc, g_thumbSolidBrush); + } + return 1; + } + return DefWindowProcW(h, msg, w, l); +} + +// Apply the overlay backdrop to thumbHost. Win11 22H2+ gets public acrylic via +// DWMWA_SYSTEMBACKDROP_TYPE; older builds get an opaque tinted fill. +static void ApplyBackdrop(HWND hwnd, bool light) +{ + if (SupportsSystemBackdrop()) + { + g_thumbSolidMode = false; + MARGINS glass{ -1, -1, -1, -1 }; + DwmExtendFrameIntoClientArea(hwnd, &glass); + int backdrop = DWMSBT_TRANSIENTWINDOW; + DwmSetWindowAttribute(hwnd, DWMWA_SYSTEMBACKDROP_TYPE, &backdrop, sizeof(backdrop)); + } + else + { + g_thumbSolidMode = true; + if (g_thumbSolidBrush) + DeleteObject(g_thumbSolidBrush); + g_thumbSolidBrush = CreateSolidBrush(AltTabStyle::BackdropSolidRef(light)); + } +} + +// =================== Window enumeration / activation =================== + +static std::wstring ProcessImagePath(HWND hwnd) +{ + DWORD pid = 0; + GetWindowThreadProcessId(hwnd, &pid); + if (!pid) + return std::wstring(); + + HANDLE proc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); + if (!proc) + return std::wstring(); + + wchar_t buf[MAX_PATH * 2]; + DWORD len = static_cast(sizeof(buf) / sizeof(buf[0])); + std::wstring result; + if (QueryFullProcessImageNameW(proc, 0, buf, &len)) + result.assign(buf, len); + CloseHandle(proc); + return result; +} + +// True for paths whose filename is ApplicationFrameHost.exe (the shared host that +// owns every UWP/packaged app's top-level ApplicationFrameWindow). +static bool IsApplicationFrameHost(const std::wstring& path) +{ + size_t slash = path.find_last_of(L"\\/"); + const wchar_t* name = path.c_str() + (slash == std::wstring::npos ? 0 : slash + 1); + return _wcsicmp(name, L"ApplicationFrameHost.exe") == 0; +} + +struct CoreWindowFind +{ + DWORD hostPid = 0; + HWND found = nullptr; +}; + +static BOOL CALLBACK FindCoreWindowProc(HWND child, LPARAM lp) +{ + CoreWindowFind* cf = reinterpret_cast(lp); + wchar_t cls[64]; + if (GetClassNameW(child, cls, ARRAYSIZE(cls)) && + wcscmp(cls, L"Windows.UI.Core.CoreWindow") == 0) + { + DWORD pid = 0; + GetWindowThreadProcessId(child, &pid); + if (pid && pid != cf->hostPid) + { + cf->found = child; + return FALSE; // stop enumerating + } + } + return TRUE; +} + +// Image path that identifies the *real* owning app. For UWP/packaged windows the +// top-level window belongs to ApplicationFrameHost.exe, so all packaged apps would +// otherwise group together. Resolve to the hosted CoreWindow's actual process so +// each packaged app is grouped on its own. +static std::wstring RealProcessImagePath(HWND hwnd) +{ + std::wstring path = ProcessImagePath(hwnd); + if (!IsApplicationFrameHost(path)) + return path; + + DWORD hostPid = 0; + GetWindowThreadProcessId(hwnd, &hostPid); + + CoreWindowFind cf; + cf.hostPid = hostPid; + EnumChildWindows(hwnd, FindCoreWindowProc, reinterpret_cast(&cf)); + if (cf.found) + { + std::wstring real = ProcessImagePath(cf.found); + if (!real.empty()) + return real; + } + return path; +} + +// Queries the Win32 state that feeds the pure Alt-Tab eligibility predicate. Keeps +// the original short-circuits so no extra system calls are made for windows that are +// already disqualified (e.g. the DWM cloak query is skipped for invisible windows). +static AltWindowCycleLogic::WindowEligibility QueryWindowEligibility(HWND hwnd) +{ + AltWindowCycleLogic::WindowEligibility eligibility; + + eligibility.isVisible = IsWindowVisible(hwnd) != FALSE; + if (!eligibility.isVisible) + return eligibility; + + int cloaked = 0; + eligibility.isCloaked = SUCCEEDED(DwmGetWindowAttribute(hwnd, DWMWA_CLOAKED, &cloaked, sizeof(cloaked))) && cloaked; + if (eligibility.isCloaked) + return eligibility; + + HWND walk = GetAncestor(hwnd, GA_ROOTOWNER); + HWND tryPopup = nullptr; + for (;;) + { + tryPopup = GetLastActivePopup(walk); + if (tryPopup == walk) + break; + if (IsWindowVisible(tryPopup)) + break; + walk = tryPopup; + } + eligibility.isAltTabRepresentative = (walk == hwnd); + if (!eligibility.isAltTabRepresentative) + return eligibility; + + LONG_PTR exStyle = GetWindowLongPtrW(hwnd, GWL_EXSTYLE); + eligibility.isToolWindow = (exStyle & WS_EX_TOOLWINDOW) != 0; + eligibility.isAppWindow = (exStyle & WS_EX_APPWINDOW) != 0; + return eligibility; +} + +struct EnumCtx +{ + std::vector candidates; +}; + +static BOOL CALLBACK EnumProc(HWND hwnd, LPARAM lp) +{ + EnumCtx* ctx = reinterpret_cast(lp); + + AltWindowCycleLogic::CandidateWindow candidate; + candidate.id = reinterpret_cast(hwnd); + candidate.eligibility = QueryWindowEligibility(hwnd); + + // Resolve the owning process only for eligible windows, matching the original + // short-circuit (avoids an OpenProcess for windows that are dropped anyway). + if (AltWindowCycleLogic::IsAltTabEligible(candidate.eligibility)) + candidate.processKey = RealProcessImagePath(hwnd); + + ctx->candidates.push_back(std::move(candidate)); + return TRUE; +} + +// Collect Alt-Tab-eligible windows of the foreground app in Z-order (MRU). +static bool GetAppWindows(HWND& foreground, std::vector& windows) +{ + windows.clear(); + foreground = GetForegroundWindow(); + if (!foreground) + return false; + + const std::wstring foregroundKey = RealProcessImagePath(foreground); + if (foregroundKey.empty()) + return false; + + EnumCtx ctx; + EnumWindows(EnumProc, reinterpret_cast(&ctx)); + + const std::vector selected = + AltWindowCycleLogic::SelectCycleWindows(foregroundKey, ctx.candidates); + + windows.reserve(selected.size()); + for (const unsigned long long id : selected) + windows.push_back(reinterpret_cast(static_cast(id))); + + return !windows.empty(); +} + +static void ForceForeground(HWND hwnd) +{ + if (IsIconic(hwnd)) + ShowWindow(hwnd, SW_RESTORE); + + DWORD fgThread = GetWindowThreadProcessId(GetForegroundWindow(), nullptr); + DWORD myThread = GetCurrentThreadId(); + + if (fgThread && fgThread != myThread) + AttachThreadInput(myThread, fgThread, TRUE); + + BringWindowToTop(hwnd); + SetForegroundWindow(hwnd); + SetFocus(hwnd); + + if (fgThread && fgThread != myThread) + AttachThreadInput(myThread, fgThread, FALSE); +} + +// =================== UI-thread message plumbing =============================== + +// Custom thread messages posted to the dedicated UI thread. +// WM_AWC_HOTKEY: wParam = forward flag (0/1), lParam = held modifier mask. +static const UINT WM_AWC_HOTKEY = WM_APP + 1; +static const UINT WM_AWC_CANCEL = WM_APP + 2; + +static DWORD g_uiThreadId = 0; + +// Escape has to cancel the overlay, but the overlay is WS_EX_NOACTIVATE and never +// receives keyboard focus. A low-level hook installed only while the overlay is +// visible keeps Escape out of the runner's global hotkey table, so PowerToys never +// claims Alt+Escape (a Windows shell shortcut) while the module sits idle. +static HHOOK g_escapeHook = nullptr; + +static LRESULT CALLBACK EscapeHookProc(int code, WPARAM wParam, LPARAM lParam) +{ + if (code == HC_ACTION && (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN)) + { + const auto* keyInfo = reinterpret_cast(lParam); + + // Keep this callback bounded: teardown runs on the UI thread's own message + // loop rather than inline, so the OS low-level hook timeout is never at risk. + if (keyInfo && keyInfo->vkCode == VK_ESCAPE && g_uiThreadId && + PostThreadMessageW(g_uiThreadId, WM_AWC_CANCEL, 0, 0)) + { + return 1; + } + } + + return CallNextHookEx(nullptr, code, wParam, lParam); +} + +// =================== Switcher class =================== + +class Switcher +{ +public: + bool Init(HINSTANCE instance); + void Shutdown(); + void OnHotkey(bool forward, unsigned int holdModifiers); + void OnCancel(); + +private: + enum class St { Idle, Visible }; + + static const UINT_PTR TIMER_ID = 1; + static const UINT TIMER_MS = 25; + + static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); + + void OnTick(); + void Commit(); + void Cancel(); + void ShowOverlayWindow(); + void HideOverlayWindow(); + void SetSelection(int index); + void RenderLayered(); + void OnThemeChanged(); + void ComputeLayout(const RECT& work, int& x, int& y, int& panelW, int& panelH); + void RegisterThumbnails(); + void UnregisterThumbnails(); + void InstallEscapeHook(); + void RemoveEscapeHook(); + void EnsureFont(); + + RECT TileRect(int index) const; + RECT PreviewRect(const RECT& tile) const; + RECT HeaderRect(const RECT& tile) const; + + int Scaled(int v) const { return AltWindowCycleLogic::ScaledValue(scale, v); } + + HINSTANCE hinst = nullptr; + HWND overlay = nullptr; + HWND thumbHost = nullptr; + int ovX = 0, ovY = 0, ovW = 0, ovH = 0; + + St state = St::Idle; + std::vector windows; + std::vector thumbs; + std::vector icons; + std::vector titles; + HWND anchorWindow = nullptr; + int selected = 0; + int pageStart = 0; + unsigned int activeHoldModifiers = AltWindowCycleLogic::ModifierAlt; + + double scale = 1.0; + AltWindowCycleLogic::OverlayLayout overlayLayout; + int cols = 1, rows = 1; + int pad = 0, gap = 0, tileW = 0, tileH = 0, previewH = 0, inner = 0, radius = 0; + int cardTrimBottom = 0; + int headerH = 0, iconSize = 0; + + HFONT font = nullptr; + double fontScale = 0.0; +}; + +// ---- lifecycle --------------------------------------------------------------- + +bool Switcher::Init(HINSTANCE instance) +{ + hinst = instance; + + WNDCLASSW hc = {}; + hc.style = CS_HREDRAW | CS_VREDRAW; + hc.lpfnWndProc = &ThumbHostProc; + hc.hInstance = hinst; + hc.hCursor = LoadCursorW(nullptr, IDC_ARROW); + hc.hbrBackground = nullptr; + hc.lpszClassName = L"AltWindowCycleThumbHost"; + RegisterClassW(&hc); + + thumbHost = CreateWindowExW( + WS_EX_TOPMOST | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE, + hc.lpszClassName, L"", WS_POPUP | WS_DISABLED, + 0, 0, 0, 0, nullptr, nullptr, hinst, nullptr); + if (!thumbHost) + return false; + + WNDCLASSW wc = {}; + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = &Switcher::WndProc; + wc.hInstance = hinst; + wc.hCursor = LoadCursorW(nullptr, IDC_ARROW); + wc.hbrBackground = nullptr; + wc.lpszClassName = L"AltWindowCycleOverlay"; + RegisterClassW(&wc); + + overlay = CreateWindowExW( + WS_EX_TOPMOST | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE | WS_EX_LAYERED, + wc.lpszClassName, L"", WS_POPUP, + 0, 0, 0, 0, nullptr, nullptr, hinst, nullptr); + if (!overlay) + return false; + + SetWindowLongPtrW(overlay, GWLP_USERDATA, reinterpret_cast(this)); + return true; +} + +void Switcher::Shutdown() +{ + RemoveEscapeHook(); + UnregisterThumbnails(); + if (thumbHost) + { + DestroyWindow(thumbHost); + thumbHost = nullptr; + } + if (font) + { + DeleteObject(font); + font = nullptr; + } + if (overlay) + { + KillTimer(overlay, TIMER_ID); + DestroyWindow(overlay); + overlay = nullptr; + } + UnregisterClassW(L"AltWindowCycleOverlay", hinst); + UnregisterClassW(L"AltWindowCycleThumbHost", hinst); + if (g_thumbSolidBrush) + { + DeleteObject(g_thumbSolidBrush); + g_thumbSolidBrush = nullptr; + } + state = St::Idle; +} + +// ---- state machine ----------------------------------------------------------- + +#pragma warning(suppress : 26497) +static unsigned int CurrentModifiersDown() +{ + unsigned int modifiers = 0; + const auto isDown = [](int vk) { + return (GetAsyncKeyState(vk) & 0x8000) != 0; + }; + + if (isDown(VK_MENU)) + { + modifiers |= AltWindowCycleLogic::ModifierAlt; + } + if (isDown(VK_CONTROL)) + { + modifiers |= AltWindowCycleLogic::ModifierCtrl; + } + if (isDown(VK_SHIFT)) + { + modifiers |= AltWindowCycleLogic::ModifierShift; + } + if (isDown(VK_LWIN) || isDown(VK_RWIN)) + { + modifiers |= AltWindowCycleLogic::ModifierWin; + } + + return modifiers; +} + +void Switcher::OnHotkey(bool forward, unsigned int holdModifiers) +{ + if (state == St::Idle) + { + HWND fg; + if (!GetAppWindows(fg, windows) || windows.size() < 2) + return; + + int idx = -1; + for (size_t i = 0; i < windows.size(); ++i) + if (windows[i] == fg) { idx = static_cast(i); break; } + if (idx < 0) + idx = 0; + + anchorWindow = fg; + activeHoldModifiers = AltWindowCycleLogic::StableHoldModifiers(holdModifiers); + const auto firstHotkey = AltWindowCycleLogic::BeginCycle(idx, static_cast(windows.size()), forward); + if (firstHotkey.action != AltWindowCycleLogic::FirstHotkeyAction::ShowOverlay) + return; + + selected = firstHotkey.selected; + ShowOverlayWindow(); + state = St::Visible; + SetTimer(overlay, TIMER_ID, TIMER_MS, nullptr); + } + else + { + selected = AltWindowCycleLogic::WrapIndex(forward ? selected + 1 : selected - 1, static_cast(windows.size())); + SetSelection(selected); + } +} + +void Switcher::OnTick() +{ + if (!AltWindowCycleLogic::AreRequiredModifiersDown(activeHoldModifiers, CurrentModifiersDown())) + { + Commit(); + return; + } +} + +void Switcher::OnCancel() +{ + if (state == St::Visible) + { + Cancel(); + } +} + +void Switcher::Commit() +{ + KillTimer(overlay, TIMER_ID); + if (state == St::Visible) + HideOverlayWindow(); + anchorWindow = nullptr; + + if (selected >= 0 && selected < static_cast(windows.size())) + { + HWND target = windows[selected]; + if (IsWindow(target)) + ForceForeground(target); + } + state = St::Idle; +} + +void Switcher::Cancel() +{ + KillTimer(overlay, TIMER_ID); + if (state == St::Visible) + HideOverlayWindow(); + anchorWindow = nullptr; + state = St::Idle; +} + +// ---- overlay window ---------------------------------------------------------- + +static HMONITOR GetAnchorMonitor(HWND anchor) +{ + return MonitorFromWindow(anchor, MONITOR_DEFAULTTONEAREST); +} + +static UINT GetMonitorEffectiveDpi(HMONITOR mon) +{ + UINT dpiX = 96, dpiY = 96; + if (mon && SUCCEEDED(GetDpiForMonitor(mon, MDT_EFFECTIVE_DPI, &dpiX, &dpiY)) && dpiX) + return dpiX; + return 96; +} + +static RECT GetWorkArea(HMONITOR mon) +{ + MONITORINFO mi = {}; + mi.cbSize = sizeof(mi); + if (mon && GetMonitorInfoW(mon, &mi)) + return mi.rcWork; + RECT fallback = { 0, 0, 1920, 1080 }; + return fallback; +} + +static UINT GetDpiForHostOnMonitor(HWND host, HMONITOR mon, const RECT& work) +{ + if (host && mon) + { + SetWindowPos(host, nullptr, work.left, work.top, 1, 1, + SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_HIDEWINDOW); + UINT dpi = GetDpiForWindow(host); + if (dpi) + return dpi; + } + return GetMonitorEffectiveDpi(mon); +} + +static HICON GetWindowIcon(HWND hwnd) +{ + DWORD_PTR res = 0; + HICON icon = nullptr; + if (SendMessageTimeoutW(hwnd, WM_GETICON, ICON_SMALL2, 0, SMTO_ABORTIFHUNG, 100, &res) && res) + icon = reinterpret_cast(res); + if (!icon && SendMessageTimeoutW(hwnd, WM_GETICON, ICON_BIG, 0, SMTO_ABORTIFHUNG, 100, &res) && res) + icon = reinterpret_cast(res); + if (!icon) + icon = reinterpret_cast(GetClassLongPtrW(hwnd, GCLP_HICONSM)); + if (!icon) + icon = reinterpret_cast(GetClassLongPtrW(hwnd, GCLP_HICON)); + return icon; +} + +static std::wstring GetTitle(HWND hwnd); + +void Switcher::ShowOverlayWindow() +{ + if (windows.empty()) + return; + + int anchorIdx = selected; + if (anchorIdx < 0) anchorIdx = 0; + if (anchorIdx >= static_cast(windows.size())) anchorIdx = 0; + HWND anchor = IsWindow(anchorWindow) ? anchorWindow : windows[anchorIdx]; + + HMONITOR mon = GetAnchorMonitor(anchor); + RECT work = GetWorkArea(mon); + UINT dpi = GetDpiForHostOnMonitor(thumbHost, mon, work); + scale = dpi / 96.0; + + int x, y, panelW, panelH; + ComputeLayout(work, x, y, panelW, panelH); + + EnsureFont(); + + icons.clear(); + titles.clear(); + for (HWND w : windows) + { + icons.push_back(GetWindowIcon(w)); + titles.push_back(GetTitle(w)); + } + + ovX = x; ovY = y; ovW = panelW; ovH = panelH; + + SetWindowPos(thumbHost, HWND_TOPMOST, x, y, panelW, panelH, + SWP_NOACTIVATE | SWP_NOOWNERZORDER); + ApplyBackdrop(thumbHost, AltTabStyle::IsLightTheme()); + DWORD cornerPref = DWMWCP_ROUND; + DwmSetWindowAttribute(thumbHost, DWMWA_WINDOW_CORNER_PREFERENCE, + &cornerPref, sizeof(cornerPref)); + HRGN rgn = CreateRoundRectRgn(0, 0, panelW + 1, panelH + 1, + 2 * Scaled(8), 2 * Scaled(8)); + if (rgn != nullptr) + { + // SetWindowRgn takes ownership of the region on success; on failure the + // caller still owns it, so delete it to avoid leaking the HRGN. + if (SetWindowRgn(thumbHost, rgn, FALSE) == 0) + { + DeleteObject(rgn); + } + } + RedrawWindow(thumbHost, nullptr, nullptr, + RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW | RDW_ALLCHILDREN); + RegisterThumbnails(); + + // Update the hidden layered bitmap before showing it. Otherwise, monitor/DPI + // switches can flash the previous-size overlay for one frame. + RenderLayered(); + SetWindowPos(thumbHost, HWND_TOPMOST, x, y, panelW, panelH, + SWP_NOACTIVATE | SWP_SHOWWINDOW); + SetWindowPos(overlay, HWND_TOPMOST, x, y, panelW, panelH, + SWP_NOACTIVATE | SWP_SHOWWINDOW); + SetWindowPos(thumbHost, overlay, 0, 0, 0, 0, + SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); + InstallEscapeHook(); +} + +void Switcher::HideOverlayWindow() +{ + RemoveEscapeHook(); + ShowWindow(overlay, SW_HIDE); + ShowWindow(thumbHost, SW_HIDE); + UnregisterThumbnails(); + icons.clear(); + titles.clear(); +} + +void Switcher::InstallEscapeHook() +{ + if (!g_escapeHook) + { + g_escapeHook = SetWindowsHookExW(WH_KEYBOARD_LL, EscapeHookProc, nullptr, 0); + } +} + +void Switcher::RemoveEscapeHook() +{ + if (g_escapeHook) + { + UnhookWindowsHookEx(g_escapeHook); + g_escapeHook = nullptr; + } +} + +void Switcher::SetSelection(int index) +{ + selected = index; + const int nextPageStart = AltWindowCycleLogic::PageStartForSelection( + selected, + static_cast(windows.size()), + overlayLayout.pageSize); + if (nextPageStart != pageStart) + { + pageStart = nextPageStart; + RegisterThumbnails(); + } + RenderLayered(); +} + +// React to a live OS Light/Dark theme switch while the overlay is visible: +// refresh the backdrop (re-tints the solid fallback) and repaint the chrome. +void Switcher::OnThemeChanged() +{ + if (state != St::Visible) + return; + ApplyBackdrop(thumbHost, AltTabStyle::IsLightTheme()); + RedrawWindow(thumbHost, nullptr, nullptr, + RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW | RDW_ALLCHILDREN); + RenderLayered(); +} + +void Switcher::ComputeLayout(const RECT& work, int& x, int& y, int& panelW, int& panelH) +{ + overlayLayout = AltWindowCycleLogic::ComputeOverlayLayout(work, static_cast(windows.size()), scale); + pad = overlayLayout.pad; + gap = overlayLayout.gap; + tileW = overlayLayout.tileW; + headerH = overlayLayout.headerH; + previewH = overlayLayout.previewH; + inner = overlayLayout.inner; + radius = overlayLayout.radius; + cardTrimBottom = overlayLayout.cardTrimBottom; + iconSize = overlayLayout.iconSize; + tileH = overlayLayout.tileH; + cols = overlayLayout.cols; + rows = overlayLayout.rows; + pageStart = AltWindowCycleLogic::PageStartForSelection( + selected, + static_cast(windows.size()), + overlayLayout.pageSize); + + x = overlayLayout.panelX; + y = overlayLayout.panelY; + panelW = overlayLayout.panelW; + panelH = overlayLayout.panelH; +} + +RECT Switcher::TileRect(int index) const +{ + return AltWindowCycleLogic::TileRect(overlayLayout, index); +} + +RECT Switcher::PreviewRect(const RECT& tile) const +{ + return AltWindowCycleLogic::PreviewRect(overlayLayout, tile); +} + +RECT Switcher::HeaderRect(const RECT& tile) const +{ + return AltWindowCycleLogic::HeaderRect(overlayLayout, tile); +} + +static SIZE QueryThumbSize(HTHUMBNAIL th) +{ + SIZE s = { 0, 0 }; + DwmQueryThumbnailSourceSize(th, &s); + return s; +} + +static SIZE ClientSourceSize(HWND hwnd) +{ + RECT cr = {}; + if (GetClientRect(hwnd, &cr)) + { + SIZE s = { cr.right - cr.left, cr.bottom - cr.top }; + if (s.cx > 0 && s.cy > 0) + return s; + } + return { 0, 0 }; +} + +void Switcher::RegisterThumbnails() +{ + UnregisterThumbnails(); + + const int pageEnd = (std::min)( + pageStart + overlayLayout.pageSize, + static_cast(windows.size())); + for (int windowIndex = pageStart, slot = 0; windowIndex < pageEnd; ++windowIndex, ++slot) + { + RECT dest = PreviewRect(TileRect(slot)); + HTHUMBNAIL th = nullptr; + if (FAILED(DwmRegisterThumbnail(thumbHost, windows[windowIndex], &th)) || !th) + { + thumbs.push_back(nullptr); + continue; + } + thumbs.push_back(th); + + SIZE clientSize = IsIconic(windows[windowIndex]) ? SIZE{ 0, 0 } : ClientSourceSize(windows[windowIndex]); + BOOL clientOnly = TRUE; + if (clientSize.cx <= 0 || clientSize.cy <= 0) + { + clientSize = QueryThumbSize(th); + clientOnly = FALSE; + } + RECT avail = { 0, 0, clientSize.cx, clientSize.cy }; + if (clientOnly) + { + int ix = (std::min)(2, (int)(avail.right - avail.left) / 4); + int iy = (std::min)(2, (int)(avail.bottom - avail.top) / 4); + avail.left += ix; avail.right -= ix; + avail.top += iy; avail.bottom -= iy; + } + RECT rcSrc = AltWindowCycleLogic::CoverSource(dest, avail); + DWM_THUMBNAIL_PROPERTIES props = {}; + props.dwFlags = DWM_TNP_RECTDESTINATION | DWM_TNP_RECTSOURCE | + DWM_TNP_VISIBLE | DWM_TNP_OPACITY | DWM_TNP_SOURCECLIENTAREAONLY; + props.rcDestination = dest; + props.rcSource = rcSrc; + props.opacity = 255; + props.fVisible = TRUE; + props.fSourceClientAreaOnly = clientOnly; + DwmUpdateThumbnailProperties(th, &props); + } +} + +void Switcher::UnregisterThumbnails() +{ + for (HTHUMBNAIL th : thumbs) + if (th) + DwmUnregisterThumbnail(th); + thumbs.clear(); +} + +void Switcher::EnsureFont() +{ + if (font && fontScale == scale) + return; + if (font) + { + DeleteObject(font); + font = nullptr; + } + int height = -Scaled(14); + font = CreateFontW(height, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, + DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, + CLEARTYPE_NATURAL_QUALITY, DEFAULT_PITCH | FF_DONTCARE, L"Segoe UI"); + fontScale = scale; +} + +// ---- rendering helpers ------------------------------------------------------- + +static void BuildRoundRect(Gdiplus::GraphicsPath& path, const Gdiplus::RectF& r, Gdiplus::REAL rad) +{ + path.Reset(); + Gdiplus::REAL d = rad * 2; + if (d <= 0 || d > r.Width || d > r.Height) + { + path.AddRectangle(r); + return; + } + path.AddArc(r.X, r.Y, d, d, 180, 90); + path.AddArc(r.GetRight() - d, r.Y, d, d, 270, 90); + path.AddArc(r.GetRight() - d, r.GetBottom() - d, d, d, 0, 90); + path.AddArc(r.X, r.GetBottom() - d, d, d, 90, 90); + path.CloseFigure(); +} + +// Square top edge, rounded bottom corners. Used for the live preview viewport so +// the full-bleed DWM thumbnail reads as rounded at the bottom of the card. +static void BuildBottomRoundRect(Gdiplus::GraphicsPath& path, const Gdiplus::RectF& r, Gdiplus::REAL rad) +{ + path.Reset(); + Gdiplus::REAL d = rad * 2; + if (d <= 0 || d > r.Width || d > r.Height) + { + path.AddRectangle(r); + return; + } + path.AddLine(r.X, r.Y, r.GetRight(), r.Y); + path.AddLine(r.GetRight(), r.Y, r.GetRight(), r.GetBottom() - rad); + path.AddArc(r.GetRight() - d, r.GetBottom() - d, d, d, 0, 90); + path.AddArc(r.X, r.GetBottom() - d, d, d, 90, 90); + path.CloseFigure(); +} + +static Gdiplus::RectF InflateF(const RECT& r, int by) +{ + return Gdiplus::RectF( + static_cast(r.left - by), + static_cast(r.top - by), + static_cast((r.right - r.left) + 2 * by), + static_cast((r.bottom - r.top) + 2 * by)); +} + +static std::wstring GetTitle(HWND hwnd) +{ + wchar_t buf[256]; + int len = GetWindowTextW(hwnd, buf, static_cast(sizeof(buf) / sizeof(buf[0]))); + return std::wstring(buf, len > 0 ? len : 0); +} + +// Draw HICON into a premultiplied-alpha DIB without disturbing the alpha channel +// of already-opaque pixels outside the icon shape. +static void DrawIconOverPARGB(void* destBits, int destW, int destH, + HICON icon, int x, int y, int size) +{ + if (!destBits || !icon || destW <= 0 || destH <= 0 || size <= 0) + return; + + HDC screen = GetDC(nullptr); + HDC iconDC = CreateCompatibleDC(screen); + if (!iconDC) + { + ReleaseDC(nullptr, screen); + return; + } + + BITMAPINFO bi = {}; + bi.bmiHeader.biSize = sizeof(bi.bmiHeader); + bi.bmiHeader.biWidth = size; + bi.bmiHeader.biHeight = -size; + bi.bmiHeader.biPlanes = 1; + bi.bmiHeader.biBitCount = 32; + bi.bmiHeader.biCompression = BI_RGB; + + void* iconBits = nullptr; + HBITMAP dib = CreateDIBSection(screen, &bi, DIB_RGB_COLORS, &iconBits, nullptr, 0); + if (!dib) + { + DeleteDC(iconDC); + ReleaseDC(nullptr, screen); + return; + } + + HGDIOBJ oldBmp = SelectObject(iconDC, dib); + ZeroMemory(iconBits, static_cast(size) * size * 4); + DrawIconEx(iconDC, 0, 0, icon, size, size, 0, nullptr, DI_NORMAL); + + BYTE* src = static_cast(iconBits); + BYTE* dst = static_cast(destBits); + + bool hasAlpha = false; + for (int i = 0; i < size * size; ++i) + { + if (src[i * 4 + 3] != 0) + { + hasAlpha = true; + break; + } + } + + // Legacy (1-bit mask) icons carry no per-pixel alpha, so DrawIconEx leaves the + // alpha channel at 0 and a pure-black opaque pixel is indistinguishable from a + // transparent one by color alone. Render the icon over a white background on + // a second pass: pixels identical on both backgrounds are opaque (preserving black + // detail), pixels that differ by ~full white are transparent. + void* whiteBits = nullptr; + HBITMAP whiteDib = nullptr; + if (!hasAlpha) + { + whiteDib = CreateDIBSection(screen, &bi, DIB_RGB_COLORS, &whiteBits, nullptr, 0); + if (whiteDib) + { + SelectObject(iconDC, whiteDib); + memset(whiteBits, 0xFF, static_cast(size) * size * 4); + DrawIconEx(iconDC, 0, 0, icon, size, size, 0, nullptr, DI_NORMAL); + } + } + BYTE* white = static_cast(whiteBits); + + for (int sy = 0; sy < size; ++sy) + { + int dy = y + sy; + if (dy < 0 || dy >= destH) + continue; + for (int sx = 0; sx < size; ++sx) + { + int dx = x + sx; + if (dx < 0 || dx >= destW) + continue; + + size_t srcOff = (static_cast(sy) * size + sx) * 4; + BYTE* s = src + srcOff; + BYTE* d = dst + (static_cast(dy) * destW + dx) * 4; + + if (hasAlpha) + { + int a = s[3]; + if (a == 0) + continue; + + int sb = s[0], sg = s[1], sr = s[2]; + if (sb > a || sg > a || sr > a) + { + sb = (sb * a + 127) / 255; + sg = (sg * a + 127) / 255; + sr = (sr * a + 127) / 255; + } + int inv = 255 - a; + d[0] = static_cast((std::min)(255, sb + (d[0] * inv + 127) / 255)); + d[1] = static_cast((std::min)(255, sg + (d[1] * inv + 127) / 255)); + d[2] = static_cast((std::min)(255, sr + (d[2] * inv + 127) / 255)); + d[3] = 255; + } + else + { + bool opaque; + if (white) + { + BYTE* whitePx = white + srcOff; + int diff = (whitePx[0] - s[0]) + (whitePx[1] - s[1]) + (whitePx[2] - s[2]); + opaque = diff < 384; // < half of 3*255 → covered on both backgrounds + } + else + { + opaque = (s[0] || s[1] || s[2]); + } + if (opaque) + { + d[0] = s[0]; d[1] = s[1]; d[2] = s[2]; d[3] = 255; + } + } + } + } + + SelectObject(iconDC, oldBmp); + DeleteObject(dib); + if (whiteDib) + DeleteObject(whiteDib); + DeleteDC(iconDC); + ReleaseDC(nullptr, screen); +} + +static void DrawHeaderText(BYTE* destBits, int destW, int destH, HFONT fontHandle, + const RECT& rc, const std::wstring& text, + COLORREF textColor, + UINT alignFlag = DT_LEFT) +{ + if (!destBits || destW <= 0 || destH <= 0 || text.empty() || !fontHandle) + return; + + int w = rc.right - rc.left; + int h = rc.bottom - rc.top; + if (w <= 0 || h <= 0) + return; + + HDC screen = GetDC(nullptr); + HDC textDC = CreateCompatibleDC(screen); + + BITMAPINFO bi = {}; + bi.bmiHeader.biSize = sizeof(bi.bmiHeader); + bi.bmiHeader.biWidth = w; + bi.bmiHeader.biHeight = -h; + bi.bmiHeader.biPlanes = 1; + bi.bmiHeader.biBitCount = 32; + bi.bmiHeader.biCompression = BI_RGB; + + void* scratchBits = nullptr; + HBITMAP scratch = CreateDIBSection(screen, &bi, DIB_RGB_COLORS, &scratchBits, nullptr, 0); + if (!scratch) + { + DeleteDC(textDC); + ReleaseDC(nullptr, screen); + return; + } + + HGDIOBJ oldBmp = SelectObject(textDC, scratch); + HGDIOBJ oldFont = SelectObject(textDC, fontHandle); + RECT fill = { 0, 0, w, h }; + // Render white text on a black scratch; the luminance doubles as a coverage + // mask so the glyphs can be composited onto the translucent card without + // painting an opaque background box behind them. + HBRUSH bg = CreateSolidBrush(RGB(0, 0, 0)); + FillRect(textDC, &fill, bg); + DeleteObject(bg); + + int oldBk = SetBkMode(textDC, TRANSPARENT); + COLORREF oldColor = SetTextColor(textDC, RGB(255, 255, 255)); + + RECT textRc = fill; + DrawTextW(textDC, text.c_str(), static_cast(text.size()), &textRc, + alignFlag | DT_SINGLELINE | DT_VCENTER | DT_END_ELLIPSIS | DT_NOPREFIX); + + SetTextColor(textDC, oldColor); + SetBkMode(textDC, oldBk); + SelectObject(textDC, oldFont); + + const int tb = GetBValue(textColor); + const int tg = GetGValue(textColor); + const int tr = GetRValue(textColor); + const BYTE* src = static_cast(scratchBits); + for (int y = 0; y < h; ++y) + { + int dy = rc.top + y; + if (dy < 0 || dy >= destH) + continue; + for (int x = 0; x < w; ++x) + { + int dx = rc.left + x; + if (dx < 0 || dx >= destW) + continue; + + const BYTE* s = src + (static_cast(y) * w + x) * 4; + int a = (s[0] + s[1] + s[2] + 1) / 3; // glyph coverage 0..255 + if (a == 0) + continue; + + // Source-over onto premultiplied-alpha dest (BGRA). The text is opaque, + // so its premultiplied contribution is colour * coverage. + BYTE* d = destBits + (static_cast(dy) * destW + dx) * 4; + int inv = 255 - a; + d[0] = static_cast((tb * a + 127) / 255 + (d[0] * inv + 127) / 255); + d[1] = static_cast((tg * a + 127) / 255 + (d[1] * inv + 127) / 255); + d[2] = static_cast((tr * a + 127) / 255 + (d[2] * inv + 127) / 255); + d[3] = static_cast(a + (d[3] * inv + 127) / 255); + } + } + + SelectObject(textDC, oldBmp); + DeleteObject(scratch); + DeleteDC(textDC); + ReleaseDC(nullptr, screen); +} + +static COLORREF GetAccentColor() +{ + DWORD color = 0, size = sizeof(color), type = 0; + HKEY key = nullptr; + if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\DWM", + 0, KEY_QUERY_VALUE, &key) == ERROR_SUCCESS) + { + LONG r = RegQueryValueExW(key, L"AccentColor", nullptr, &type, + reinterpret_cast(&color), &size); + RegCloseKey(key); + if (r == ERROR_SUCCESS && type == REG_DWORD) + return color & 0x00FFFFFF; + } + DWORD argb = 0; + BOOL opaque = FALSE; + if (SUCCEEDED(DwmGetColorizationColor(&argb, &opaque))) + return RGB((argb >> 16) & 0xFF, (argb >> 8) & 0xFF, argb & 0xFF); + + return AltTabStyle::AccentFallbackRef(); +} + +void Switcher::RenderLayered() +{ + int w = ovW, h = ovH; + if (w <= 0 || h <= 0) + return; + + HDC screenDC = GetDC(nullptr); + HDC memDC = CreateCompatibleDC(screenDC); + + BITMAPINFO bi = {}; + bi.bmiHeader.biSize = sizeof(bi.bmiHeader); + bi.bmiHeader.biWidth = w; + bi.bmiHeader.biHeight = -h; + bi.bmiHeader.biPlanes = 1; + bi.bmiHeader.biBitCount = 32; + bi.bmiHeader.biCompression = BI_RGB; + + void* bits = nullptr; + HBITMAP dib = CreateDIBSection(screenDC, &bi, DIB_RGB_COLORS, &bits, nullptr, 0); + if (!dib) + { + DeleteDC(memDC); + ReleaseDC(nullptr, screenDC); + return; + } + HGDIOBJ oldBmp = SelectObject(memDC, dib); + ZeroMemory(bits, static_cast(w) * h * 4); + + { + Gdiplus::Bitmap bmp(w, h, w * 4, PixelFormat32bppPARGB, + static_cast(bits)); + Gdiplus::Graphics g(&bmp); + g.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); + g.SetTextRenderingHint(Gdiplus::TextRenderingHintAntiAliasGridFit); + + // Leave the panel background transparent; acrylic lives on thumbHost. + bool light = AltTabStyle::IsLightTheme(); + Gdiplus::REAL panelRadius = static_cast(Scaled(8)); + RECT panelRect = { 0, 0, w, h }; + Gdiplus::GraphicsPath panel; + BuildRoundRect(panel, InflateF(panelRect, 0), panelRadius); + Gdiplus::SolidBrush panelBrush(AltTabStyle::Transparent()); + g.SetCompositingMode(Gdiplus::CompositingModeSourceCopy); + g.FillPath(&panelBrush, &panel); + g.SetCompositingMode(Gdiplus::CompositingModeSourceOver); + Gdiplus::Pen panelStroke(AltTabStyle::SurfaceStrokeDefault(light), + static_cast((std::max)(1, Scaled(1)))); + g.DrawPath(&panelStroke, &panel); + + COLORREF accent = GetAccentColor(); + Gdiplus::Color accentClr = AltTabStyle::Accent(accent); + + const int pageEnd = (std::min)( + pageStart + overlayLayout.pageSize, + static_cast(windows.size())); + for (int windowIndex = pageStart, slot = 0; windowIndex < pageEnd; ++windowIndex, ++slot) + { + RECT tile = TileRect(slot); + bool sel = (windowIndex == selected); + RECT pv = PreviewRect(tile); + + // Full rounded card chrome. The live DWM preview is a rectangular + // viewport inside it, which avoids fighting public DWM's square thumbnail. + Gdiplus::GraphicsPath cardPath; + BuildRoundRect(cardPath, InflateF(tile, 0), static_cast(radius)); + Gdiplus::SolidBrush cardBrush(AltTabStyle::Card(light)); + g.FillPath(&cardBrush, &cardPath); + + g.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); + Gdiplus::Pen cardPen(AltTabStyle::CardStroke(light), + static_cast((std::max)(1, Scaled(1)))); + g.DrawPath(&cardPen, &cardPath); + + int pw = pv.right - pv.left; + int ph = pv.bottom - pv.top; + if (pw > 0 && ph > 0) + { + Gdiplus::RectF pvF( + static_cast(pv.left), + static_cast(pv.top), + static_cast(pw), + static_cast(ph)); + + // Bottom-rounded viewport for the DWM thumbnail (square top, rounded + // bottom corners matching the card radius). + Gdiplus::GraphicsPath pvHole; + BuildBottomRoundRect(pvHole, pvF, static_cast(radius)); + + // The DWM thumbnail is a square rectangle that lives on the acrylic + // host behind this layer. Lay an OPAQUE card backing across the whole + // preview (clipped to the rounded card so it can't spill past the + // card's corners), then punch the rounded-bottom hole. The thumbnail + // only shows through the rounded viewport; its square bottom corners + // stay hidden behind the opaque backing. (A translucent card alone + // would let those square corners bleed through.) + COLORREF cardSolid = AltTabStyle::CardRef(light); + Gdiplus::SolidBrush previewBacking(Gdiplus::Color( + 255, GetRValue(cardSolid), GetGValue(cardSolid), GetBValue(cardSolid))); + g.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); + g.SetClip(&cardPath, Gdiplus::CombineModeReplace); + g.SetCompositingMode(Gdiplus::CompositingModeSourceOver); + g.FillRectangle(&previewBacking, pvF); + + // Punch the rounded-bottom transparent hole; the acrylic host with the + // live thumbnail composites behind it. + g.SetCompositingMode(Gdiplus::CompositingModeSourceCopy); + Gdiplus::SolidBrush previewBrush(AltTabStyle::Transparent()); + g.FillPath(&previewBrush, &pvHole); + g.SetCompositingMode(Gdiplus::CompositingModeSourceOver); + g.ResetClip(); + } + + // Header tab: app icon + window title. + RECT hdr = HeaderRect(tile); + int textLeft = hdr.left; + HICON ic = (windowIndex < static_cast(icons.size())) ? icons[windowIndex] : nullptr; + if (ic) + { + int iy = tile.top + (headerH - iconSize) / 2; + g.Flush(); + DrawIconOverPARGB(bits, w, h, ic, hdr.left, iy, iconSize); + textLeft = hdr.left + iconSize + Scaled(8); + } + + const std::wstring* text = (windowIndex < static_cast(titles.size())) ? &titles[windowIndex] : nullptr; + if (text) + { + g.Flush(); + RECT textRc = { textLeft, tile.top, hdr.right, tile.top + headerH }; + DrawHeaderText(static_cast(bits), w, h, font, textRc, *text, + AltTabStyle::HeaderTextRef(light)); + } + + // Two-ring accent focus ring around the selected tile. + if (sel) + { + int gPad = Scaled(6); + int gOut = gPad + Scaled(2); + int outerRadius = Scaled(18); + int innerRadius = outerRadius - Scaled(2); + Gdiplus::GraphicsPath innerRing, out; + BuildRoundRect(innerRing, InflateF(tile, gPad), + static_cast(innerRadius)); + BuildRoundRect(out, InflateF(tile, gOut), + static_cast(outerRadius)); + Gdiplus::Pen darkPen(AltTabStyle::FocusShadow(light), + static_cast((std::max)(1, Scaled(1)))); + Gdiplus::Pen accentPen(accentClr, + static_cast((std::max)(2, Scaled(4)))); + g.DrawPath(&darkPen, &innerRing); + g.DrawPath(&accentPen, &out); + } + } + + // Pagination affordance: when the cycle set doesn't fit on one screenful, + // the panel is otherwise indistinguishable from "these are all my windows". + const int totalPages = AltWindowCycleLogic::PageCount( + static_cast(windows.size()), overlayLayout.pageSize); + if (totalPages > 1) + { + const int currentPage = (pageStart / (std::max)(1, overlayLayout.pageSize)) + 1; + const std::wstring pageText = + std::to_wstring(currentPage) + L" / " + std::to_wstring(totalPages); + RECT pageRc = { pad, h - pad, w - pad, h }; + DrawHeaderText(static_cast(bits), w, h, font, pageRc, pageText, + AltTabStyle::HeaderTextRef(light), DT_CENTER); + } + + g.Flush(); + } + + POINT ptDst = { ovX, ovY }; + SIZE sz = { w, h }; + POINT ptSrc = { 0, 0 }; + BLENDFUNCTION bf = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA }; + UpdateLayeredWindow(overlay, screenDC, &ptDst, &sz, memDC, &ptSrc, 0, &bf, ULW_ALPHA); + + SelectObject(memDC, oldBmp); + DeleteObject(dib); + DeleteDC(memDC); + ReleaseDC(nullptr, screenDC); +} + +LRESULT CALLBACK Switcher::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + Switcher* self = reinterpret_cast(GetWindowLongPtrW(hwnd, GWLP_USERDATA)); + if (self) + { + switch (msg) + { + case WM_TIMER: + if (wParam == TIMER_ID) + { + self->OnTick(); + return 0; + } + break; + case WM_ERASEBKGND: + return 1; + case WM_SETTINGCHANGE: + if (lParam && + lstrcmpiW(reinterpret_cast(lParam), L"ImmersiveColorSet") == 0) + { + self->OnThemeChanged(); + return 0; + } + break; + } + } + return DefWindowProcW(hwnd, msg, wParam, lParam); +} + +// =================== Dedicated UI thread ===================================== + +static Switcher g_switcher; +static HANDLE g_uiThread = nullptr; +static std::atomic g_initOk{ false }; +static std::atomic g_shutdownRequested{ false }; +static HINSTANCE g_threadHinst = nullptr; +static HANDLE g_threadReadyEvent = nullptr; + +// The runner calls enable()/disable()/on_hotkey() on the same thread that services +// the centralized WH_KEYBOARD_LL hook, and Windows silently drops hooks that exceed +// LowLevelHooksTimeout (300 ms by default). Every wait on that thread stays well +// under that budget; only destroy(), which is followed by FreeLibrary, joins +// without a bound. +static const DWORD UIThreadInitWaitMs = 200; +static const DWORD UIThreadJoinTimeoutMs = 200; +static const DWORD UIThreadJoinPollMs = 20; + +static void CloseUIThreadHandle() +{ + CloseHandle(g_uiThread); + g_uiThread = nullptr; + g_uiThreadId = 0; + g_threadHinst = nullptr; + g_initOk.store(false); +} + +static void ClearExitedUIThread() +{ + if (g_uiThread && WaitForSingleObject(g_uiThread, 0) == WAIT_OBJECT_0) + { + CloseUIThreadHandle(); + } +} + +// Asks the UI thread to quit and waits up to `timeoutMs` for it to exit. Returns +// true when the thread is gone and the globals have been reset; false means the +// thread is still winding down and the handle was deliberately left open. +static bool RequestShutdownAndJoinUIThread(DWORD timeoutMs) +{ + if (!g_uiThread) + { + return true; + } + + g_shutdownRequested.store(true); + + const bool bounded = timeoutMs != INFINITE; + const ULONGLONG deadline = bounded ? GetTickCount64() + timeoutMs : 0; + + // The queue may not exist yet when initialization times out, so WM_QUIT is + // re-posted until the bounded UI-thread work completes and the thread exits. + for (;;) + { + if (g_uiThreadId) + { + PostThreadMessageW(g_uiThreadId, WM_QUIT, 0, 0); + } + + DWORD slice = UIThreadJoinPollMs; + if (bounded) + { + const ULONGLONG now = GetTickCount64(); + if (now >= deadline) + { + return false; + } + + slice = static_cast((std::min)(UIThreadJoinPollMs, deadline - now)); + } + + if (WaitForSingleObject(g_uiThread, slice) == WAIT_OBJECT_0) + { + break; + } + } + + CloseUIThreadHandle(); + return true; +} + +static bool PostHotkeyToUIThread(bool forward, unsigned int holdModifiers) +{ + return g_uiThreadId && + PostThreadMessageW(g_uiThreadId, WM_AWC_HOTKEY, forward ? 1u : 0u, static_cast(holdModifiers)) != FALSE; +} + +static DWORD WINAPI UIThreadProc(LPVOID param) +{ + UNREFERENCED_PARAMETER(param); + + HINSTANCE hinst = g_threadHinst; + HANDLE readyEvent = g_threadReadyEvent; + g_threadReadyEvent = nullptr; + + // Set per-monitor v2 DPI awareness on this thread so overlay layout and DWM + // thumbnail rects line up on any monitor. Does not affect the host process. + SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + + Gdiplus::GdiplusStartupInput gdiplusStartupInput = {}; + ULONG_PTR token = 0; + Gdiplus::GdiplusStartup(&token, &gdiplusStartupInput, nullptr); + + const bool initOk = g_switcher.Init(hinst); + g_initOk.store(initOk); + + // Signal the caller that initialization is complete (success or failure). + if (readyEvent) + { + SetEvent(readyEvent); + CloseHandle(readyEvent); + } + + if (!initOk || g_shutdownRequested.load()) + { + g_switcher.Shutdown(); + if (token) + Gdiplus::GdiplusShutdown(token); + return initOk ? 0 : 1; + } + + MSG msg; + while (GetMessageW(&msg, nullptr, 0, 0) > 0) + { + if (msg.hwnd == nullptr && msg.message == WM_AWC_HOTKEY) + { + g_switcher.OnHotkey(msg.wParam != 0, static_cast(msg.lParam)); + } + else if (msg.hwnd == nullptr && msg.message == WM_AWC_CANCEL) + { + g_switcher.OnCancel(); + } + else + { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + } + + // Destroy all overlay resources BEFORE GdiplusShutdown. + g_switcher.Shutdown(); + + if (token) + Gdiplus::GdiplusShutdown(token); + + return 0; +} + +// =================== Public module API ======================================= + +bool InitializeAltWindowCycle(HINSTANCE hinst) +{ + ClearExitedUIThread(); + + // A previous disable() may have left a thread winding down. Give it a short, + // bounded chance to finish rather than racing a second UI thread onto the + // single global Switcher. + if (g_uiThread != nullptr && g_shutdownRequested.load()) + { + RequestShutdownAndJoinUIThread(UIThreadJoinTimeoutMs); + } + + if (g_uiThread != nullptr) + { + if (g_shutdownRequested.load()) + { + Logger::error("AltWindowCycle UI thread from a previous session is still running; enable aborted"); + return false; + } + + return true; // already running or still starting + } + + g_initOk.store(false); + g_shutdownRequested.store(false); + + HANDLE readyEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); + if (!readyEvent) + return false; + + HANDLE readyEventForThread = nullptr; + if (!DuplicateHandle(GetCurrentProcess(), readyEvent, GetCurrentProcess(), &readyEventForThread, 0, FALSE, DUPLICATE_SAME_ACCESS)) + { + CloseHandle(readyEvent); + return false; + } + + g_threadHinst = hinst; + g_threadReadyEvent = readyEventForThread; + + DWORD tid = 0; + g_uiThread = CreateThread(nullptr, 0, UIThreadProc, nullptr, 0, &tid); + if (!g_uiThread) + { + g_threadReadyEvent = nullptr; + CloseHandle(readyEventForThread); + CloseHandle(readyEvent); + return false; + } + g_uiThreadId = tid; + + // Briefly wait for Init() so an outright failure is reported synchronously. A + // timeout is not fatal: the thread keeps starting and the first hotkey simply + // arrives once its message queue exists. + const DWORD waitResult = WaitForSingleObject(readyEvent, UIThreadInitWaitMs); + CloseHandle(readyEvent); + if (waitResult == WAIT_OBJECT_0 && !g_initOk.load()) + { + RequestShutdownAndJoinUIThread(UIThreadJoinTimeoutMs); + return false; + } + + return true; +} + +void ShutdownAltWindowCycle(bool blockUntilExit) +{ + if (!RequestShutdownAndJoinUIThread(blockUntilExit ? INFINITE : UIThreadJoinTimeoutMs)) + { + Logger::warn("AltWindowCycle UI thread did not exit within the disable() budget; it will finish asynchronously"); + } +} + +bool HandleAltWindowCycleHotkey(bool forward, unsigned int holdModifiers) +{ + ClearExitedUIThread(); + if (!g_uiThread || g_shutdownRequested.load()) + return false; + + return PostHotkeyToUIThread(forward, holdModifiers); +} diff --git a/src/modules/AltWindowCycle/AltWindowCycle.h b/src/modules/AltWindowCycle/AltWindowCycle.h new file mode 100644 index 0000000000..db18b289f4 --- /dev/null +++ b/src/modules/AltWindowCycle/AltWindowCycle.h @@ -0,0 +1,21 @@ +#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). +// +// The runner drives enable()/disable() on the same thread that services the +// centralized low-level keyboard hook, so disable() must not stall it: pass +// `blockUntilExit = false` there and let the UI thread finish on its own. Pass +// `blockUntilExit = true` only from destroy(), where the runner unloads the DLL +// afterwards and the thread must be gone first. +void ShutdownAltWindowCycle(bool blockUntilExit); + +// Called from on_hotkey() on the runner thread. Posts to the UI thread without +// enumerating windows. `holdModifiers` is an AltWindowCycleLogic modifier mask +// that controls which modifier release commits the visible cycle. +bool HandleAltWindowCycleHotkey(bool forward, unsigned int holdModifiers); diff --git a/src/modules/AltWindowCycle/AltWindowCycle.rc b/src/modules/AltWindowCycle/AltWindowCycle.rc new file mode 100644 index 0000000000..e9b85fafdf --- /dev/null +++ b/src/modules/AltWindowCycle/AltWindowCycle.rc @@ -0,0 +1,40 @@ +#include +#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 diff --git a/src/modules/AltWindowCycle/AltWindowCycle.vcxproj b/src/modules/AltWindowCycle/AltWindowCycle.vcxproj new file mode 100644 index 0000000000..008499528e --- /dev/null +++ b/src/modules/AltWindowCycle/AltWindowCycle.vcxproj @@ -0,0 +1,130 @@ + + + + + + 15.0 + {4d1d41c7-e22b-4a8b-8805-660a100e1d79} + Win32Proj + AltWindowCycle + AltWindowCycle + + + + DynamicLibrary + true + Unicode + + + DynamicLibrary + false + true + Unicode + + + + + + + + + + + + $(RepoRoot)$(Platform)\$(Configuration)\ + PowerToys.AltWindowCycle + + + true + + + false + + + + Level3 + Disabled + true + _DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + MultiThreadedDebug + stdcpplatest + Use + pch.h + + + Windows + true + $(OutDir)$(TargetName)$(TargetExt) + dwmapi.lib;gdi32.lib;gdiplus.lib;shcore.lib;%(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + true + NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + MultiThreaded + stdcpplatest + Use + pch.h + + + Windows + true + true + true + $(OutDir)$(TargetName)$(TargetExt) + dwmapi.lib;gdi32.lib;gdiplus.lib;shcore.lib;%(AdditionalDependencies) + + + + + $(RepoRoot)src\;$(RepoRoot)src\modules;$(RepoRoot)src\common\Telemetry;%(AdditionalIncludeDirectories) + + + + + + + + + + + + + + Create + + + + + + + + + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + + + {6955446d-23f7-4023-9bb3-8657f904af99} + + + + + + + + + + + + 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}. + + + + + diff --git a/src/modules/AltWindowCycle/AltWindowCycle.vcxproj.filters b/src/modules/AltWindowCycle/AltWindowCycle.vcxproj.filters new file mode 100644 index 0000000000..2470812045 --- /dev/null +++ b/src/modules/AltWindowCycle/AltWindowCycle.vcxproj.filters @@ -0,0 +1,56 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Resource Files + + + + + + diff --git a/src/modules/AltWindowCycle/AltWindowCycleLogic.h b/src/modules/AltWindowCycle/AltWindowCycleLogic.h new file mode 100644 index 0000000000..3e88c1b110 --- /dev/null +++ b/src/modules/AltWindowCycle/AltWindowCycleLogic.h @@ -0,0 +1,295 @@ +#pragma once + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +#include +#include +#include +#include + +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 pageSize = 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(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; + } + + const int totalRows = (safeWindowCount + layout.cols - 1) / layout.cols; + const int rowsFromWork = (std::max)(1, (workH - 2 * layout.pad + layout.gap) / (layout.tileH + layout.gap)); + layout.rows = (std::min)(totalRows, rowsFromWork); + layout.pageSize = (std::min)(safeWindowCount, layout.cols * layout.rows); + layout.panelW = 2 * layout.pad + layout.cols * layout.tileW + (layout.cols - 1) * layout.gap; + layout.panelH = 2 * layout.pad + layout.rows * layout.tileH + (std::max)(0, layout.rows - 1) * layout.gap; + layout.panelX = work.left + (workW - layout.panelW) / 2; + layout.panelY = work.top + (workH - layout.panelH) / 2; + if (layout.panelX < work.left) + { + layout.panelX = work.left; + } + if (layout.panelY < work.top) + { + layout.panelY = work.top; + } + + return layout; + } + + inline int PageStartForSelection(int selectedIndex, int windowCount, int pageSize) + { + if (windowCount <= 0 || pageSize <= 0) + { + return 0; + } + + const int normalizedIndex = (std::min)((std::max)(0, selectedIndex), windowCount - 1); + return (normalizedIndex / pageSize) * pageSize; + } + + // Number of fixed-size pages the cycle set is split across. 0 when there is + // nothing to page; 1 means every window fits on screen at once. + inline int PageCount(int windowCount, int pageSize) + { + if (windowCount <= 0 || pageSize <= 0) + { + return 0; + } + + return (windowCount + pageSize - 1) / pageSize; + } + + 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(dw) / dh; + const double srcA = static_cast(aw) / ah; + if (srcA > destA) + { + const int cw = (std::max)(1, static_cast(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(aw / destA + 0.5)); + const int y = avail.top + (ah - ch) / 2; + return { avail.left, y, avail.right, y + ch }; + } + + // =================== Same-process window selection =================== + // + // The live module queries Win32 for each window's state; these helpers own the + // pure decision + ordering logic so the Alt-Tab cycle set can be unit-tested + // without a live desktop. + + struct WindowEligibility + { + bool isVisible = false; + bool isCloaked = false; + // True when the window is the representative window for its owner chain, i.e. + // the GetLastActivePopup walk from its root owner lands back on the window + // itself. Owned secondary windows (whose owner is visible) are not. + bool isAltTabRepresentative = false; + bool isToolWindow = false; + bool isAppWindow = false; + }; + + // Mirrors the classic "IsAltTabWindow" predicate: a window participates in the + // cycle only when it is visible, not cloaked, the representative window of its + // owner chain, and either not a tool window or explicitly opted in as an app window. + constexpr bool IsAltTabEligible(const WindowEligibility& window) + { + return window.isVisible && + !window.isCloaked && + window.isAltTabRepresentative && + (!window.isToolWindow || window.isAppWindow); + } + + struct CandidateWindow + { + unsigned long long id = 0; + WindowEligibility eligibility; + // Resolved owning-process image path (UWP windows resolved to the real app, + // not ApplicationFrameHost). Empty when it could not be determined. + std::wstring processKey; + }; + + // Case-insensitive process-key match, mirroring the live _wcsicmp grouping. An + // empty candidate key never matches. + inline bool ProcessKeyEquals(const std::wstring& candidateKey, const std::wstring& foregroundKey) + { + return !candidateKey.empty() && _wcsicmp(candidateKey.c_str(), foregroundKey.c_str()) == 0; + } + + // Given the foreground app's process key and the windows enumerated in Z-order + // (top-most first == MRU), return the ids that make up the Alt-Tab cycle set for + // that app, preserving enumeration order. Ineligible windows (invisible, cloaked, + // owned, or tool-only) and windows from other processes are dropped. Returns empty + // when the foreground key is unknown. + inline std::vector SelectCycleWindows( + const std::wstring& foregroundProcessKey, + const std::vector& enumeratedInZOrder) + { + std::vector result; + if (foregroundProcessKey.empty()) + { + return result; + } + + for (const auto& candidate : enumeratedInZOrder) + { + if (!IsAltTabEligible(candidate.eligibility)) + { + continue; + } + + if (ProcessKeyEquals(candidate.processKey, foregroundProcessKey)) + { + result.push_back(candidate.id); + } + } + + return result; + } +} diff --git a/src/modules/AltWindowCycle/UnitTests/AltWindowCycleLogicTests.cpp b/src/modules/AltWindowCycle/UnitTests/AltWindowCycleLogicTests.cpp new file mode 100644 index 0000000000..3140fd0f58 --- /dev/null +++ b/src/modules/AltWindowCycle/UnitTests/AltWindowCycleLogicTests.cpp @@ -0,0 +1,424 @@ +#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"); + } + + AltWindowCycleLogic::CandidateWindow MakeCandidate( + unsigned long long id, + const std::wstring& processKey, + bool visible = true, + bool cloaked = false, + bool representative = true, + bool toolWindow = false, + bool appWindow = false) + { + AltWindowCycleLogic::CandidateWindow candidate; + candidate.id = id; + candidate.processKey = processKey; + candidate.eligibility.isVisible = visible; + candidate.eligibility.isCloaked = cloaked; + candidate.eligibility.isAltTabRepresentative = representative; + candidate.eligibility.isToolWindow = toolWindow; + candidate.eligibility.isAppWindow = appWindow; + return candidate; + } + + void AssertIds(std::initializer_list expected, const std::vector& actual) + { + Assert::AreEqual(static_cast(expected.size()), static_cast(actual.size()), L"cycle set size"); + size_t i = 0; + for (const unsigned long long id : expected) + { + Assert::IsTrue(id == actual[i], L"cycle set id/order mismatch"); + ++i; + } + } + } + + 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(4, layout.pageSize); + 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(6, layout.pageSize); + 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); + Assert::AreEqual(0, layout.pageSize); + } + + 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); + } + + // =================== Same-process window selection =================== + + TEST_METHOD(IsAltTabEligibleAcceptsVisibleRepresentativeNonToolWindow) + { + Assert::IsTrue(AltWindowCycleLogic::IsAltTabEligible({ true, false, true, false })); + } + + TEST_METHOD(IsAltTabEligibleRejectsEachDisqualifyingCondition) + { + // Invisible. + Assert::IsFalse(AltWindowCycleLogic::IsAltTabEligible({ false, false, true, false })); + // Cloaked (e.g. a background virtual-desktop window). + Assert::IsFalse(AltWindowCycleLogic::IsAltTabEligible({ true, true, true, false })); + // Owned / not the representative window of its owner chain. + Assert::IsFalse(AltWindowCycleLogic::IsAltTabEligible({ true, false, false, false })); + // Tool window. + Assert::IsFalse(AltWindowCycleLogic::IsAltTabEligible({ true, false, true, true })); + } + + TEST_METHOD(IsAltTabEligibleAcceptsToolWindowOptedInAsAppWindow) + { + Assert::IsTrue(AltWindowCycleLogic::IsAltTabEligible({ true, false, true, true, true })); + } + + TEST_METHOD(ProcessKeyEqualsIsCaseInsensitiveAndRejectsEmptyCandidate) + { + Assert::IsTrue(AltWindowCycleLogic::ProcessKeyEquals(L"C:\\Apps\\Foo.exe", L"c:\\apps\\foo.EXE")); + Assert::IsFalse(AltWindowCycleLogic::ProcessKeyEquals(L"C:\\Apps\\Foo.exe", L"C:\\Apps\\Bar.exe")); + Assert::IsFalse(AltWindowCycleLogic::ProcessKeyEquals(L"", L"C:\\Apps\\Foo.exe")); + } + + TEST_METHOD(SelectCycleWindowsKeepsOnlyForegroundProcessInEnumerationOrder) + { + const std::wstring fg = L"C:\\Apps\\Editor.exe"; + const std::vector candidates = { + MakeCandidate(10, L"C:\\Apps\\Editor.exe"), + MakeCandidate(20, L"C:\\Apps\\Browser.exe"), + MakeCandidate(30, L"c:\\apps\\editor.EXE"), // same app, different casing + MakeCandidate(40, L"C:\\Apps\\Browser.exe"), + MakeCandidate(50, L"C:\\Apps\\Editor.exe"), + }; + + AssertIds({ 10, 30, 50 }, AltWindowCycleLogic::SelectCycleWindows(fg, candidates)); + } + + TEST_METHOD(SelectCycleWindowsExcludesIneligibleWindowsOfTheSameApp) + { + const std::wstring fg = L"C:\\Apps\\Editor.exe"; + const std::vector candidates = { + MakeCandidate(1, fg), // eligible + MakeCandidate(2, fg, /*visible*/ false), + MakeCandidate(3, fg, /*visible*/ true, /*cloaked*/ true), + MakeCandidate(4, fg, /*visible*/ true, /*cloaked*/ false, /*representative*/ false), + MakeCandidate(5, fg, /*visible*/ true, /*cloaked*/ false, /*representative*/ true, /*tool*/ true), + MakeCandidate(6, fg), // eligible + }; + + AssertIds({ 1, 6 }, AltWindowCycleLogic::SelectCycleWindows(fg, candidates)); + } + + TEST_METHOD(SelectCycleWindowsReturnsEmptyWhenForegroundKeyUnknown) + { + const std::vector candidates = { + MakeCandidate(1, L"C:\\Apps\\Editor.exe"), + }; + + Assert::AreEqual(0u, static_cast(AltWindowCycleLogic::SelectCycleWindows(L"", candidates).size())); + } + + TEST_METHOD(SelectCycleWindowsHandlesZeroAndSingleCandidate) + { + const std::wstring fg = L"C:\\Apps\\Editor.exe"; + + Assert::AreEqual(0u, static_cast(AltWindowCycleLogic::SelectCycleWindows(fg, {}).size())); + + const std::vector single = { MakeCandidate(99, fg) }; + AssertIds({ 99 }, AltWindowCycleLogic::SelectCycleWindows(fg, single)); + } + + TEST_METHOD(SelectCycleWindowsIncludesEveryWindowOfATiedProcess) + { + // Several windows of the same app (the real grouping key is the resolved + // process image path, so multiple windows / duplicate pids all cycle). + const std::wstring fg = L"C:\\Apps\\Editor.exe"; + const std::vector candidates = { + MakeCandidate(7, fg), + MakeCandidate(8, fg), + MakeCandidate(9, fg), + }; + + AssertIds({ 7, 8, 9 }, AltWindowCycleLogic::SelectCycleWindows(fg, candidates)); + } + + TEST_METHOD(SelectCycleWindowsDropsCandidatesWithNoResolvedProcess) + { + const std::wstring fg = L"C:\\Apps\\Editor.exe"; + const std::vector candidates = { + MakeCandidate(1, fg), + MakeCandidate(2, L""), // eligible flags but process could not be resolved + MakeCandidate(3, fg), + }; + + AssertIds({ 1, 3 }, AltWindowCycleLogic::SelectCycleWindows(fg, candidates)); + } + + // =================== Layout / index edge cases =================== + + TEST_METHOD(ComputeOverlayLayoutClampsColumnsToWindowCount) + { + const RECT work = { 0, 0, 1920, 1080 }; + + const auto layout = AltWindowCycleLogic::ComputeOverlayLayout(work, 1, 1.0); + + Assert::AreEqual(1, layout.cols); + Assert::AreEqual(1, layout.rows); + } + + TEST_METHOD(ComputeOverlayLayoutRespectsMaxColumns) + { + const RECT work = { 0, 0, 1920, 1080 }; + + const auto layout = AltWindowCycleLogic::ComputeOverlayLayout(work, 10, 1.0, 3); + + Assert::AreEqual(3, layout.cols); + Assert::AreEqual(4, layout.rows); // ceil(10 / 3) + } + + TEST_METHOD(ComputeOverlayLayoutClampsColumnsAndRowsToNarrowWorkArea) + { + const RECT work = { 0, 0, 640, 480 }; + + const auto layout = AltWindowCycleLogic::ComputeOverlayLayout(work, 8, 1.0); + + // (640 - 64 + 26) / (270 + 26) = 2 columns fit the work area. + Assert::AreEqual(2, layout.cols); + Assert::AreEqual(1, layout.rows); + Assert::AreEqual(2, layout.pageSize); + Assert::IsTrue(layout.panelH <= work.bottom - work.top); + } + + TEST_METHOD(ComputeOverlayLayoutScalesPaddingAcrossDpi) + { + const RECT work = { 0, 0, 1920, 1080 }; + + Assert::AreEqual(32, AltWindowCycleLogic::ComputeOverlayLayout(work, 4, 1.0).pad); + Assert::AreEqual(40, AltWindowCycleLogic::ComputeOverlayLayout(work, 4, 1.25).pad); + Assert::AreEqual(48, AltWindowCycleLogic::ComputeOverlayLayout(work, 4, 1.5).pad); + Assert::AreEqual(64, AltWindowCycleLogic::ComputeOverlayLayout(work, 4, 2.0).pad); + } + + TEST_METHOD(ComputeOverlayLayoutPaginatesRowsThatDoNotFitWorkArea) + { + const RECT work = { 0, 0, 1920, 1080 }; + + const auto layout = AltWindowCycleLogic::ComputeOverlayLayout(work, 13, 1.5); + + Assert::AreEqual(4, layout.cols); + Assert::AreEqual(2, layout.rows); + Assert::AreEqual(8, layout.pageSize); + Assert::IsTrue(layout.panelH <= work.bottom - work.top); + } + + TEST_METHOD(PageStartForSelectionKeepsSelectedWindowOnPage) + { + Assert::AreEqual(0, AltWindowCycleLogic::PageStartForSelection(0, 13, 8)); + Assert::AreEqual(0, AltWindowCycleLogic::PageStartForSelection(7, 13, 8)); + Assert::AreEqual(8, AltWindowCycleLogic::PageStartForSelection(8, 13, 8)); + Assert::AreEqual(8, AltWindowCycleLogic::PageStartForSelection(12, 13, 8)); + Assert::AreEqual(0, AltWindowCycleLogic::PageStartForSelection(-1, 13, 8)); + Assert::AreEqual(8, AltWindowCycleLogic::PageStartForSelection(13, 13, 8)); + Assert::AreEqual(0, AltWindowCycleLogic::PageStartForSelection(0, 0, 0)); + } + + TEST_METHOD(PageCountReportsPagesForPaginationAffordance) + { + Assert::AreEqual(1, AltWindowCycleLogic::PageCount(1, 8)); + Assert::AreEqual(1, AltWindowCycleLogic::PageCount(8, 8)); + Assert::AreEqual(2, AltWindowCycleLogic::PageCount(9, 8)); + Assert::AreEqual(2, AltWindowCycleLogic::PageCount(13, 8)); + Assert::AreEqual(3, AltWindowCycleLogic::PageCount(17, 8)); + Assert::AreEqual(0, AltWindowCycleLogic::PageCount(0, 8)); + Assert::AreEqual(0, AltWindowCycleLogic::PageCount(13, 0)); + } + + TEST_METHOD(TileRectAdvancesAcrossColumnsAndRows) + { + const RECT work = { 0, 0, 1920, 1080 }; + const auto layout = AltWindowCycleLogic::ComputeOverlayLayout(work, 8, 1.0); + + Assert::AreEqual(6, layout.cols); // sanity: second row exists + + // Second column, first row. + AssertRectEqual({ 328, 32, 598, 234 }, AltWindowCycleLogic::TileRect(layout, 1)); + // First column, second row. + AssertRectEqual({ 32, 260, 302, 462 }, AltWindowCycleLogic::TileRect(layout, 6)); + } + + TEST_METHOD(WrapIndexNormalizesNegativeAndOversizedIndices) + { + Assert::AreEqual(2, AltWindowCycleLogic::WrapIndex(-4, 3)); + Assert::AreEqual(1, AltWindowCycleLogic::WrapIndex(7, 3)); + Assert::AreEqual(0, AltWindowCycleLogic::WrapIndex(0, 5)); + Assert::AreEqual(0, AltWindowCycleLogic::WrapIndex(-3, 3)); + } + + TEST_METHOD(BeginCycleClampsNegativeCurrentIndex) + { + const auto forward = AltWindowCycleLogic::BeginCycle(-1, 3, true); + Assert::AreEqual(1, forward.selected); + Assert::IsTrue(forward.action == AltWindowCycleLogic::FirstHotkeyAction::ShowOverlay); + + const auto backward = AltWindowCycleLogic::BeginCycle(-5, 3, false); + Assert::AreEqual(2, backward.selected); + } + + TEST_METHOD(StableHoldModifiersStripsUnknownBitsAndPrefersNonShift) + { + const unsigned int expected = + AltWindowCycleLogic::ModifierAlt | AltWindowCycleLogic::ModifierCtrl | AltWindowCycleLogic::ModifierWin; + Assert::AreEqual(expected, AltWindowCycleLogic::StableHoldModifiers(0xFFu)); + } + }; +} diff --git a/src/modules/AltWindowCycle/UnitTests/AltWindowCycleUnitTests.vcxproj b/src/modules/AltWindowCycle/UnitTests/AltWindowCycleUnitTests.vcxproj new file mode 100644 index 0000000000..0702920436 --- /dev/null +++ b/src/modules/AltWindowCycle/UnitTests/AltWindowCycleUnitTests.vcxproj @@ -0,0 +1,49 @@ + + + + + 17.0 + {9EBE5C3A-4A37-477E-9176-07C7CF0EBA6E} + Win32Proj + AltWindowCycleUnitTests + NativeUnitTestProject + AltWindowCycle.UnitTests + + + DynamicLibrary + false + false + + + + + + + + + + + + $(RepoRoot)$(Platform)\$(Configuration)\tests\AltWindowCycle\ + + + + ..;$(RepoRoot)src\;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) + WIN32;%(PreprocessorDefinitions) + true + NotUsing + + + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + + + + + + + + + + + + diff --git a/src/modules/AltWindowCycle/UnitTests/AltWindowCycleUnitTests.vcxproj.filters b/src/modules/AltWindowCycle/UnitTests/AltWindowCycleUnitTests.vcxproj.filters new file mode 100644 index 0000000000..7c49eebe84 --- /dev/null +++ b/src/modules/AltWindowCycle/UnitTests/AltWindowCycleUnitTests.vcxproj.filters @@ -0,0 +1,23 @@ + + + + + {1345DADF-2D71-454C-A6D9-B9698E920E1A} + cpp;c;cc;cxx + + + {8439D5F4-E4A2-4A72-A6D7-579E2A8D1260} + h;hh;hpp;hxx + + + + + Source Files + + + + + Header Files + + + diff --git a/src/modules/AltWindowCycle/dllmain.cpp b/src/modules/AltWindowCycle/dllmain.cpp new file mode 100644 index 0000000000..e4ec851151 --- /dev/null +++ b/src/modules/AltWindowCycle/dllmain.cpp @@ -0,0 +1,305 @@ +// dllmain.cpp : Defines the entry point for the DLL application. +#include "pch.h" + +#include +#include +#include + +#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; + + // The configurable hotkeys stay in Settings UI order. Escape is handled by a + // scoped low-level hook inside the module while the overlay is visible, so it + // deliberately does not appear here. + enum HotkeyId : size_t + { + HotkeyNext = 0, + HotkeyPrevious, + HotkeyCount + }; + + 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); + } + + // The overlay commits when the held modifier is released, so a shortcut with no + // modifier at all would flash the overlay and immediately dismiss it. Settings UI + // won't produce one, but a hand-edited settings.json can. + static bool HasModifier(const PowertoyModuleIface::Hotkey& hotkey) + { + return hotkey.win || hotkey.ctrl || hotkey.shift || hotkey.alt; + } +} + +// 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(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 + { + // The runner unloads this DLL right after destroy() returns, so the UI + // thread must be fully gone before the module code disappears. + ShutdownAltWindowCycle(true); // 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(&__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(&__ImageBase)); + if (m_enabled) + { + Trace::EnableAltWindowCycle(true); + } + } + + virtual void disable() override + { + m_enabled = false; + Trace::EnableAltWindowCycle(false); + + // disable() runs on the runner thread that services the centralized + // low-level keyboard hook, so it must never block on the UI thread. + ShutdownAltWindowCycle(false); + } + + virtual bool is_enabled() override + { + return m_enabled; + } + + virtual bool is_enabled_by_default() const override + { + return false; + } + + virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override + { + if (hotkeys && buffer_size >= HotkeyCount) + { + hotkeys[HotkeyNext] = m_nextHotkey; + hotkeys[HotkeyPrevious] = m_previousHotkey; + } + + return HotkeyCount; + } + + virtual bool on_hotkey(size_t hotkeyId) override + { + if (!m_enabled) + { + return false; + } + + if (hotkeyId == HotkeyNext) + { + Trace::CycleWindow(true); + return HandleAltWindowCycleHotkey(true, ModifierMaskFromHotkey(m_nextHotkey)); + } + + if (hotkeyId == HotkeyPrevious) + { + 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 || !HasModifier(m_nextHotkey)) + { + 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 || !HasModifier(m_previousHotkey)) + { + 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(); +} diff --git a/src/modules/AltWindowCycle/packages.config b/src/modules/AltWindowCycle/packages.config new file mode 100644 index 0000000000..97349a856f --- /dev/null +++ b/src/modules/AltWindowCycle/packages.config @@ -0,0 +1,4 @@ + + + + diff --git a/src/modules/AltWindowCycle/pch.cpp b/src/modules/AltWindowCycle/pch.cpp new file mode 100644 index 0000000000..1d9f38c57d --- /dev/null +++ b/src/modules/AltWindowCycle/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/src/modules/AltWindowCycle/pch.h b/src/modules/AltWindowCycle/pch.h new file mode 100644 index 0000000000..6cca81757f --- /dev/null +++ b/src/modules/AltWindowCycle/pch.h @@ -0,0 +1,15 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include diff --git a/src/modules/AltWindowCycle/resource.h b/src/modules/AltWindowCycle/resource.h new file mode 100644 index 0000000000..2c37d4ef03 --- /dev/null +++ b/src/modules/AltWindowCycle/resource.h @@ -0,0 +1,13 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by AltWindowCycle.rc + +////////////////////////////// +// Non-localizable + +#define FILE_DESCRIPTION "PowerToys Window Hopper" +#define INTERNAL_NAME "AltWindowCycle" +#define ORIGINAL_FILENAME "PowerToys.AltWindowCycle.dll" + +// Non-localizable +////////////////////////////// diff --git a/src/modules/AltWindowCycle/trace.cpp b/src/modules/AltWindowCycle/trace.cpp new file mode 100644 index 0000000000..3668901de9 --- /dev/null +++ b/src/modules/AltWindowCycle/trace.cpp @@ -0,0 +1,33 @@ +#include "pch.h" +#include "trace.h" + +#include + +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")); +} diff --git a/src/modules/AltWindowCycle/trace.h b/src/modules/AltWindowCycle/trace.h new file mode 100644 index 0000000000..67b943ba9b --- /dev/null +++ b/src/modules/AltWindowCycle/trace.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +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; +}; diff --git a/src/runner/main.cpp b/src/runner/main.cpp index 1a132ad816..dd7a480835 100644 --- a/src/runner/main.cpp +++ b/src/runner/main.cpp @@ -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) diff --git a/src/settings-ui/QuickAccess.UI/Helpers/ModuleGpoHelper.cs b/src/settings-ui/QuickAccess.UI/Helpers/ModuleGpoHelper.cs index bfde5227c2..55fd433bd3 100644 --- a/src/settings-ui/QuickAccess.UI/Helpers/ModuleGpoHelper.cs +++ b/src/settings-ui/QuickAccess.UI/Helpers/ModuleGpoHelper.cs @@ -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(), diff --git a/src/settings-ui/Settings.UI.Library/AltWindowCycleProperties.cs b/src/settings-ui/Settings.UI.Library/AltWindowCycleProperties.cs new file mode 100644 index 0000000000..ddf6635055 --- /dev/null +++ b/src/settings-ui/Settings.UI.Library/AltWindowCycleProperties.cs @@ -0,0 +1,47 @@ +// 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); + + private HotkeySettings _nextWindowShortcut; + + private HotkeySettings _previousWindowShortcut; + + [JsonPropertyName("next_window_shortcut")] + public HotkeySettings NextWindowShortcut + { + get => _nextWindowShortcut ?? DefaultNextWindowShortcut; + set => _nextWindowShortcut = value; + } + + [JsonPropertyName("previous_window_shortcut")] + public HotkeySettings PreviousWindowShortcut + { + get => _previousWindowShortcut ?? DefaultPreviousWindowShortcut; + set => _previousWindowShortcut = value; + } + + public AltWindowCycleProperties() + { + NextWindowShortcut = DefaultNextWindowShortcut; + PreviousWindowShortcut = DefaultPreviousWindowShortcut; + } + } +} diff --git a/src/settings-ui/Settings.UI.Library/AltWindowCycleSettings.cs b/src/settings-ui/Settings.UI.Library/AltWindowCycleSettings.cs new file mode 100644 index 0000000000..790f6dec21 --- /dev/null +++ b/src/settings-ui/Settings.UI.Library/AltWindowCycleSettings.cs @@ -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 + { + 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; + } + } +} diff --git a/src/settings-ui/Settings.UI.Library/EnabledModules.cs b/src/settings-ui/Settings.UI.Library/EnabledModules.cs index 2680ae262c..15527cdc6f 100644 --- a/src/settings-ui/Settings.UI.Library/EnabledModules.cs +++ b/src/settings-ui/Settings.UI.Library/EnabledModules.cs @@ -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")] diff --git a/src/settings-ui/Settings.UI.Library/Helpers/ModuleHelper.cs b/src/settings-ui/Settings.UI.Library/Helpers/ModuleHelper.cs index 8734620153..ec3def6f89 100644 --- a/src/settings-ui/Settings.UI.Library/Helpers/ModuleHelper.cs +++ b/src/settings-ui/Settings.UI.Library/Helpers/ModuleHelper.cs @@ -30,6 +30,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.Helpers return moduleType switch { ModuleType.AdvancedPaste => "ms-appx:///Assets/Settings/Icons/AdvancedPaste.png", + ModuleType.AltWindowCycle => "ms-appx:///Assets/Settings/Icons/WindowHopper.png", ModuleType.Workspaces => "ms-appx:///Assets/Settings/Icons/Workspaces.png", ModuleType.PowerOCR => "ms-appx:///Assets/Settings/Icons/TextExtractor.png", ModuleType.PowerAccent => "ms-appx:///Assets/Settings/Icons/QuickAccent.png", @@ -56,6 +57,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 +99,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 +144,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, diff --git a/src/settings-ui/Settings.UI.Library/SettingsSerializationContext.cs b/src/settings-ui/Settings.UI.Library/SettingsSerializationContext.cs index 96c6fb682b..16a71048a5 100644 --- a/src/settings-ui/Settings.UI.Library/SettingsSerializationContext.cs +++ b/src/settings-ui/Settings.UI.Library/SettingsSerializationContext.cs @@ -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))] diff --git a/src/settings-ui/Settings.UI.UnitTests/ModelsTests/AltWindowCycleSettingsTests.cs b/src/settings-ui/Settings.UI.UnitTests/ModelsTests/AltWindowCycleSettingsTests.cs new file mode 100644 index 0000000000..3827458f34 --- /dev/null +++ b/src/settings-ui/Settings.UI.UnitTests/ModelsTests/AltWindowCycleSettingsTests.cs @@ -0,0 +1,116 @@ +// 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; + +using Microsoft.PowerToys.Settings.UI.Library; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace CommonLibTest +{ + [TestClass] + public class AltWindowCycleSettingsTests + { + // VK_OEM_3 (the backtick / grave-accent key) that both default shortcuts bind to. + private const int OemTilde = 0xC0; + + [TestMethod] + public void Defaults_ShouldMatchDocumentedHotkeys() + { + var settings = new AltWindowCycleSettings(); + + Assert.AreEqual(AltWindowCycleSettings.ModuleName, settings.Name); + Assert.AreEqual("1.0", settings.Version); + Assert.IsNotNull(settings.Properties); + + // Next window: Alt+` + AssertHotkey(settings.Properties.NextWindowShortcut, win: false, ctrl: false, alt: true, shift: false, code: OemTilde); + + // Previous window: Shift+Alt+` + AssertHotkey(settings.Properties.PreviousWindowShortcut, win: false, ctrl: false, alt: true, shift: true, code: OemTilde); + } + + [TestMethod] + public void ToJsonString_ShouldContainExpectedKeys() + { + var settings = new AltWindowCycleSettings(); + + var json = settings.ToJsonString(); + + StringAssert.Contains(json, AltWindowCycleSettings.ModuleName); + StringAssert.Contains(json, "\"properties\""); + StringAssert.Contains(json, "next_window_shortcut"); + StringAssert.Contains(json, "previous_window_shortcut"); + } + + [TestMethod] + public void RoundTrip_WithDefaults_ShouldPreserveAllValues() + { + var original = new AltWindowCycleSettings(); + + var deserialized = JsonSerializer.Deserialize(original.ToJsonString()); + + Assert.IsNotNull(deserialized); + Assert.AreEqual(original.Name, deserialized.Name); + Assert.AreEqual(original.Version, deserialized.Version); + Assert.IsNotNull(deserialized.Properties); + AssertHotkeyEqual(original.Properties.NextWindowShortcut, deserialized.Properties.NextWindowShortcut); + AssertHotkeyEqual(original.Properties.PreviousWindowShortcut, deserialized.Properties.PreviousWindowShortcut); + } + + [TestMethod] + public void RoundTrip_WithCustomHotkeys_ShouldPreserveOverrides() + { + var original = new AltWindowCycleSettings(); + original.Properties.NextWindowShortcut = new HotkeySettings(true, false, false, false, 0x4E); // Win+N + original.Properties.PreviousWindowShortcut = new HotkeySettings(true, false, false, true, 0x50); // Win+Shift+P + + var deserialized = JsonSerializer.Deserialize(original.ToJsonString()); + + Assert.IsNotNull(deserialized); + AssertHotkey(deserialized.Properties.NextWindowShortcut, win: true, ctrl: false, alt: false, shift: false, code: 0x4E); + AssertHotkey(deserialized.Properties.PreviousWindowShortcut, win: true, ctrl: false, alt: false, shift: true, code: 0x50); + } + + [TestMethod] + public void Deserialize_WithNullHotkeys_ShouldUseDefaults() + { + const string Json = "{\"properties\":{\"next_window_shortcut\":null,\"previous_window_shortcut\":null}}"; + + var deserialized = JsonSerializer.Deserialize(Json); + + Assert.IsNotNull(deserialized); + AssertHotkey(deserialized.Properties.NextWindowShortcut, win: false, ctrl: false, alt: true, shift: false, code: OemTilde); + AssertHotkey(deserialized.Properties.PreviousWindowShortcut, win: false, ctrl: false, alt: true, shift: true, code: OemTilde); + } + + [TestMethod] + public void ShouldBeRegisteredInSerializationContext() + { + var options = new JsonSerializerOptions + { + TypeInfoResolver = SettingsSerializationContext.Default, + }; + + var typeInfo = options.TypeInfoResolver?.GetTypeInfo(typeof(AltWindowCycleSettings), options); + + Assert.IsNotNull(typeInfo, "AltWindowCycleSettings must be registered in SettingsSerializationContext for Native AOT serialization."); + } + + private static void AssertHotkey(HotkeySettings hotkey, bool win, bool ctrl, bool alt, bool shift, int code) + { + Assert.IsNotNull(hotkey); + Assert.AreEqual(win, hotkey.Win, "Win modifier mismatch."); + Assert.AreEqual(ctrl, hotkey.Ctrl, "Ctrl modifier mismatch."); + Assert.AreEqual(alt, hotkey.Alt, "Alt modifier mismatch."); + Assert.AreEqual(shift, hotkey.Shift, "Shift modifier mismatch."); + Assert.AreEqual(code, hotkey.Code, "Key code mismatch."); + } + + private static void AssertHotkeyEqual(HotkeySettings expected, HotkeySettings actual) + { + AssertHotkey(actual, expected.Win, expected.Ctrl, expected.Alt, expected.Shift, expected.Code); + } + } +} diff --git a/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/AltWindowCycle.cs b/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/AltWindowCycle.cs new file mode 100644 index 0000000000..a4ee978b08 --- /dev/null +++ b/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/AltWindowCycle.cs @@ -0,0 +1,141 @@ +// 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.IO.Abstractions; +using System.Text.Json; + +using Microsoft.PowerToys.Settings.UI.Library; +using Microsoft.PowerToys.Settings.UI.UnitTests.Mocks; +using Microsoft.PowerToys.Settings.UI.ViewModels; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace ViewModelTests +{ + [TestClass] + public class AltWindowCycle + { + private Mock mockGeneralSettingsUtils; + + private Mock mockAltWindowCycleSettingsUtils; + + [TestInitialize] + public void SetUpStubSettingUtils() + { + mockGeneralSettingsUtils = ISettingsUtilsMocks.GetStubSettingsUtils(); + mockAltWindowCycleSettingsUtils = ISettingsUtilsMocks.GetStubSettingsUtils(); + } + + private AltWindowCycleViewModel CreateViewModel(Mock settingsUtilsMock, Func ipcCallback = null) + { + ipcCallback ??= msg => 0; + return new AltWindowCycleViewModel( + settingsUtilsMock.Object, + SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), + SettingsRepository.GetInstance(mockAltWindowCycleSettingsUtils.Object), + ipcCallback); + } + + [TestMethod] + public void Constructor_ShouldLoadDefaultShortcutsFromSettings() + { + var settingsUtilsMock = new Mock(new FileSystem(), null); + var viewModel = CreateViewModel(settingsUtilsMock); + + Assert.IsNotNull(viewModel.NextWindowShortcut); + Assert.IsTrue(viewModel.NextWindowShortcut.Alt); + Assert.IsFalse(viewModel.NextWindowShortcut.Shift); + Assert.AreEqual(0xC0, viewModel.NextWindowShortcut.Code); + + Assert.IsNotNull(viewModel.PreviousWindowShortcut); + Assert.IsTrue(viewModel.PreviousWindowShortcut.Alt); + Assert.IsTrue(viewModel.PreviousWindowShortcut.Shift); + Assert.AreEqual(0xC0, viewModel.PreviousWindowShortcut.Code); + } + + [TestMethod] + public void IsEnabled_WhenSet_ShouldSendEnabledGeneralSettings() + { + var settingsUtilsMock = new Mock(new FileSystem(), null); + + var ipcInvoked = false; + Func sendMockIPCConfigMSG = msg => + { + OutGoingGeneralSettings snd = JsonSerializer.Deserialize(msg); + Assert.IsTrue(snd.GeneralSettings.Enabled.AltWindowCycle); + ipcInvoked = true; + return 0; + }; + + var viewModel = CreateViewModel(settingsUtilsMock, sendMockIPCConfigMSG); + + viewModel.IsEnabled = true; + + Assert.IsTrue(ipcInvoked, "Enabling the module should send an OutGoingGeneralSettings IPC message."); + Assert.IsTrue(viewModel.IsEnabled); + } + + [TestMethod] + public void GpoNotConfigured_ShouldNotReportPolicyManaged() + { + var settingsUtilsMock = new Mock(new FileSystem(), null); + var viewModel = CreateViewModel(settingsUtilsMock); + + // With no enterprise policy applied in the test environment, the module must be + // user-controllable (not reported as GPO-managed). + Assert.IsFalse(viewModel.IsEnabledGpoConfigured); + } + + [TestMethod] + public void NextWindowShortcut_WhenChanged_ShouldPersistSettings() + { + var settingsUtilsMock = new Mock(new FileSystem(), null); + var viewModel = CreateViewModel(settingsUtilsMock); + + var newShortcut = new HotkeySettings(true, true, false, false, 0x4E); // Win+Ctrl+N + viewModel.NextWindowShortcut = newShortcut; + + Assert.AreSame(newShortcut, viewModel.NextWindowShortcut); + settingsUtilsMock.Verify( + x => x.SaveSettings( + It.Is(json => json.Contains("next_window_shortcut")), + It.Is(name => name == AltWindowCycleSettings.ModuleName), + It.IsAny()), + Times.AtLeastOnce); + } + + [TestMethod] + public void PreviousWindowShortcut_WhenChanged_ShouldPersistSettings() + { + var settingsUtilsMock = new Mock(new FileSystem(), null); + var viewModel = CreateViewModel(settingsUtilsMock); + + var newShortcut = new HotkeySettings(true, true, false, true, 0x50); // Win+Ctrl+Shift+P + viewModel.PreviousWindowShortcut = newShortcut; + + Assert.AreSame(newShortcut, viewModel.PreviousWindowShortcut); + settingsUtilsMock.Verify( + x => x.SaveSettings( + It.Is(json => json.Contains("previous_window_shortcut")), + It.Is(name => name == AltWindowCycleSettings.ModuleName), + It.IsAny()), + Times.AtLeastOnce); + } + + [TestMethod] + public void NextWindowShortcut_WhenSetToNull_ShouldFallBackToDefault() + { + var settingsUtilsMock = new Mock(new FileSystem(), null); + var viewModel = CreateViewModel(settingsUtilsMock); + + viewModel.NextWindowShortcut = null; + + Assert.IsNotNull(viewModel.NextWindowShortcut); + Assert.IsTrue(viewModel.NextWindowShortcut.Alt); + Assert.IsFalse(viewModel.NextWindowShortcut.Shift); + Assert.AreEqual(0xC0, viewModel.NextWindowShortcut.Code); + } + } +} diff --git a/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/OobeShellViewModelTests.cs b/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/OobeShellViewModelTests.cs new file mode 100644 index 0000000000..b9599655ab --- /dev/null +++ b/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/OobeShellViewModelTests.cs @@ -0,0 +1,29 @@ +// 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.Linq; + +using Microsoft.PowerToys.Settings.UI.OOBE.Enums; +using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace ViewModelTests +{ + [TestClass] + public class OobeShellViewModelTests + { + [TestMethod] + public void ModulesPlacesAltWindowCycleImmediatelyBeforeWorkspaces() + { + var viewModel = new OobeShellViewModel(); + var moduleNames = viewModel.Modules.Select(module => module.ModuleName).ToList(); + + int altWindowCycleIndex = moduleNames.IndexOf(PowerToysModules.AltWindowCycle.ToString()); + int workspacesIndex = moduleNames.IndexOf(PowerToysModules.Workspaces.ToString()); + + Assert.IsTrue(altWindowCycleIndex >= 0); + Assert.AreEqual(altWindowCycleIndex + 1, workspacesIndex); + } + } +} diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Icons/WindowHopper.png b/src/settings-ui/Settings.UI/Assets/Settings/Icons/WindowHopper.png new file mode 100644 index 0000000000000000000000000000000000000000..3f67dccd5aa71bd657f6e365f006fc58fe7a36fa GIT binary patch literal 1398 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 z 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 G`2PSth=?Zu literal 0 HcmV?d00001 diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Modules/WindowHopper.png b/src/settings-ui/Settings.UI/Assets/Settings/Modules/WindowHopper.png new file mode 100644 index 0000000000000000000000000000000000000000..5195ea0780547c8c8417164be853ad3aca395931 GIT binary patch literal 23130 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 P=YN#sRAsAWOrie;eKuun literal 0 HcmV?d00001 diff --git a/src/settings-ui/Settings.UI/Helpers/ModuleGpoHelper.cs b/src/settings-ui/Settings.UI/Helpers/ModuleGpoHelper.cs index d615d7aa9b..af781e81dd 100644 --- a/src/settings-ui/Settings.UI/Helpers/ModuleGpoHelper.cs +++ b/src/settings-ui/Settings.UI/Helpers/ModuleGpoHelper.cs @@ -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), diff --git a/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs b/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs index a4c26d208c..ee2ca98f2a 100644 --- a/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs +++ b/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs @@ -37,5 +37,6 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Enums GrabAndMove, NewPlus, ZoomIt, + AltWindowCycle, } } diff --git a/src/settings-ui/Settings.UI/OOBE/ViewModel/OobeShellViewModel.cs b/src/settings-ui/Settings.UI/OOBE/ViewModel/OobeShellViewModel.cs index 8482c5226a..439259fa12 100644 --- a/src/settings-ui/Settings.UI/OOBE/ViewModel/OobeShellViewModel.cs +++ b/src/settings-ui/Settings.UI/OOBE/ViewModel/OobeShellViewModel.cs @@ -44,6 +44,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.ViewModel (PowerToysModules.TextExtractor, false), (PowerToysModules.MeasureTool, false), (PowerToysModules.Hosts, false), + (PowerToysModules.AltWindowCycle, false), (PowerToysModules.Workspaces, false), (PowerToysModules.GrabAndMove, false), (PowerToysModules.RegistryPreview, false), diff --git a/src/settings-ui/Settings.UI/SerializationContext/SourceGenerationContextContext.cs b/src/settings-ui/Settings.UI/SerializationContext/SourceGenerationContextContext.cs index b85afe1f1c..2f67f52faa 100644 --- a/src/settings-ui/Settings.UI/SerializationContext/SourceGenerationContextContext.cs +++ b/src/settings-ui/Settings.UI/SerializationContext/SourceGenerationContextContext.cs @@ -22,6 +22,7 @@ namespace Microsoft.PowerToys.Settings.UI.SerializationContext; [JsonSerializable(typeof(Dictionary>))] [JsonSerializable(typeof(FileLocksmithSettings))] [JsonSerializable(typeof(FindMyMouseSettings))] +[JsonSerializable(typeof(AltWindowCycleSettings))] [JsonSerializable(typeof(IList))] [JsonSerializable(typeof(KeyboardManagerSettings))] [JsonSerializable(typeof(LightSwitchSettings))] diff --git a/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs index 1a5bf974f2..e23eb1dbde 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs @@ -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); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAltWindowCycle.xaml b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAltWindowCycle.xaml new file mode 100644 index 0000000000..2792d6a861 --- /dev/null +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAltWindowCycle.xaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + +