diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index d678404ba..09f024255 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -5,7 +5,9 @@ abcdef abcdefgh ABCDEFGHIJKLMNOPQRSTUVWXYZ abgr +abi ABlocked +ABOUTBOX Abug accctrl Acceleratorkeys @@ -204,6 +206,7 @@ CANRENAME Captureascreenshot CAPTURECHANGED CASESENSITIVE +cassert CAtl CCDDEE ccf @@ -382,6 +385,7 @@ CXVIRTUALSCREEN cxx cxxopts CYSMICON +CYVIRTUALSCREEN cziplib Dac dacl @@ -493,6 +497,8 @@ DPICHANGED DPolicy DPopup DPSAPI +DQTAT +DQTYPE DRAWFRAME drawingcolor dreamsofameaningfullife @@ -788,6 +794,7 @@ hotspot HPAINTBUFFER hpj hpp +HRAWINPUT hread HREDRAW href @@ -807,6 +814,7 @@ Htmdid html htt http +HTTRANSPARENT hwb HWINEVENTHOOK hwnd @@ -840,6 +848,7 @@ ICollection IColor ICommand IComparer +ICompositor ICONERROR ICONINFORMATION IContext @@ -854,6 +863,7 @@ IDesktop IDictionary IDirectory IDispatch +IDispatcher IDisposable idl IDLIST @@ -941,6 +951,7 @@ INPUTHARDWARE INPUTKEYBOARD INPUTLANGCHANGED INPUTMOUSE +INPUTSINK INPUTTYPE INSTALLDESKTOPSHORTCUT INSTALLDIR @@ -1000,7 +1011,6 @@ IRepository IResult ISavable isbi -iss ISearch IService isetting @@ -1009,6 +1019,7 @@ IShell ISingle ISmart isocpp +iss IStorage IStream istreambuf @@ -1019,9 +1030,9 @@ ITab ITask ITemplate ITEMSTATEICONCLICK +ITerminal ITest ith -ITerminal IThrottled IThumbnail ITrigger @@ -1039,6 +1050,7 @@ IWeb IWIC IWindows IWork +IXaml IXml ixx IYUV @@ -1148,6 +1160,7 @@ lmcons LMEM LMENU lnk +LOADSTRING LOCALAPPDATA LOCALDISPLAY localhost @@ -1169,6 +1182,7 @@ lowlevel LOWORD lparam LPBYTE +LPCITEMIDLIST LPCMINVOKECOMMANDINFO LPCREATESTRUCT LPCTSTR @@ -1215,6 +1229,7 @@ LVS LVSIL LWA lwin +LZero lzw mailto MAINICON @@ -1328,6 +1343,7 @@ msedge MSGFLT mshtmdid msi +msiexec MSIFASTINSTALL MSIHANDLE MSIINSTALLER @@ -1570,6 +1586,7 @@ phwnd pici pid pidl +PIDLIST PINDIR pinfo pinvoke @@ -1602,6 +1619,7 @@ powerlauncher powerpreview powerrename POWERRENAMETEST +POWERRENAMEUIHOST powershell powertoy powertoysinterop @@ -1695,6 +1713,7 @@ QUERYOPEN QUEUESYNC Quickime QUICKLAYOUTSWITCH +QUNS qwertyuiopasdfghjklzxcvbnm qword qwrtyuiopsghjklzxvnm @@ -1702,6 +1721,9 @@ Radiobuttons RAII RAlt randyrants +RAWINPUT +RAWINPUTDEVICE +RAWINPUTHEADER RAWPATH rbegin Rbp @@ -1786,6 +1808,7 @@ rgs rhs ricardosantos Richtext +RIDEV RIGHTSCROLLBAR riid riverar @@ -2165,7 +2188,9 @@ tsx TYMED typedef TYPEKEY +TYPEKEYBOARD TYPELIB +TYPEMOUSE typename typeof typeparam @@ -2468,6 +2493,7 @@ XSmall XStr XToolset xunit +XVIRTUALSCREEN Yaml YDiff YIncrement @@ -2478,6 +2504,7 @@ YStr YUY yuyoyuppe YUYV +YVIRTUALSCREEN YVU YVYU ZEROINIT diff --git a/.pipelines/pipeline.user.windows.yml b/.pipelines/pipeline.user.windows.yml index 1e46e0810..22f6cf723 100644 --- a/.pipelines/pipeline.user.windows.yml +++ b/.pipelines/pipeline.user.windows.yml @@ -170,7 +170,10 @@ build: - 'modules\launcher\Wox.dll' - 'modules\launcher\Wox.Infrastructure.dll' - 'modules\launcher\Wox.Plugin.dll' + - 'modules\MouseUtils\FindMyMouse.dll' - 'modules\PowerRename\PowerRenameExt.dll' + - 'modules\PowerRename\PowerRenameUILib.dll' + - 'modules\PowerRename\PowerRename.exe' - 'modules\ShortcutGuide\ShortcutGuide\PowerToys.ShortcutGuide.exe' - 'modules\ShortcutGuide\ShortcutGuideModuleInterface\ShortcutGuideModuleInterface.dll' - 'modules\VideoConference\VideoConferenceModule.dll' diff --git a/COMMUNITY.md b/COMMUNITY.md index a1ed96bdd..6c005bbed 100644 --- a/COMMUNITY.md +++ b/COMMUNITY.md @@ -72,6 +72,10 @@ PowerToys Awake is a tool to keep your computer awake. Color Picker is from Martin. +### [@oldnewthing](https://github.com/oldnewthing) - Raymond Chen + +Find My Mouse is based on Raymond Chen's SuperSonar. + ### Microsoft InVEST team This amazing team helped PowerToys develop PowerToys Run and Keyboard manager as well as update our Settings to v2. @alekhyareddy28, @arjunbalgovind, @jyuwono @laviusmotileng-ms, @ryanbodrug-microsoft, @saahmedm, @somil55, @traies, @udit3333 diff --git a/Cpp.Build.props b/Cpp.Build.props index b696fe747..1f7d2a393 100644 --- a/Cpp.Build.props +++ b/Cpp.Build.props @@ -76,7 +76,7 @@ - 10.0.17134.0 + 10.0.18362.0 @@ -84,7 +84,6 @@ v142 $(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\ Unicode - Spectre diff --git a/PowerToys.sln b/PowerToys.sln index 4d0113baa..e185f09f7 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -5,7 +5,6 @@ MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "runner", "src\runner\runner.vcxproj", "{9412D5C6-2CF2-4FC2-A601-B55508EA9B27}" ProjectSection(ProjectDependencies) = postProject {217DF501-135C-4E38-BFC8-99D4821032EA} = {217DF501-135C-4E38-BFC8-99D4821032EA} - {0E072714-D127-460B-AFAD-B4C40B412798} = {0E072714-D127-460B-AFAD-B4C40B412798} {48804216-2A0E-4168-A6D8-9CD068D14227} = {48804216-2A0E-4168-A6D8-9CD068D14227} {51920F1F-C28C-4ADF-8660-4238766796C2} = {51920F1F-C28C-4ADF-8660-4238766796C2} {6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB} = {6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB} @@ -57,26 +56,18 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "powerrename", "powerrename" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRenameExt", "src\modules\powerrename\dll\PowerRenameExt.vcxproj", "{B25AC7A5-FB9F-4789-B392-D5C85E948670}" ProjectSection(ProjectDependencies) = postProject - {0E072714-D127-460B-AFAD-B4C40B412798} = {0E072714-D127-460B-AFAD-B4C40B412798} {51920F1F-C28C-4ADF-8660-4238766796C2} = {51920F1F-C28C-4ADF-8660-4238766796C2} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRenameLib", "src\modules\powerrename\lib\PowerRenameLib.vcxproj", "{51920F1F-C28C-4ADF-8660-4238766796C2}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRenameUI", "src\modules\powerrename\ui\PowerRenameUI.vcxproj", "{0E072714-D127-460B-AFAD-B4C40B412798}" - ProjectSection(ProjectDependencies) = postProject - {51920F1F-C28C-4ADF-8660-4238766796C2} = {51920F1F-C28C-4ADF-8660-4238766796C2} - EndProjectSection -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRenameTest", "src\modules\powerrename\testapp\PowerRenameTest.vcxproj", "{A3935CF4-46C5-4A88-84D3-6B12E16E6BA2}" ProjectSection(ProjectDependencies) = postProject - {0E072714-D127-460B-AFAD-B4C40B412798} = {0E072714-D127-460B-AFAD-B4C40B412798} {51920F1F-C28C-4ADF-8660-4238766796C2} = {51920F1F-C28C-4ADF-8660-4238766796C2} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRenameUnitTests", "src\modules\powerrename\unittests\PowerRenameLibUnitTests.vcxproj", "{2151F984-E006-4A9F-92EF-C6DDE3DC8413}" ProjectSection(ProjectDependencies) = postProject - {0E072714-D127-460B-AFAD-B4C40B412798} = {0E072714-D127-460B-AFAD-B4C40B412798} {51920F1F-C28C-4ADF-8660-4238766796C2} = {51920F1F-C28C-4ADF-8660-4238766796C2} {B25AC7A5-FB9F-4789-B392-D5C85E948670} = {B25AC7A5-FB9F-4789-B392-D5C85E948670} EndProjectSection @@ -84,9 +75,6 @@ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ModuleTemplateCompileTest", "tools\project_template\ModuleTemplate\ModuleTemplateCompileTest.vcxproj", "{64A80062-4D8B-4229-8A38-DFA1D7497749}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRenameUWPUI", "src\modules\powerrename\UWPui\PowerRenameUWPUI.vcxproj", "{0485F45C-EA7A-4BB5-804B-3E8D14699387}" - ProjectSection(ProjectDependencies) = postProject - {0E072714-D127-460B-AFAD-B4C40B412798} = {0E072714-D127-460B-AFAD-B4C40B412798} - EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KeyboardManager", "src\modules\keyboardmanager\dll\KeyboardManager.vcxproj", "{89F34AF7-1C34-4A72-AA6E-534BCF972BD9}" EndProject @@ -181,11 +169,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "powerpreview", "src\modules {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF} = {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF} EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "powerpreviewTest", "src\modules\previewpane\powerpreviewTest\powerpreviewTest.vcxproj", "{47310AB4-9034-4BD1-8D8B-E88AD21A171B}" - ProjectSection(ProjectDependencies) = postProject - {217DF501-135C-4E38-BFC8-99D4821032EA} = {217DF501-135C-4E38-BFC8-99D4821032EA} - EndProjectSection -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "settings-ui", "settings-ui", "{C3081D9A-1586-441A-B5F4-ED815B3719C1}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.PowerToys.Settings.UI", "src\settings-ui\Microsoft.PowerToys.Settings.UI\Microsoft.PowerToys.Settings.UI.csproj", "{A7D5099E-F0FD-4BF3-8522-5A682759F915}" @@ -194,6 +177,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject src\.editorconfig = src\.editorconfig src\tests\win-app-driver\packages.config = src\tests\win-app-driver\packages.config + Solution.props = Solution.props EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Settings.UI.Library", "src\settings-ui\Microsoft.PowerToys.Settings.UI.Library\Microsoft.PowerToys.Settings.UI.Library.csproj", "{B1BCC8C6-46B5-4BFA-8F22-20F32D99EC6A}" @@ -283,13 +267,17 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "utils", "utils", "{B39DC643 src\common\utils\EventLocker.h = src\common\utils\EventLocker.h src\common\utils\EventWaiter.h = src\common\utils\EventWaiter.h src\common\utils\exec.h = src\common\utils\exec.h + src\common\utils\game_mode.h = src\common\utils\game_mode.h + src\common\utils\HDropIterator.h = src\common\utils\HDropIterator.h src\common\utils\HttpClient.h = src\common\utils\HttpClient.h src\common\utils\json.h = src\common\utils\json.h src\common\utils\logger_helper.h = src\common\utils\logger_helper.h + src\common\utils\modulesRegistry.h = src\common\utils\modulesRegistry.h src\common\utils\MsiUtils.h = src\common\utils\MsiUtils.h src\common\utils\os-detect.h = src\common\utils\os-detect.h src\common\utils\process_path.h = src\common\utils\process_path.h src\common\utils\ProcessWaiter.h = src\common\utils\ProcessWaiter.h + src\common\utils\registry.h = src\common\utils\registry.h src\common\utils\resources.h = src\common\utils\resources.h src\common\utils\string_utils.h = src\common\utils\string_utils.h src\common\utils\timeutil.h = src\common\utils\timeutil.h @@ -372,6 +360,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plu EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.WindowsTerminal.UnitTests", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.WindowsTerminal.UnitTests\Microsoft.PowerToys.Run.Plugin.WindowsTerminal.UnitTests.csproj", "{4ED320BC-BA04-4D42-8D15-CBE62151F08B}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRenameUIHost", "src\modules\powerrename\PowerRenameUIHost\PowerRenameUIHost.vcxproj", "{F7EC4E6C-19CA-4FBD-9918-B8AC5FEF4F63}" + ProjectSection(ProjectDependencies) = postProject + {4642D596-723F-4BFC-894C-46811219AC4A} = {4642D596-723F-4BFC-894C-46811219AC4A} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRenameUILib", "src\modules\powerrename\PowerRenameUILib\PowerRenameUILib.vcxproj", "{4642D596-723F-4BFC-894C-46811219AC4A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MouseUtils", "MouseUtils", "{322566EF-20DC-43A6-B9F8-616AF942579A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FindMyMouse", "src\modules\MouseUtils\FindMyMouse\FindMyMouse.vcxproj", "{E94FD11C-0591-456F-899F-EFC0CA548336}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -422,12 +421,6 @@ Global {51920F1F-C28C-4ADF-8660-4238766796C2}.Release|x64.ActiveCfg = Release|x64 {51920F1F-C28C-4ADF-8660-4238766796C2}.Release|x64.Build.0 = Release|x64 {51920F1F-C28C-4ADF-8660-4238766796C2}.Release|x86.ActiveCfg = Release|x64 - {0E072714-D127-460B-AFAD-B4C40B412798}.Debug|x64.ActiveCfg = Debug|x64 - {0E072714-D127-460B-AFAD-B4C40B412798}.Debug|x64.Build.0 = Debug|x64 - {0E072714-D127-460B-AFAD-B4C40B412798}.Debug|x86.ActiveCfg = Debug|x64 - {0E072714-D127-460B-AFAD-B4C40B412798}.Release|x64.ActiveCfg = Release|x64 - {0E072714-D127-460B-AFAD-B4C40B412798}.Release|x64.Build.0 = Release|x64 - {0E072714-D127-460B-AFAD-B4C40B412798}.Release|x86.ActiveCfg = Release|x64 {A3935CF4-46C5-4A88-84D3-6B12E16E6BA2}.Debug|x64.ActiveCfg = Debug|x64 {A3935CF4-46C5-4A88-84D3-6B12E16E6BA2}.Debug|x64.Build.0 = Debug|x64 {A3935CF4-46C5-4A88-84D3-6B12E16E6BA2}.Debug|x86.ActiveCfg = Debug|x64 @@ -602,12 +595,6 @@ Global {217DF501-135C-4E38-BFC8-99D4821032EA}.Release|x64.ActiveCfg = Release|x64 {217DF501-135C-4E38-BFC8-99D4821032EA}.Release|x64.Build.0 = Release|x64 {217DF501-135C-4E38-BFC8-99D4821032EA}.Release|x86.ActiveCfg = Release|x64 - {47310AB4-9034-4BD1-8D8B-E88AD21A171B}.Debug|x64.ActiveCfg = Debug|x64 - {47310AB4-9034-4BD1-8D8B-E88AD21A171B}.Debug|x64.Build.0 = Debug|x64 - {47310AB4-9034-4BD1-8D8B-E88AD21A171B}.Debug|x86.ActiveCfg = Debug|x64 - {47310AB4-9034-4BD1-8D8B-E88AD21A171B}.Release|x64.ActiveCfg = Release|x64 - {47310AB4-9034-4BD1-8D8B-E88AD21A171B}.Release|x64.Build.0 = Release|x64 - {47310AB4-9034-4BD1-8D8B-E88AD21A171B}.Release|x86.ActiveCfg = Release|x64 {A7D5099E-F0FD-4BF3-8522-5A682759F915}.Debug|x64.ActiveCfg = Debug|x64 {A7D5099E-F0FD-4BF3-8522-5A682759F915}.Debug|x64.Build.0 = Debug|x64 {A7D5099E-F0FD-4BF3-8522-5A682759F915}.Debug|x86.ActiveCfg = Debug|x64 @@ -981,6 +968,24 @@ Global {4ED320BC-BA04-4D42-8D15-CBE62151F08B}.Release|x64.Build.0 = Release|x64 {4ED320BC-BA04-4D42-8D15-CBE62151F08B}.Release|x86.ActiveCfg = Release|Any CPU {4ED320BC-BA04-4D42-8D15-CBE62151F08B}.Release|x86.Build.0 = Release|Any CPU + {F7EC4E6C-19CA-4FBD-9918-B8AC5FEF4F63}.Debug|x64.ActiveCfg = Debug|x64 + {F7EC4E6C-19CA-4FBD-9918-B8AC5FEF4F63}.Debug|x64.Build.0 = Debug|x64 + {F7EC4E6C-19CA-4FBD-9918-B8AC5FEF4F63}.Debug|x86.ActiveCfg = Debug|x64 + {F7EC4E6C-19CA-4FBD-9918-B8AC5FEF4F63}.Release|x64.ActiveCfg = Release|x64 + {F7EC4E6C-19CA-4FBD-9918-B8AC5FEF4F63}.Release|x64.Build.0 = Release|x64 + {F7EC4E6C-19CA-4FBD-9918-B8AC5FEF4F63}.Release|x86.ActiveCfg = Release|x64 + {4642D596-723F-4BFC-894C-46811219AC4A}.Debug|x64.ActiveCfg = Debug|x64 + {4642D596-723F-4BFC-894C-46811219AC4A}.Debug|x64.Build.0 = Debug|x64 + {4642D596-723F-4BFC-894C-46811219AC4A}.Debug|x86.ActiveCfg = Debug|x64 + {4642D596-723F-4BFC-894C-46811219AC4A}.Release|x64.ActiveCfg = Release|x64 + {4642D596-723F-4BFC-894C-46811219AC4A}.Release|x64.Build.0 = Release|x64 + {4642D596-723F-4BFC-894C-46811219AC4A}.Release|x86.ActiveCfg = Release|x64 + {E94FD11C-0591-456F-899F-EFC0CA548336}.Debug|x64.ActiveCfg = Debug|x64 + {E94FD11C-0591-456F-899F-EFC0CA548336}.Debug|x64.Build.0 = Debug|x64 + {E94FD11C-0591-456F-899F-EFC0CA548336}.Debug|x86.ActiveCfg = Debug|x64 + {E94FD11C-0591-456F-899F-EFC0CA548336}.Release|x64.ActiveCfg = Release|x64 + {E94FD11C-0591-456F-899F-EFC0CA548336}.Release|x64.Build.0 = Release|x64 + {E94FD11C-0591-456F-899F-EFC0CA548336}.Release|x86.ActiveCfg = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -995,7 +1000,6 @@ Global {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} {B25AC7A5-FB9F-4789-B392-D5C85E948670} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3} {51920F1F-C28C-4ADF-8660-4238766796C2} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3} - {0E072714-D127-460B-AFAD-B4C40B412798} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3} {A3935CF4-46C5-4A88-84D3-6B12E16E6BA2} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3} {2151F984-E006-4A9F-92EF-C6DDE3DC8413} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3} {0485F45C-EA7A-4BB5-804B-3E8D14699387} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3} @@ -1028,7 +1032,6 @@ Global {060D75DA-2D1C-48E6-A4A1-6F0718B64661} = {2F305555-C296-497E-AC20-5FA1B237996A} {748417CA-F17E-487F-9411-CAFB6D3F4877} = {2F305555-C296-497E-AC20-5FA1B237996A} {217DF501-135C-4E38-BFC8-99D4821032EA} = {2F305555-C296-497E-AC20-5FA1B237996A} - {47310AB4-9034-4BD1-8D8B-E88AD21A171B} = {2F305555-C296-497E-AC20-5FA1B237996A} {A7D5099E-F0FD-4BF3-8522-5A682759F915} = {C3081D9A-1586-441A-B5F4-ED815B3719C1} {B1BCC8C6-46B5-4BFA-8F22-20F32D99EC6A} = {C3081D9A-1586-441A-B5F4-ED815B3719C1} {F055103B-F80B-4D0C-BF48-057C55620033} = {5A7818A8-109C-4E1C-850D-1A654E234B0E} @@ -1098,6 +1101,10 @@ Global {F40C3397-1834-4530-B2D9-8F8B8456BCDF} = {2F305555-C296-497E-AC20-5FA1B237996A} {A2D583F0-B70C-4462-B1F0-8E81AFB7BA85} = {4AFC9975-2456-4C70-94A4-84073C1CED93} {4ED320BC-BA04-4D42-8D15-CBE62151F08B} = {4AFC9975-2456-4C70-94A4-84073C1CED93} + {F7EC4E6C-19CA-4FBD-9918-B8AC5FEF4F63} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3} + {4642D596-723F-4BFC-894C-46811219AC4A} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3} + {322566EF-20DC-43A6-B9F8-616AF942579A} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} + {E94FD11C-0591-456F-899F-EFC0CA548336} = {322566EF-20DC-43A6-B9F8-616AF942579A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} diff --git a/README.md b/README.md index 8765efb7d..1f4708bc0 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Microsoft PowerToys is a set of utilities for power users to tune and streamline | [Awake](https://aka.ms/PowerToysOverview_Awake) | [Color Picker](https://aka.ms/PowerToysOverview_ColorPicker) | [FancyZones](https://aka.ms/PowerToysOverview_FancyZones) | | [File Explorer Add-ons](https://aka.ms/PowerToysOverview_FileExplorerAddOns) | [Image Resizer](https://aka.ms/PowerToysOverview_ImageResizer) | [Keyboard Manager](https://aka.ms/PowerToysOverview_KeyboardManager) | | [PowerRename](https://aka.ms/PowerToysOverview_PowerRename) | [PowerToys Run](https://aka.ms/PowerToysOverview_PowerToysRun) | [Shortcut Guide](https://aka.ms/PowerToysOverview_ShortcutGuide) | -| [Video Conference Mute (Experimental)](https://aka.ms/PowerToysOverview_VideoConference) | | | +| [Video Conference Mute (Experimental)](https://aka.ms/PowerToysOverview_VideoConference) | [Mouse utilities](https://aka.ms/PowerToysOverview_MouseUtilities) | | ## Installing and running Microsoft PowerToys @@ -47,7 +47,7 @@ To install the Video Conference mute, please use the [v0.46 experimental version Download PowerToys from [WinGet](https://github.com/microsoft/winget-cli#installing-the-client). To install PowerToys, run the following command from the command line / PowerShell: ```powershell -WinGet install powertoys +winget install Microsoft.PowerToys -s winget ``` ### Other install methods @@ -84,7 +84,7 @@ Our goals for the [v0.47 release cycle](https://github.com/microsoft/PowerToys/i Notably, based on the community feedback received, PowerToys has re-introduced the highly-requested ability to activate Shortcut Guide via holding the Win key. PowerToys also now allows various commands in PowerToys Run to be used in either the universal English phrasing or system-localized translation. The great feedback the community provides is invaluable in helping PowerToys continually grow and improve as a product. -An experimental version of PowerToys (v0.48) will be released the week of October 4th, introducing improvements to our Video Conference Mute utility! All updates from the v0.47.1 release will still apply in v0.48. +An experimental version of PowerToys ([v0.48.1](https://github.com/microsoft/PowerToys/releases/tag/v0.48.1)) is also available, introducing improvements to our Video Conference Mute utility! All updates from the v0.47.1 release apply in v0.48.1. #### Highlights from v0.47 diff --git a/Solution.props b/Solution.props new file mode 100644 index 000000000..1598df055 --- /dev/null +++ b/Solution.props @@ -0,0 +1,6 @@ + + + + $(IntDir)Generated Files\ + + diff --git a/doc/devdocs/akaLinks.md b/doc/devdocs/akaLinks.md index e81275efe..064035fd1 100644 --- a/doc/devdocs/akaLinks.md +++ b/doc/devdocs/akaLinks.md @@ -25,6 +25,7 @@ | PowerToysOverview_FileExplorerAddOns | https://docs.microsoft.com/windows/powertoys/file-explorer | | PowerToysOverview_ImageResizer | https://docs.microsoft.com/windows/powertoys/image-resizer | | PowerToysOverview_KeyboardManager | https://docs.microsoft.com/windows/powertoys/keyboard-manager | +| PowerToysOverview_MouseUtilities | https://docs.microsoft.com/windows/powertoys/mouse-utilities | | PowerToysOverview_PowerRename | https://docs.microsoft.com/windows/powertoys/powerrename | | PowerToysOverview_PowerToysRun | https://docs.microsoft.com/windows/powertoys/run | | PowerToysOverview_ShortcutGuide | https://docs.microsoft.com/windows/powertoys/shortcut-guide | diff --git a/doc/devdocs/modules/powerrename.md b/doc/devdocs/modules/powerrename.md index b22984e6f..79a72002f 100644 --- a/doc/devdocs/modules/powerrename.md +++ b/doc/devdocs/modules/powerrename.md @@ -21,6 +21,3 @@ TODO #### [`trace.cpp`](/src/modules/powerrename/lib/trace.cpp) TODO - -#### [`PowerRenameUI.cpp`](/src/modules/powerrename/ui/PowerRenameUI.cpp) -TODO \ No newline at end of file diff --git a/doc/images/icons/MouseUtils.png b/doc/images/icons/MouseUtils.png new file mode 100644 index 000000000..08a730476 Binary files /dev/null and b/doc/images/icons/MouseUtils.png differ diff --git a/doc/images/overview/MouseUtils_large.png b/doc/images/overview/MouseUtils_large.png new file mode 100644 index 000000000..1e60dec57 Binary files /dev/null and b/doc/images/overview/MouseUtils_large.png differ diff --git a/doc/images/overview/MouseUtils_small.png b/doc/images/overview/MouseUtils_small.png new file mode 100644 index 000000000..cb1c3907b Binary files /dev/null and b/doc/images/overview/MouseUtils_small.png differ diff --git a/doc/images/overview/Original/MouseUtils.png b/doc/images/overview/Original/MouseUtils.png new file mode 100644 index 000000000..8e374e3ce Binary files /dev/null and b/doc/images/overview/Original/MouseUtils.png differ diff --git a/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.cpp b/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.cpp index 42e17afe8..eace67434 100644 --- a/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.cpp +++ b/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.cpp @@ -165,6 +165,51 @@ std::optional get_installed_powertoys_version() }; } +void ReLaunchElevatedAndExit() +{ + std::wstring params; + int nCmdArgs = 0; + LPWSTR* argList = CommandLineToArgvW(GetCommandLineW(), &nCmdArgs); + for (int i = 1; i < nCmdArgs; ++i) + { + if (std::wstring_view{ argList[i] }.find(L' ') != std::wstring_view::npos) + { + params += L'"'; + params += argList[i]; + params += L'"'; + } + else + { + params += argList[i]; + } + + if (i != nCmdArgs - 1) + { + params += L' '; + } + } + + const auto processHandle = run_elevated(argList[0], params.c_str()); + if (!processHandle) + { + spdlog::error("Couldn't restart elevated: ({})", GetLastError()); + return; + } + + if (WaitForSingleObject(processHandle, 3600000) == WAIT_OBJECT_0) + { + DWORD exitCode = 0; + GetExitCodeProcess(processHandle, &exitCode); + std::exit(exitCode); + } + else + { + spdlog::error("Elevated setup process timed out after 60m: ({})", GetLastError()); + TerminateProcess(processHandle, 0); + std::exit(1); + } +} + int Bootstrapper(HINSTANCE hInstance) { winrt::init_apartment(); @@ -299,7 +344,14 @@ int Bootstrapper(HINSTANCE hInstance) } } - // Setup MSI UI visibility and restart as elevated if required + // Always elevate bootstrapper process since it invokes msiexec multiple times, + // so we can avoid multiple UAC confirmations + if (!is_process_elevated()) + { + ReLaunchElevatedAndExit(); + } + + // Setup MSI UI visibility if (!noFullUI) { MsiSetInternalUI(INSTALLUILEVEL_FULL, nullptr); @@ -307,58 +359,7 @@ int Bootstrapper(HINSTANCE hInstance) if (g_Silent) { - if (is_process_elevated()) - { - MsiSetInternalUI(INSTALLUILEVEL_NONE, nullptr); - } - else - { - spdlog::debug("MSI doesn't support silent mode without elevation => restarting elevated"); - // MSI fails to run in silent mode due to a suppressed UAC w/o elevation, - // so we restart ourselves elevated with the same args - std::wstring params; - int nCmdArgs = 0; - LPWSTR* argList = CommandLineToArgvW(GetCommandLineW(), &nCmdArgs); - for (int i = 1; i < nCmdArgs; ++i) - { - if (std::wstring_view{ argList[i] }.find(L' ') != std::wstring_view::npos) - { - params += L'"'; - params += argList[i]; - params += L'"'; - } - else - { - params += argList[i]; - } - - if (i != nCmdArgs - 1) - { - params += L' '; - } - } - - const auto processHandle = run_elevated(argList[0], params.c_str()); - if (!processHandle) - { - spdlog::error("Couldn't restart elevated to enable silent mode! ({})", GetLastError()); - return 1; - } - - if (WaitForSingleObject(processHandle, 3600000) == WAIT_OBJECT_0) - { - DWORD exitCode = 0; - GetExitCodeProcess(processHandle, &exitCode); - return exitCode; - } - else - { - spdlog::error("Elevated setup process timed out after 60m => using basic MSI UI ({})", GetLastError()); - // Couldn't install using the completely silent mode in an hour, use basic UI. - TerminateProcess(processHandle, 0); - MsiSetInternalUI(INSTALLUILEVEL_BASIC, nullptr); - } - } + MsiSetInternalUI(INSTALLUILEVEL_NONE, nullptr); } // Try killing PowerToys and prevent future processes launch by acquiring app mutex diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs index 43518096c..aaab0b125 100644 --- a/installer/PowerToysSetup/Product.wxs +++ b/installer/PowerToysSetup/Product.wxs @@ -10,6 +10,7 @@ + @@ -77,21 +78,19 @@ - - - - - - - + + NOT Installed and CREATESCHEDULEDTASK = 1 + + NOT Installed + @@ -104,6 +103,10 @@ Installed and (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL") + + Installed AND (REMOVE="ALL") + + + + + + + + + + + + @@ -235,7 +263,9 @@ - + + + @@ -270,6 +300,10 @@ + + + + @@ -480,109 +514,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -689,7 +621,15 @@ - + + + + + + + + + @@ -702,6 +642,19 @@ + + + + + + + + + + + + + @@ -903,21 +856,21 @@ - + - + - + @@ -999,10 +952,11 @@ + - + @@ -1012,6 +966,7 @@ + diff --git a/installer/PowerToysSetupCustomActions/CustomAction.cpp b/installer/PowerToysSetupCustomActions/CustomAction.cpp index 384375e72..f1b43265e 100644 --- a/installer/PowerToysSetupCustomActions/CustomAction.cpp +++ b/installer/PowerToysSetupCustomActions/CustomAction.cpp @@ -3,6 +3,7 @@ #include #include "../../src/common/utils/MsiUtils.h" +#include "../../src/common/utils/modulesRegistry.h" #include "../../src/common/updating/installer.h" #include "../../src/common/version/version.h" @@ -25,6 +26,69 @@ const DWORD USERNAME_LEN = UNLEN + 1; // User Name + '\0' static const wchar_t* POWERTOYS_EXE_COMPONENT = L"{A2C66D91-3485-4D00-B04D-91844E6B345B}"; static const wchar_t* POWERTOYS_UPGRADE_CODE = L"{42B84BF7-5FBF-473B-9C8B-049DC16F7708}"; +HRESULT getInstallFolder(MSIHANDLE hInstall, std::wstring& installationDir) +{ + DWORD len = 0; + wchar_t _[1]; + MsiGetPropertyW(hInstall, L"CustomActionData", _, &len); + len += 1; + installationDir.resize(len); + HRESULT hr = MsiGetPropertyW(hInstall, L"CustomActionData", installationDir.data(), &len); + if(installationDir.length()) + { + installationDir.resize(installationDir.length() - 1); + } + ExitOnFailure(hr, "Failed to get INSTALLFOLDER property."); +LExit: + return hr; +} +UINT __stdcall ApplyModulesRegistryChangeSetsCA(MSIHANDLE hInstall) +{ + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + std::wstring installationFolder; + + hr = WcaInitialize(hInstall, "ApplyModulesRegistryChangeSets"); + ExitOnFailure(hr, "Failed to initialize"); + hr = getInstallFolder(hInstall, installationFolder); + ExitOnFailure(hr, "Failed to get installfolder."); + for (const auto& changeSet : getAllModulesChangeSets(installationFolder, false)) + { + if (!changeSet.apply()) + { + WcaLog(LOGMSG_STANDARD, "Couldn't apply registry changeSet"); + } + } + + ExitOnFailure(hr, "Failed to extract msix"); + +LExit: + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} + +UINT __stdcall UnApplyModulesRegistryChangeSetsCA(MSIHANDLE hInstall) +{ + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + std::wstring installationFolder; + + hr = WcaInitialize(hInstall, "UndoModulesRegistryChangeSets"); // original func name is too long + ExitOnFailure(hr, "Failed to initialize"); + hr = getInstallFolder(hInstall, installationFolder); + ExitOnFailure(hr, "Failed to get installfolder."); + for (const auto& changeSet : getAllModulesChangeSets(installationFolder, false)) + { + changeSet.unApply(); + } + + ExitOnFailure(hr, "Failed to extract msix"); + +LExit: + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} + UINT __stdcall InstallEmbeddedMSIXCA(MSIHANDLE hInstall) { HRESULT hr = S_OK; diff --git a/installer/PowerToysSetupCustomActions/CustomAction.def b/installer/PowerToysSetupCustomActions/CustomAction.def index c72095544..f8e4d8448 100644 --- a/installer/PowerToysSetupCustomActions/CustomAction.def +++ b/installer/PowerToysSetupCustomActions/CustomAction.def @@ -1,6 +1,7 @@ LIBRARY "PowerToysSetupCustomActions" EXPORTS + ApplyModulesRegistryChangeSetsCA CreateScheduledTaskCA DetectPrevInstallPathCA RemoveScheduledTasksCA @@ -17,5 +18,6 @@ EXPORTS CertifyVirtualCameraDriverCA InstallVirtualCameraDriverCA InstallEmbeddedMSIXCA + UnApplyModulesRegistryChangeSetsCA UninstallVirtualCameraDriverCA UninstallEmbeddedMSIXCA \ No newline at end of file diff --git a/src/common/Microsoft.PowerToys.Common.UI/SettingsDeepLink.cs b/src/common/Microsoft.PowerToys.Common.UI/SettingsDeepLink.cs index c5054ba7d..e8a6cfdce 100644 --- a/src/common/Microsoft.PowerToys.Common.UI/SettingsDeepLink.cs +++ b/src/common/Microsoft.PowerToys.Common.UI/SettingsDeepLink.cs @@ -19,6 +19,7 @@ namespace Microsoft.PowerToys.Common.UI Run, ImageResizer, KBM, + MouseUtils, PowerRename, FileExplorer, ShortcutGuide, @@ -43,6 +44,8 @@ namespace Microsoft.PowerToys.Common.UI return "ImageResizer"; case SettingsWindow.KBM: return "KBM"; + case SettingsWindow.MouseUtils: + return "MouseUtils"; case SettingsWindow.PowerRename: return "PowerRename"; case SettingsWindow.FileExplorer: diff --git a/src/common/SettingsAPI/settings_objects.cpp b/src/common/SettingsAPI/settings_objects.cpp index 5a5f6940a..f7bd57384 100644 --- a/src/common/SettingsAPI/settings_objects.cpp +++ b/src/common/SettingsAPI/settings_objects.cpp @@ -313,7 +313,7 @@ namespace PowerToysSettings return json::has(props, name) && json::has(props.GetNamedObject(name), L"value", type); } - std::optional PowerToyValues::get_bool_value(std::wstring_view property_name) + std::optional PowerToyValues::get_bool_value(std::wstring_view property_name) const { if (!has_property(m_json, property_name, json::JsonValueType::Boolean)) { @@ -322,7 +322,7 @@ namespace PowerToysSettings return m_json.GetNamedObject(L"properties").GetNamedObject(property_name).GetNamedBoolean(L"value"); } - std::optional PowerToyValues::get_int_value(std::wstring_view property_name) + std::optional PowerToyValues::get_int_value(std::wstring_view property_name) const { if (!has_property(m_json, property_name, json::JsonValueType::Number)) { @@ -331,7 +331,7 @@ namespace PowerToysSettings return static_cast(m_json.GetNamedObject(L"properties").GetNamedObject(property_name).GetNamedNumber(L"value")); } - std::optional PowerToyValues::get_string_value(std::wstring_view property_name) + std::optional PowerToyValues::get_string_value(std::wstring_view property_name) const { if (!has_property(m_json, property_name, json::JsonValueType::String)) { @@ -340,7 +340,7 @@ namespace PowerToysSettings return m_json.GetNamedObject(L"properties").GetNamedObject(property_name).GetNamedString(L"value").c_str(); } - std::optional PowerToyValues::get_json(std::wstring_view property_name) + std::optional PowerToyValues::get_json(std::wstring_view property_name) const { if (!has_property(m_json, property_name, json::JsonValueType::Object)) { diff --git a/src/common/SettingsAPI/settings_objects.h b/src/common/SettingsAPI/settings_objects.h index 0a8b11c2d..a09f7afb6 100644 --- a/src/common/SettingsAPI/settings_objects.h +++ b/src/common/SettingsAPI/settings_objects.h @@ -79,10 +79,10 @@ namespace PowerToysSettings m_json.GetNamedObject(L"properties").SetNamedValue(name, prop_value); } - std::optional get_bool_value(std::wstring_view property_name); - std::optional get_int_value(std::wstring_view property_name); - std::optional get_string_value(std::wstring_view property_name); - std::optional get_json(std::wstring_view property_name); + std::optional get_bool_value(std::wstring_view property_name) const; + std::optional get_int_value(std::wstring_view property_name) const; + std::optional get_string_value(std::wstring_view property_name) const; + std::optional get_json(std::wstring_view property_name) const; json::JsonObject get_raw_json(); std::wstring serialize(); diff --git a/src/common/interop/pch.h b/src/common/interop/pch.h index 409d905e3..2f415a620 100644 --- a/src/common/interop/pch.h +++ b/src/common/interop/pch.h @@ -10,5 +10,6 @@ #define WIN32_LEAN_AND_MEAN // add headers that you want to pre-compile here #include +#include #endif //PCH_H diff --git a/src/common/logger/logger_settings.h b/src/common/logger/logger_settings.h index d4ea19a40..35b398792 100644 --- a/src/common/logger/logger_settings.h +++ b/src/common/logger/logger_settings.h @@ -23,6 +23,7 @@ struct LogSettings inline const static std::wstring shortcutGuideLogPath = L"ShortcutGuideLogs\\shortcut-guide-log.txt"; inline const static std::string keyboardManagerLoggerName = "keyboard-manager"; inline const static std::wstring keyboardManagerLogPath = L"Logs\\keyboard-manager-log.txt"; + inline const static std::string findMyMouseLoggerName = "find-my-mouse"; inline const static int retention = 30; std::wstring logLevel; LogSettings(); diff --git a/src/common/utils/HDropIterator.h b/src/common/utils/HDropIterator.h new file mode 100644 index 000000000..7fbc0d717 --- /dev/null +++ b/src/common/utils/HDropIterator.h @@ -0,0 +1,58 @@ +#pragma once +#include + +class HDropIterator +{ +public: + HDropIterator(IDataObject* pDataObject) + { + _current = 0; + + FORMATETC formatetc = { + CF_HDROP, + NULL, + DVASPECT_CONTENT, + -1, + TYMED_HGLOBAL + }; + + pDataObject->GetData(&formatetc, &m_medium); + + _listCount = DragQueryFile((HDROP)m_medium.hGlobal, 0xFFFFFFFF, NULL, 0); + } + + ~HDropIterator() + { + ReleaseStgMedium(&m_medium); + } + + void First() + { + _current = 0; + } + + void Next() + { + _current++; + } + + bool IsDone() const + { + return _current >= _listCount; + } + + LPTSTR CurrentItem() const + { + UINT cch = DragQueryFile((HDROP)m_medium.hGlobal, _current, NULL, 0) + 1; + LPTSTR pszPath = (LPTSTR)malloc(sizeof(TCHAR) * cch); + + DragQueryFile((HDROP)m_medium.hGlobal, _current, pszPath, cch); + + return pszPath; + } + +private: + UINT _listCount; + STGMEDIUM m_medium; + UINT _current; +}; diff --git a/src/common/utils/game_mode.h b/src/common/utils/game_mode.h new file mode 100644 index 000000000..05e4e7dd1 --- /dev/null +++ b/src/common/utils/game_mode.h @@ -0,0 +1,12 @@ +#pragma once +#include + +inline bool detect_game_mode() +{ + QUERY_USER_NOTIFICATION_STATE notification_state; + if (SHQueryUserNotificationState(¬ification_state) != S_OK) + { + return false; + } + return (notification_state == QUNS_RUNNING_D3D_FULL_SCREEN); +} diff --git a/src/common/utils/modulesRegistry.h b/src/common/utils/modulesRegistry.h new file mode 100644 index 000000000..60be46bc0 --- /dev/null +++ b/src/common/utils/modulesRegistry.h @@ -0,0 +1,88 @@ +#pragma once + +#include "registry.h" + +#include + +namespace fs = std::filesystem; + +inline registry::ChangeSet getSvgPreviewHandlerChangeSet(const std::wstring installationDir, const bool perUser) +{ + using namespace registry::shellex; + return generatePreviewHandler(PreviewHandlerType::preview, + perUser, + L"{ddee2b8a-6807-48a6-bb20-2338174ff779}", + get_std_product_version(), + (fs::path{ installationDir } / + LR"d(modules\FileExplorerPreview\SvgPreviewHandler.comhost.dll)d") + .wstring(), + registry::DOTNET_COMPONENT_CATEGORY_CLSID, + L"Microsoft.PowerToys.PreviewHandler.Svg.SvgPreviewHandler", + L"Svg Preview Handler", + L".svg"); +} + +inline registry::ChangeSet getMdPreviewHandlerChangeSet(const std::wstring installationDir, const bool perUser) +{ + using namespace registry::shellex; + return generatePreviewHandler(PreviewHandlerType::preview, + perUser, + L"{45769bcc-e8fd-42d0-947e-02beef77a1f5}", + get_std_product_version(), + (fs::path{ installationDir } / LR"d(modules\FileExplorerPreview\MarkdownPreviewHandler.comhost.dll)d").wstring(), + registry::DOTNET_COMPONENT_CATEGORY_CLSID, + L"Microsoft.PowerToys.PreviewHandler.Markdown.MarkdownPreviewHandler", + L"Markdown Preview Handler", + L".md"); +} + +inline registry::ChangeSet getPdfPreviewHandlerChangeSet(const std::wstring installationDir, const bool perUser) +{ + using namespace registry::shellex; + return generatePreviewHandler(PreviewHandlerType::preview, + perUser, + L"{07665729-6243-4746-95b7-79579308d1b2}", + get_std_product_version(), + (fs::path{ installationDir } / LR"d(modules\FileExplorerPreview\PdfPreviewHandler.comhost.dll)d").wstring(), + registry::DOTNET_COMPONENT_CATEGORY_CLSID, + L"Microsoft.PowerToys.PreviewHandler.Pdf.PdfPreviewHandler", + L"Pdf Preview Handler", + L".pdf"); +} + +inline registry::ChangeSet getSvgThumbnailHandlerChangeSet(const std::wstring installationDir, const bool perUser) +{ + using namespace registry::shellex; + return generatePreviewHandler(PreviewHandlerType::thumbnail, + perUser, + L"{36B27788-A8BB-4698-A756-DF9F11F64F84}", + get_std_product_version(), + (fs::path{ installationDir } / LR"d(modules\FileExplorerPreview\SvgThumbnailProvider.comhost.dll)d").wstring(), + registry::DOTNET_COMPONENT_CATEGORY_CLSID, + L"Microsoft.PowerToys.ThumbnailHandler.Svg.SvgThumbnailProvider", + L"Svg Thumbnail Provider", + L".svg"); +} + +inline registry::ChangeSet getPdfThumbnailHandlerChangeSet(const std::wstring installationDir, const bool perUser) +{ + using namespace registry::shellex; + return generatePreviewHandler(PreviewHandlerType::thumbnail, + perUser, + L"{BCC13D15-9720-4CC4-8371-EA74A274741E}", + get_std_product_version(), + (fs::path{ installationDir } / LR"d(modules\FileExplorerPreview\PdfThumbnailProvider.comhost.dll)d").wstring(), + registry::DOTNET_COMPONENT_CATEGORY_CLSID, + L"Microsoft.PowerToys.ThumbnailHandler.Pdf.PdfThumbnailProvider", + L"Pdf Thumbnail Provider", + L".pdf"); +} + +inline std::vector getAllModulesChangeSets(const std::wstring installationDir, const bool perUser) +{ + return { getSvgPreviewHandlerChangeSet(installationDir, perUser), + getMdPreviewHandlerChangeSet(installationDir, perUser), + getPdfPreviewHandlerChangeSet(installationDir, perUser), + getSvgThumbnailHandlerChangeSet(installationDir, perUser), + getPdfThumbnailHandlerChangeSet(installationDir, perUser) }; +} \ No newline at end of file diff --git a/src/common/utils/registry.h b/src/common/utils/registry.h new file mode 100644 index 000000000..d0ebcb187 --- /dev/null +++ b/src/common/utils/registry.h @@ -0,0 +1,329 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +#include "../version/version.h" + +namespace registry +{ + namespace detail + { + struct on_exit + { + std::function f; + + on_exit(std::function f) : + f{ std::move(f) } {} + ~on_exit() { f(); } + }; + + template + inline constexpr bool always_false_v = false; + + template + struct overloaded : Ts... + { + using Ts::operator()...; + }; + + template + overloaded(Ts...) -> overloaded; + } + struct ValueChange + { + using value_t = std::variant; + static constexpr size_t VALUE_BUFFER_SIZE = 512; + + HKEY scope{}; + std::wstring path; + std::optional name; // none == default + value_t value; + + ValueChange(const HKEY scope, std::wstring path, std::optional name, value_t value) : + scope{ scope }, path{ std::move(path) }, name{ std::move(name) }, value{ std::move(value) } + { + } + + bool isApplied() const + { + HKEY key{}; + if (RegOpenKeyExW(scope, path.c_str(), 0, KEY_READ, &key) != ERROR_SUCCESS) + { + return false; + } + detail::on_exit closeKey{ [key] { RegCloseKey(key); } }; + + const DWORD expectedType = valueTypeToWinapiType(value); + + DWORD retrievedType{}; + wchar_t buffer[VALUE_BUFFER_SIZE]; + DWORD valueSize = sizeof(buffer); + if (RegQueryValueExW(key, + name.has_value() ? name->c_str() : nullptr, + 0, + &retrievedType, + reinterpret_cast(&buffer), + &valueSize) != ERROR_SUCCESS) + { + return false; + } + + if (expectedType != retrievedType) + { + return false; + } + + if (const auto retrievedValue = bufferToValue(buffer, valueSize, retrievedType)) + { + return value == retrievedValue; + } + else + { + return false; + } + } + + bool apply() const + { + HKEY key{}; + + if (RegCreateKeyExW(scope, path.c_str(), 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_WRITE, nullptr, &key, nullptr) != + ERROR_SUCCESS) + { + return false; + } + detail::on_exit closeKey{ [key] { RegCloseKey(key); } }; + + wchar_t buffer[VALUE_BUFFER_SIZE]; + DWORD valueSize; + DWORD valueType; + + valueToBuffer(value, buffer, valueSize, valueType); + return RegSetValueExW(key, + name.has_value() ? name->c_str() : nullptr, + 0, + valueType, + reinterpret_cast(buffer), + valueSize) == ERROR_SUCCESS; + } + + bool unApply() const + { + HKEY key{}; + if (RegOpenKeyExW(scope, path.c_str(), 0, KEY_ALL_ACCESS, &key) != ERROR_SUCCESS) + { + return false; + } + detail::on_exit closeKey{ [key] { RegCloseKey(key); } }; + + // delete the value itself + if (RegDeleteKeyValueW(scope, path.c_str(), name.has_value() ? name->c_str() : nullptr) != ERROR_SUCCESS) + { + return false; + } + + // Check if the path doesn't contain anything and delete it if so + DWORD nValues = 0; + DWORD maxValueLen = 0; + const auto ok = + RegQueryInfoKeyW( + key, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, &nValues, nullptr, &maxValueLen, nullptr, nullptr) == + ERROR_SUCCESS; + + if (ok && (!nValues || !maxValueLen)) + { + RegDeleteTreeW(scope, path.c_str()); + } + return true; + } + + bool requiresElevation() const { return scope == HKEY_LOCAL_MACHINE; } + + private: + static DWORD valueTypeToWinapiType(const value_t& v) + { + return std::visit( + [](auto&& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) + return REG_DWORD; + else if constexpr (std::is_same_v) + return REG_SZ; + else + static_assert(always_false_v, "support for this registry type is not implemented"); + }, + v); + } + + static void valueToBuffer(const value_t& value, wchar_t buffer[VALUE_BUFFER_SIZE], DWORD& valueSize, DWORD& type) + { + using detail::overloaded; + + std::visit(overloaded{ [&](DWORD value) { + *reinterpret_cast(buffer) = value; + type = REG_DWORD; + valueSize = sizeof(value); + }, + [&](const std::wstring& value) { + assert(value.size() < VALUE_BUFFER_SIZE); + value.copy(buffer, value.size()); + type = REG_SZ; + valueSize = static_cast(sizeof(wchar_t) * value.size()); + } }, + value); + } + + static std::optional bufferToValue(const wchar_t buffer[VALUE_BUFFER_SIZE], + const DWORD valueSize, + const DWORD type) + { + switch (type) + { + case REG_DWORD: + return *(DWORD*)buffer; + case REG_SZ: + { + if (!valueSize) + { + return std::wstring{}; + } + std::wstring result{ buffer, valueSize / sizeof(wchar_t) }; + while (result[result.size() - 1] == L'\0') + { + result.resize(result.size() - 1); + } + return result; + } + default: + return std::nullopt; + } + } + }; + + struct ChangeSet + { + std::vector changes; + + bool isApplied() const + { + for (const auto& c : changes) + { + if (!c.isApplied()) + { + return false; + } + } + return true; + } + + bool apply() const + { + bool ok = true; + for (const auto& c : changes) + { + ok = c.apply() && ok; + } + return ok; + } + + bool unApply() const + { + bool ok = true; + for (const auto& c : changes) + { + ok = c.unApply() && ok; + } + return ok; + } + }; + + const inline std::wstring DOTNET_COMPONENT_CATEGORY_CLSID = L"{62C8FE65-4EBB-45E7-B440-6E39B2CDBF29}"; + const inline std::wstring ITHUMBNAIL_PROVIDER_CLSID = L"{E357FCCD-A995-4576-B01F-234630154E96}"; + const inline std::wstring IPREVIEW_HANDLER_CLSID = L"{8895b1c6-b41f-4c1c-a562-0d564250836f}"; + + namespace shellex + { + enum PreviewHandlerType + { + preview, + thumbnail + }; + + inline registry::ChangeSet generatePreviewHandler(const PreviewHandlerType handlerType, + const bool perUser, + std::wstring handlerClsid, + std::wstring powertoysVersion, + std::wstring fullPathToHandler, + std::wstring handlerCategory, + std::wstring className, + std::wstring displayName, + std::wstring fileType) + { + const HKEY scope = perUser ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE; + + std::wstring clsidPath = L"Software\\Classes\\CLSID"; + clsidPath += L'\\'; + clsidPath += handlerClsid; + + std::wstring inprocServerPath = clsidPath; + inprocServerPath += L'\\'; + inprocServerPath += L"InprocServer32"; + + std::wstring implementedCategoriesPath = clsidPath + LR"d(\Implemented Categories\)d"; + implementedCategoriesPath += handlerCategory; + + std::wstring assemblyKeyValue; + if (const auto lastDotPos = className.rfind(L'.'); lastDotPos != std::wstring::npos) + { + assemblyKeyValue = className.substr(lastDotPos + 1); + } + else + { + assemblyKeyValue = className; + } + + assemblyKeyValue += L", Version="; + assemblyKeyValue += powertoysVersion; + assemblyKeyValue += L", Culture=neutral"; + + std::wstring versionPath = inprocServerPath; + versionPath += L'\\'; + versionPath += powertoysVersion; + + std::wstring fileAssociationPath = L"Software\\Classes\\"; + fileAssociationPath += fileType; + fileAssociationPath += L"\\shellex\\"; + fileAssociationPath += handlerType == PreviewHandlerType::preview ? IPREVIEW_HANDLER_CLSID : ITHUMBNAIL_PROVIDER_CLSID; + + using vec_t = std::vector; + // TODO: verify that we actually need all of those + vec_t changes = { { scope, clsidPath, L"DisplayName", displayName }, + { scope, clsidPath, std::nullopt, className }, + { scope, implementedCategoriesPath, std::nullopt, L"" }, + { scope, inprocServerPath, std::nullopt, fullPathToHandler }, + { scope, inprocServerPath, L"Assembly", assemblyKeyValue }, + { scope, inprocServerPath, L"Class", className }, + { scope, inprocServerPath, L"ThreadingModel", L"Both" }, + { scope, versionPath, L"Assembly", assemblyKeyValue }, + { scope, versionPath, L"Class", className }, + { scope, fileAssociationPath, std::nullopt, handlerClsid } }; + if (handlerType == PreviewHandlerType::preview) + { + const std::wstring previewHostClsid = L"{6d2b5079-2f0b-48dd-ab7f-97cec514d30b}"; + const std::wstring previewHandlerListPath = LR"(Software\Microsoft\Windows\CurrentVersion\PreviewHandlers)"; + + changes.push_back({ scope, clsidPath, L"AppID", previewHostClsid }); + changes.push_back({ scope, previewHandlerListPath, handlerClsid, displayName }); + } + + return registry::ChangeSet{ .changes = std::move(changes) }; + } + } +} diff --git a/src/common/version/version.h b/src/common/version/version.h index 4cf88d8ac..4a6f34784 100644 --- a/src/common/version/version.h +++ b/src/common/version/version.h @@ -33,5 +33,14 @@ inline std::wstring get_product_version() L"." + std::to_wstring(VERSION_MINOR) + L"." + std::to_wstring(VERSION_REVISION); + return version; +} + +inline std::wstring get_std_product_version() +{ + static std::wstring version = L"v" + std::to_wstring(VERSION_MAJOR) + + L"." + std::to_wstring(VERSION_MINOR) + + L"." + std::to_wstring(VERSION_REVISION) + L".0"; + return version; } \ No newline at end of file diff --git a/src/modules/MouseUtils/FindMyMouse/Directory.Build.targets b/src/modules/MouseUtils/FindMyMouse/Directory.Build.targets new file mode 100644 index 000000000..53fe8fc42 --- /dev/null +++ b/src/modules/MouseUtils/FindMyMouse/Directory.Build.targets @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/powerpreviewTest/powerpreviewTest.rc b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.base.rc similarity index 85% rename from src/modules/previewpane/powerpreviewTest/powerpreviewTest.rc rename to src/modules/MouseUtils/FindMyMouse/FindMyMouse.base.rc index 3bee3499c..0bcdeca2e 100644 --- a/src/modules/previewpane/powerpreviewTest/powerpreviewTest.rc +++ b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.base.rc @@ -1,6 +1,10 @@ #include #include "resource.h" -#include "../../../common/version/version.h" +#include "../../../../common/version/version.h" + +#define APSTUDIO_READONLY_SYMBOLS +#include "winres.h" +#undef APSTUDIO_READONLY_SYMBOLS 1 VERSIONINFO FILEVERSION FILE_VERSION @@ -13,7 +17,7 @@ FILEFLAGS 0x0L #endif FILEOS VOS_NT_WINDOWS32 FILETYPE VFT_DLL -FILESUBTYPE VFT2_UNKNOWN +FILESUBTYPE VFT2_UNKNOWN BEGIN BLOCK "StringFileInfo" BEGIN diff --git a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp new file mode 100644 index 000000000..33e182e43 --- /dev/null +++ b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp @@ -0,0 +1,828 @@ +// FindMyMouse.cpp : Based on Raymond Chen's SuperSonar.cpp +// +#include "pch.h" +#include "FindMyMouse.h" +#include "trace.h" +#include "common/utils/game_mode.h" + +#ifdef COMPOSITION +namespace winrt +{ + using namespace winrt::Windows::System; + using namespace winrt::Windows::UI::Composition; +} + +namespace ABI +{ + using namespace ABI::Windows::System; + using namespace ABI::Windows::UI::Composition::Desktop; +} +#endif + +bool m_doNotActivateOnGameMode = true; + +#pragma region Super_Sonar_Base_Code + +template +struct SuperSonar +{ + bool Initialize(HINSTANCE hinst); + void Terminate(); + +protected: + // You are expected to override these, as appropriate. + + DWORD GetExtendedStyle() + { + return 0; + } + + LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam) noexcept + { + return BaseWndProc(message, wParam, lParam); + } + + void BeforeMoveSonar() {} + void AfterMoveSonar() {} + void SetSonarVisibility(bool visible) = delete; + +protected: + // Base class members you can access. + D* Shim() { return static_cast(this); } + LRESULT BaseWndProc(UINT message, WPARAM wParam, LPARAM lParam) noexcept; + + HWND m_hwnd; + POINT m_sonarPos = ptNowhere; + + static constexpr int SonarRadius = 100; + static constexpr int SonarZoomFactor = 9; + static constexpr DWORD FadeDuration = 500; + static constexpr int FinalAlphaNumerator = 1; + static constexpr int FinalAlphaDenominator = 2; + winrt::DispatcherQueueController m_dispatcherQueueController{ nullptr }; + +private: + static bool IsEqual(POINT const& p1, POINT const& p2) + { + return p1.x == p2.x && p1.y == p2.y; + } + + static constexpr POINT ptNowhere = { -1, -1 }; + + static constexpr DWORD TIMER_ID_TRACK = 100; + static constexpr DWORD IdlePeriod = 1000; + + // Activate sonar: Hit LeftControl twice. + enum class SonarState + { + Idle, + ControlDown1, + ControlUp1, + ControlDown2, + ControlUp2, + }; + + HWND m_hwndOwner; + SonarState m_sonarState = SonarState::Idle; + POINT m_lastKeyPos{}; + DWORD m_lastKeyTime{}; + + static constexpr DWORD NoSonar = 0; + static constexpr DWORD SonarWaitingForMouseMove = 1; + DWORD m_sonarStart = NoSonar; + bool m_isSnoopingMouse = false; + +private: + static constexpr auto className = L"FindMyMouse"; + + // Use the runner name for the Window title. Otherwise, since Find My Mouse has an actual visual, its Window name will be the one shown in Task Manager after being shown. + static constexpr auto windowTitle = L"PowerToys Runner"; + + static LRESULT CALLBACK s_WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); + + BOOL OnSonarCreate(); + void OnSonarDestroy(); + void OnSonarInput(WPARAM flags, HRAWINPUT hInput); + void OnSonarKeyboardInput(RAWINPUT const& input); + void OnSonarMouseInput(RAWINPUT const& input); + void OnMouseTimer(); + + void StartSonar(); + void StopSonar(); + + void UpdateMouseSnooping(); +}; + +template +bool SuperSonar::Initialize(HINSTANCE hinst) +{ + SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + + WNDCLASS wc{}; + + if (!GetClassInfoW(hinst, className, &wc)) + { + wc.lpfnWndProc = s_WndProc; + wc.hInstance = hinst; + wc.hIcon = LoadIcon(hinst, IDI_APPLICATION); + wc.hCursor = LoadCursor(nullptr, IDC_ARROW); + wc.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH); + wc.lpszClassName = className; + + if (!RegisterClassW(&wc)) + { + return false; + } + } + + m_hwndOwner = CreateWindow(L"static", nullptr, WS_POPUP, 0, 0, 0, 0, nullptr, nullptr, hinst, nullptr); + + DWORD exStyle = WS_EX_TRANSPARENT | WS_EX_LAYERED | Shim()->GetExtendedStyle(); + return CreateWindowExW(exStyle, className, windowTitle, WS_POPUP, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, m_hwndOwner, nullptr, hinst, this) != nullptr; +} + +template +void SuperSonar::Terminate() +{ + auto dispatcherQueue = m_dispatcherQueueController.DispatcherQueue(); + bool enqueueSucceeded = dispatcherQueue.TryEnqueue([=]() { + DestroyWindow(m_hwndOwner); + }); + if (!enqueueSucceeded) + { + Logger::error("Couldn't enqueue message to destroy the sonar Window."); + } +} + +template +LRESULT SuperSonar::s_WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + SuperSonar* self; + if (message == WM_NCCREATE) + { + auto info = (LPCREATESTRUCT)lParam; + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)info->lpCreateParams); + self = (SuperSonar*)info->lpCreateParams; + self->m_hwnd = hwnd; + } + else + { + self = (SuperSonar*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + } + if (self) + { + return self->Shim()->WndProc(message, wParam, lParam); + } + else + { + return DefWindowProc(hwnd, message, wParam, lParam); + } +} + +template +LRESULT SuperSonar::BaseWndProc(UINT message, WPARAM wParam, LPARAM lParam) noexcept +{ + switch (message) + { + case WM_CREATE: + return OnSonarCreate() ? 0 : -1; + + case WM_DESTROY: + OnSonarDestroy(); + break; + + case WM_INPUT: + OnSonarInput(wParam, (HRAWINPUT)lParam); + break; + + case WM_TIMER: + switch (wParam) + { + case TIMER_ID_TRACK: + OnMouseTimer(); + break; + } + break; + + case WM_NCHITTEST: + return HTTRANSPARENT; + } + + return DefWindowProc(m_hwnd, message, wParam, lParam); +} + +template +BOOL SuperSonar::OnSonarCreate() +{ + RAWINPUTDEVICE keyboard{}; + keyboard.usUsagePage = HID_USAGE_PAGE_GENERIC; + keyboard.usUsage = HID_USAGE_GENERIC_KEYBOARD; + keyboard.dwFlags = RIDEV_INPUTSINK; + keyboard.hwndTarget = m_hwnd; + return RegisterRawInputDevices(&keyboard, 1, sizeof(keyboard)); +} + +template +void SuperSonar::OnSonarDestroy() +{ + PostQuitMessage(0); +} + +template +void SuperSonar::OnSonarInput(WPARAM flags, HRAWINPUT hInput) +{ + RAWINPUT input; + UINT size = sizeof(input); + auto result = GetRawInputData(hInput, RID_INPUT, &input, &size, sizeof(RAWINPUTHEADER)); + if ((int)result < sizeof(RAWINPUTHEADER)) + { + return; + } + + switch (input.header.dwType) + { + case RIM_TYPEKEYBOARD: + OnSonarKeyboardInput(input); + break; + case RIM_TYPEMOUSE: + OnSonarMouseInput(input); + break; + } +} + +template +void SuperSonar::OnSonarKeyboardInput(RAWINPUT const& input) +{ + // Don't activate if game mode is on. + if (m_doNotActivateOnGameMode && detect_game_mode()) + { + return; + } + + if (input.data.keyboard.VKey != VK_CONTROL) + { + StopSonar(); + return; + } + + bool pressed = (input.data.keyboard.Flags & RI_KEY_BREAK) == 0; + bool rightCtrl = (input.data.keyboard.Flags & RI_KEY_E0) != 0; + + // Deal with rightCtrl first. + if (rightCtrl) + { + /* + * SuperSonar originally exited when pressing right control after pressing left control twice. + * We take care of exiting FindMyMouse through module disabling in PowerToys settings instead. + if (m_sonarState == SonarState::ControlUp2) + { + Terminate(); + } + */ + StopSonar(); + return; + } + + switch (m_sonarState) + { + case SonarState::Idle: + if (pressed) + { + m_sonarState = SonarState::ControlDown1; + m_lastKeyTime = GetTickCount(); + m_lastKeyPos = {}; + GetCursorPos(&m_lastKeyPos); + UpdateMouseSnooping(); + } + break; + + case SonarState::ControlDown1: + if (!pressed) + { + m_sonarState = SonarState::ControlUp1; + } + break; + + case SonarState::ControlUp1: + case SonarState::ControlUp2: + if (pressed) + { + m_sonarState = SonarState::ControlDown2; + auto now = GetTickCount(); + POINT ptCursor{}; + if (GetCursorPos(&ptCursor) && + now - m_lastKeyTime <= GetDoubleClickTime() && + IsEqual(m_lastKeyPos, ptCursor)) + { + StartSonar(); + } + m_lastKeyTime = now; + m_lastKeyPos = ptCursor; + } + break; + + case SonarState::ControlDown2: + if (!pressed) + { + m_sonarState = SonarState::ControlUp2; + } + break; + } +} + +template +void SuperSonar::OnSonarMouseInput(RAWINPUT const& input) +{ + if (input.data.mouse.usButtonFlags) + { + StopSonar(); + } + else if (m_sonarStart != NoSonar) + { + OnMouseTimer(); + } +} + +template +void SuperSonar::StartSonar() +{ + Logger::info("Focusing the sonar on the mouse cursor."); + Trace::MousePointerFocused(); + // Cover the entire virtual screen. + SetWindowPos(m_hwnd, HWND_TOPMOST, GetSystemMetrics(SM_XVIRTUALSCREEN), GetSystemMetrics(SM_YVIRTUALSCREEN), GetSystemMetrics(SM_CXVIRTUALSCREEN), GetSystemMetrics(SM_CYVIRTUALSCREEN), 0); + m_sonarPos = ptNowhere; + OnMouseTimer(); + UpdateMouseSnooping(); + Shim()->SetSonarVisibility(true); +} + +template +void SuperSonar::StopSonar() +{ + if (m_sonarStart != NoSonar) + { + m_sonarStart = NoSonar; + Shim()->SetSonarVisibility(false); + KillTimer(m_hwnd, TIMER_ID_TRACK); + } + m_sonarState = SonarState::Idle; + UpdateMouseSnooping(); +} + +template +void SuperSonar::OnMouseTimer() +{ + auto now = GetTickCount(); + + // If mouse has moved, then reset the sonar timer. + POINT ptCursor{}; + if (!GetCursorPos(&ptCursor)) + { + // We are no longer the active desktop - done. + StopSonar(); + return; + } + ScreenToClient(m_hwnd, &ptCursor); + + if (IsEqual(m_sonarPos, ptCursor)) + { + // Mouse is stationary. + if (m_sonarStart != SonarWaitingForMouseMove && now - m_sonarStart >= IdlePeriod) + { + StopSonar(); + return; + } + } + else + { + // Mouse has moved. + if (IsEqual(m_sonarPos, ptNowhere)) + { + // Initial call, mark sonar as active but waiting for first mouse-move. + now = SonarWaitingForMouseMove; + } + SetTimer(m_hwnd, TIMER_ID_TRACK, IdlePeriod, nullptr); + Shim()->BeforeMoveSonar(); + m_sonarPos = ptCursor; + m_sonarStart = now; + Shim()->AfterMoveSonar(); + } +} + +template +void SuperSonar::UpdateMouseSnooping() +{ + bool wantSnoopingMouse = m_sonarStart != NoSonar || m_sonarState != SonarState::Idle; + if (m_isSnoopingMouse != wantSnoopingMouse) + { + m_isSnoopingMouse = wantSnoopingMouse; + RAWINPUTDEVICE mouse{}; + mouse.usUsagePage = HID_USAGE_PAGE_GENERIC; + mouse.usUsage = HID_USAGE_GENERIC_MOUSE; + if (wantSnoopingMouse) + { + mouse.dwFlags = RIDEV_INPUTSINK; + mouse.hwndTarget = m_hwnd; + } + else + { + mouse.dwFlags = RIDEV_REMOVE; + mouse.hwndTarget = nullptr; + } + RegisterRawInputDevices(&mouse, 1, sizeof(mouse)); + } +} + +struct CompositionSpotlight : SuperSonar +{ + static constexpr UINT WM_OPACITY_ANIMATION_COMPLETED = WM_APP; + static constexpr float SonarRadiusFloat = static_cast(SonarRadius); + + DWORD GetExtendedStyle() + { + return WS_EX_NOREDIRECTIONBITMAP; + } + + void AfterMoveSonar() + { + m_spotlight.Offset({ (float)m_sonarPos.x, (float)m_sonarPos.y, 0.0f }); + } + + LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam) noexcept + { + switch (message) + { + case WM_CREATE: + return OnCompositionCreate() && BaseWndProc(message, wParam, lParam); + + case WM_OPACITY_ANIMATION_COMPLETED: + OnOpacityAnimationCompleted(); + break; + } + return BaseWndProc(message, wParam, lParam); + } + + void SetSonarVisibility(bool visible) + { + m_batch = m_compositor.GetCommitBatch(winrt::CompositionBatchTypes::Animation); + m_batch.Completed([hwnd = m_hwnd](auto&&, auto&&) { + PostMessage(hwnd, WM_OPACITY_ANIMATION_COMPLETED, 0, 0); + }); + m_root.Opacity(visible ? static_cast(FinalAlphaNumerator) / FinalAlphaDenominator : 0.0f); + if (visible) + { + ShowWindow(m_hwnd, SW_SHOWNOACTIVATE); + } + } + +private: + bool OnCompositionCreate() + try + { + // We need a dispatcher queue. + DispatcherQueueOptions options = { + sizeof(options), + DQTYPE_THREAD_CURRENT, + DQTAT_COM_ASTA, + }; + ABI::IDispatcherQueueController* controller; + winrt::check_hresult(CreateDispatcherQueueController(options, &controller)); + *winrt::put_abi(m_dispatcherQueueController) = controller; + + // Create the compositor for our window. + m_compositor = winrt::Compositor(); + ABI::IDesktopWindowTarget* target; + winrt::check_hresult(m_compositor.as()->CreateDesktopWindowTarget(m_hwnd, false, &target)); + *winrt::put_abi(m_target) = target; + + // Our composition tree: + // + // [root] ContainerVisual + // \ LayerVisual + // \[gray backdrop] + // [spotlight] + m_root = m_compositor.CreateContainerVisual(); + m_root.RelativeSizeAdjustment({ 1.0f, 1.0f }); // fill the parent + m_root.Opacity(0.0f); + m_target.Root(m_root); + + auto layer = m_compositor.CreateLayerVisual(); + layer.RelativeSizeAdjustment({ 1.0f, 1.0f }); // fill the parent + m_root.Children().InsertAtTop(layer); + + auto backdrop = m_compositor.CreateSpriteVisual(); + backdrop.RelativeSizeAdjustment({ 1.0f, 1.0f }); // fill the parent + backdrop.Brush(m_compositor.CreateColorBrush({ 255, 0, 0, 0 })); + layer.Children().InsertAtTop(backdrop); + + m_circleGeometry = m_compositor.CreateEllipseGeometry(); // radius set via expression animation + auto circleShape = m_compositor.CreateSpriteShape(m_circleGeometry); + circleShape.FillBrush(m_compositor.CreateColorBrush({ 255, 255, 255, 255 })); + circleShape.Offset({ SonarRadiusFloat * SonarZoomFactor, SonarRadiusFloat * SonarZoomFactor }); + m_spotlight = m_compositor.CreateShapeVisual(); + m_spotlight.Size({ SonarRadiusFloat * 2 * SonarZoomFactor, SonarRadiusFloat * 2 * SonarZoomFactor }); + m_spotlight.AnchorPoint({ 0.5f, 0.5f }); + m_spotlight.Shapes().Append(circleShape); + + layer.Children().InsertAtTop(m_spotlight); + + // Implicitly animate the alpha. + auto animation = m_compositor.CreateScalarKeyFrameAnimation(); + animation.Target(L"Opacity"); + animation.InsertExpressionKeyFrame(1.0f, L"this.FinalValue"); + animation.Duration(std::chrono::milliseconds{ FadeDuration }); + auto collection = m_compositor.CreateImplicitAnimationCollection(); + collection.Insert(L"Opacity", animation); + m_root.ImplicitAnimations(collection); + + // Radius of spotlight shrinks as opacity increases. + // At opacity zero, it is SonarRadius * SonarZoomFactor. + // At maximum opacity, it is SonarRadius. + auto radiusExpression = m_compositor.CreateExpressionAnimation(); + radiusExpression.SetReferenceParameter(L"Root", m_root); + wchar_t expressionText[256]; + winrt::check_hresult(StringCchPrintfW(expressionText, ARRAYSIZE(expressionText), L"Lerp(Vector2(%d, %d), Vector2(%d, %d), Root.Opacity * %d / %d)", SonarRadius * SonarZoomFactor, SonarRadius * SonarZoomFactor, SonarRadius, SonarRadius, FinalAlphaDenominator, FinalAlphaNumerator)); + radiusExpression.Expression(expressionText); + m_circleGeometry.StartAnimation(L"Radius", radiusExpression); + + return true; + } + catch (...) + { + return false; + } + + void OnOpacityAnimationCompleted() + { + if (m_root.Opacity() < 0.01f) + { + ShowWindow(m_hwnd, SW_HIDE); + } + } + +private: + winrt::Compositor m_compositor{ nullptr }; + winrt::Desktop::DesktopWindowTarget m_target{ nullptr }; + winrt::ContainerVisual m_root{ nullptr }; + winrt::CompositionEllipseGeometry m_circleGeometry{ nullptr }; + winrt::ShapeVisual m_spotlight{ nullptr }; + winrt::CompositionCommitBatch m_batch{ nullptr }; +}; + +template +struct GdiSonar : SuperSonar +{ + LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam) noexcept + { + switch (message) + { + case WM_CREATE: + SetLayeredWindowAttributes(this->m_hwnd, 0, 0, LWA_ALPHA); + break; + + case WM_TIMER: + switch (wParam) + { + case TIMER_ID_FADE: + OnFadeTimer(); + break; + } + break; + + case WM_PAINT: + this->Shim()->OnPaint(); + break; + } + return this->BaseWndProc(message, wParam, lParam); + } + + void BeforeMoveSonar() { this->Shim()->InvalidateSonar(); } + void AfterMoveSonar() { this->Shim()->InvalidateSonar(); } + + void SetSonarVisibility(bool visible) + { + m_alphaTarget = visible ? MaxAlpha : 0; + m_fadeStart = GetTickCount() - FadeFramePeriod; + SetTimer(this->m_hwnd, TIMER_ID_FADE, FadeFramePeriod, nullptr); + OnFadeTimer(); + } + + void OnFadeTimer() + { + auto now = GetTickCount(); + auto step = (int)((now - m_fadeStart) * MaxAlpha / this->FadeDuration); + + this->Shim()->InvalidateSonar(); + if (m_alpha < m_alphaTarget) + { + m_alpha += step; + if (m_alpha > m_alphaTarget) + m_alpha = m_alphaTarget; + } + else if (m_alpha > m_alphaTarget) + { + m_alpha -= step; + if (m_alpha < m_alphaTarget) + m_alpha = m_alphaTarget; + } + SetLayeredWindowAttributes(this->m_hwnd, 0, (BYTE)m_alpha, LWA_ALPHA); + this->Shim()->InvalidateSonar(); + if (m_alpha == m_alphaTarget) + { + KillTimer(this->m_hwnd, TIMER_ID_FADE); + if (m_alpha == 0) + { + ShowWindow(this->m_hwnd, SW_HIDE); + } + } + else + { + ShowWindow(this->m_hwnd, SW_SHOWNOACTIVATE); + } + } + +protected: + int CurrentSonarRadius() + { + int range = MaxAlpha - m_alpha; + int radius = this->SonarRadius + this->SonarRadius * range * (this->SonarZoomFactor - 1) / MaxAlpha; + return radius; + } + +private: + static constexpr DWORD FadeFramePeriod = 10; + static constexpr int MaxAlpha = SuperSonar::FinalAlphaNumerator * 255 / SuperSonar::FinalAlphaDenominator; + static constexpr DWORD TIMER_ID_FADE = 101; + +private: + int m_alpha = 0; + int m_alphaTarget = 0; + DWORD m_fadeStart = 0; +}; + +struct GdiSpotlight : GdiSonar +{ + void InvalidateSonar() + { + RECT rc; + auto radius = CurrentSonarRadius(); + rc.left = this->m_sonarPos.x - radius; + rc.top = this->m_sonarPos.y - radius; + rc.right = this->m_sonarPos.x + radius; + rc.bottom = this->m_sonarPos.y + radius; + InvalidateRect(this->m_hwnd, &rc, FALSE); + } + + void OnPaint() + { + PAINTSTRUCT ps; + BeginPaint(this->m_hwnd, &ps); + + auto radius = CurrentSonarRadius(); + auto spotlight = CreateRoundRectRgn( + this->m_sonarPos.x - radius, this->m_sonarPos.y - radius, this->m_sonarPos.x + radius, this->m_sonarPos.y + radius, radius * 2, radius * 2); + + FillRgn(ps.hdc, spotlight, (HBRUSH)GetStockObject(WHITE_BRUSH)); + Sleep(1000 / 60); + ExtSelectClipRgn(ps.hdc, spotlight, RGN_DIFF); + FillRect(ps.hdc, &ps.rcPaint, (HBRUSH)GetStockObject(BLACK_BRUSH)); + DeleteObject(spotlight); + + EndPaint(this->m_hwnd, &ps); + } +}; + +struct GdiCrosshairs : GdiSonar +{ + void InvalidateSonar() + { + RECT rc; + auto radius = CurrentSonarRadius(); + GetClientRect(m_hwnd, &rc); + rc.left = m_sonarPos.x - radius; + rc.right = m_sonarPos.x + radius; + InvalidateRect(m_hwnd, &rc, FALSE); + + GetClientRect(m_hwnd, &rc); + rc.top = m_sonarPos.y - radius; + rc.bottom = m_sonarPos.y + radius; + InvalidateRect(m_hwnd, &rc, FALSE); + } + + void OnPaint() + { + PAINTSTRUCT ps; + BeginPaint(this->m_hwnd, &ps); + + auto radius = CurrentSonarRadius(); + RECT rc; + + HBRUSH white = (HBRUSH)GetStockObject(WHITE_BRUSH); + + rc.left = m_sonarPos.x - radius; + rc.top = ps.rcPaint.top; + rc.right = m_sonarPos.x + radius; + rc.bottom = ps.rcPaint.bottom; + FillRect(ps.hdc, &rc, white); + + rc.left = ps.rcPaint.left; + rc.top = m_sonarPos.y - radius; + rc.right = ps.rcPaint.right; + rc.bottom = m_sonarPos.y + radius; + FillRect(ps.hdc, &rc, white); + + HBRUSH black = (HBRUSH)GetStockObject(BLACK_BRUSH); + + // Top left + rc.left = ps.rcPaint.left; + rc.top = ps.rcPaint.top; + rc.right = m_sonarPos.x - radius; + rc.bottom = m_sonarPos.y - radius; + FillRect(ps.hdc, &rc, black); + + // Top right + rc.left = m_sonarPos.x + radius; + rc.top = ps.rcPaint.top; + rc.right = ps.rcPaint.right; + rc.bottom = m_sonarPos.y - radius; + FillRect(ps.hdc, &rc, black); + + // Bottom left + rc.left = ps.rcPaint.left; + rc.top = m_sonarPos.y + radius; + rc.right = m_sonarPos.x - radius; + rc.bottom = ps.rcPaint.bottom; + FillRect(ps.hdc, &rc, black); + + // Bottom right + rc.left = m_sonarPos.x + radius; + rc.top = m_sonarPos.y + radius; + rc.right = ps.rcPaint.right; + rc.bottom = ps.rcPaint.bottom; + FillRect(ps.hdc, &rc, black); + + EndPaint(this->m_hwnd, &ps); + } +}; + +#pragma endregion Super_Sonar_Base_Code + + +#pragma region Super_Sonar_API + +CompositionSpotlight* m_sonar = nullptr; + +void FindMyMouseDisable() +{ + if (m_sonar != nullptr) + { + Logger::info("Terminating a sonar instance."); + m_sonar->Terminate(); + } +} + +bool FindMyMouseIsEnabled() +{ + return (m_sonar != nullptr); +} + +void FindMyMouseSetDoNotActivateOnGameMode(bool doNotActivate) +{ + m_doNotActivateOnGameMode = doNotActivate; +} + +// Based on SuperSonar's original wWinMain. +int FindMyMouseMain(HINSTANCE hinst) +{ + Logger::info("Starting a sonar instance."); + if (m_sonar != nullptr) + { + Logger::error("A sonar instance was still working when trying to start a new one."); + return 0; + } + + CompositionSpotlight sonar; + if (!sonar.Initialize(hinst)) + { + Logger::error("Couldn't initialize a sonar instance."); + return 0; + } + m_sonar = &sonar; + Logger::info("Initialized the sonar instance."); + + MSG msg; + + // Main message loop: + while (GetMessage(&msg, nullptr, 0, 0)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + Logger::info("Sonar message loop ended."); + m_sonar = nullptr; + + return (int)msg.wParam; +} + +#pragma endregion Super_Sonar_API diff --git a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.h b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.h new file mode 100644 index 000000000..d5c4653d1 --- /dev/null +++ b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.h @@ -0,0 +1,6 @@ +#pragma once +#include "pch.h" +int FindMyMouseMain(HINSTANCE hinst); +void FindMyMouseDisable(); +bool FindMyMouseIsEnabled(); +void FindMyMouseSetDoNotActivateOnGameMode(bool doNotActivate); diff --git a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj new file mode 100644 index 000000000..575955bd0 --- /dev/null +++ b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj @@ -0,0 +1,146 @@ + + + + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {e94fd11c-0591-456f-899f-efc0ca548336} + Win32Proj + FindMyMouse + true + 10.0.18362.0 + FindMyMouse + + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + + + + + + + + + + + + + true + $(SolutionDir)$(Platform)\$(Configuration)\modules\MouseUtils\ + + + false + $(SolutionDir)$(Platform)\$(Configuration)\modules\MouseUtils\ + + + + Level3 + Disabled + true + _DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + MultiThreadedDebug + stdcpplatest + + + Windows + true + $(OutDir)$(TargetName)$(TargetExt) + + + + + Level3 + MaxSpeed + true + true + true + NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + MultiThreaded + stdcpplatest + + + Windows + true + true + true + $(OutDir)$(TargetName)$(TargetExt) + + + + + $(SolutionDir)src\;$(SolutionDir)src\modules;$(SolutionDir)src\common\Telemetry;%(AdditionalIncludeDirectories) + + + + + Use + pch.h + + + + + + + + + + + + + + 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}. + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/powerpreviewTest/powerpreviewTest.vcxproj.filters b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj.filters similarity index 54% rename from src/modules/previewpane/powerpreviewTest/powerpreviewTest.vcxproj.filters rename to src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj.filters index 6f11ff53c..f2c5464f7 100644 --- a/src/modules/previewpane/powerpreviewTest/powerpreviewTest.vcxproj.filters +++ b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj.filters @@ -3,22 +3,31 @@ {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + h;hh;hpp;hxx;h++;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 + + {875a08c6-f610-4667-bd0f-80171ed96072} + + + Source Files + Source Files - + + Source Files + + Source Files @@ -26,16 +35,28 @@ Header Files - + Header Files + + Header Files + + + Generated Files + - + + Resource Files - + + + Resource Files + - + + Generated Files + \ No newline at end of file diff --git a/src/modules/MouseUtils/FindMyMouse/dllmain.cpp b/src/modules/MouseUtils/FindMyMouse/dllmain.cpp new file mode 100644 index 000000000..495d74a4b --- /dev/null +++ b/src/modules/MouseUtils/FindMyMouse/dllmain.cpp @@ -0,0 +1,186 @@ +#include "pch.h" +#include +#include +#include "trace.h" +#include "FindMyMouse.h" +#include +#include + + +namespace +{ + const wchar_t JSON_KEY_PROPERTIES[] = L"properties"; + const wchar_t JSON_KEY_VALUE[] = L"value"; + const wchar_t JSON_KEY_DO_NOT_ACTIVATE_ON_GAME_MODE[] = L"do_not_activate_on_game_mode"; +} + +extern "C" IMAGE_DOS_HEADER __ImageBase; + +HMODULE m_hModule; + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) +{ + m_hModule = hModule; + 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; +} + +// The PowerToy name that will be shown in the settings. +const static wchar_t* MODULE_NAME = L"FindMyMouse"; +// Add a description that will we shown in the module settings page. +const static wchar_t* MODULE_DESC = L"Focus the mouse pointer"; + +// Implement the PowerToy Module Interface and all the required methods. +class FindMyMouse : public PowertoyModuleIface +{ +private: + // The PowerToy state. + bool m_enabled = false; + + // Load initial settings from the persisted values. + void init_settings(); + + // Helper function to extract the settings + void parse_settings(PowerToysSettings::PowerToyValues& settings); + +public: + // Constructor + FindMyMouse() + { + LoggerHelpers::init_logger(MODULE_NAME, L"ModuleInterface", LogSettings::findMyMouseLoggerName); + init_settings(); + }; + + // Destroy the powertoy and free memory + virtual void destroy() override + { + delete this; + } + + // Return the localized display name of the powertoy + virtual const wchar_t* get_name() override + { + return MODULE_NAME; + } + + // Return the non localized key of the powertoy, this will be cached by the runner + virtual const wchar_t* get_key() override + { + return MODULE_NAME; + } + + // Return JSON with the configuration options. + virtual bool get_config(wchar_t* buffer, int* buffer_size) override + { + HINSTANCE hinstance = reinterpret_cast(&__ImageBase); + + // Create a Settings object. + PowerToysSettings::Settings settings(hinstance, get_name()); + settings.set_description(MODULE_DESC); + + return settings.serialize_to_buffer(buffer, buffer_size); + } + + // Signal from the Settings editor to call a custom action. + // This can be used to spawn more complex editors. + virtual void call_custom_action(const wchar_t* action) override + { + } + + // Called by the runner to pass the updated settings values as a serialized JSON. + virtual void set_config(const wchar_t* config) override + { + try + { + // Parse the input JSON string. + PowerToysSettings::PowerToyValues values = + PowerToysSettings::PowerToyValues::from_json_string(config, get_key()); + + parse_settings(values); + + values.save_to_settings_file(); + } + catch (std::exception&) + { + // Improper JSON. + } + } + + // Enable the powertoy + virtual void enable() + { + m_enabled = true; + Trace::EnableFindMyMouse(true); + std::thread([]() { FindMyMouseMain(m_hModule); }).detach(); + } + + // Disable the powertoy + virtual void disable() + { + m_enabled = false; + Trace::EnableFindMyMouse(false); + FindMyMouseDisable(); + } + + // Returns if the powertoys is enabled + virtual bool is_enabled() override + { + return m_enabled; + } +}; + +// Load the settings file. +void FindMyMouse::init_settings() +{ + try + { + // Load and parse the settings file for this PowerToy. + PowerToysSettings::PowerToyValues settings = + PowerToysSettings::PowerToyValues::load_from_settings_file(FindMyMouse::get_key()); + parse_settings(settings); + } + catch (std::exception&) + { + // Error while loading from the settings file. Let default values stay as they are. + } +} + +void FindMyMouse::parse_settings(PowerToysSettings::PowerToyValues& settings) +{ + FindMyMouseSetDoNotActivateOnGameMode(true); + + auto settingsObject = settings.get_raw_json(); + if (settingsObject.GetView().Size()) + { + try + { + auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_DO_NOT_ACTIVATE_ON_GAME_MODE); + FindMyMouseSetDoNotActivateOnGameMode((bool)jsonPropertiesObject.GetNamedBoolean(JSON_KEY_VALUE)); + } + catch (...) + { + Logger::warn("Failed to get 'do not activate on game mode' setting"); + } + } + else + { + Logger::info("Find My Mouse settings are empty"); + } +} + + +extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create() +{ + return new FindMyMouse(); +} \ No newline at end of file diff --git a/src/modules/powerrename/ui/packages.config b/src/modules/MouseUtils/FindMyMouse/packages.config similarity index 100% rename from src/modules/powerrename/ui/packages.config rename to src/modules/MouseUtils/FindMyMouse/packages.config diff --git a/src/modules/previewpane/powerpreviewTest/pch.cpp b/src/modules/MouseUtils/FindMyMouse/pch.cpp similarity index 100% rename from src/modules/previewpane/powerpreviewTest/pch.cpp rename to src/modules/MouseUtils/FindMyMouse/pch.cpp diff --git a/src/modules/MouseUtils/FindMyMouse/pch.h b/src/modules/MouseUtils/FindMyMouse/pch.h new file mode 100644 index 000000000..6dbc25600 --- /dev/null +++ b/src/modules/MouseUtils/FindMyMouse/pch.h @@ -0,0 +1,20 @@ +#pragma once + +#define COMPOSITION +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +#include +#include +#include + +#ifdef COMPOSITION +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include diff --git a/src/modules/MouseUtils/FindMyMouse/resource.base.h b/src/modules/MouseUtils/FindMyMouse/resource.base.h new file mode 100644 index 000000000..83354c288 --- /dev/null +++ b/src/modules/MouseUtils/FindMyMouse/resource.base.h @@ -0,0 +1,14 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by FindMyMouse.rc + +////////////////////////////// +// Non-localizable + +#define FILE_DESCRIPTION "PowerToys FindMyMouse" +#define INTERNAL_NAME "FindMyMouse" +#define ORIGINAL_FILENAME "FindMyMouse.dll" +#define IDS_KEYBOARDMANAGER_ICON 1001 + +// Non-localizable +////////////////////////////// diff --git a/src/modules/MouseUtils/FindMyMouse/trace.cpp b/src/modules/MouseUtils/FindMyMouse/trace.cpp new file mode 100644 index 000000000..a5cfe0241 --- /dev/null +++ b/src/modules/MouseUtils/FindMyMouse/trace.cpp @@ -0,0 +1,40 @@ +#include "pch.h" +#include "trace.h" + +TRACELOGGING_DEFINE_PROVIDER( + g_hProvider, + "Microsoft.PowerToys", + // {38e8889b-9731-53f5-e901-e8a7c1753074} + (0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74), + TraceLoggingOptionProjectTelemetry()); + +void Trace::RegisterProvider() noexcept +{ + TraceLoggingRegister(g_hProvider); +} + +void Trace::UnregisterProvider() noexcept +{ + TraceLoggingUnregister(g_hProvider); +} + +// Log if the user has FindMyMouse enabled or disabled +void Trace::EnableFindMyMouse(const bool enabled) noexcept +{ + TraceLoggingWrite( + g_hProvider, + "FindMyMouse_EnableFindMyMouse", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), + TraceLoggingBoolean(enabled, "Enabled")); +} + +// Log that the user activated the module by focusing the mouse pointer +void Trace::MousePointerFocused() noexcept +{ + TraceLoggingWrite( + g_hProvider, + "FindMyMouse_MousePointerFocused", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); +} diff --git a/src/modules/MouseUtils/FindMyMouse/trace.h b/src/modules/MouseUtils/FindMyMouse/trace.h new file mode 100644 index 000000000..623ce6017 --- /dev/null +++ b/src/modules/MouseUtils/FindMyMouse/trace.h @@ -0,0 +1,14 @@ +#pragma once + +class Trace +{ +public: + static void RegisterProvider() noexcept; + static void UnregisterProvider() noexcept; + + // Log if the user has FindMyMouse enabled or disabled + static void EnableFindMyMouse(const bool enabled) noexcept; + + // Log that the user activated the module by focusing the mouse pointer + static void MousePointerFocused() noexcept; +}; diff --git a/src/modules/awake/Awake/Core/TrayHelper.cs b/src/modules/awake/Awake/Core/TrayHelper.cs index 96ea80bad..adba7b05f 100644 --- a/src/modules/awake/Awake/Core/TrayHelper.cs +++ b/src/modules/awake/Awake/Core/TrayHelper.cs @@ -175,7 +175,7 @@ namespace Awake.Core }; // No keep-awake menu item. - ToolStripMenuItem? passiveMenuItem = new ToolStripMenuItem + CheckButtonToolStripMenuItem? passiveMenuItem = new CheckButtonToolStripMenuItem { Text = "Off (Passive)", }; @@ -189,7 +189,7 @@ namespace Awake.Core }; // Indefinite keep-awake menu item. - ToolStripMenuItem? indefiniteMenuItem = new ToolStripMenuItem + CheckButtonToolStripMenuItem? indefiniteMenuItem = new CheckButtonToolStripMenuItem { Text = "Keep awake indefinitely", }; @@ -202,7 +202,7 @@ namespace Awake.Core indefiniteKeepAwakeCallback(); }; - ToolStripMenuItem? displayOnMenuItem = new ToolStripMenuItem + CheckButtonToolStripMenuItem? displayOnMenuItem = new CheckButtonToolStripMenuItem { Text = "Keep screen on", }; @@ -222,6 +222,7 @@ namespace Awake.Core }; timedMenuItem.Checked = mode == AwakeMode.TIMED; + timedMenuItem.AccessibleName = timedMenuItem.Text + (timedMenuItem.Checked ? ". Checked. " : ". UnChecked. "); ToolStripMenuItem? halfHourMenuItem = new ToolStripMenuItem { @@ -284,5 +285,38 @@ namespace Awake.Core TrayIcon.Text = text; TrayIcon.ContextMenuStrip = contextMenuStrip; } + + private class CheckButtonToolStripMenuItemAccessibleObject : ToolStripItem.ToolStripItemAccessibleObject + { + private CheckButtonToolStripMenuItem _menuItem; + + public CheckButtonToolStripMenuItemAccessibleObject(CheckButtonToolStripMenuItem menuItem) + : base(menuItem) + { + _menuItem = menuItem; + } + + public override AccessibleRole Role + { + get + { + return AccessibleRole.CheckButton; + } + } + + public override string Name => _menuItem.Text + ", " + Role + ", " + (_menuItem.Checked ? "Checked" : "Unchecked"); + } + + private class CheckButtonToolStripMenuItem : ToolStripMenuItem + { + public CheckButtonToolStripMenuItem() + { + } + + protected override AccessibleObject CreateAccessibilityInstance() + { + return new CheckButtonToolStripMenuItemAccessibleObject(this); + } + } } } diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/App.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/App.xaml.cs index 0cd4e262b..29eabeaea 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/App.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/App.xaml.cs @@ -3,16 +3,12 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Input; +using FancyZonesEditor.Logs; using FancyZonesEditor.Utils; using ManagedCommon; using Microsoft.PowerToys.Common.UI; @@ -25,26 +21,7 @@ namespace FancyZonesEditor public partial class App : Application, IDisposable { // Non-localizable strings - private const string CrashReportLogFile = "FZEditorCrashLog.txt"; - private const string ErrorReportLogFile = "FZEditorErrorLog.txt"; private const string PowerToysIssuesURL = "https://aka.ms/powerToysReportBug"; - - private const string CrashReportExceptionTag = "Exception"; - private const string CrashReportSourceTag = "Source: "; - private const string CrashReportTargetAssemblyTag = "TargetAssembly: "; - private const string CrashReportTargetModuleTag = "TargetModule: "; - private const string CrashReportTargetSiteTag = "TargetSite: "; - private const string CrashReportEnvironmentTag = "Environment"; - private const string CrashReportCommandLineTag = "* Command Line: "; - private const string CrashReportTimestampTag = "* Timestamp: "; - private const string CrashReportOSVersionTag = "* OS Version: "; - private const string CrashReportIntPtrLengthTag = "* IntPtr Length: "; - private const string CrashReportx64Tag = "* x64: "; - private const string CrashReportCLRVersionTag = "* CLR Version: "; - private const string CrashReportAssembliesTag = "Assemblies - "; - private const string CrashReportDynamicAssemblyTag = "dynamic assembly doesn't have location"; - private const string CrashReportLocationNullTag = "location is null or empty"; - private const string ParsingErrorReportTag = "Settings parsing error"; private const string ParsingErrorDataTag = "Data: "; @@ -60,6 +37,8 @@ namespace FancyZonesEditor private EventWaitHandle _eventHandle; + private Thread _exitWaitThread; + public static bool DebugMode { get @@ -84,14 +63,8 @@ namespace FancyZonesEditor Overlay = new Overlay(); MainWindowSettings = new MainWindowSettingsModel(); - new Thread(() => - { - _eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, interop.Constants.FZEExitEvent()); - if (_eventHandle.WaitOne()) - { - Environment.Exit(0); - } - }).Start(); + _exitWaitThread = new Thread(App_WaitExit); + _exitWaitThread.Start(); } private void OnStartup(object sender, StartupEventArgs e) @@ -100,6 +73,7 @@ namespace FancyZonesEditor RunnerHelper.WaitForPowerToysRunner(PowerToysPID, () => { + Logger.LogInfo("Runner exited"); Environment.Exit(0); }); @@ -139,15 +113,7 @@ namespace FancyZonesEditor // Error message if parsing failed if (!parseResult.Result) { - var sb = new StringBuilder(); - sb.AppendLine(); - sb.AppendLine("## " + ParsingErrorReportTag); - sb.AppendLine(); - sb.AppendLine(parseResult.Message); - sb.AppendLine(); - sb.AppendLine(ParsingErrorDataTag); - sb.AppendLine(parseResult.MalformedData); - + Logger.LogError(ParsingErrorReportTag + ": " + parseResult.Message + "; " + ParsingErrorDataTag + ": " + parseResult.MalformedData); MessageBox.Show(parseResult.Message, FancyZonesEditor.Properties.Resources.Error_Parsing_Zones_Settings_Title, MessageBoxButton.OK); } @@ -159,10 +125,26 @@ namespace FancyZonesEditor private void OnExit(object sender, ExitEventArgs e) { + Dispose(); + if (_eventHandle != null) { _eventHandle.Set(); } + + _exitWaitThread.Join(); + + Logger.LogInfo("FancyZones Editor exited"); + } + + private void App_WaitExit() + { + _eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, interop.Constants.FZEExitEvent()); + if (_eventHandle.WaitOne()) + { + Logger.LogInfo("Exit event triggered"); + Environment.Exit(0); + } } public void App_KeyUp(object sender, System.Windows.Input.KeyEventArgs e) @@ -197,126 +179,20 @@ namespace FancyZonesEditor MessageBox.Show(fullMessage, FancyZonesEditor.Properties.Resources.Error_Exception_Message_Box_Title); } - public static void ShowExceptionReportMessageBox(string reportData) - { - var fileStream = File.OpenWrite(ErrorReportLogFile); - using (var sw = new StreamWriter(fileStream)) - { - sw.Write(reportData); - sw.Flush(); - } - - fileStream.Close(); - - ShowReportMessageBox(fileStream.Name); - } - private void OnUnhandledException(object sender, UnhandledExceptionEventArgs args) { - var fileStream = File.OpenWrite(CrashReportLogFile); - using (var sw = new StreamWriter(fileStream)) - { - sw.Write(FormatException((Exception)args.ExceptionObject)); - } - - fileStream.Close(); - - ShowReportMessageBox(fileStream.Name); + Logger.LogError("Unhandled exception", (Exception)args.ExceptionObject); + ShowReportMessageBox(); } - private static void ShowReportMessageBox(string fileName) + private static void ShowReportMessageBox() { MessageBox.Show( - FancyZonesEditor.Properties.Resources.Crash_Report_Message_Box_Text_Part1 + - Path.GetFullPath(fileName) + - "\n" + - FancyZonesEditor.Properties.Resources.Crash_Report_Message_Box_Text_Part2 + + FancyZonesEditor.Properties.Resources.Crash_Report_Message_Box_Text + PowerToysIssuesURL, FancyZonesEditor.Properties.Resources.Fancy_Zones_Editor_App_Title); } - private static string FormatException(Exception ex) - { - var sb = new StringBuilder(); - sb.AppendLine(); - sb.AppendLine("## " + CrashReportExceptionTag); - sb.AppendLine(); - sb.AppendLine("```"); - - var exlist = new List(); - - while (ex != null) - { - var exsb = new StringBuilder(); - exsb.Append(ex.GetType().FullName); - exsb.Append(": "); - exsb.AppendLine(ex.Message); - if (ex.Source != null) - { - exsb.Append(" " + CrashReportSourceTag); - exsb.AppendLine(ex.Source); - } - - if (ex.TargetSite != null) - { - exsb.Append(" " + CrashReportTargetAssemblyTag); - exsb.AppendLine(ex.TargetSite.Module.Assembly.ToString()); - exsb.Append(" " + CrashReportTargetModuleTag); - exsb.AppendLine(ex.TargetSite.Module.ToString()); - exsb.Append(" " + CrashReportTargetSiteTag); - exsb.AppendLine(ex.TargetSite.ToString()); - } - - exsb.AppendLine(ex.StackTrace); - exlist.Add(exsb); - - ex = ex.InnerException; - } - - foreach (var result in exlist.Select(o => o.ToString()).Reverse()) - { - sb.AppendLine(result); - } - - sb.AppendLine("```"); - sb.AppendLine(); - - sb.AppendLine("## " + CrashReportEnvironmentTag); - sb.AppendLine(CrashReportCommandLineTag + Environment.CommandLine); - - // Using InvariantCulture since this is used for a timestamp internally - sb.AppendLine(CrashReportTimestampTag + DateTime.Now.ToString(CultureInfo.InvariantCulture)); - sb.AppendLine(CrashReportOSVersionTag + Environment.OSVersion.VersionString); - sb.AppendLine(CrashReportIntPtrLengthTag + IntPtr.Size); - sb.AppendLine(CrashReportx64Tag + Environment.Is64BitOperatingSystem); - sb.AppendLine(CrashReportCLRVersionTag + Environment.Version); - sb.AppendLine("## " + CrashReportAssembliesTag + AppDomain.CurrentDomain.FriendlyName); - sb.AppendLine(); - foreach (var ass in AppDomain.CurrentDomain.GetAssemblies().OrderBy(o => o.GlobalAssemblyCache ? 50 : 0)) - { - sb.Append("* "); - sb.Append(ass.FullName); - sb.Append(" ("); - - if (ass.IsDynamic) - { - sb.Append(CrashReportDynamicAssemblyTag); - } - else if (string.IsNullOrEmpty(ass.Location)) - { - sb.Append(CrashReportLocationNullTag); - } - else - { - sb.Append(ass.Location); - } - - sb.AppendLine(")"); - } - - return sb.ToString(); - } - protected virtual void Dispose(bool disposing) { if (!_isDisposed) @@ -329,6 +205,7 @@ namespace FancyZonesEditor // TODO: free unmanaged resources (unmanaged objects) and override finalizer // TODO: set large fields to null _isDisposed = true; + Logger.LogInfo("FancyZones Editor disposed"); } } diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/CanvasEditorWindow.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/CanvasEditorWindow.xaml.cs index 849e5f54f..d6e38930b 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/CanvasEditorWindow.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/CanvasEditorWindow.xaml.cs @@ -4,6 +4,7 @@ using System.Windows; using System.Windows.Input; +using FancyZonesEditor.Logs; using FancyZonesEditor.Models; namespace FancyZonesEditor @@ -34,11 +35,13 @@ namespace FancyZonesEditor private void OnAddZone(object sender, RoutedEventArgs e) { + Logger.LogInfo("Add zone"); _model.AddZone(); } protected new void OnCancel(object sender, RoutedEventArgs e) { + Logger.LogInfo("Cancel changes"); base.OnCancel(sender, e); _stashedModel.RestoreTo(_model); } diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/EditorWindow.cs b/src/modules/fancyzones/editor/FancyZonesEditor/EditorWindow.cs index 06a2b4ac1..ca2e7a643 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/EditorWindow.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/EditorWindow.cs @@ -4,6 +4,7 @@ using System; using System.Windows; +using FancyZonesEditor.Logs; using FancyZonesEditor.Models; namespace FancyZonesEditor @@ -12,6 +13,7 @@ namespace FancyZonesEditor { protected void OnSaveApplyTemplate(object sender, RoutedEventArgs e) { + Logger.LogTrace(); var mainEditor = App.Overlay; if (mainEditor.CurrentDataContext is LayoutModel model) { diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/GridData.cs b/src/modules/fancyzones/editor/FancyZonesEditor/GridData.cs index c760b4a81..67a828619 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/GridData.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/GridData.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Windows.Controls; +using FancyZonesEditor.Logs; using FancyZonesEditor.Models; namespace FancyZonesEditor @@ -417,6 +418,8 @@ namespace FancyZonesEditor public void DoMerge(List indices) { + Logger.LogTrace(); + if (indices.Count == 0) { return; @@ -455,6 +458,7 @@ namespace FancyZonesEditor public void Split(int zoneIndex, int position, Orientation orientation) { + Logger.LogTrace(); if (!CanSplit(zoneIndex, position, orientation)) { return; @@ -519,6 +523,8 @@ namespace FancyZonesEditor public void Drag(int resizerIndex, int delta) { + Logger.LogTrace(); + if (!CanDrag(resizerIndex, delta)) { return; diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/GridEditor.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/GridEditor.xaml.cs index c539d92ab..2c31bd354 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/GridEditor.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/GridEditor.xaml.cs @@ -10,6 +10,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Input; +using FancyZonesEditor.Logs; using FancyZonesEditor.Models; namespace FancyZonesEditor @@ -316,6 +317,8 @@ namespace FancyZonesEditor private void OnSplit(object sender, SplitEventArgs args) { + Logger.LogTrace(); + MergeCancelClick(null, null); var zonePanel = sender as GridZone; @@ -488,6 +491,7 @@ namespace FancyZonesEditor private void OnMergeComplete(object o, MouseButtonEventArgs e) { + Logger.LogTrace(); _inMergeDrag = false; var selectedIndices = new List(); diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/LayoutPreview.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/LayoutPreview.xaml.cs index 65472fec1..1fc8f48d8 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/LayoutPreview.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/LayoutPreview.xaml.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Windows; using System.Windows.Controls; using System.Windows.Media; +using FancyZonesEditor.Logs; using FancyZonesEditor.Models; namespace FancyZonesEditor @@ -83,6 +84,11 @@ namespace FancyZonesEditor { _model = (LayoutModel)DataContext; + if (_model != null) + { + Logger.LogInfo("Loaded " + _model.Name); + } + RenderPreview(); } diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Logs/Logger.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Logs/Logger.cs new file mode 100644 index 000000000..1998ddd51 --- /dev/null +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Logs/Logger.cs @@ -0,0 +1,101 @@ +// 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.Diagnostics; +using System.Globalization; +using System.IO; +using System.IO.Abstractions; +using System.Reflection; +using interop; + +namespace FancyZonesEditor.Logs +{ + public static class Logger + { + private static readonly IFileSystem _fileSystem = new FileSystem(); + private static readonly Assembly Assembly = Assembly.GetExecutingAssembly(); + public static readonly string Version = FileVersionInfo.GetVersionInfo(Assembly.Location).ProductVersion; + private static readonly string ApplicationLogPath = Path.Combine(Constants.AppDataPath(), "FancyZones\\Editor\\Logs\\", Version); + + private static readonly string Error = "Error"; + private static readonly string Warning = "Warning"; + private static readonly string Info = "Info"; + private static readonly string Debug = "Debug"; + private static readonly string TraceFlag = "Trace"; + + static Logger() + { + if (!_fileSystem.Directory.Exists(ApplicationLogPath)) + { + _fileSystem.Directory.CreateDirectory(ApplicationLogPath); + } + + // Using InvariantCulture since this is used for a log file name + var logFilePath = _fileSystem.Path.Combine(ApplicationLogPath, "Log_" + DateTime.Now.ToString(@"yyyy-MM-dd", CultureInfo.InvariantCulture) + ".txt"); + + Trace.Listeners.Add(new TextWriterTraceListener(logFilePath)); + + Trace.AutoFlush = true; + } + + public static void LogError(string message) + { + Log(message, Error); + } + + public static void LogError(string message, Exception ex) + { + Log( + message + Environment.NewLine + + ex?.Message + Environment.NewLine + + "Inner exception: " + Environment.NewLine + + ex?.InnerException?.Message + Environment.NewLine + + "Stack trace: " + Environment.NewLine + + ex?.StackTrace, + Error); + } + + public static void LogWarning(string message) + { + Log(message, Warning); + } + + public static void LogInfo(string message) + { + Log(message, Info); + } + + public static void LogDebug(string message) + { + Log(message, Debug); + } + + public static void LogTrace() + { + Log(string.Empty, TraceFlag); + } + + private static void Log(string message, string type) + { + Trace.WriteLine("[" + DateTime.Now.TimeOfDay + "] [" + type + "] " + GetCallerInfo()); + Trace.Indent(); + if (message != string.Empty) + { + Trace.WriteLine(message); + } + + Trace.Unindent(); + } + + private static string GetCallerInfo() + { + StackTrace stackTrace = new StackTrace(); + + var methodName = stackTrace.GetFrame(3)?.GetMethod(); + var className = methodName?.DeclaringType.Name; + return className + "::" + methodName?.Name; + } + } +} diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs index db920bd93..36718455d 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs @@ -4,13 +4,12 @@ using System; using System.Collections.Generic; -using System.Diagnostics; -using System.IO; using System.Windows; using System.Windows.Automation; using System.Windows.Automation.Peers; using System.Windows.Controls; using System.Windows.Input; +using FancyZonesEditor.Logs; using FancyZonesEditor.Models; using FancyZonesEditor.Utils; using Microsoft.PowerToys.Common.UI; @@ -42,20 +41,7 @@ namespace FancyZonesEditor DataContext = _settings; KeyUp += MainWindow_KeyUp; - - // Prevent closing the dialog with enter - PreviewKeyDown += (object sender, KeyEventArgs e) => - { - if (e.Key == Key.Enter && _openedDialog != null && _openedDialog.IsVisible) - { - var source = e.OriginalSource as RadioButton; - if (source != null && source.IsChecked != true) - { - source.IsChecked = true; - e.Handled = true; - } - } - }; + PreviewKeyDown += MainWindow_PreviewKeyDown; if (spanZonesAcrossMonitors) { @@ -85,6 +71,20 @@ namespace FancyZonesEditor } } + // Prevent closing the dialog with enter + private void MainWindow_PreviewKeyDown(object sender, KeyEventArgs e) + { + if (e.Key == Key.Enter && _openedDialog != null && _openedDialog.IsVisible) + { + var source = e.OriginalSource as RadioButton; + if (source != null && source.IsChecked != true) + { + source.IsChecked = true; + e.Handled = true; + } + } + } + private void LayoutItem_MouseDoubleClick(object sender, MouseButtonEventArgs e) { CloseDialog(sender); @@ -164,6 +164,8 @@ namespace FancyZonesEditor private async void NewLayoutButton_Click(object sender, RoutedEventArgs e) { + Logger.LogTrace(); + if (_openedDialog != null) { // another dialog already opened @@ -195,6 +197,8 @@ namespace FancyZonesEditor private void DuplicateLayout_Click(object sender, RoutedEventArgs e) { + Logger.LogTrace(); + EditLayoutDialog.Hide(); var mainEditor = App.Overlay; @@ -217,7 +221,7 @@ namespace FancyZonesEditor name = name.TrimEnd(); } - AnnounceSuccessfulLayoutCreation(name); + Announce(name, FancyZonesEditor.Properties.Resources.Layout_Creation_Announce); int maxCustomIndex = 0; foreach (LayoutModel customModel in MainWindowSettingsModel.CustomModels) { @@ -249,18 +253,19 @@ namespace FancyZonesEditor App.FancyZonesEditorIO.SerializeZoneSettings(); } - private void AnnounceSuccessfulLayoutCreation(string name) + private void Announce(string name, string message) { - if (AutomationPeer.ListenerExists(AutomationEvents.MenuOpened)) + if (AutomationPeer.ListenerExists(AutomationEvents.MenuOpened) && _createLayoutAnnounce != null) { var peer = UIElementAutomationPeer.FromElement(_createLayoutAnnounce); - AutomationProperties.SetName(_createLayoutAnnounce, name + " " + FancyZonesEditor.Properties.Resources.Layout_Creation_Announce); + AutomationProperties.SetName(_createLayoutAnnounce, name + " " + message); peer?.RaiseAutomationEvent(AutomationEvents.MenuOpened); } } private void Apply() { + Logger.LogTrace(); var mainEditor = App.Overlay; if (mainEditor.CurrentDataContext is LayoutModel model) { @@ -272,6 +277,7 @@ namespace FancyZonesEditor private void OnClosing(object sender, EventArgs e) { + Logger.LogTrace(); CancelLayoutChanges(); App.FancyZonesEditorIO.SerializeZoneSettings(); @@ -281,12 +287,15 @@ namespace FancyZonesEditor private void DeleteLayout_Click(object sender, RoutedEventArgs e) { + Logger.LogTrace(); EditLayoutDialog.Hide(); DeleteLayout((FrameworkElement)sender); } private async void EditLayout_Click(object sender, RoutedEventArgs e) { + Logger.LogTrace(); + // Avoid trying to open the same dialog twice. if (_openedDialog != null) { @@ -310,6 +319,7 @@ namespace FancyZonesEditor private void EditZones_Click(object sender, RoutedEventArgs e) { + Logger.LogTrace(); var dataContext = ((FrameworkElement)sender).DataContext; Select((LayoutModel)dataContext); EditLayoutDialog.Hide(); @@ -342,6 +352,8 @@ namespace FancyZonesEditor private void NewLayoutDialog_PrimaryButtonClick(ModernWpf.Controls.ContentDialog sender, ModernWpf.Controls.ContentDialogButtonClickEventArgs args) { + Logger.LogTrace(); + LayoutModel selectedLayoutModel; if (GridLayoutRadioButton.IsChecked == true) @@ -393,6 +405,8 @@ namespace FancyZonesEditor // EditLayout: Save changes private void EditLayoutDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args) { + Logger.LogTrace(); + var mainEditor = App.Overlay; if (!(mainEditor.CurrentDataContext is LayoutModel model)) { @@ -415,6 +429,8 @@ namespace FancyZonesEditor private async void DeleteLayout(FrameworkElement element) { + Logger.LogTrace(); + var dialog = new ContentDialog() { Title = Properties.Resources.Are_You_Sure, @@ -454,6 +470,7 @@ namespace FancyZonesEditor private void Dialog_Opened(ContentDialog sender, ContentDialogOpenedEventArgs args) { + Announce(sender.Name, FancyZonesEditor.Properties.Resources.Edit_Layout_Open_Announce); _openedDialog = sender; } @@ -498,7 +515,10 @@ namespace FancyZonesEditor private void TextBox_GotKeyboardFocus(object sender, RoutedEventArgs e) { TextBox tb = sender as TextBox; - tb.SelectionStart = tb.Text.Length; + if (tb != null) + { + tb.SelectionStart = tb.Text.Length; + } } private void CancelLayoutChanges() diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Overlay.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Overlay.cs index 443030c0c..65ef74d77 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Overlay.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Overlay.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Windows; using System.Windows.Controls; +using FancyZonesEditor.Logs; using FancyZonesEditor.Models; namespace FancyZonesEditor @@ -134,6 +135,8 @@ namespace FancyZonesEditor public void Show() { + Logger.LogTrace(); + var mainWindowSettings = ((App)Application.Current).MainWindowSettings; if (_layoutPreview != null) { @@ -154,6 +157,8 @@ namespace FancyZonesEditor public void ShowLayout() { + Logger.LogTrace(); + MainWindowSettingsModel settings = ((App)Application.Current).MainWindowSettings; CurrentDataContext = settings.UpdateSelectedLayoutModel(); @@ -201,6 +206,8 @@ namespace FancyZonesEditor public void OpenEditor(LayoutModel model) { + Logger.LogTrace(); + _layoutPreview = null; if (CurrentDataContext is GridLayoutModel) { @@ -229,6 +236,8 @@ namespace FancyZonesEditor public void CloseEditor() { + Logger.LogTrace(); + var mainWindowSettings = ((App)Application.Current).MainWindowSettings; _editorLayout = null; diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.Designer.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.Designer.cs index 9309724bf..07f7f07cd 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.Designer.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.Designer.cs @@ -150,21 +150,12 @@ namespace FancyZonesEditor.Properties { } } - /// - /// Looks up a localized string similar to Error logged to . - /// - public static string Crash_Report_Message_Box_Text_Part1 { - get { - return ResourceManager.GetString("Crash_Report_Message_Box_Text_Part1", resourceCulture); - } - } - /// /// Looks up a localized string similar to Please report the bug to . /// - public static string Crash_Report_Message_Box_Text_Part2 { + public static string Crash_Report_Message_Box_Text { get { - return ResourceManager.GetString("Crash_Report_Message_Box_Text_Part2", resourceCulture); + return ResourceManager.GetString("Crash_Report_Message_Box_Text", resourceCulture); } } @@ -284,6 +275,15 @@ namespace FancyZonesEditor.Properties { return ResourceManager.GetString("Edit_Layout", resourceCulture); } } + + /// + /// Looks up a localized string similar to opened. + /// + public static string Edit_Layout_Open_Announce { + get { + return ResourceManager.GetString("Edit_Layout_Open_Announce", resourceCulture); + } + } /// /// Looks up a localized string similar to Edit zones. diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.resx b/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.resx index c0fac817b..e6345e165 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.resx +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.resx @@ -135,10 +135,7 @@ Grid layout editor - - Error logged to - - + Please report the bug to @@ -374,6 +371,9 @@ Edit layout + + opened + custom layout was created successfully. diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Utils/FancyZonesEditorIO.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Utils/FancyZonesEditorIO.cs index b55640738..a89c867a9 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Utils/FancyZonesEditorIO.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Utils/FancyZonesEditorIO.cs @@ -11,6 +11,7 @@ using System.IO.Abstractions; using System.Text; using System.Text.Json; using System.Windows; +using FancyZonesEditor.Logs; using FancyZonesEditor.Models; namespace FancyZonesEditor.Utils @@ -247,6 +248,8 @@ namespace FancyZonesEditor.Utils // All strings in this function shouldn't be localized. public static void ParseCommandLineArguments() { + Logger.LogTrace(); + string[] args = Environment.GetCommandLineArgs(); if (args.Length < 2 && !App.DebugMode) @@ -399,8 +402,9 @@ namespace FancyZonesEditor.Utils App.Overlay.Monitors.Add(monitor); } } - catch (Exception) + catch (Exception ex) { + Logger.LogError("Invalid command line arguments: " + args[1], ex); MessageBox.Show(Properties.Resources.Error_Invalid_Arguments, Properties.Resources.Error_Message_Box_Title); ((App)Application.Current).Shutdown(); } @@ -408,6 +412,8 @@ namespace FancyZonesEditor.Utils public ParsingResult ParseParams() { + Logger.LogTrace(); + if (_fileSystem.File.Exists(FancyZonesEditorParamsFile)) { string data = string.Empty; @@ -471,6 +477,7 @@ namespace FancyZonesEditor.Utils } catch (Exception ex) { + Logger.LogError("Editor params parsing error", ex); return new ParsingResult(false, ex.Message, data); } @@ -484,6 +491,8 @@ namespace FancyZonesEditor.Utils public ParsingResult ParseZoneSettings() { + Logger.LogTrace(); + _unusedDevices.Clear(); if (_fileSystem.File.Exists(FancyZonesSettingsFile)) @@ -498,6 +507,7 @@ namespace FancyZonesEditor.Utils } catch (Exception ex) { + Logger.LogError("Zone settings parsing error", ex); return new ParsingResult(false, ex.Message, settingsString); } @@ -515,6 +525,7 @@ namespace FancyZonesEditor.Utils } catch (Exception ex) { + Logger.LogError("Zone settings parsing error", ex); return new ParsingResult(false, ex.Message, settingsString); } } @@ -524,6 +535,8 @@ namespace FancyZonesEditor.Utils public void SerializeZoneSettings() { + Logger.LogTrace(); + ZoneSettingsWrapper zoneSettings = new ZoneSettingsWrapper { }; zoneSettings.Devices = new List(); zoneSettings.CustomZoneSets = new List(); @@ -680,8 +693,9 @@ namespace FancyZonesEditor.Utils zoneSettings.QuickLayoutKeys.Add(wrapper); } - catch (Exception) + catch (Exception ex) { + Logger.LogError("Serialize quick layout keys error", ex); } } } @@ -693,12 +707,15 @@ namespace FancyZonesEditor.Utils } catch (Exception ex) { + Logger.LogError("Serialize zone settings error", ex); App.ShowExceptionMessageBox(Properties.Resources.Error_Applying_Layout, ex); } } private string ReadFile(string fileName) { + Logger.LogTrace(); + Stream inputStream = _fileSystem.File.Open(fileName, FileMode.Open); using (StreamReader reader = new StreamReader(inputStream)) { @@ -710,6 +727,8 @@ namespace FancyZonesEditor.Utils private bool SetDevices(List devices) { + Logger.LogTrace(); + if (devices == null) { return false; @@ -763,6 +782,8 @@ namespace FancyZonesEditor.Utils private bool SetCustomLayouts(List customLayouts) { + Logger.LogTrace(); + if (customLayouts == null) { return false; @@ -791,9 +812,10 @@ namespace FancyZonesEditor.Utils layout = ParseGridInfo(zoneSet); } } - catch (Exception) + catch (Exception ex) { result = false; + Logger.LogError("Parse custom layout error", ex); continue; } @@ -813,6 +835,8 @@ namespace FancyZonesEditor.Utils private bool SetTemplateLayouts(List templateLayouts) { + Logger.LogTrace(); + if (templateLayouts == null) { return false; @@ -840,6 +864,8 @@ namespace FancyZonesEditor.Utils private bool SetQuickLayoutSwitchKeys(List quickSwitchKeys) { + Logger.LogTrace(); + if (quickSwitchKeys == null) { return false; @@ -971,50 +997,5 @@ namespace FancyZonesEditor.Utils return string.Empty; } } - - private static string ParsingCmdArgsErrorReport(string args, int count, string targetMonitorName, List monitorData, List monitors) - { - var sb = new StringBuilder(); - - sb.AppendLine(); - sb.AppendLine("```"); - sb.AppendLine(" ## Command-line arguments:"); - sb.AppendLine(); - sb.AppendLine(args); - - sb.AppendLine(); - sb.AppendLine("```"); - sb.AppendLine(" ## Parsed command-line arguments:"); - sb.AppendLine(); - - sb.Append("Span zones across monitors: "); - sb.AppendLine(App.Overlay.SpanZonesAcrossMonitors.ToString()); - - // using CultureInfo.InvariantCulture since this is for PowerToys team - sb.Append("Monitors count: "); - sb.AppendLine(count.ToString(CultureInfo.InvariantCulture)); - sb.Append("Target monitor: "); - sb.AppendLine(targetMonitorName); - - sb.AppendLine(); - sb.AppendLine(" # Per monitor data:"); - sb.AppendLine(); - foreach (NativeMonitorData data in monitorData) - { - sb.AppendLine(data.ToString()); - } - - sb.AppendLine(); - sb.AppendLine("```"); - sb.AppendLine(" ## Monitors discovered:"); - sb.AppendLine(); - - foreach (Monitor m in monitors) - { - sb.AppendLine(m.Device.ToString()); - } - - return sb.ToString(); - } } } diff --git a/src/modules/imageresizer/dll/ContextMenuHandler.cpp b/src/modules/imageresizer/dll/ContextMenuHandler.cpp index 6674cffa5..681dabce7 100644 --- a/src/modules/imageresizer/dll/ContextMenuHandler.cpp +++ b/src/modules/imageresizer/dll/ContextMenuHandler.cpp @@ -2,11 +2,11 @@ #include "pch.h" #include "ContextMenuHandler.h" -#include "HDropIterator.h" #include "Settings.h" #include #include #include +#include #include "trace.h" diff --git a/src/modules/imageresizer/dll/HDropIterator.cpp b/src/modules/imageresizer/dll/HDropIterator.cpp deleted file mode 100644 index bfc23b39e..000000000 --- a/src/modules/imageresizer/dll/HDropIterator.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include "pch.h" -#include "HDropIterator.h" - -HDropIterator::HDropIterator(IDataObject* pdtobj) -{ - _current = 0; - - FORMATETC formatetc = { - CF_HDROP, - NULL, - DVASPECT_CONTENT, - -1, - TYMED_HGLOBAL - }; - - pdtobj->GetData(&formatetc, &m_medium); - - _listCount = DragQueryFile((HDROP)m_medium.hGlobal, 0xFFFFFFFF, NULL, 0); -} - -HDropIterator::~HDropIterator() -{ - ReleaseStgMedium(&m_medium); -} - -void HDropIterator::First() -{ - _current = 0; -} - -void HDropIterator::Next() -{ - _current++; -} - -bool HDropIterator::IsDone() const -{ - return _current >= _listCount; -} - -LPTSTR HDropIterator::CurrentItem() const -{ - UINT cch = DragQueryFile((HDROP)m_medium.hGlobal, _current, NULL, 0) + 1; - LPTSTR pszPath = (LPTSTR)malloc(sizeof(TCHAR) * cch); - - DragQueryFile((HDROP)m_medium.hGlobal, _current, pszPath, cch); - - return pszPath; -} diff --git a/src/modules/imageresizer/dll/HDropIterator.h b/src/modules/imageresizer/dll/HDropIterator.h deleted file mode 100644 index 8f1d26151..000000000 --- a/src/modules/imageresizer/dll/HDropIterator.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -class HDropIterator -{ -public: - HDropIterator(IDataObject *pDataObject); - ~HDropIterator(); - void First(); - void Next(); - bool IsDone() const; - LPTSTR CurrentItem() const; - -private: - UINT _listCount; - STGMEDIUM m_medium; - UINT _current; -}; diff --git a/src/modules/imageresizer/dll/ImageResizerExt.vcxproj b/src/modules/imageresizer/dll/ImageResizerExt.vcxproj index c5b18627e..d97f61e4a 100644 --- a/src/modules/imageresizer/dll/ImageResizerExt.vcxproj +++ b/src/modules/imageresizer/dll/ImageResizerExt.vcxproj @@ -77,7 +77,6 @@ - false @@ -97,7 +96,6 @@ - diff --git a/src/modules/imageresizer/dll/ImageResizerExt.vcxproj.filters b/src/modules/imageresizer/dll/ImageResizerExt.vcxproj.filters index bbd3a2437..dd6a23aa9 100644 --- a/src/modules/imageresizer/dll/ImageResizerExt.vcxproj.filters +++ b/src/modules/imageresizer/dll/ImageResizerExt.vcxproj.filters @@ -28,9 +28,6 @@ Source Files - - Source Files - Source Files @@ -57,9 +54,6 @@ Header Files - - Header Files - Header Files diff --git a/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.cpp b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.cpp new file mode 100644 index 000000000..28c85ab5a --- /dev/null +++ b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.cpp @@ -0,0 +1,917 @@ +// PowerRenameUIHost.cpp : Defines the entry point for the application. +// +#include "pch.h" + +#include "PowerRenameUIHost.h" +#include +#include +#include +#include +#include +#include + +#define MAX_LOADSTRING 100 + +const wchar_t c_WindowClass[] = L"PowerRename"; +HINSTANCE g_hostHInst; + +int AppWindow::Show(HINSTANCE hInstance, std::vector files) +{ + auto window = AppWindow(hInstance, files); + window.CreateAndShowWindow(); + return window.MessageLoop(window.m_accelerators.get()); +} + +LRESULT AppWindow::MessageHandler(UINT message, WPARAM wParam, LPARAM lParam) noexcept +{ + switch (message) + { + HANDLE_MSG(WindowHandle(), WM_CREATE, OnCreate); + HANDLE_MSG(WindowHandle(), WM_COMMAND, OnCommand); + HANDLE_MSG(WindowHandle(), WM_DESTROY, OnDestroy); + HANDLE_MSG(WindowHandle(), WM_SIZE, OnResize); + default: + return base_type::MessageHandler(message, wParam, lParam); + } + + return base_type::MessageHandler(message, wParam, lParam); +} + +AppWindow::AppWindow(HINSTANCE hInstance, std::vector files) noexcept : + m_instance{ hInstance }, m_managerEvents{ this } +{ + HRESULT hr = CPowerRenameManager::s_CreateInstance(&m_prManager); + // Create the factory for our items + CComPtr prItemFactory; + hr = CPowerRenameItem::s_CreateInstance(nullptr, IID_PPV_ARGS(&prItemFactory)); + hr = m_prManager->PutRenameItemFactory(prItemFactory); + hr = m_prManager->Advise(&m_managerEvents, &m_cookie); + + if (SUCCEEDED(hr)) + { + CComPtr shellItemArray; + // To test PowerRenameUIHost uncomment this line and update the path to + // your local (absolute or relative) path which you want to see in PowerRename + //files.push_back(L""); + + if (!files.empty()) + { + hr = CreateShellItemArrayFromPaths(files, &shellItemArray); + if (SUCCEEDED(hr)) + { + CComPtr enumShellItems; + hr = shellItemArray->EnumItems(&enumShellItems); + if (SUCCEEDED(hr)) + { + EnumerateShellItems(enumShellItems); + } + } + } + } +} + +void AppWindow::CreateAndShowWindow() +{ + m_accelerators.reset(LoadAcceleratorsW(m_instance, MAKEINTRESOURCE(IDC_POWERRENAMEUIHOST))); + + WNDCLASSEXW wcex = { sizeof(wcex) }; + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = WndProc; + wcex.hInstance = m_instance; + wcex.hIcon = LoadIconW(m_instance, MAKEINTRESOURCE(IDC_POWERRENAMEUIHOST)); + wcex.hCursor = LoadCursorW(nullptr, IDC_ARROW); + wcex.hbrBackground = reinterpret_cast(COLOR_WINDOW + 1); + wcex.lpszClassName = c_WindowClass; + wcex.hIconSm = LoadIconW(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); + RegisterClassExW(&wcex); // don't test result, handle error at CreateWindow + + wchar_t title[64]; + LoadStringW(m_instance, IDS_APP_TITLE, title, ARRAYSIZE(title)); + + m_window = CreateWindowW(c_WindowClass, title, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, m_instance, this); + THROW_LAST_ERROR_IF(!m_window); + + ShowWindow(m_window, SW_SHOWNORMAL); + UpdateWindow(m_window); + SetFocus(m_window); +} + +bool AppWindow::OnCreate(HWND, LPCREATESTRUCT) noexcept +{ + m_mainUserControl = winrt::PowerRenameUILib::MainWindow(); + m_xamlIsland = CreateDesktopWindowsXamlSource(WS_TABSTOP, m_mainUserControl); + + PopulateExplorerItems(); + SetHandlers(); + ReadSettings(); + + m_mainUserControl.UIUpdatesItem().ButtonRenameEnabled(false); + InitAutoComplete(); + SearchReplaceChanged(); + return true; +} + +void AppWindow::OnCommand(HWND, int id, HWND hwndControl, UINT codeNotify) noexcept +{ + switch (id) + { + case IDM_ABOUT: + DialogBoxW(m_instance, MAKEINTRESOURCE(IDD_ABOUTBOX), WindowHandle(), [](HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) -> INT_PTR { + switch (message) + { + case WM_INITDIALOG: + return TRUE; + + case WM_COMMAND: + if ((LOWORD(wParam) == IDOK) || (LOWORD(wParam) == IDCANCEL)) + { + EndDialog(hDlg, LOWORD(wParam)); + return TRUE; + } + break; + } + return FALSE; + }); + break; + + case IDM_EXIT: + PostQuitMessage(0); + break; + } +} + +void AppWindow::OnDestroy(HWND hwnd) noexcept +{ + base_type::OnDestroy(hwnd); +} + +void AppWindow::OnResize(HWND, UINT state, int cx, int cy) noexcept +{ + SetWindowPos(m_xamlIsland, NULL, 0, 0, cx, cy, SWP_SHOWWINDOW); +} + +HRESULT AppWindow::CreateShellItemArrayFromPaths( + std::vector files, + IShellItemArray** shellItemArray) +{ + *shellItemArray = nullptr; + PIDLIST_ABSOLUTE* itemList = nullptr; + itemList = new (std::nothrow) PIDLIST_ABSOLUTE[files.size()]; + HRESULT hr = itemList ? S_OK : E_OUTOFMEMORY; + UINT itemsCnt = 0; + for (const auto& file : files) + { + const DWORD BUFSIZE = 4096; + TCHAR buffer[BUFSIZE] = TEXT(""); + auto retval = GetFullPathName(file.c_str(), BUFSIZE, buffer, NULL); + if (retval != 0 && PathFileExists(buffer)) + { + hr = SHParseDisplayName(buffer, nullptr, &itemList[itemsCnt], 0, nullptr); + ++itemsCnt; + } + } + if (SUCCEEDED(hr) && itemsCnt > 0) + { + hr = SHCreateShellItemArrayFromIDLists(itemsCnt, const_cast(itemList), shellItemArray); + + for (UINT i = 0; i < itemsCnt; i++) + { + CoTaskMemFree(itemList[i]); + } + } + else + { + hr = E_FAIL; + } + + delete[] itemList; + return hr; +} + +void AppWindow::PopulateExplorerItems() +{ + UINT count = 0; + m_prManager->GetVisibleItemCount(&count); + + UINT currDepth = 0; + std::stack parents{}; + UINT prevId = 0; + parents.push(0); + + for (UINT i = 0; i < count; ++i) + { + CComPtr renameItem; + if (SUCCEEDED(m_prManager->GetVisibleItemByIndex(i, &renameItem))) + { + int id = 0; + renameItem->GetId(&id); + + PWSTR originalName = nullptr; + renameItem->GetOriginalName(&originalName); + PWSTR newName = nullptr; + renameItem->GetNewName(&newName); + + bool selected; + renameItem->GetSelected(&selected); + + UINT depth = 0; + renameItem->GetDepth(&depth); + + bool isFolder = false; + bool isSubFolderContent = false; + winrt::check_hresult(renameItem->GetIsFolder(&isFolder)); + + if (depth > currDepth) + { + parents.push(prevId); + currDepth = depth; + } + else + { + while (currDepth > depth) + { + parents.pop(); + currDepth--; + } + currDepth = depth; + } + m_mainUserControl.AddExplorerItem( + id, originalName, newName == nullptr ? hstring{} : hstring{ newName }, isFolder ? 0 : 1, parents.top(), selected); + prevId = id; + } + } +} + +HRESULT AppWindow::InitAutoComplete() +{ + HRESULT hr = S_OK; + if (CSettingsInstance().GetMRUEnabled()) + { + hr = CPowerRenameMRU::CPowerRenameMRUSearch_CreateInstance(&m_searchMRU); + if (SUCCEEDED(hr)) + { + for (const auto& item : m_searchMRU->GetMRUStrings()) + { + if (!item.empty()) + { + m_mainUserControl.AppendSearchMRU(item); + } + } + } + + if (SUCCEEDED(hr)) + { + hr = CPowerRenameMRU::CPowerRenameMRUReplace_CreateInstance(&m_replaceMRU); + if (SUCCEEDED(hr)) + { + for (const auto& item : m_replaceMRU->GetMRUStrings()) + { + if (!item.empty()) + { + m_mainUserControl.AppendReplaceMRU(item); + } + } + } + } + } + + return hr; +} + +HRESULT AppWindow::EnumerateShellItems(_In_ IEnumShellItems* enumShellItems) +{ + HRESULT hr = S_OK; + // Enumerate the data object and populate the manager + if (m_prManager) + { + m_disableCountUpdate = true; + + // Ensure we re-create the enumerator + m_prEnum = nullptr; + hr = CPowerRenameEnum::s_CreateInstance(nullptr, m_prManager, IID_PPV_ARGS(&m_prEnum)); + if (SUCCEEDED(hr)) + { + hr = m_prEnum->Start(enumShellItems); + } + + m_disableCountUpdate = false; + } + + return hr; +} + +void AppWindow::SearchReplaceChanged(bool forceRenaming) +{ + // Pass updated search and replace terms to the IPowerRenameRegEx handler + CComPtr prRegEx; + if (m_prManager && SUCCEEDED(m_prManager->GetRenameRegEx(&prRegEx))) + { + winrt::hstring searchTerm = m_mainUserControl.AutoSuggestBoxSearch().Text(); + prRegEx->PutSearchTerm(searchTerm.c_str(), forceRenaming); + + winrt::hstring replaceTerm = m_mainUserControl.AutoSuggestBoxReplace().Text(); + prRegEx->PutReplaceTerm(replaceTerm.c_str(), forceRenaming); + } +} + +void AppWindow::ValidateFlags(PowerRenameFlags flag) +{ + if (flag == Uppercase) + { + if (m_mainUserControl.ToggleButtonUpperCase().IsChecked()) + { + m_mainUserControl.ToggleButtonLowerCase().IsChecked(false); + m_mainUserControl.ToggleButtonTitleCase().IsChecked(false); + m_mainUserControl.ToggleButtonCapitalize().IsChecked(false); + } + } + else if (flag == Lowercase) + { + if (m_mainUserControl.ToggleButtonLowerCase().IsChecked()) + { + m_mainUserControl.ToggleButtonUpperCase().IsChecked(false); + m_mainUserControl.ToggleButtonTitleCase().IsChecked(false); + m_mainUserControl.ToggleButtonCapitalize().IsChecked(false); + } + } + else if (flag == Titlecase) + { + if (m_mainUserControl.ToggleButtonTitleCase().IsChecked()) + { + m_mainUserControl.ToggleButtonUpperCase().IsChecked(false); + m_mainUserControl.ToggleButtonLowerCase().IsChecked(false); + m_mainUserControl.ToggleButtonCapitalize().IsChecked(false); + } + } + else if (flag == Capitalized) + { + if (m_mainUserControl.ToggleButtonCapitalize().IsChecked()) + { + m_mainUserControl.ToggleButtonUpperCase().IsChecked(false); + m_mainUserControl.ToggleButtonLowerCase().IsChecked(false); + m_mainUserControl.ToggleButtonTitleCase().IsChecked(false); + } + } + + m_flagValidationInProgress = true; +} + +void AppWindow::UpdateFlag(PowerRenameFlags flag, UpdateFlagCommand command) +{ + DWORD flags{}; + m_prManager->GetFlags(&flags); + + if (command == UpdateFlagCommand::Set) + { + flags |= flag; + } + else if (command == UpdateFlagCommand::Reset) + { + flags &= ~flag; + } + + // Ensure we update flags + if (m_prManager) + { + m_prManager->PutFlags(flags); + } +} + +void AppWindow::SetHandlers() +{ + m_mainUserControl.UIUpdatesItem().PropertyChanged([&](winrt::Windows::Foundation::IInspectable const& sender, Data::PropertyChangedEventArgs const& e) { + std::wstring property{ e.PropertyName() }; + if (property == L"ShowAll") + { + SwitchView(); + } + else if (property == L"ChangedItemId") + { + ToggleItem(m_mainUserControl.UIUpdatesItem().ChangedExplorerItemId(), m_mainUserControl.UIUpdatesItem().Checked()); + } + else if (property == L"ToggleAll") + { + ToggleAll(); + } + else if (property == L"Rename") + { + Rename(m_mainUserControl.UIUpdatesItem().CloseUIWindow()); + } + }); + + // AutoSuggestBox Search + m_mainUserControl.AutoSuggestBoxSearch().TextChanged([&](winrt::Windows::Foundation::IInspectable const& sender, AutoSuggestBoxTextChangedEventArgs const&) { + SearchReplaceChanged(); + }); + + // AutoSuggestBox Replace + m_mainUserControl.AutoSuggestBoxReplace().TextChanged([&](winrt::Windows::Foundation::IInspectable const& sender, AutoSuggestBoxTextChangedEventArgs const&) { + SearchReplaceChanged(); + }); + + // ToggleButton UpperCase + m_mainUserControl.ToggleButtonUpperCase().Checked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + ValidateFlags(Uppercase); + UpdateFlag(Uppercase, UpdateFlagCommand::Set); + }); + m_mainUserControl.ToggleButtonUpperCase().Unchecked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + UpdateFlag(Uppercase, UpdateFlagCommand::Reset); + }); + + // ToggleButton LowerCase + m_mainUserControl.ToggleButtonLowerCase().Checked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + ValidateFlags(Lowercase); + UpdateFlag(Lowercase, UpdateFlagCommand::Set); + }); + m_mainUserControl.ToggleButtonLowerCase().Unchecked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + UpdateFlag(Lowercase, UpdateFlagCommand::Reset); + }); + + // ToggleButton TitleCase + m_mainUserControl.ToggleButtonTitleCase().Checked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + ValidateFlags(Titlecase); + UpdateFlag(Titlecase, UpdateFlagCommand::Set); + }); + m_mainUserControl.ToggleButtonTitleCase().Unchecked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + UpdateFlag(Titlecase, UpdateFlagCommand::Reset); + }); + + // ToggleButton Capitalize + m_mainUserControl.ToggleButtonCapitalize().Checked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + ValidateFlags(Capitalized); + UpdateFlag(Capitalized, UpdateFlagCommand::Set); + }); + m_mainUserControl.ToggleButtonCapitalize().Unchecked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + UpdateFlag(Capitalized, UpdateFlagCommand::Reset); + }); + + // CheckBox Regex + m_mainUserControl.CheckBoxRegex().Checked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + ValidateFlags(UseRegularExpressions); + UpdateFlag(UseRegularExpressions, UpdateFlagCommand::Set); + }); + m_mainUserControl.CheckBoxRegex().Unchecked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + UpdateFlag(UseRegularExpressions, UpdateFlagCommand::Reset); + }); + + // CheckBox CaseSensitive + m_mainUserControl.CheckBoxCaseSensitive().Checked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + ValidateFlags(CaseSensitive); + UpdateFlag(CaseSensitive, UpdateFlagCommand::Set); + }); + m_mainUserControl.CheckBoxCaseSensitive().Unchecked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + UpdateFlag(CaseSensitive, UpdateFlagCommand::Reset); + }); + + // ComboBox RenameParts + m_mainUserControl.ComboBoxRenameParts().SelectionChanged([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + if (m_mainUserControl.ComboBoxRenameParts().SelectedIndex() == 0) + { // Filename + extension + UpdateFlag(NameOnly, UpdateFlagCommand::Reset); + UpdateFlag(ExtensionOnly, UpdateFlagCommand::Reset); + } + else if (m_mainUserControl.ComboBoxRenameParts().SelectedIndex() == 1) // Filename Only + { + ValidateFlags(NameOnly); + UpdateFlag(ExtensionOnly, UpdateFlagCommand::Reset); + UpdateFlag(NameOnly, UpdateFlagCommand::Set); + } + else if (m_mainUserControl.ComboBoxRenameParts().SelectedIndex() == 2) // Extension Only + { + ValidateFlags(ExtensionOnly); + UpdateFlag(NameOnly, UpdateFlagCommand::Reset); + UpdateFlag(ExtensionOnly, UpdateFlagCommand::Set); + } + }); + + // CheckBox MatchAllOccurences + m_mainUserControl.CheckBoxMatchAll().Checked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + ValidateFlags(MatchAllOccurences); + UpdateFlag(MatchAllOccurences, UpdateFlagCommand::Set); + }); + m_mainUserControl.CheckBoxMatchAll().Unchecked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + UpdateFlag(MatchAllOccurences, UpdateFlagCommand::Reset); + }); + + // ToggleButton IncludeFiles + m_mainUserControl.ToggleButtonIncludeFiles().Checked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + ValidateFlags(ExcludeFiles); + UpdateFlag(ExcludeFiles, UpdateFlagCommand::Reset); + }); + m_mainUserControl.ToggleButtonIncludeFiles().Unchecked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + UpdateFlag(ExcludeFiles, UpdateFlagCommand::Set); + }); + + // ToggleButton IncludeFolders + m_mainUserControl.ToggleButtonIncludeFolders().Checked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + ValidateFlags(ExcludeFolders); + UpdateFlag(ExcludeFolders, UpdateFlagCommand::Reset); + }); + m_mainUserControl.ToggleButtonIncludeFolders().Unchecked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + UpdateFlag(ExcludeFolders, UpdateFlagCommand::Set); + }); + + // ToggleButton IncludeSubfolders + m_mainUserControl.ToggleButtonIncludeSubfolders().Checked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + ValidateFlags(ExcludeSubfolders); + UpdateFlag(ExcludeSubfolders, UpdateFlagCommand::Reset); + }); + m_mainUserControl.ToggleButtonIncludeSubfolders().Unchecked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + UpdateFlag(ExcludeSubfolders, UpdateFlagCommand::Set); + }); + + // CheckBox EnumerateItems + m_mainUserControl.ToggleButtonEnumerateItems().Checked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + ValidateFlags(EnumerateItems); + UpdateFlag(EnumerateItems, UpdateFlagCommand::Set); + }); + m_mainUserControl.ToggleButtonEnumerateItems().Unchecked([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + UpdateFlag(EnumerateItems, UpdateFlagCommand::Reset); + }); + + // ButtonSettings + m_mainUserControl.ButtonSettings().Click([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + OpenSettingsApp(); + }); +} + +void AppWindow::ToggleItem(int32_t id, bool checked) +{ + CComPtr spItem; + m_prManager->GetItemById(id, &spItem); + spItem->PutSelected(checked); + UpdateCounts(); +} + +void AppWindow::ToggleAll() +{ + UINT itemCount = 0; + m_prManager->GetItemCount(&itemCount); + bool selected = m_mainUserControl.CheckBoxSelectAll().IsChecked().GetBoolean(); + for (UINT i = 0; i < itemCount; i++) + { + CComPtr spItem; + if (SUCCEEDED(m_prManager->GetItemByIndex(i, &spItem))) + { + spItem->PutSelected(selected); + } + } + UpdateCounts(); +} + +void AppWindow::SwitchView() +{ + m_prManager->SwitchFilter(0); + PopulateExplorerItems(); +} + +void AppWindow::Rename(bool closeWindow) +{ + if (m_prManager) + { + m_prManager->Rename(m_window, closeWindow); + } + + // Persist the current settings. We only do this when + // a rename is actually performed. Not when the user + // closes/cancels the dialog. + WriteSettings(); +} + +HRESULT AppWindow::ReadSettings() +{ + // Check if we should read flags from settings + // or the defaults from the manager. + DWORD flags = 0; + if (CSettingsInstance().GetPersistState()) + { + flags = CSettingsInstance().GetFlags(); + + m_mainUserControl.AutoSuggestBoxSearch().Text(CSettingsInstance().GetSearchText().c_str()); + m_mainUserControl.AutoSuggestBoxReplace().Text(CSettingsInstance().GetReplaceText().c_str()); + } + else + { + m_prManager->GetFlags(&flags); + } + + m_prManager->PutFlags(flags); + SetCheckboxesFromFlags(flags); + + return S_OK; +} + +HRESULT AppWindow::WriteSettings() +{ + // Check if we should store our settings + if (CSettingsInstance().GetPersistState()) + { + DWORD flags = 0; + m_prManager->GetFlags(&flags); + CSettingsInstance().SetFlags(flags); + + winrt::hstring searchTerm = m_mainUserControl.AutoSuggestBoxSearch().Text(); + CSettingsInstance().SetSearchText(std::wstring{ searchTerm }); + + if (CSettingsInstance().GetMRUEnabled() && m_searchMRU) + { + CComPtr spSearchMRU; + if (SUCCEEDED(m_searchMRU->QueryInterface(IID_PPV_ARGS(&spSearchMRU)))) + { + spSearchMRU->AddMRUString(searchTerm.c_str()); + } + } + + winrt::hstring replaceTerm = m_mainUserControl.AutoSuggestBoxReplace().Text(); + CSettingsInstance().SetReplaceText(std::wstring{ replaceTerm }); + + if (CSettingsInstance().GetMRUEnabled() && m_replaceMRU) + { + CComPtr spReplaceMRU; + if (SUCCEEDED(m_replaceMRU->QueryInterface(IID_PPV_ARGS(&spReplaceMRU)))) + { + spReplaceMRU->AddMRUString(replaceTerm.c_str()); + } + } + + Trace::SettingsChanged(); + } + + return S_OK; +} + +HRESULT AppWindow::OpenSettingsApp() +{ + std::wstring path = get_module_folderpath(g_hostHInst); + path += L"\\..\\..\\PowerToys.exe"; + + std::wstring openSettings = L"--open-settings=PowerRename"; + + CString commandLine; + commandLine.Format(_T("\"%s\""), path.c_str()); + commandLine.AppendFormat(_T(" %s"), openSettings.c_str()); + + int nSize = commandLine.GetLength() + 1; + LPTSTR lpszCommandLine = new TCHAR[nSize]; + _tcscpy_s(lpszCommandLine, nSize, commandLine); + + STARTUPINFO startupInfo; + ZeroMemory(&startupInfo, sizeof(STARTUPINFO)); + startupInfo.cb = sizeof(STARTUPINFO); + startupInfo.wShowWindow = SW_SHOWNORMAL; + + PROCESS_INFORMATION processInformation; + + // Start the resizer + CreateProcess( + NULL, + lpszCommandLine, + NULL, + NULL, + TRUE, + 0, + NULL, + NULL, + &startupInfo, + &processInformation); + + delete[] lpszCommandLine; + + if (!CloseHandle(processInformation.hProcess)) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + if (!CloseHandle(processInformation.hThread)) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + return S_OK; +} + +void AppWindow::SetCheckboxesFromFlags(DWORD flags) +{ + if (flags & CaseSensitive) + { + m_mainUserControl.CheckBoxCaseSensitive().IsChecked(true); + } + if (flags & MatchAllOccurences) + { + m_mainUserControl.CheckBoxMatchAll().IsChecked(true); + } + if (flags & UseRegularExpressions) + { + m_mainUserControl.CheckBoxRegex().IsChecked(true); + } + if (flags & EnumerateItems) + { + m_mainUserControl.ToggleButtonEnumerateItems().IsChecked(true); + } + if (flags & ExcludeFiles) + { + m_mainUserControl.ToggleButtonIncludeFiles().IsChecked(false); + } + if (flags & ExcludeFolders) + { + m_mainUserControl.ToggleButtonIncludeFolders().IsChecked(false); + } + if (flags & ExcludeSubfolders) + { + m_mainUserControl.ToggleButtonIncludeSubfolders().IsChecked(false); + } + if (flags & NameOnly) + { + m_mainUserControl.ComboBoxRenameParts().SelectedIndex(1); + } + else if (flags & ExtensionOnly) + { + m_mainUserControl.ComboBoxRenameParts().SelectedIndex(2); + } + if (flags & Uppercase) + { + m_mainUserControl.ToggleButtonUpperCase().IsChecked(true); + } + else if (flags & Lowercase) + { + m_mainUserControl.ToggleButtonLowerCase().IsChecked(true); + } + else if (flags & Titlecase) + { + m_mainUserControl.ToggleButtonTitleCase().IsChecked(true); + } + else if (flags & Capitalized) + { + m_mainUserControl.ToggleButtonCapitalize().IsChecked(true); + } +} + +void AppWindow::UpdateCounts() +{ + // This method is CPU intensive. We disable it during certain operations + // for performance reasons. + if (m_disableCountUpdate) + { + return; + } + + UINT selectedCount = 0; + UINT renamingCount = 0; + if (m_prManager) + { + m_prManager->GetSelectedItemCount(&selectedCount); + m_prManager->GetRenameItemCount(&renamingCount); + } + + if (m_selectedCount != selectedCount || + m_renamingCount != renamingCount) + { + m_selectedCount = selectedCount; + m_renamingCount = renamingCount; + + // Update counts UI elements if/when added + + // Update Rename button state + m_mainUserControl.UIUpdatesItem().ButtonRenameEnabled(renamingCount > 0); + } +} + +HRESULT AppWindow::OnItemAdded(_In_ IPowerRenameItem* renameItem) +{ + return S_OK; +} + +HRESULT AppWindow::OnUpdate(_In_ IPowerRenameItem* renameItem) +{ + int id; + HRESULT hr = renameItem->GetId(&id); + if (SUCCEEDED(hr)) + { + PWSTR newName = nullptr; + hr = renameItem->GetNewName(&newName); + if (SUCCEEDED(hr)) + { + hstring newNameStr = newName == nullptr ? hstring{} : newName; + m_mainUserControl.UpdateExplorerItem(id, newNameStr); + } + } + + UpdateCounts(); + return S_OK; +} + +HRESULT AppWindow::OnRename(_In_ IPowerRenameItem* renameItem) +{ + int id; + HRESULT hr = renameItem->GetId(&id); + if (SUCCEEDED(hr)) + { + PWSTR newName = nullptr; + hr = renameItem->GetOriginalName(&newName); + if (SUCCEEDED(hr)) + { + hstring newNameStr = newName == nullptr ? hstring{} : newName; + m_mainUserControl.UpdateRenamedExplorerItem(id, newNameStr); + } + } + + UpdateCounts(); + return S_OK; +} + +HRESULT AppWindow::OnError(_In_ IPowerRenameItem* renameItem) +{ + return S_OK; +} + +HRESULT AppWindow::OnRegExStarted(_In_ DWORD threadId) +{ + return S_OK; +} + +HRESULT AppWindow::OnRegExCanceled(_In_ DWORD threadId) +{ + return S_OK; +} + +HRESULT AppWindow::OnRegExCompleted(_In_ DWORD threadId) +{ + if (m_flagValidationInProgress) + { + m_flagValidationInProgress = false; + } + else + { + DWORD filter = 0; + m_prManager->GetFilter(&filter); + if (filter == PowerRenameFilters::ShouldRename) + { + m_mainUserControl.ExplorerItems().Clear(); + PopulateExplorerItems(); + } + } + + return S_OK; +} + +HRESULT AppWindow::OnRenameStarted() +{ + return S_OK; +} + +HRESULT AppWindow::OnRenameCompleted(bool closeUIWindowAfterRenaming) +{ + if (closeUIWindowAfterRenaming) + { + // Close the window + PostMessage(m_window, WM_CLOSE, (WPARAM)0, (LPARAM)0); + } + else + { + // Force renaming work to start so newly renamed items are processed right away + SearchReplaceChanged(true); + } + return S_OK; +} + +int APIENTRY wWinMain(_In_ HINSTANCE hInstance, + _In_opt_ HINSTANCE hPrevInstance, + _In_ LPWSTR lpCmdLine, + _In_ int nCmdShow) +{ +#define BUFSIZE 4096 * 4 + + HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); + if (hStdin == INVALID_HANDLE_VALUE) + ExitProcess(1); + BOOL bSuccess; + WCHAR chBuf[BUFSIZE]; + DWORD dwRead; + std::vector files; + for (;;) + { + // Read from standard input and stop on error or no data. + bSuccess = ReadFile(hStdin, chBuf, BUFSIZE * sizeof(wchar_t), &dwRead, NULL); + + if (!bSuccess || dwRead == 0) + break; + + std::wstring inputBatch{ chBuf, dwRead / sizeof(wchar_t) }; + + std::wstringstream ss(inputBatch); + std::wstring item; + wchar_t delimiter = '?'; + while (std::getline(ss, item, delimiter)) + { + files.push_back(item); + } + + if (!bSuccess) + break; + } + + g_hostHInst = hInstance; + winrt::init_apartment(winrt::apartment_type::single_threaded); + + winrt::PowerRenameUILib::App app; + const auto result = AppWindow::Show(hInstance, files); + app.Close(); +} diff --git a/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.h b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.h new file mode 100644 index 000000000..80b16cf6b --- /dev/null +++ b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.h @@ -0,0 +1,153 @@ +#pragma once +#include "pch.h" + +#include "resource.h" +#include "XamlBridge.h" + +#include +#include +#include +#include +#include + +#include +#include +#pragma push_macro("GetCurrentTime") +#undef GetCurrentTime +#include +#pragma pop_macro("GetCurrentTime") +#include +#include +#include +#include +#include +#include +#include + +using namespace winrt; +using namespace Windows::UI; +using namespace Windows::UI::Xaml; +using namespace Windows::UI::Composition; +using namespace Windows::UI::Xaml::Hosting; +using namespace Windows::Foundation::Numerics; +using namespace Windows::UI::Xaml::Controls; + +class AppWindow : public DesktopWindowT +{ +public: + // Proxy class to Advise() PRManager, as AppWindow can't implement IPowerRenameManagerEvents + class UIHostPowerRenameManagerEvents : public IPowerRenameManagerEvents + { + public: + UIHostPowerRenameManagerEvents(AppWindow* app) : + m_refCount{ 1 }, m_app{ app } + { + } + + IFACEMETHODIMP_(ULONG) + AddRef() + { + return InterlockedIncrement(&m_refCount); + } + + IFACEMETHODIMP_(ULONG) + Release() + { + long refCount = InterlockedDecrement(&m_refCount); + + if (refCount == 0) + { + delete this; + } + return refCount; + } + + IFACEMETHODIMP QueryInterface(_In_ REFIID riid, _Outptr_ void** ppv) + { + static const QITAB qit[] = { + QITABENT(UIHostPowerRenameManagerEvents, IPowerRenameManagerEvents), + { 0 } + }; + return QISearch(this, qit, riid, ppv); + } + + HRESULT OnItemAdded(_In_ IPowerRenameItem* renameItem) override { return m_app->OnItemAdded(renameItem); } + HRESULT OnUpdate(_In_ IPowerRenameItem* renameItem) override { return m_app->OnUpdate(renameItem); } + HRESULT OnRename(_In_ IPowerRenameItem* renameItem) override { return m_app->OnRename(renameItem); } + HRESULT OnError(_In_ IPowerRenameItem* renameItem) override { return m_app->OnError(renameItem); } + HRESULT OnRegExStarted(_In_ DWORD threadId) override { return m_app->OnRegExStarted(threadId); } + HRESULT OnRegExCanceled(_In_ DWORD threadId) override { return m_app->OnRegExCanceled(threadId); } + HRESULT OnRegExCompleted(_In_ DWORD threadId) override { return m_app->OnRegExCompleted(threadId); } + HRESULT OnRenameStarted() override { return m_app->OnRenameStarted(); } + HRESULT OnRenameCompleted(bool closeUIWindowAfterRenaming) override { return m_app->OnRenameCompleted(closeUIWindowAfterRenaming); } + + private: + long m_refCount; + + AppWindow* m_app; + }; + + static int Show(HINSTANCE hInstance, std::vector files); + LRESULT MessageHandler(UINT message, WPARAM wParam, LPARAM lParam) noexcept; + +private: + enum class UpdateFlagCommand + { + Set = 0, + Reset + }; + + AppWindow(HINSTANCE hInstance, std::vector files) noexcept; + void CreateAndShowWindow(); + bool OnCreate(HWND, LPCREATESTRUCT) noexcept; + void OnCommand(HWND, int id, HWND hwndControl, UINT codeNotify) noexcept; + void OnDestroy(HWND hwnd) noexcept; + void OnResize(HWND, UINT state, int cx, int cy) noexcept; + HRESULT CreateShellItemArrayFromPaths(std::vector files, IShellItemArray** shellItemArray); + + void PopulateExplorerItems(); + HRESULT InitAutoComplete(); + HRESULT EnumerateShellItems(_In_ IEnumShellItems* enumShellItems); + void SearchReplaceChanged(bool forceRenaming = false); + void ValidateFlags(PowerRenameFlags flag); + void UpdateFlag(PowerRenameFlags flag, UpdateFlagCommand command); + void SetHandlers(); + void ToggleItem(int32_t id, bool checked); + void ToggleAll(); + void SwitchView(); + void Rename(bool closeWindow); + HRESULT ReadSettings(); + HRESULT WriteSettings(); + HRESULT OpenSettingsApp(); + void SetCheckboxesFromFlags(DWORD flags); + void UpdateCounts(); + + HRESULT OnItemAdded(_In_ IPowerRenameItem* renameItem); + HRESULT OnUpdate(_In_ IPowerRenameItem* renameItem); + HRESULT OnRename(_In_ IPowerRenameItem* renameItem); + HRESULT OnError(_In_ IPowerRenameItem* renameItem); + HRESULT OnRegExStarted(_In_ DWORD threadId); + HRESULT OnRegExCanceled(_In_ DWORD threadId); + HRESULT OnRegExCompleted(_In_ DWORD threadId); + HRESULT OnRenameStarted(); + HRESULT OnRenameCompleted(bool closeUIWindowAfterRenaming); + + wil::unique_haccel m_accelerators; + const HINSTANCE m_instance; + HWND m_xamlIsland{}; + HWND m_window{}; + winrt::PowerRenameUILib::MainWindow m_mainUserControl{ nullptr }; + + bool m_disableCountUpdate = false; + CComPtr m_prManager; + CComPtr m_dataSource; + CComPtr m_prEnum; + UIHostPowerRenameManagerEvents m_managerEvents; + DWORD m_cookie = 0; + CComPtr m_searchMRU; + CComPtr m_replaceMRU; + UINT m_selectedCount = 0; + UINT m_renamingCount = 0; + + bool m_flagValidationInProgress = false; +}; diff --git a/src/modules/powerrename/ui/PowerRename.ico b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.ico similarity index 100% rename from src/modules/powerrename/ui/PowerRename.ico rename to src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.ico diff --git a/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.rc b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.rc new file mode 100644 index 000000000..c2ed5aca3 --- /dev/null +++ b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.rc @@ -0,0 +1,150 @@ +//Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE resource. +// +#ifndef APSTUDIO_INVOKED +#include "targetver.h" +#endif +#define APSTUDIO_HIDDEN_SYMBOLS +#include "windows.h" +#undef APSTUDIO_HIDDEN_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE 9, 1 + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. + +IDI_POWERRENAMEUIHOST ICON "PowerRenameUIHost.ico" +IDI_SMALL ICON "small.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Menu +// + +IDC_POWERRENAMEUIHOST MENU +BEGIN + POPUP "&File" + BEGIN + MENUITEM "E&xit", IDM_EXIT + END + POPUP "&Help" + BEGIN + MENUITEM "&About ...", IDM_ABOUT + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Accelerator +// + +IDC_POWERRENAMEUIHOST ACCELERATORS +BEGIN + "?", IDM_ABOUT, ASCII, ALT + "/", IDM_ABOUT, ASCII, ALT +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_ABOUTBOX DIALOGEX 0, 0, 170, 62 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "About PowerRenameUIHost" +FONT 8, "MS Shell Dlg" +BEGIN + ICON IDR_MAINFRAME,IDC_STATIC,14,14,21,20 + LTEXT "PowerRenameUIHost, Version 1.0",IDC_STATIC,42,14,114,8,SS_NOPREFIX + LTEXT "Copyright (c) 2021",IDC_STATIC,42,26,114,8 + DEFPUSHBUTTON "OK",IDOK,113,41,50,14,WS_GROUP +END + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_ABOUTBOX, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 163 + TOPMARGIN, 7 + BOTTOMMARGIN, 55 + END +END +#endif // APSTUDIO_INVOKED + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#ifndef APSTUDIO_INVOKED\r\n" + "#include ""targetver.h""\r\n" + "#endif\r\n" + "#define APSTUDIO_HIDDEN_SYMBOLS\r\n" + "#include ""windows.h""\r\n" + "#undef APSTUDIO_HIDDEN_SYMBOLS\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDC_POWERRENAMEUIHOST "POWERRENAMEUIHOST" + IDS_APP_TITLE "PowerRename" +END + +#endif +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE resource. +// + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + \ No newline at end of file diff --git a/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.vcxproj b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.vcxproj new file mode 100644 index 000000000..483078fe1 --- /dev/null +++ b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.vcxproj @@ -0,0 +1,165 @@ + + + + + + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {f7ec4e6c-19ca-4fbd-9918-b8ac5fef4f63} + PowerRenameUIHost + 10.0.18362.0 + $(WindowsTargetPlatformVersion) + + + + Application + true + v142 + Unicode + false + + + Application + false + v142 + true + Unicode + false + + + + + + + + + + + + + + + true + $(SolutionDir)$(Platform)\$(Configuration)\modules\PowerRename\ + PowerRename + + + false + $(SolutionDir)$(Platform)\$(Configuration)\modules\PowerRename\ + PowerRename + + + + Level3 + true + _DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + $(ProjectDir)..\..\..\;$(ProjectDir)..\..\..\common\Telemetry;$(ProjectDir)..\lib;%(AdditionalIncludeDirectories) + + + Windows + true + + + PerMonitorHighDPIAware + + + + + Level3 + true + true + true + NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + true + $(ProjectDir)..\..\..\;$(ProjectDir)..\..\..\common\Telemetry;$(ProjectDir)..\lib;%(AdditionalIncludeDirectories) + + + Windows + true + true + true + + + PerMonitorHighDPIAware + + + + + + + + + + + + + Create + Create + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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}. + + + + + + + + + + + + + + PowerRenameUILib + + + $(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\;$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\Generated Files\; + + + + + {51920f1f-c28c-4adf-8660-4238766796c2} + + + \ No newline at end of file diff --git a/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.vcxproj.filters b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.vcxproj.filters new file mode 100644 index 000000000..8be01112e --- /dev/null +++ b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.vcxproj.filters @@ -0,0 +1,67 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;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 + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + + + Resource Files + + + + + Resource Files + + + Resource Files + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/PowerRenameUIHost/Resource.h b/src/modules/powerrename/PowerRenameUIHost/Resource.h new file mode 100644 index 000000000..3f2ed88ba --- /dev/null +++ b/src/modules/powerrename/PowerRenameUIHost/Resource.h @@ -0,0 +1,30 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by PowerRenameUIHost.rc + +#define IDS_APP_TITLE 103 + +#define IDR_MAINFRAME 128 +#define IDD_POWERRENAMEUIHOST_DIALOG 102 +#define IDD_ABOUTBOX 103 +#define IDM_ABOUT 104 +#define IDM_EXIT 105 +#define IDI_POWERRENAMEUIHOST 107 +#define IDI_SMALL 108 +#define IDC_POWERRENAMEUIHOST 109 +#define IDC_MYICON 2 +#ifndef IDC_STATIC +#define IDC_STATIC -1 +#endif +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS + +#define _APS_NO_MFC 130 +#define _APS_NEXT_RESOURCE_VALUE 129 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 110 +#endif +#endif diff --git a/src/modules/powerrename/PowerRenameUIHost/XamlBridge.cpp b/src/modules/powerrename/PowerRenameUIHost/XamlBridge.cpp new file mode 100644 index 000000000..f5b551e5e --- /dev/null +++ b/src/modules/powerrename/PowerRenameUIHost/XamlBridge.cpp @@ -0,0 +1,249 @@ +#include "pch.h" + +#include "XamlBridge.h" +#include "Resource.h" +#include + +bool DesktopWindow::FilterMessage(const MSG* msg) +{ + // When multiple child windows are present it is needed to pre dispatch messages to all + // DesktopWindowXamlSource instances so keyboard accelerators and + // keyboard focus work correctly. + for (auto& xamlSource : m_xamlSources) + { + BOOL xamlSourceProcessedMessage = FALSE; + winrt::check_hresult(xamlSource.as()->PreTranslateMessage(msg, &xamlSourceProcessedMessage)); + if (xamlSourceProcessedMessage != FALSE) + { + return true; + } + } + + return false; +} + +const auto static invalidReason = static_cast(-1); + +winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason GetReasonFromKey(WPARAM key) +{ + auto reason = invalidReason; + if (key == VK_TAB) + { + byte keyboardState[256] = {}; + WINRT_VERIFY(::GetKeyboardState(keyboardState)); + reason = (keyboardState[VK_SHIFT] & 0x80) ? + winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Last : + winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::First; + } + else if (key == VK_LEFT) + { + reason = winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Left; + } + else if (key == VK_RIGHT) + { + reason = winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Right; + } + else if (key == VK_UP) + { + reason = winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Up; + } + else if (key == VK_DOWN) + { + reason = winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Down; + } + return reason; +} + +winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource DesktopWindow::GetNextFocusedIsland(const MSG* msg) +{ + if (msg->message == WM_KEYDOWN) + { + const auto key = msg->wParam; + auto reason = GetReasonFromKey(key); + if (reason != invalidReason) + { + const BOOL previous = + (reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::First || + reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Down || + reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Right) ? + false : + true; + + const auto currentFocusedWindow = ::GetFocus(); + const auto nextElement = ::GetNextDlgTabItem(m_window.get(), currentFocusedWindow, previous); + for (auto& xamlSource : m_xamlSources) + { + HWND islandWnd{}; + winrt::check_hresult(xamlSource.as()->get_WindowHandle(&islandWnd)); + if (nextElement == islandWnd) + { + return xamlSource; + } + } + } + } + + return nullptr; +} + +winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource DesktopWindow::GetFocusedIsland() +{ + for (auto& xamlSource : m_xamlSources) + { + if (xamlSource.HasFocus()) + { + return xamlSource; + } + } + return nullptr; +} + +bool DesktopWindow::NavigateFocus(MSG* msg) +{ + if (const auto nextFocusedIsland = GetNextFocusedIsland(msg)) + { + const auto previousFocusedWindow = ::GetFocus(); + RECT rect = {}; + WINRT_VERIFY(::GetWindowRect(previousFocusedWindow, &rect)); + const auto nativeIsland = nextFocusedIsland.as(); + HWND islandWnd = nullptr; + winrt::check_hresult(nativeIsland->get_WindowHandle(&islandWnd)); + POINT pt = { rect.left, rect.top }; + SIZE size = { rect.right - rect.left, rect.bottom - rect.top }; + ::ScreenToClient(islandWnd, &pt); + const auto hintRect = winrt::Windows::Foundation::Rect({ static_cast(pt.x), static_cast(pt.y), static_cast(size.cx), static_cast(size.cy) }); + const auto reason = GetReasonFromKey(msg->wParam); + const auto request = winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationRequest(reason, hintRect); + m_lastFocusRequestId = request.CorrelationId(); + const auto result = nextFocusedIsland.NavigateFocus(request); + return result.WasFocusMoved(); + } + else + { + const bool islandIsFocused = GetFocusedIsland() != nullptr; + byte keyboardState[256] = {}; + WINRT_VERIFY(::GetKeyboardState(keyboardState)); + const bool isMenuModifier = keyboardState[VK_MENU] & 0x80; + if (islandIsFocused && !isMenuModifier) + { + return false; + } + const bool isDialogMessage = !!IsDialogMessage(m_window.get(), msg); + return isDialogMessage; + } +} + +int DesktopWindow::MessageLoop(HACCEL accelerators) +{ + MSG msg = {}; + while (GetMessage(&msg, nullptr, 0, 0)) + { + const bool processedMessage = FilterMessage(&msg); + if (!processedMessage && !TranslateAcceleratorW(msg.hwnd, accelerators, &msg)) + { + if (!NavigateFocus(&msg)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + } + return static_cast(msg.wParam); +} + +static const WPARAM invalidKey = (WPARAM)-1; + +WPARAM GetKeyFromReason(winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason reason) +{ + auto key = invalidKey; + if (reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Last || reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::First) + { + key = VK_TAB; + } + else if (reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Left) + { + key = VK_LEFT; + } + else if (reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Right) + { + key = VK_RIGHT; + } + else if (reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Up) + { + key = VK_UP; + } + else if (reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Down) + { + key = VK_DOWN; + } + return key; +} + +void DesktopWindow::OnTakeFocusRequested(winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource const& sender, winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSourceTakeFocusRequestedEventArgs const& args) +{ + if (args.Request().CorrelationId() != m_lastFocusRequestId) + { + const auto reason = args.Request().Reason(); + const BOOL previous = + (reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::First || + reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Down || + reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Right) ? + false : + true; + + const auto nativeXamlSource = sender.as(); + HWND senderHwnd = nullptr; + winrt::check_hresult(nativeXamlSource->get_WindowHandle(&senderHwnd)); + + MSG msg = {}; + msg.hwnd = senderHwnd; + msg.message = WM_KEYDOWN; + msg.wParam = GetKeyFromReason(reason); + if (!NavigateFocus(&msg)) + { + const auto nextElement = ::GetNextDlgTabItem(m_window.get(), senderHwnd, previous); + ::SetFocus(nextElement); + } + } + else + { + const auto request = winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationRequest(winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Restore); + m_lastFocusRequestId = request.CorrelationId(); + sender.NavigateFocus(request); + } +} + +HWND DesktopWindow::CreateDesktopWindowsXamlSource(DWORD extraWindowStyles, const winrt::Windows::UI::Xaml::UIElement& content) +{ + winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource desktopSource; + + auto interop = desktopSource.as(); + // Parent the DesktopWindowXamlSource object to current window + winrt::check_hresult(interop->AttachToWindow(m_window.get())); + HWND xamlSourceWindow{}; // Lifetime controlled desktopSource + winrt::check_hresult(interop->get_WindowHandle(&xamlSourceWindow)); + const DWORD style = GetWindowLongW(xamlSourceWindow, GWL_STYLE) | extraWindowStyles; + SetWindowLongW(xamlSourceWindow, GWL_STYLE, style); + + desktopSource.Content(content); + + m_takeFocusEventRevokers.push_back(desktopSource.TakeFocusRequested(winrt::auto_revoke, { this, &DesktopWindow::OnTakeFocusRequested })); + m_xamlSources.push_back(desktopSource); + + return xamlSourceWindow; +} + +void DesktopWindow::ClearXamlIslands() +{ + for (auto& takeFocusRevoker : m_takeFocusEventRevokers) + { + takeFocusRevoker.revoke(); + } + m_takeFocusEventRevokers.clear(); + + for (auto& xamlSource : m_xamlSources) + { + xamlSource.Close(); + } + m_xamlSources.clear(); +} diff --git a/src/modules/powerrename/PowerRenameUIHost/XamlBridge.h b/src/modules/powerrename/PowerRenameUIHost/XamlBridge.h new file mode 100644 index 000000000..24056ec28 --- /dev/null +++ b/src/modules/powerrename/PowerRenameUIHost/XamlBridge.h @@ -0,0 +1,108 @@ +#pragma once + +#include // To enable support for non-WinRT interfaces, unknwn.h must be included before any C++/WinRT headers. +#include +#include +#include +#pragma push_macro("GetCurrentTime") +#undef GetCurrentTime +#include +#pragma pop_macro("GetCurrentTime") +#include +#include +#include +#include + +class DesktopWindow +{ +protected: + int MessageLoop(HACCEL accelerators); + HWND CreateDesktopWindowsXamlSource(DWORD extraStyles, const winrt::Windows::UI::Xaml::UIElement& content); + void ClearXamlIslands(); + + HWND WindowHandle() const + { + return m_window.get(); + } + + static void OnNCCreate(HWND window, LPARAM lparam) noexcept + { + auto cs = reinterpret_cast(lparam); + auto that = static_cast(cs->lpCreateParams); + that->m_window.reset(window); // take ownership of the window + SetWindowLongPtrW(window, GWLP_USERDATA, reinterpret_cast(that)); + } + +private: + winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource GetFocusedIsland(); + bool FilterMessage(const MSG* msg); + void OnTakeFocusRequested(winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource const& sender, winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSourceTakeFocusRequestedEventArgs const& args); + winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource GetNextFocusedIsland(const MSG* msg); + bool NavigateFocus(MSG* msg); + + wil::unique_hwnd m_window; + winrt::guid m_lastFocusRequestId; + std::vector m_takeFocusEventRevokers; + std::vector m_xamlSources; +}; + +template +struct DesktopWindowT : public DesktopWindow +{ +protected: + using base_type = DesktopWindowT; + + static LRESULT __stdcall WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept + { + if (message == WM_NCCREATE) + { + OnNCCreate(window, lparam); + } + else if (message == WM_NCDESTROY) + { + SetWindowLongPtrW(window, GWLP_USERDATA, 0); + } + else if (auto that = reinterpret_cast(GetWindowLongPtrW(window, GWLP_USERDATA))) + { + return that->MessageHandler(message, wparam, lparam); + } + + return DefWindowProcW(window, message, wparam, lparam); + } + + LRESULT MessageHandler(UINT message, WPARAM wParam, LPARAM lParam) noexcept + { + switch (message) + { + HANDLE_MSG(WindowHandle(), WM_DESTROY, OnDestroy); + HANDLE_MSG(WindowHandle(), WM_ACTIVATE, OnActivate); + HANDLE_MSG(WindowHandle(), WM_SETFOCUS, OnSetFocus); + } + return DefWindowProcW(WindowHandle(), message, wParam, lParam); + } + + void OnDestroy(HWND) + { + ClearXamlIslands(); + PostQuitMessage(0); + } + +private: + void OnActivate(HWND, UINT state, HWND hwndActDeact, BOOL fMinimized) + { + if (state == WA_INACTIVE) + { + m_hwndLastFocus = GetFocus(); + } + } + + void OnSetFocus(HWND, HWND hwndOldFocus) + { + if (m_hwndLastFocus) + { + SetFocus(m_hwndLastFocus); + } + } + + HWND m_hwndLastFocus = nullptr; +}; diff --git a/src/modules/powerrename/PowerRenameUIHost/app.manifest b/src/modules/powerrename/PowerRenameUIHost/app.manifest new file mode 100644 index 000000000..1b5754681 --- /dev/null +++ b/src/modules/powerrename/PowerRenameUIHost/app.manifest @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/PowerRenameUIHost/framework.h b/src/modules/powerrename/PowerRenameUIHost/framework.h new file mode 100644 index 000000000..ce362d71f --- /dev/null +++ b/src/modules/powerrename/PowerRenameUIHost/framework.h @@ -0,0 +1,15 @@ +// header.h : include file for standard system include files, +// or project specific include files +// + +#pragma once + +#include "targetver.h" +// #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files +#include +// C RunTime Header Files +#include +#include +#include +#include diff --git a/src/modules/powerrename/PowerRenameUIHost/packages.config b/src/modules/powerrename/PowerRenameUIHost/packages.config new file mode 100644 index 000000000..cb4fc6898 --- /dev/null +++ b/src/modules/powerrename/PowerRenameUIHost/packages.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/ui/pch.cpp b/src/modules/powerrename/PowerRenameUIHost/pch.cpp similarity index 100% rename from src/modules/powerrename/ui/pch.cpp rename to src/modules/powerrename/PowerRenameUIHost/pch.cpp diff --git a/src/modules/powerrename/PowerRenameUIHost/pch.h b/src/modules/powerrename/PowerRenameUIHost/pch.h new file mode 100644 index 000000000..61ca50ecd --- /dev/null +++ b/src/modules/powerrename/PowerRenameUIHost/pch.h @@ -0,0 +1,5 @@ +#pragma once + +#include "framework.h" +#include +#include diff --git a/src/modules/powerrename/PowerRenameUIHost/small.ico b/src/modules/powerrename/PowerRenameUIHost/small.ico new file mode 100644 index 000000000..4f111feaa Binary files /dev/null and b/src/modules/powerrename/PowerRenameUIHost/small.ico differ diff --git a/src/modules/powerrename/ui/targetver.h b/src/modules/powerrename/PowerRenameUIHost/targetver.h similarity index 100% rename from src/modules/powerrename/ui/targetver.h rename to src/modules/powerrename/PowerRenameUIHost/targetver.h diff --git a/src/modules/powerrename/PowerRenameUILib/App.cpp b/src/modules/powerrename/PowerRenameUILib/App.cpp new file mode 100644 index 000000000..f1840cbe1 --- /dev/null +++ b/src/modules/powerrename/PowerRenameUILib/App.cpp @@ -0,0 +1,18 @@ +#include "pch.h" +#include "App.h" +#include "App.g.cpp" +using namespace winrt; +using namespace Windows::UI::Xaml; +namespace winrt::PowerRenameUILib::implementation +{ + App::App() + { + Initialize(); + AddRef(); + m_inner.as<::IUnknown>()->Release(); + } + App::~App() + { + Close(); + } +} diff --git a/src/modules/powerrename/PowerRenameUILib/App.h b/src/modules/powerrename/PowerRenameUILib/App.h new file mode 100644 index 000000000..2ba215af1 --- /dev/null +++ b/src/modules/powerrename/PowerRenameUILib/App.h @@ -0,0 +1,18 @@ +#pragma once +#include "App.g.h" +#include "App.base.h" +namespace winrt::PowerRenameUILib::implementation +{ + class App : public AppT2 + { + public: + App(); + ~App(); + }; +} +namespace winrt::PowerRenameUILib::factory_implementation +{ + class App : public AppT + { + }; +} diff --git a/src/modules/powerrename/PowerRenameUILib/App.idl b/src/modules/powerrename/PowerRenameUILib/App.idl new file mode 100644 index 000000000..648b2544a --- /dev/null +++ b/src/modules/powerrename/PowerRenameUILib/App.idl @@ -0,0 +1,7 @@ +namespace PowerRenameUILib +{ + [default_interface] runtimeclass App : Microsoft.Toolkit.Win32.UI.XamlHost.XamlApplication + { + App(); + } +} \ No newline at end of file diff --git a/src/modules/powerrename/PowerRenameUILib/App.xaml b/src/modules/powerrename/PowerRenameUILib/App.xaml new file mode 100644 index 000000000..7795f1b7b --- /dev/null +++ b/src/modules/powerrename/PowerRenameUILib/App.xaml @@ -0,0 +1,1066 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Visible + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Filename + extension + Filename only + Extension only + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +