chore: import upstream snapshot with attribution
Spell checking / Check Spelling (push) Has been cancelled
Spell checking / Update PR (push) Has been cancelled
Publish Dev Docs Website / build (push) Failing after 1s
Publish Dev Docs Website / deploy (push) Has been skipped
Spell checking / Report (Push) (push) Has been cancelled
Spell checking / Report (PR) (push) Has been cancelled
Spell checking / Check Spelling (push) Has been cancelled
Spell checking / Update PR (push) Has been cancelled
Publish Dev Docs Website / build (push) Failing after 1s
Publish Dev Docs Website / deploy (push) Has been skipped
Spell checking / Report (Push) (push) Has been cancelled
Spell checking / Report (PR) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity
|
||||
version="1.0.0.0"
|
||||
processorArchitecture="*"
|
||||
name="Microsoft.PowerToys.ModuleLoader"
|
||||
type="win32"
|
||||
/>
|
||||
<description>PowerToys Module Loader - Standalone module testing utility</description>
|
||||
|
||||
<!-- Per-Monitor DPI Awareness V2 -->
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
|
||||
<!-- Request administrator execution level if needed -->
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
|
||||
<!-- Windows 10+ compatibility -->
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
<!-- Windows 11 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9b}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
||||
@@ -0,0 +1,205 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|ARM64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>17.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}</ProjectGuid>
|
||||
<RootNamespace>ModuleLoader</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
<ProjectName>ModuleLoader</ProjectName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
|
||||
<IntDir>$(Platform)\$(Configuration)\</IntDir>
|
||||
<TargetName>ModuleLoader</TargetName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
|
||||
<IntDir>$(Platform)\$(Configuration)\</IntDir>
|
||||
<TargetName>ModuleLoader</TargetName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
|
||||
<IntDir>$(Platform)\$(Configuration)\</IntDir>
|
||||
<TargetName>ModuleLoader</TargetName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
|
||||
<IntDir>$(Platform)\$(Configuration)\</IntDir>
|
||||
<TargetName>ModuleLoader</TargetName>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)src;$(RepoRoot)src\modules\interface;$(RepoRoot)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<TreatWarningAsError>false</TreatWarningAsError>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>kernel32.lib;user32.lib;Shcore.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalManifestDependencies>type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'</AdditionalManifestDependencies>
|
||||
</Link>
|
||||
<Manifest>
|
||||
<AdditionalManifestFiles>$(ProjectDir)ModuleLoader.manifest</AdditionalManifestFiles>
|
||||
</Manifest>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)src;$(RepoRoot)src\modules\interface;$(RepoRoot)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<TreatWarningAsError>false</TreatWarningAsError>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>kernel32.lib;user32.lib;Shcore.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalManifestDependencies>type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'</AdditionalManifestDependencies>
|
||||
</Link>
|
||||
<Manifest>
|
||||
<AdditionalManifestFiles>$(ProjectDir)ModuleLoader.manifest</AdditionalManifestFiles>
|
||||
</Manifest>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)src;$(RepoRoot)src\modules\interface;$(RepoRoot)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<TreatWarningAsError>false</TreatWarningAsError>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>kernel32.lib;user32.lib;Shcore.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalManifestDependencies>type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'</AdditionalManifestDependencies>
|
||||
</Link>
|
||||
<Manifest>
|
||||
<AdditionalManifestFiles>$(ProjectDir)ModuleLoader.manifest</AdditionalManifestFiles>
|
||||
</Manifest>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)src;$(RepoRoot)src\modules\interface;$(RepoRoot)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<TreatWarningAsError>false</TreatWarningAsError>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>kernel32.lib;user32.lib;Shcore.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalManifestDependencies>type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'</AdditionalManifestDependencies>
|
||||
</Link>
|
||||
<Manifest>
|
||||
<AdditionalManifestFiles>$(ProjectDir)ModuleLoader.manifest</AdditionalManifestFiles>
|
||||
</Manifest>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="src\main.cpp" />
|
||||
<ClCompile Include="src\ModuleLoader.cpp" />
|
||||
<ClCompile Include="src\SettingsLoader.cpp" />
|
||||
<ClCompile Include="src\HotkeyManager.cpp" />
|
||||
<ClCompile Include="src\ConsoleHost.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="src\ModuleLoader.h" />
|
||||
<ClInclude Include="src\SettingsLoader.h" />
|
||||
<ClInclude Include="src\HotkeyManager.h" />
|
||||
<ClInclude Include="src\ConsoleHost.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="README.md" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="src\main.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\ModuleLoader.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\SettingsLoader.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\HotkeyManager.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\ConsoleHost.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="src\ModuleLoader.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\SettingsLoader.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\HotkeyManager.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\ConsoleHost.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="README.md" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,483 @@
|
||||
# Sharing ModuleLoader and Modules
|
||||
|
||||
This guide explains how to share the ModuleLoader tool and PowerToy modules with others for testing purposes.
|
||||
|
||||
## Overview
|
||||
|
||||
The ModuleLoader is designed to be a **portable, standalone testing tool** that can be shared with module developers and testers. It has minimal dependencies and can work with any compatible PowerToy module DLL.
|
||||
|
||||
---
|
||||
|
||||
## What You Need to Share
|
||||
|
||||
### For Testing a Module (e.g., CursorWrap)
|
||||
|
||||
#### **Minimum Package** (Recommended for Quick Testing)
|
||||
|
||||
1. **ModuleLoader.exe** - The standalone loader application
|
||||
- Location: `x64\Debug\ModuleLoader.exe` or `x64\Release\ModuleLoader.exe`
|
||||
- No additional DLLs required (uses only Windows system libraries)
|
||||
|
||||
2. **The Module DLL** - The PowerToy module to test
|
||||
- Example: `CursorWrap.dll` from `x64\Debug\` or `x64\Release\`
|
||||
- Location varies by module (see module-specific locations below)
|
||||
|
||||
3. **settings.json** - Module configuration (place in same folder as the DLL)
|
||||
- **NEW**: Settings can be placed alongside the module DLL for portable testing
|
||||
- Location: Same directory as the module DLL (e.g., `settings.json` next to `CursorWrap.dll`)
|
||||
- Falls back to: `%LOCALAPPDATA%\Microsoft\PowerToys\<ModuleName>\settings.json` if not found locally
|
||||
|
||||
#### **Complete Standalone Package** (For Users Without PowerToys Installed)
|
||||
|
||||
1. **ModuleLoader.exe**
|
||||
2. **Module DLL**
|
||||
3. **Sample settings.json** - Pre-configured settings file
|
||||
4. **Installation instructions** - See "Standalone Package Setup" section below
|
||||
|
||||
---
|
||||
|
||||
### Debug Builds
|
||||
If you build the module in Debug configuration:
|
||||
- The module will output debug messages via `OutputDebugString()`
|
||||
- View these with [DebugView](https://learn.microsoft.com/sysinternals/downloads/debugview) or Visual Studio Output window
|
||||
- Example: CursorWrap outputs detailed topology and cursor wrapping debug info
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Module-Specific File Locations
|
||||
|
||||
### CursorWrap
|
||||
```
|
||||
Files to share:
|
||||
- x64\Debug\CursorWrap.dll (or Release)
|
||||
- %LOCALAPPDATA%\Microsoft\PowerToys\CursorWrap\settings.json
|
||||
|
||||
Size: ~100KB
|
||||
```
|
||||
|
||||
### MouseHighlighter
|
||||
```
|
||||
Files to share:
|
||||
- x64\Debug\MouseHighlighter.dll (or Release)
|
||||
- %LOCALAPPDATA%\Microsoft\PowerToys\MouseHighlighter\settings.json
|
||||
|
||||
Size: ~150KB
|
||||
```
|
||||
|
||||
### FindMyMouse
|
||||
```
|
||||
Files to share:
|
||||
- x64\Debug\FindMyMouse.dll (or Release)
|
||||
- %LOCALAPPDATA%\Microsoft\PowerToys\FindMyMouse\settings.json
|
||||
|
||||
Size: ~120KB
|
||||
```
|
||||
|
||||
### MousePointerCrosshairs
|
||||
```
|
||||
Files to share:
|
||||
- x64\Debug\MousePointerCrosshairs.dll (or Release)
|
||||
- %LOCALAPPDATA%\Microsoft\PowerToys\MousePointerCrosshairs\settings.json
|
||||
|
||||
Size: ~140KB
|
||||
```
|
||||
|
||||
### MouseJump
|
||||
```
|
||||
Files to share:
|
||||
- x64\Debug\MouseJump.dll (or Release)
|
||||
- %LOCALAPPDATA%\Microsoft\PowerToys\MouseJump\settings.json
|
||||
|
||||
Note: MouseJump is a UI-based module and may not work fully with ModuleLoader
|
||||
Size: ~200KB
|
||||
```
|
||||
|
||||
### AlwaysOnTop
|
||||
```
|
||||
Files to share:
|
||||
- x64\Debug\AlwaysOnTop.dll (or Release)
|
||||
- %LOCALAPPDATA%\Microsoft\PowerToys\AlwaysOnTop\settings.json
|
||||
|
||||
Size: ~100KB
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dependency Analysis
|
||||
|
||||
### ModuleLoader.exe Dependencies
|
||||
**Windows System Libraries Only** (automatically available on all Windows systems):
|
||||
- `KERNEL32.dll` - Core Windows API
|
||||
- `USER32.dll` - User interface functions
|
||||
- `SHELL32.dll` - Shell functions
|
||||
- `ole32.dll` - COM library
|
||||
|
||||
**No PowerToys dependencies required!** The ModuleLoader is completely standalone.
|
||||
|
||||
### Module DLL Dependencies (Typical)
|
||||
Most PowerToy modules depend on:
|
||||
- Windows system DLLs (automatically available)
|
||||
- PowerToys common libraries (if any, they're typically statically linked)
|
||||
- **Module settings** - Must be present in `%LOCALAPPDATA%\Microsoft\PowerToys\<ModuleName>\`
|
||||
|
||||
**Important**: Modules are generally **self-contained** and statically link most dependencies. You typically only need the module DLL itself.
|
||||
|
||||
---
|
||||
|
||||
## Creating a Standalone Package
|
||||
|
||||
### Step 1: Prepare the Files
|
||||
|
||||
Create a folder structure like this:
|
||||
```
|
||||
ModuleLoaderPackage\
|
||||
??? ModuleLoader.exe
|
||||
??? CursorWrap.dll (or other module)
|
||||
??? settings.json (module settings - placed locally!)
|
||||
```
|
||||
|
||||
**NEW Simplified Structure**: You can now place `settings.json` directly alongside the module DLL! The ModuleLoader will check this location first before looking in the standard PowerToys settings directories.
|
||||
|
||||
### Step 2: Extract Settings from Your Machine
|
||||
|
||||
```powershell
|
||||
# Copy settings from your development machine
|
||||
$moduleName = "CursorWrap" # Change as needed
|
||||
$settingsPath = "$env:LOCALAPPDATA\Microsoft\PowerToys\$moduleName\settings.json"
|
||||
Copy-Item $settingsPath ".\settings\$moduleName\settings.json"
|
||||
```
|
||||
|
||||
### Step 3: Create Installation Instructions (README.txt)
|
||||
|
||||
```text
|
||||
PowerToys Module Testing Package
|
||||
=================================
|
||||
|
||||
This package contains the ModuleLoader tool for testing PowerToy modules.
|
||||
|
||||
Contents:
|
||||
- ModuleLoader.exe : Standalone module loader
|
||||
- modules\*.dll : PowerToy module(s) to test
|
||||
- settings\*\*.json : Module configuration files
|
||||
|
||||
Setup (First Time):
|
||||
-------------------
|
||||
1. Create settings directory:
|
||||
%LOCALAPPDATA%\Microsoft\PowerToys\
|
||||
|
||||
2. Copy settings:
|
||||
Copy the entire "settings\<ModuleName>" folder to:
|
||||
%LOCALAPPDATA%\Microsoft\PowerToys\
|
||||
|
||||
Example for CursorWrap:
|
||||
Copy "settings\CursorWrap" to:
|
||||
%LOCALAPPDATA%\Microsoft\PowerToys\CursorWrap\
|
||||
|
||||
Usage:
|
||||
------
|
||||
ModuleLoader.exe modules\CursorWrap.dll
|
||||
|
||||
The tool will:
|
||||
- Load the module DLL
|
||||
- Read settings from %LOCALAPPDATA%\Microsoft\PowerToys\<ModuleName>\
|
||||
- Register hotkeys
|
||||
- Enable the module
|
||||
|
||||
Press Ctrl+C to exit.
|
||||
Press the module's hotkey to toggle functionality.
|
||||
|
||||
Requirements:
|
||||
-------------
|
||||
- Windows 10 1803 or later
|
||||
- No PowerToys installation required!
|
||||
|
||||
Troubleshooting:
|
||||
----------------
|
||||
If you see "Settings file not found":
|
||||
1. Make sure you copied the settings folder correctly
|
||||
2. Check that the path is:
|
||||
%LOCALAPPDATA%\Microsoft\PowerToys\<ModuleName>\settings.json
|
||||
3. You can also run PowerToys once to generate default settings
|
||||
|
||||
Debug Logs:
|
||||
-----------
|
||||
Module logs are written to:
|
||||
%LOCALAPPDATA%\Microsoft\PowerToys\<ModuleName>\Logs\
|
||||
|
||||
For debug builds, use DebugView to see real-time output.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Distribution Methods
|
||||
|
||||
### Method 1: ZIP Archive
|
||||
```powershell
|
||||
# Create a complete package
|
||||
$moduleName = "CursorWrap"
|
||||
$packageName = "ModuleLoader-$moduleName-Package"
|
||||
|
||||
# Collect files
|
||||
New-Item $packageName -ItemType Directory
|
||||
Copy-Item "x64\Debug\ModuleLoader.exe" "$packageName\"
|
||||
New-Item "$packageName\modules" -ItemType Directory
|
||||
Copy-Item "x64\Debug\$moduleName.dll" "$packageName\modules\"
|
||||
New-Item "$packageName\settings\$moduleName" -ItemType Directory -Force
|
||||
Copy-Item "$env:LOCALAPPDATA\Microsoft\PowerToys\$moduleName\settings.json" "$packageName\settings\$moduleName\"
|
||||
|
||||
# Create README
|
||||
@"
|
||||
See README in the tools\module_loader folder for instructions
|
||||
"@ | Out-File "$packageName\README.txt"
|
||||
|
||||
# Zip it
|
||||
Compress-Archive -Path $packageName -DestinationPath "$packageName.zip"
|
||||
```
|
||||
|
||||
### Method 2: Direct Share (Advanced Users)
|
||||
For developers who already have PowerToys installed:
|
||||
```powershell
|
||||
# Just share the executables
|
||||
Copy-Item "x64\Debug\ModuleLoader.exe" "\\ShareLocation\"
|
||||
Copy-Item "x64\Debug\CursorWrap.dll" "\\ShareLocation\"
|
||||
```
|
||||
|
||||
They can run: `ModuleLoader.exe CursorWrap.dll`
|
||||
(Settings will be loaded from their existing PowerToys installation)
|
||||
|
||||
---
|
||||
|
||||
## Platform-Specific Notes
|
||||
|
||||
### x64 vs ARM64
|
||||
|
||||
**Important**: Match architectures!
|
||||
- `x64\Debug\ModuleLoader.exe` ? Only works with `x64` module DLLs
|
||||
- `ARM64\Debug\ModuleLoader.exe` ? Only works with `ARM64` module DLLs
|
||||
|
||||
**Distribution Tip**: Provide both architectures if targeting multiple platforms:
|
||||
```
|
||||
ModuleLoaderPackage\
|
||||
??? x64\
|
||||
? ??? ModuleLoader.exe
|
||||
? ??? modules\CursorWrap.dll
|
||||
??? ARM64\
|
||||
? ??? ModuleLoader.exe
|
||||
? ??? modules\CursorWrap.dll
|
||||
??? settings\...
|
||||
```
|
||||
|
||||
### Debug vs Release
|
||||
|
||||
**Debug builds**:
|
||||
- Larger file size
|
||||
- Include debug symbols
|
||||
- Verbose logging via `OutputDebugString()`
|
||||
- Recommended for testing/development
|
||||
|
||||
**Release builds**:
|
||||
- Smaller file size
|
||||
- Optimized performance
|
||||
- Minimal logging
|
||||
- Recommended for end-user testing
|
||||
|
||||
---
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
Before sharing a module package:
|
||||
|
||||
- [ ] ModuleLoader.exe is included
|
||||
- [ ] Module DLL is included (matching architecture)
|
||||
- [ ] Sample settings.json is included
|
||||
- [ ] README/instructions are included
|
||||
- [ ] Tested on a clean machine (no PowerToys installed)
|
||||
- [ ] Verified hotkeys work
|
||||
- [ ] Verified Ctrl+C exits cleanly
|
||||
- [ ] Confirmed settings path in documentation
|
||||
|
||||
---
|
||||
|
||||
## Advanced: Portable Package Script
|
||||
|
||||
Here's a complete PowerShell script to create a fully portable package:
|
||||
|
||||
```powershell
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$ModuleName,
|
||||
|
||||
[ValidateSet("Debug", "Release")]
|
||||
[string]$Configuration = "Debug",
|
||||
|
||||
[ValidateSet("x64", "ARM64")]
|
||||
[string]$Platform = "x64"
|
||||
)
|
||||
|
||||
$packageName = "ModuleLoader-$ModuleName-$Platform-$Configuration"
|
||||
$packagePath = ".\$packageName"
|
||||
|
||||
Write-Host "Creating portable package: $packageName" -ForegroundColor Green
|
||||
|
||||
# Create structure
|
||||
New-Item $packagePath -ItemType Directory -Force | Out-Null
|
||||
New-Item "$packagePath\modules" -ItemType Directory -Force | Out-Null
|
||||
New-Item "$packagePath\settings\$ModuleName" -ItemType Directory -Force | Out-Null
|
||||
|
||||
# Copy ModuleLoader
|
||||
$loaderPath = "$Platform\$Configuration\ModuleLoader.exe"
|
||||
if (Test-Path $loaderPath) {
|
||||
Copy-Item $loaderPath "$packagePath\"
|
||||
Write-Host "? Copied ModuleLoader.exe" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "? ModuleLoader.exe not found at $loaderPath" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Copy Module DLL
|
||||
$modulePath = "$Platform\$Configuration\$ModuleName.dll"
|
||||
if (Test-Path $modulePath) {
|
||||
Copy-Item $modulePath "$packagePath\modules\"
|
||||
Write-Host "? Copied $ModuleName.dll" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "? $ModuleName.dll not found at $modulePath" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Copy Settings
|
||||
$settingsPath = "$env:LOCALAPPDATA\Microsoft\PowerToys\$ModuleName\settings.json"
|
||||
if (Test-Path $settingsPath) {
|
||||
Copy-Item $settingsPath "$packagePath\settings\$ModuleName\"
|
||||
Write-Host "? Copied settings.json" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "? Settings not found at $settingsPath - creating placeholder" -ForegroundColor Yellow
|
||||
@"
|
||||
{
|
||||
"name": "$ModuleName",
|
||||
"version": "1.0"
|
||||
}
|
||||
"@ | Out-File "$packagePath\settings\$ModuleName\settings.json"
|
||||
}
|
||||
|
||||
# Create README
|
||||
@"
|
||||
PowerToys $ModuleName Testing Package
|
||||
======================================
|
||||
|
||||
Configuration: $Configuration
|
||||
Platform: $Platform
|
||||
|
||||
Setup Instructions:
|
||||
-------------------
|
||||
1. Copy the 'settings\$ModuleName' folder to:
|
||||
%LOCALAPPDATA%\Microsoft\PowerToys\
|
||||
|
||||
2. Run:
|
||||
ModuleLoader.exe modules\$ModuleName.dll
|
||||
|
||||
3. Press Ctrl+C to exit
|
||||
|
||||
Logs are written to:
|
||||
%LOCALAPPDATA%\Microsoft\PowerToys\$ModuleName\Logs\
|
||||
|
||||
For more information, see:
|
||||
https://github.com/microsoft/PowerToys/tree/main/tools/module_loader
|
||||
"@ | Out-File "$packagePath\README.txt"
|
||||
|
||||
# Create ZIP
|
||||
$zipPath = "$packageName.zip"
|
||||
Compress-Archive -Path $packagePath -DestinationPath $zipPath -Force
|
||||
Write-Host "? Created $zipPath" -ForegroundColor Green
|
||||
|
||||
# Show summary
|
||||
Write-Host "`nPackage Contents:" -ForegroundColor Cyan
|
||||
Get-ChildItem $packagePath -Recurse | ForEach-Object {
|
||||
Write-Host " $($_.FullName.Replace($packagePath, ''))"
|
||||
}
|
||||
|
||||
Write-Host "`nPackage ready: $zipPath" -ForegroundColor Green
|
||||
Write-Host "Size: $([math]::Round((Get-Item $zipPath).Length / 1KB, 2)) KB"
|
||||
```
|
||||
|
||||
**Usage**:
|
||||
```powershell
|
||||
.\CreateModulePackage.ps1 -ModuleName "CursorWrap" -Configuration Release -Platform x64
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## FAQ
|
||||
|
||||
### Q: Can I share just ModuleLoader.exe and the module DLL?
|
||||
**A**: Yes, but the recipient must have PowerToys installed (or manually create the settings file).
|
||||
|
||||
### Q: Does the tester need PowerToys installed?
|
||||
**A**: No, if you provide the complete package with settings. ModuleLoader is fully standalone.
|
||||
|
||||
### Q: What if settings.json doesn't exist?
|
||||
**A**: ModuleLoader will show an error. Either:
|
||||
1. Run PowerToys once with the module enabled to generate settings
|
||||
2. Manually create a minimal settings.json file
|
||||
3. Include a sample settings.json in your package
|
||||
|
||||
### Q: Can I test modules on a virtual machine?
|
||||
**A**: Yes! This is a great use case. Just copy the package to the VM - no PowerToys installation needed.
|
||||
|
||||
### Q: Do I need to include PDB files?
|
||||
**A**: Only for debugging. For normal testing, just the EXE and DLL are sufficient.
|
||||
|
||||
### Q: Can I distribute this to end users?
|
||||
**A**: ModuleLoader is a **development/testing tool**, not intended for end-user distribution. For production use, direct users to install PowerToys.
|
||||
|
||||
---
|
||||
|
||||
## Security Considerations
|
||||
|
||||
When sharing module DLLs:
|
||||
|
||||
1. **Verify Source**: Only share modules you built from trusted source code
|
||||
2. **Scan for Malware**: Run antivirus scans on the package before sharing
|
||||
3. **HTTPS Only**: Use secure channels (HTTPS, OneDrive, SharePoint) for distribution
|
||||
4. **Hash Verification**: Consider providing SHA256 hashes for file integrity:
|
||||
```powershell
|
||||
Get-FileHash ModuleLoader.exe -Algorithm SHA256
|
||||
Get-FileHash modules\CursorWrap.dll -Algorithm SHA256
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example Package (CursorWrap)
|
||||
|
||||
Here's what a complete CursorWrap testing package looks like:
|
||||
|
||||
```
|
||||
ModuleLoader-CursorWrap-x64-Debug.zip (220 KB)
|
||||
?
|
||||
??? ModuleLoader-CursorWrap-x64-Debug\
|
||||
??? ModuleLoader.exe (160 KB)
|
||||
??? README.txt (2 KB)
|
||||
??? modules\
|
||||
? ??? CursorWrap.dll (55 KB)
|
||||
??? settings\
|
||||
??? CursorWrap\
|
||||
??? settings.json (3 KB)
|
||||
```
|
||||
|
||||
**Total package size**: ~220 KB (compressed)
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
For issues with ModuleLoader, see:
|
||||
- [ModuleLoader README](./README.md)
|
||||
- [PowerToys Documentation](https://aka.ms/PowerToysOverview)
|
||||
- [PowerToys GitHub Issues](https://github.com/microsoft/PowerToys/issues)
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
ModuleLoader is part of PowerToys and is licensed under the MIT License.
|
||||
See the LICENSE file in the PowerToys repository root for details.
|
||||
@@ -0,0 +1,80 @@
|
||||
// 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.
|
||||
|
||||
#include "ConsoleHost.h"
|
||||
#include <iostream>
|
||||
|
||||
bool ConsoleHost::s_exitRequested = false;
|
||||
|
||||
ConsoleHost::ConsoleHost(ModuleLoader& moduleLoader, HotkeyManager& hotkeyManager)
|
||||
: m_moduleLoader(moduleLoader)
|
||||
, m_hotkeyManager(hotkeyManager)
|
||||
{
|
||||
}
|
||||
|
||||
ConsoleHost::~ConsoleHost()
|
||||
{
|
||||
}
|
||||
|
||||
BOOL WINAPI ConsoleHost::ConsoleCtrlHandler(DWORD ctrlType)
|
||||
{
|
||||
switch (ctrlType)
|
||||
{
|
||||
case CTRL_C_EVENT:
|
||||
case CTRL_BREAK_EVENT:
|
||||
case CTRL_CLOSE_EVENT:
|
||||
std::wcout << L"\nCtrl+C received, shutting down...\n";
|
||||
s_exitRequested = true;
|
||||
|
||||
// Post a quit message to break the message loop
|
||||
PostQuitMessage(0);
|
||||
return TRUE;
|
||||
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
void ConsoleHost::Run()
|
||||
{
|
||||
// Install console control handler
|
||||
if (!SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE))
|
||||
{
|
||||
std::wcerr << L"Warning: Failed to set console control handler\n";
|
||||
}
|
||||
|
||||
s_exitRequested = false;
|
||||
|
||||
// Message loop
|
||||
MSG msg;
|
||||
while (!s_exitRequested)
|
||||
{
|
||||
// Wait for a message with a timeout so we can check s_exitRequested
|
||||
DWORD result = MsgWaitForMultipleObjects(0, nullptr, FALSE, 100, QS_ALLINPUT);
|
||||
|
||||
if (result == WAIT_OBJECT_0)
|
||||
{
|
||||
// Process all pending messages
|
||||
while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
|
||||
{
|
||||
if (msg.message == WM_QUIT)
|
||||
{
|
||||
s_exitRequested = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (msg.message == WM_HOTKEY)
|
||||
{
|
||||
m_hotkeyManager.HandleHotkey(static_cast<int>(msg.wParam), m_moduleLoader);
|
||||
}
|
||||
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove console control handler
|
||||
SetConsoleCtrlHandler(ConsoleCtrlHandler, FALSE);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
#include "ModuleLoader.h"
|
||||
#include "HotkeyManager.h"
|
||||
|
||||
/// <summary>
|
||||
/// Console host that runs the message loop and handles Ctrl+C
|
||||
/// </summary>
|
||||
class ConsoleHost
|
||||
{
|
||||
public:
|
||||
ConsoleHost(ModuleLoader& moduleLoader, HotkeyManager& hotkeyManager);
|
||||
~ConsoleHost();
|
||||
|
||||
// Prevent copying
|
||||
ConsoleHost(const ConsoleHost&) = delete;
|
||||
ConsoleHost& operator=(const ConsoleHost&) = delete;
|
||||
|
||||
/// <summary>
|
||||
/// Run the message loop until Ctrl+C is pressed
|
||||
/// </summary>
|
||||
void Run();
|
||||
|
||||
private:
|
||||
ModuleLoader& m_moduleLoader;
|
||||
HotkeyManager& m_hotkeyManager;
|
||||
static bool s_exitRequested;
|
||||
|
||||
/// <summary>
|
||||
/// Console control handler (for Ctrl+C)
|
||||
/// </summary>
|
||||
static BOOL WINAPI ConsoleCtrlHandler(DWORD ctrlType);
|
||||
};
|
||||
@@ -0,0 +1,279 @@
|
||||
// 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.
|
||||
|
||||
#include "HotkeyManager.h"
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
HotkeyManager::HotkeyManager()
|
||||
: m_nextHotkeyId(1) // Start from 1
|
||||
, m_hotkeyExRegistered(false)
|
||||
, m_hotkeyExId(0)
|
||||
{
|
||||
}
|
||||
|
||||
HotkeyManager::~HotkeyManager()
|
||||
{
|
||||
UnregisterAll();
|
||||
}
|
||||
|
||||
UINT HotkeyManager::ConvertModifiers(bool win, bool ctrl, bool alt, bool shift) const
|
||||
{
|
||||
UINT modifiers = MOD_NOREPEAT; // Prevent repeat events
|
||||
if (win) modifiers |= MOD_WIN;
|
||||
if (ctrl) modifiers |= MOD_CONTROL;
|
||||
if (alt) modifiers |= MOD_ALT;
|
||||
if (shift) modifiers |= MOD_SHIFT;
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
bool HotkeyManager::RegisterModuleHotkeys(ModuleLoader& moduleLoader)
|
||||
{
|
||||
if (!moduleLoader.IsLoaded())
|
||||
{
|
||||
std::wcerr << L"Error: Module not loaded\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool anyRegistered = false;
|
||||
|
||||
// First, try the newer GetHotkeyEx() API
|
||||
auto hotkeyEx = moduleLoader.GetHotkeyEx();
|
||||
if (hotkeyEx.has_value())
|
||||
{
|
||||
std::wcout << L"Module has HotkeyEx activation hotkey\n";
|
||||
|
||||
UINT modifiers = hotkeyEx->modifiersMask | MOD_NOREPEAT;
|
||||
UINT vkCode = hotkeyEx->vkCode;
|
||||
|
||||
if (vkCode != 0)
|
||||
{
|
||||
int hotkeyId = m_nextHotkeyId++;
|
||||
|
||||
std::wcout << L" Registering HotkeyEx: ";
|
||||
std::wcout << ModifiersToString(modifiers) << L"+" << VKeyToString(vkCode);
|
||||
|
||||
if (RegisterHotKey(nullptr, hotkeyId, modifiers, vkCode))
|
||||
{
|
||||
m_hotkeyExRegistered = true;
|
||||
m_hotkeyExId = hotkeyId;
|
||||
|
||||
std::wcout << L" - OK (Activation/Toggle)\n";
|
||||
anyRegistered = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
DWORD error = GetLastError();
|
||||
std::wcout << L" - FAILED (Error: " << error << L")\n";
|
||||
|
||||
if (error == ERROR_HOTKEY_ALREADY_REGISTERED)
|
||||
{
|
||||
std::wcout << L" (Hotkey is already registered by another application)\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also check the legacy get_hotkeys() API
|
||||
size_t hotkeyCount = moduleLoader.GetHotkeys(nullptr, 0);
|
||||
if (hotkeyCount > 0)
|
||||
{
|
||||
std::wcout << L"Module reports " << hotkeyCount << L" legacy hotkey(s)\n";
|
||||
|
||||
// Allocate buffer and get the hotkeys
|
||||
std::vector<PowertoyModuleIface::Hotkey> hotkeys(hotkeyCount);
|
||||
size_t actualCount = moduleLoader.GetHotkeys(hotkeys.data(), hotkeyCount);
|
||||
|
||||
// Register each hotkey
|
||||
for (size_t i = 0; i < actualCount; i++)
|
||||
{
|
||||
const auto& hotkey = hotkeys[i];
|
||||
|
||||
UINT modifiers = ConvertModifiers(hotkey.win, hotkey.ctrl, hotkey.alt, hotkey.shift);
|
||||
UINT vkCode = hotkey.key;
|
||||
|
||||
if (vkCode == 0)
|
||||
{
|
||||
std::wcout << L" Skipping hotkey " << i << L" (no key code)\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
int hotkeyId = m_nextHotkeyId++;
|
||||
|
||||
std::wcout << L" Registering hotkey " << i << L": ";
|
||||
std::wcout << ModifiersToString(modifiers) << L"+" << VKeyToString(vkCode);
|
||||
|
||||
if (RegisterHotKey(nullptr, hotkeyId, modifiers, vkCode))
|
||||
{
|
||||
HotkeyInfo info;
|
||||
info.id = hotkeyId;
|
||||
info.moduleHotkeyId = i;
|
||||
info.modifiers = modifiers;
|
||||
info.vkCode = vkCode;
|
||||
info.description = ModifiersToString(modifiers) + L"+" + VKeyToString(vkCode);
|
||||
|
||||
m_registeredHotkeys.push_back(info);
|
||||
std::wcout << L" - OK\n";
|
||||
anyRegistered = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
DWORD error = GetLastError();
|
||||
std::wcout << L" - FAILED (Error: " << error << L")\n";
|
||||
|
||||
if (error == ERROR_HOTKEY_ALREADY_REGISTERED)
|
||||
{
|
||||
std::wcout << L" (Hotkey is already registered by another application)\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!anyRegistered && hotkeyCount == 0 && !hotkeyEx.has_value())
|
||||
{
|
||||
std::wcout << L"Module has no hotkeys\n";
|
||||
}
|
||||
|
||||
return anyRegistered;
|
||||
}
|
||||
|
||||
void HotkeyManager::UnregisterAll()
|
||||
{
|
||||
for (const auto& hotkey : m_registeredHotkeys)
|
||||
{
|
||||
UnregisterHotKey(nullptr, hotkey.id);
|
||||
}
|
||||
m_registeredHotkeys.clear();
|
||||
|
||||
if (m_hotkeyExRegistered)
|
||||
{
|
||||
UnregisterHotKey(nullptr, m_hotkeyExId);
|
||||
m_hotkeyExRegistered = false;
|
||||
m_hotkeyExId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool HotkeyManager::HandleHotkey(int hotkeyId, ModuleLoader& moduleLoader)
|
||||
{
|
||||
// Check if it's the HotkeyEx activation hotkey
|
||||
if (m_hotkeyExRegistered && hotkeyId == m_hotkeyExId)
|
||||
{
|
||||
std::wcout << L"\nActivation hotkey triggered (HotkeyEx)\n";
|
||||
|
||||
moduleLoader.OnHotkeyEx();
|
||||
|
||||
std::wcout << L"Module toggled via activation hotkey\n";
|
||||
std::wcout << L"Module enabled: " << (moduleLoader.IsEnabled() ? L"Yes" : L"No") << L"\n\n";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check legacy hotkeys
|
||||
for (const auto& hotkey : m_registeredHotkeys)
|
||||
{
|
||||
if (hotkey.id == hotkeyId)
|
||||
{
|
||||
std::wcout << L"\nHotkey triggered: " << hotkey.description << L"\n";
|
||||
|
||||
bool result = moduleLoader.OnHotkey(hotkey.moduleHotkeyId);
|
||||
|
||||
std::wcout << L"Module handled hotkey: " << (result ? L"Swallowed" : L"Not swallowed") << L"\n";
|
||||
std::wcout << L"Module enabled: " << (moduleLoader.IsEnabled() ? L"Yes" : L"No") << L"\n\n";
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HotkeyManager::PrintHotkeys() const
|
||||
{
|
||||
for (const auto& hotkey : m_registeredHotkeys)
|
||||
{
|
||||
std::wcout << L" " << hotkey.description << L"\n";
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring HotkeyManager::ModifiersToString(UINT modifiers) const
|
||||
{
|
||||
std::wstringstream ss;
|
||||
bool first = true;
|
||||
|
||||
if (modifiers & MOD_WIN)
|
||||
{
|
||||
if (!first) ss << L"+";
|
||||
ss << L"Win";
|
||||
first = false;
|
||||
}
|
||||
if (modifiers & MOD_CONTROL)
|
||||
{
|
||||
if (!first) ss << L"+";
|
||||
ss << L"Ctrl";
|
||||
first = false;
|
||||
}
|
||||
if (modifiers & MOD_ALT)
|
||||
{
|
||||
if (!first) ss << L"+";
|
||||
ss << L"Alt";
|
||||
first = false;
|
||||
}
|
||||
if (modifiers & MOD_SHIFT)
|
||||
{
|
||||
if (!first) ss << L"+";
|
||||
ss << L"Shift";
|
||||
first = false;
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::wstring HotkeyManager::VKeyToString(UINT vkCode) const
|
||||
{
|
||||
// Handle special keys
|
||||
switch (vkCode)
|
||||
{
|
||||
case VK_SPACE: return L"Space";
|
||||
case VK_RETURN: return L"Enter";
|
||||
case VK_ESCAPE: return L"Esc";
|
||||
case VK_TAB: return L"Tab";
|
||||
case VK_BACK: return L"Backspace";
|
||||
case VK_DELETE: return L"Del";
|
||||
case VK_INSERT: return L"Ins";
|
||||
case VK_HOME: return L"Home";
|
||||
case VK_END: return L"End";
|
||||
case VK_PRIOR: return L"PgUp";
|
||||
case VK_NEXT: return L"PgDn";
|
||||
case VK_LEFT: return L"Left";
|
||||
case VK_RIGHT: return L"Right";
|
||||
case VK_UP: return L"Up";
|
||||
case VK_DOWN: return L"Down";
|
||||
case VK_F1: return L"F1";
|
||||
case VK_F2: return L"F2";
|
||||
case VK_F3: return L"F3";
|
||||
case VK_F4: return L"F4";
|
||||
case VK_F5: return L"F5";
|
||||
case VK_F6: return L"F6";
|
||||
case VK_F7: return L"F7";
|
||||
case VK_F8: return L"F8";
|
||||
case VK_F9: return L"F9";
|
||||
case VK_F10: return L"F10";
|
||||
case VK_F11: return L"F11";
|
||||
case VK_F12: return L"F12";
|
||||
}
|
||||
|
||||
// For alphanumeric keys, use MapVirtualKey
|
||||
wchar_t keyName[256];
|
||||
UINT scanCode = MapVirtualKeyW(vkCode, MAPVK_VK_TO_VSC);
|
||||
|
||||
if (GetKeyNameTextW(scanCode << 16, keyName, 256) > 0)
|
||||
{
|
||||
return keyName;
|
||||
}
|
||||
|
||||
// Fallback to hex code
|
||||
std::wstringstream ss;
|
||||
ss << L"0x" << std::hex << vkCode;
|
||||
return ss.str();
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include "ModuleLoader.h"
|
||||
|
||||
/// <summary>
|
||||
/// Manages hotkey registration using RegisterHotKey API
|
||||
/// </summary>
|
||||
class HotkeyManager
|
||||
{
|
||||
public:
|
||||
HotkeyManager();
|
||||
~HotkeyManager();
|
||||
|
||||
// Prevent copying
|
||||
HotkeyManager(const HotkeyManager&) = delete;
|
||||
HotkeyManager& operator=(const HotkeyManager&) = delete;
|
||||
|
||||
/// <summary>
|
||||
/// Register all hotkeys from a module
|
||||
/// </summary>
|
||||
/// <param name="moduleLoader">Module to get hotkeys from</param>
|
||||
/// <returns>True if at least one hotkey was registered</returns>
|
||||
bool RegisterModuleHotkeys(ModuleLoader& moduleLoader);
|
||||
|
||||
/// <summary>
|
||||
/// Unregister all hotkeys
|
||||
/// </summary>
|
||||
void UnregisterAll();
|
||||
|
||||
/// <summary>
|
||||
/// Handle a WM_HOTKEY message
|
||||
/// </summary>
|
||||
/// <param name="hotkeyId">ID from the WM_HOTKEY message</param>
|
||||
/// <param name="moduleLoader">Module to trigger the hotkey on</param>
|
||||
/// <returns>True if the hotkey was handled</returns>
|
||||
bool HandleHotkey(int hotkeyId, ModuleLoader& moduleLoader);
|
||||
|
||||
/// <summary>
|
||||
/// Get the number of registered hotkeys
|
||||
/// </summary>
|
||||
/// <returns>Number of registered hotkeys</returns>
|
||||
size_t GetRegisteredCount() const { return m_registeredHotkeys.size() + (m_hotkeyExRegistered ? 1 : 0); }
|
||||
|
||||
/// <summary>
|
||||
/// Print registered hotkeys to console
|
||||
/// </summary>
|
||||
void PrintHotkeys() const;
|
||||
|
||||
private:
|
||||
struct HotkeyInfo
|
||||
{
|
||||
int id = 0;
|
||||
size_t moduleHotkeyId = 0;
|
||||
UINT modifiers = 0;
|
||||
UINT vkCode = 0;
|
||||
std::wstring description;
|
||||
};
|
||||
|
||||
std::vector<HotkeyInfo> m_registeredHotkeys;
|
||||
int m_nextHotkeyId;
|
||||
bool m_hotkeyExRegistered;
|
||||
int m_hotkeyExId;
|
||||
|
||||
/// <summary>
|
||||
/// Convert modifier bools to RegisterHotKey modifiers
|
||||
/// </summary>
|
||||
UINT ConvertModifiers(bool win, bool ctrl, bool alt, bool shift) const;
|
||||
|
||||
/// <summary>
|
||||
/// Get a string representation of modifiers
|
||||
/// </summary>
|
||||
std::wstring ModifiersToString(UINT modifiers) const;
|
||||
|
||||
/// <summary>
|
||||
/// Get a string representation of a virtual key code
|
||||
/// </summary>
|
||||
std::wstring VKeyToString(UINT vkCode) const;
|
||||
};
|
||||
@@ -0,0 +1,183 @@
|
||||
// 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.
|
||||
|
||||
#include "ModuleLoader.h"
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
|
||||
ModuleLoader::ModuleLoader()
|
||||
: m_hModule(nullptr)
|
||||
, m_module(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
ModuleLoader::~ModuleLoader()
|
||||
{
|
||||
if (m_module)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_module->destroy();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Ignore exceptions during cleanup
|
||||
}
|
||||
m_module = nullptr;
|
||||
}
|
||||
|
||||
if (m_hModule)
|
||||
{
|
||||
FreeLibrary(m_hModule);
|
||||
m_hModule = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool ModuleLoader::Load(const std::wstring& dllPath)
|
||||
{
|
||||
if (m_hModule || m_module)
|
||||
{
|
||||
std::wcerr << L"Error: Module already loaded\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_dllPath = dllPath;
|
||||
|
||||
// Load the DLL
|
||||
m_hModule = LoadLibraryW(dllPath.c_str());
|
||||
if (!m_hModule)
|
||||
{
|
||||
DWORD error = GetLastError();
|
||||
std::wcerr << L"Error: Failed to load DLL. Error code: " << error << L"\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the powertoy_create function
|
||||
using powertoy_create_func = PowertoyModuleIface* (*)();
|
||||
auto create_func = reinterpret_cast<powertoy_create_func>(
|
||||
GetProcAddress(m_hModule, "powertoy_create"));
|
||||
|
||||
if (!create_func)
|
||||
{
|
||||
std::wcerr << L"Error: DLL does not export 'powertoy_create' function\n";
|
||||
FreeLibrary(m_hModule);
|
||||
m_hModule = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create the module instance
|
||||
m_module = create_func();
|
||||
if (!m_module)
|
||||
{
|
||||
std::wcerr << L"Error: powertoy_create() returned nullptr\n";
|
||||
FreeLibrary(m_hModule);
|
||||
m_hModule = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::wcout << L"Module instance created successfully\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
void ModuleLoader::Enable()
|
||||
{
|
||||
if (!m_module)
|
||||
{
|
||||
throw std::runtime_error("Module not loaded");
|
||||
}
|
||||
|
||||
m_module->enable();
|
||||
}
|
||||
|
||||
void ModuleLoader::Disable()
|
||||
{
|
||||
if (!m_module)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_module->disable();
|
||||
}
|
||||
|
||||
bool ModuleLoader::IsEnabled() const
|
||||
{
|
||||
if (!m_module)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return m_module->is_enabled();
|
||||
}
|
||||
|
||||
void ModuleLoader::SetConfig(const std::wstring& configJson)
|
||||
{
|
||||
if (!m_module)
|
||||
{
|
||||
throw std::runtime_error("Module not loaded");
|
||||
}
|
||||
|
||||
m_module->set_config(configJson.c_str());
|
||||
}
|
||||
|
||||
std::wstring ModuleLoader::GetModuleName() const
|
||||
{
|
||||
if (!m_module)
|
||||
{
|
||||
return L"<not loaded>";
|
||||
}
|
||||
|
||||
const wchar_t* name = m_module->get_name();
|
||||
return name ? name : L"<unknown>";
|
||||
}
|
||||
|
||||
std::wstring ModuleLoader::GetModuleKey() const
|
||||
{
|
||||
if (!m_module)
|
||||
{
|
||||
return L"<not loaded>";
|
||||
}
|
||||
|
||||
const wchar_t* key = m_module->get_key();
|
||||
return key ? key : L"<unknown>";
|
||||
}
|
||||
|
||||
size_t ModuleLoader::GetHotkeys(PowertoyModuleIface::Hotkey* buffer, size_t bufferSize)
|
||||
{
|
||||
if (!m_module)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return m_module->get_hotkeys(buffer, bufferSize);
|
||||
}
|
||||
|
||||
bool ModuleLoader::OnHotkey(size_t hotkeyId)
|
||||
{
|
||||
if (!m_module)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return m_module->on_hotkey(hotkeyId);
|
||||
}
|
||||
|
||||
std::optional<PowertoyModuleIface::HotkeyEx> ModuleLoader::GetHotkeyEx()
|
||||
{
|
||||
if (!m_module)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return m_module->GetHotkeyEx();
|
||||
}
|
||||
|
||||
void ModuleLoader::OnHotkeyEx()
|
||||
{
|
||||
if (!m_module)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_module->OnHotkeyEx();
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <powertoy_module_interface.h>
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper class for loading and managing a PowerToy module DLL
|
||||
/// </summary>
|
||||
class ModuleLoader
|
||||
{
|
||||
public:
|
||||
ModuleLoader();
|
||||
~ModuleLoader();
|
||||
|
||||
// Prevent copying
|
||||
ModuleLoader(const ModuleLoader&) = delete;
|
||||
ModuleLoader& operator=(const ModuleLoader&) = delete;
|
||||
|
||||
/// <summary>
|
||||
/// Load a PowerToy module DLL
|
||||
/// </summary>
|
||||
/// <param name="dllPath">Path to the module DLL</param>
|
||||
/// <returns>True if successful, false otherwise</returns>
|
||||
bool Load(const std::wstring& dllPath);
|
||||
|
||||
/// <summary>
|
||||
/// Enable the loaded module
|
||||
/// </summary>
|
||||
void Enable();
|
||||
|
||||
/// <summary>
|
||||
/// Disable the loaded module
|
||||
/// </summary>
|
||||
void Disable();
|
||||
|
||||
/// <summary>
|
||||
/// Check if the module is enabled
|
||||
/// </summary>
|
||||
/// <returns>True if enabled, false otherwise</returns>
|
||||
bool IsEnabled() const;
|
||||
|
||||
/// <summary>
|
||||
/// Set configuration for the module
|
||||
/// </summary>
|
||||
/// <param name="configJson">JSON configuration string</param>
|
||||
void SetConfig(const std::wstring& configJson);
|
||||
|
||||
/// <summary>
|
||||
/// Get the module's localized name
|
||||
/// </summary>
|
||||
/// <returns>Module name</returns>
|
||||
std::wstring GetModuleName() const;
|
||||
|
||||
/// <summary>
|
||||
/// Get the module's non-localized key
|
||||
/// </summary>
|
||||
/// <returns>Module key</returns>
|
||||
std::wstring GetModuleKey() const;
|
||||
|
||||
/// <summary>
|
||||
/// Get the module's hotkeys
|
||||
/// </summary>
|
||||
/// <param name="buffer">Buffer to store hotkeys</param>
|
||||
/// <param name="bufferSize">Size of the buffer</param>
|
||||
/// <returns>Number of hotkeys returned</returns>
|
||||
size_t GetHotkeys(PowertoyModuleIface::Hotkey* buffer, size_t bufferSize);
|
||||
|
||||
/// <summary>
|
||||
/// Trigger a hotkey callback on the module
|
||||
/// </summary>
|
||||
/// <param name="hotkeyId">ID of the hotkey to trigger</param>
|
||||
/// <returns>True if the key press should be swallowed</returns>
|
||||
bool OnHotkey(size_t hotkeyId);
|
||||
|
||||
/// <summary>
|
||||
/// Check if the module is loaded
|
||||
/// </summary>
|
||||
/// <returns>True if loaded, false otherwise</returns>
|
||||
bool IsLoaded() const { return m_module != nullptr; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the module's activation hotkey (newer HotkeyEx API)
|
||||
/// </summary>
|
||||
/// <returns>Optional HotkeyEx struct</returns>
|
||||
std::optional<PowertoyModuleIface::HotkeyEx> GetHotkeyEx();
|
||||
|
||||
/// <summary>
|
||||
/// Trigger the newer-style hotkey callback on the module
|
||||
/// </summary>
|
||||
void OnHotkeyEx();
|
||||
|
||||
private:
|
||||
HMODULE m_hModule;
|
||||
PowertoyModuleIface* m_module;
|
||||
std::wstring m_dllPath;
|
||||
};
|
||||
@@ -0,0 +1,903 @@
|
||||
// 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.
|
||||
|
||||
#include "SettingsLoader.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <filesystem>
|
||||
#include <cwctype>
|
||||
#include <Shlobj.h>
|
||||
|
||||
SettingsLoader::SettingsLoader()
|
||||
{
|
||||
}
|
||||
|
||||
SettingsLoader::~SettingsLoader()
|
||||
{
|
||||
}
|
||||
|
||||
std::wstring SettingsLoader::GetPowerToysSettingsRoot() const
|
||||
{
|
||||
// Get %LOCALAPPDATA%
|
||||
PWSTR localAppDataPath = nullptr;
|
||||
HRESULT hr = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &localAppDataPath);
|
||||
|
||||
if (FAILED(hr) || !localAppDataPath)
|
||||
{
|
||||
std::wcerr << L"Error: Failed to get LOCALAPPDATA path\n";
|
||||
return L"";
|
||||
}
|
||||
|
||||
std::wstring result(localAppDataPath);
|
||||
CoTaskMemFree(localAppDataPath);
|
||||
|
||||
// Append PowerToys directory
|
||||
result += L"\\Microsoft\\PowerToys";
|
||||
return result;
|
||||
}
|
||||
|
||||
std::wstring SettingsLoader::GetSettingsPath(const std::wstring& moduleName) const
|
||||
{
|
||||
std::wstring root = GetPowerToysSettingsRoot();
|
||||
if (root.empty())
|
||||
{
|
||||
return L"";
|
||||
}
|
||||
|
||||
// Construct path: %LOCALAPPDATA%\Microsoft\PowerToys\<ModuleName>\settings.json
|
||||
std::wstring settingsPath = root + L"\\" + moduleName + L"\\settings.json";
|
||||
return settingsPath;
|
||||
}
|
||||
|
||||
std::wstring SettingsLoader::ReadFileContents(const std::wstring& filePath) const
|
||||
{
|
||||
std::wifstream file(filePath, std::ios::binary);
|
||||
if (!file.is_open())
|
||||
{
|
||||
std::wcerr << L"Error: Could not open file: " << filePath << L"\n";
|
||||
return L"";
|
||||
}
|
||||
|
||||
// Read the entire file
|
||||
std::wstringstream buffer;
|
||||
buffer << file.rdbuf();
|
||||
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
std::wstring SettingsLoader::LoadSettings(const std::wstring& moduleName, const std::wstring& moduleDllPath)
|
||||
{
|
||||
const std::wstring powerToysPrefix = L"PowerToys.";
|
||||
|
||||
// Build list of possible module name variations to try
|
||||
std::vector<std::wstring> moduleNameVariants;
|
||||
|
||||
// Try exact name first
|
||||
moduleNameVariants.push_back(moduleName);
|
||||
|
||||
// If doesn't start with "PowerToys.", try adding it
|
||||
if (moduleName.find(powerToysPrefix) != 0)
|
||||
{
|
||||
moduleNameVariants.push_back(powerToysPrefix + moduleName);
|
||||
}
|
||||
// If starts with "PowerToys.", try without it
|
||||
else
|
||||
{
|
||||
moduleNameVariants.push_back(moduleName.substr(powerToysPrefix.length()));
|
||||
}
|
||||
|
||||
// FIRST: Try same directory as the module DLL
|
||||
if (!moduleDllPath.empty())
|
||||
{
|
||||
std::filesystem::path dllPath(moduleDllPath);
|
||||
std::filesystem::path dllDirectory = dllPath.parent_path();
|
||||
|
||||
std::wstring localSettingsPath = (dllDirectory / L"settings.json").wstring();
|
||||
std::wcout << L"Trying settings path (module directory): " << localSettingsPath << L"\n";
|
||||
|
||||
if (std::filesystem::exists(localSettingsPath))
|
||||
{
|
||||
std::wstring contents = ReadFileContents(localSettingsPath);
|
||||
if (!contents.empty())
|
||||
{
|
||||
std::wcout << L"Settings file loaded from module directory (" << contents.size() << L" characters)\n";
|
||||
return contents;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SECOND: Try standard PowerToys settings locations
|
||||
for (const auto& variant : moduleNameVariants)
|
||||
{
|
||||
std::wstring settingsPath = GetSettingsPath(variant);
|
||||
|
||||
std::wcout << L"Trying settings path: " << settingsPath << L"\n";
|
||||
|
||||
// Check if file exists (case-sensitive path)
|
||||
if (std::filesystem::exists(settingsPath))
|
||||
{
|
||||
std::wstring contents = ReadFileContents(settingsPath);
|
||||
if (!contents.empty())
|
||||
{
|
||||
std::wcout << L"Settings file loaded (" << contents.size() << L" characters)\n";
|
||||
return contents;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try case-insensitive search in the parent directory
|
||||
std::wstring root = GetPowerToysSettingsRoot();
|
||||
if (!root.empty() && std::filesystem::exists(root))
|
||||
{
|
||||
try
|
||||
{
|
||||
// Search for a directory that matches case-insensitively
|
||||
for (const auto& entry : std::filesystem::directory_iterator(root))
|
||||
{
|
||||
if (entry.is_directory())
|
||||
{
|
||||
std::wstring dirName = entry.path().filename().wstring();
|
||||
|
||||
// Case-insensitive comparison
|
||||
if (_wcsicmp(dirName.c_str(), variant.c_str()) == 0)
|
||||
{
|
||||
std::wstring actualSettingsPath = entry.path().wstring() + L"\\settings.json";
|
||||
std::wcout << L"Found case-insensitive match: " << actualSettingsPath << L"\n";
|
||||
|
||||
if (std::filesystem::exists(actualSettingsPath))
|
||||
{
|
||||
std::wstring contents = ReadFileContents(actualSettingsPath);
|
||||
if (!contents.empty())
|
||||
{
|
||||
std::wcout << L"Settings file loaded (" << contents.size() << L" characters)\n";
|
||||
return contents;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const std::filesystem::filesystem_error& e)
|
||||
{
|
||||
std::wcerr << L"Error searching directory: " << e.what() << L"\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::wcerr << L"Error: Settings file not found in any expected location:\n";
|
||||
if (!moduleDllPath.empty())
|
||||
{
|
||||
std::filesystem::path dllPath(moduleDllPath);
|
||||
std::filesystem::path dllDirectory = dllPath.parent_path();
|
||||
std::wcerr << L" - " << (dllDirectory / L"settings.json").wstring() << L" (module directory)\n";
|
||||
}
|
||||
for (const auto& variant : moduleNameVariants)
|
||||
{
|
||||
std::wcerr << L" - " << GetSettingsPath(variant) << L"\n";
|
||||
}
|
||||
|
||||
return L"";
|
||||
}
|
||||
|
||||
std::wstring SettingsLoader::FindSettingsFilePath(const std::wstring& moduleName, const std::wstring& moduleDllPath)
|
||||
{
|
||||
const std::wstring powerToysPrefix = L"PowerToys.";
|
||||
|
||||
std::vector<std::wstring> moduleNameVariants;
|
||||
moduleNameVariants.push_back(moduleName);
|
||||
|
||||
if (moduleName.find(powerToysPrefix) != 0)
|
||||
{
|
||||
moduleNameVariants.push_back(powerToysPrefix + moduleName);
|
||||
}
|
||||
else
|
||||
{
|
||||
moduleNameVariants.push_back(moduleName.substr(powerToysPrefix.length()));
|
||||
}
|
||||
|
||||
// Try module directory first
|
||||
if (!moduleDllPath.empty())
|
||||
{
|
||||
std::filesystem::path dllPath(moduleDllPath);
|
||||
std::filesystem::path dllDirectory = dllPath.parent_path();
|
||||
std::wstring localSettingsPath = (dllDirectory / L"settings.json").wstring();
|
||||
|
||||
if (std::filesystem::exists(localSettingsPath))
|
||||
{
|
||||
return localSettingsPath;
|
||||
}
|
||||
}
|
||||
|
||||
// Try standard locations
|
||||
for (const auto& variant : moduleNameVariants)
|
||||
{
|
||||
std::wstring settingsPath = GetSettingsPath(variant);
|
||||
|
||||
if (std::filesystem::exists(settingsPath))
|
||||
{
|
||||
return settingsPath;
|
||||
}
|
||||
|
||||
// Case-insensitive search
|
||||
std::wstring root = GetPowerToysSettingsRoot();
|
||||
if (!root.empty() && std::filesystem::exists(root))
|
||||
{
|
||||
try
|
||||
{
|
||||
for (const auto& entry : std::filesystem::directory_iterator(root))
|
||||
{
|
||||
if (entry.is_directory())
|
||||
{
|
||||
std::wstring dirName = entry.path().filename().wstring();
|
||||
if (_wcsicmp(dirName.c_str(), variant.c_str()) == 0)
|
||||
{
|
||||
std::wstring actualSettingsPath = entry.path().wstring() + L"\\settings.json";
|
||||
if (std::filesystem::exists(actualSettingsPath))
|
||||
{
|
||||
return actualSettingsPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (...) {}
|
||||
}
|
||||
}
|
||||
|
||||
return L"";
|
||||
}
|
||||
|
||||
void SettingsLoader::DisplaySettingsInfo(const std::wstring& moduleName, const std::wstring& moduleDllPath)
|
||||
{
|
||||
std::wcout << L"\n";
|
||||
std::wcout << L"\033[1;36m"; // Cyan bold
|
||||
std::wcout << L"+----------------------------------------------------------------+\n";
|
||||
std::wcout << L"| MODULE SETTINGS INFO |\n";
|
||||
std::wcout << L"+----------------------------------------------------------------+\n";
|
||||
std::wcout << L"\033[0m";
|
||||
|
||||
std::wcout << L"\n\033[1mModule:\033[0m " << moduleName << L"\n";
|
||||
|
||||
std::wstring settingsPath = FindSettingsFilePath(moduleName, moduleDllPath);
|
||||
|
||||
if (settingsPath.empty())
|
||||
{
|
||||
std::wcout << L"\033[1;33mSettings file:\033[0m Not found\n";
|
||||
std::wcout << L"\nNo settings file found for this module.\n";
|
||||
return;
|
||||
}
|
||||
|
||||
std::wcout << L"\033[1mSettings file:\033[0m " << settingsPath << L"\n\n";
|
||||
|
||||
std::wstring settingsJson = ReadFileContents(settingsPath);
|
||||
if (settingsJson.empty())
|
||||
{
|
||||
std::wcout << L"Unable to read settings file.\n";
|
||||
return;
|
||||
}
|
||||
|
||||
std::wcout << L"\033[1;32mCurrent Settings:\033[0m\n";
|
||||
std::wcout << L"-----------------------------------------------------------------\n";
|
||||
|
||||
DisplayJsonProperties(settingsJson, 0);
|
||||
|
||||
std::wcout << L"-----------------------------------------------------------------\n\n";
|
||||
}
|
||||
|
||||
void SettingsLoader::DisplayJsonProperties(const std::wstring& settingsJson, int indent)
|
||||
{
|
||||
// Simple JSON parser for display - handles the PowerToys settings format
|
||||
// Format: { "properties": { "key": { "value": ... }, ... } }
|
||||
// Also handles hotkey settings: { "key": { "win": true, "alt": true, "code": 85 } }
|
||||
|
||||
std::string json(settingsJson.begin(), settingsJson.end());
|
||||
|
||||
// Find "properties" section
|
||||
size_t propsStart = json.find("\"properties\"");
|
||||
if (propsStart == std::string::npos)
|
||||
{
|
||||
// If no properties section, just display the raw JSON
|
||||
std::wcout << settingsJson << L"\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the opening brace after "properties":
|
||||
size_t braceStart = json.find('{', propsStart + 12);
|
||||
if (braceStart == std::string::npos) return;
|
||||
|
||||
// Parse each property
|
||||
size_t pos = braceStart + 1;
|
||||
int braceCount = 1;
|
||||
|
||||
while (pos < json.size() && braceCount > 0)
|
||||
{
|
||||
// Skip whitespace
|
||||
while (pos < json.size() && std::isspace(json[pos])) pos++;
|
||||
|
||||
// Look for property name
|
||||
if (json[pos] == '"')
|
||||
{
|
||||
size_t nameStart = pos + 1;
|
||||
size_t nameEnd = json.find('"', nameStart);
|
||||
if (nameEnd == std::string::npos) break;
|
||||
|
||||
std::string propName = json.substr(nameStart, nameEnd - nameStart);
|
||||
|
||||
// Skip to the value object
|
||||
pos = json.find('{', nameEnd);
|
||||
if (pos == std::string::npos) break;
|
||||
|
||||
size_t objStart = pos;
|
||||
|
||||
// Check if this is a hotkey object (has "win", "code" etc. but no "value")
|
||||
if (IsHotkeyObject(json, objStart))
|
||||
{
|
||||
// Parse hotkey and display
|
||||
size_t objEnd;
|
||||
std::string hotkeyStr = ParseHotkeyObject(json, objStart, objEnd);
|
||||
|
||||
std::wstring wPropName(propName.begin(), propName.end());
|
||||
std::wstring wHotkeyStr(hotkeyStr.begin(), hotkeyStr.end());
|
||||
|
||||
std::wcout << L" \033[1;34m" << wPropName << L"\033[0m: ";
|
||||
std::wcout << L"\033[1;36m" << wHotkeyStr << L"\033[0m\n";
|
||||
|
||||
pos = objEnd + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Regular property with "value" key
|
||||
int innerBraceCount = 1;
|
||||
pos++;
|
||||
|
||||
std::string valueStr = "";
|
||||
bool foundValue = false;
|
||||
|
||||
while (pos < json.size() && innerBraceCount > 0)
|
||||
{
|
||||
if (json[pos] == '{') innerBraceCount++;
|
||||
else if (json[pos] == '}') innerBraceCount--;
|
||||
else if (json[pos] == '"' && !foundValue)
|
||||
{
|
||||
size_t keyStart = pos + 1;
|
||||
size_t keyEnd = json.find('"', keyStart);
|
||||
if (keyEnd != std::string::npos)
|
||||
{
|
||||
std::string key = json.substr(keyStart, keyEnd - keyStart);
|
||||
if (key == "value")
|
||||
{
|
||||
// Find the colon and then the value
|
||||
size_t colonPos = json.find(':', keyEnd);
|
||||
if (colonPos != std::string::npos)
|
||||
{
|
||||
size_t valStart = colonPos + 1;
|
||||
while (valStart < json.size() && std::isspace(json[valStart])) valStart++;
|
||||
|
||||
// Determine value type and extract
|
||||
if (json[valStart] == '"')
|
||||
{
|
||||
size_t valEnd = json.find('"', valStart + 1);
|
||||
if (valEnd != std::string::npos)
|
||||
{
|
||||
valueStr = json.substr(valStart + 1, valEnd - valStart - 1);
|
||||
foundValue = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Number or boolean
|
||||
size_t valEnd = valStart;
|
||||
while (valEnd < json.size() && json[valEnd] != ',' && json[valEnd] != '}' && !std::isspace(json[valEnd]))
|
||||
{
|
||||
valEnd++;
|
||||
}
|
||||
valueStr = json.substr(valStart, valEnd - valStart);
|
||||
foundValue = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pos = keyEnd + 1;
|
||||
continue;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
|
||||
// Print the property
|
||||
std::wstring wPropName(propName.begin(), propName.end());
|
||||
std::wstring wValueStr(valueStr.begin(), valueStr.end());
|
||||
|
||||
std::wcout << L" \033[1;34m" << wPropName << L"\033[0m: ";
|
||||
|
||||
// Color-code based on value type
|
||||
if (valueStr == "true")
|
||||
{
|
||||
std::wcout << L"\033[1;32mtrue\033[0m";
|
||||
}
|
||||
else if (valueStr == "false")
|
||||
{
|
||||
std::wcout << L"\033[1;31mfalse\033[0m";
|
||||
}
|
||||
else if (!valueStr.empty() && (std::isdigit(valueStr[0]) || valueStr[0] == '-'))
|
||||
{
|
||||
std::wcout << L"\033[1;33m" << wValueStr << L"\033[0m";
|
||||
}
|
||||
else
|
||||
{
|
||||
std::wcout << L"\033[1;35m\"" << wValueStr << L"\"\033[0m";
|
||||
}
|
||||
std::wcout << L"\n";
|
||||
}
|
||||
else if (json[pos] == '{')
|
||||
{
|
||||
braceCount++;
|
||||
pos++;
|
||||
}
|
||||
else if (json[pos] == '}')
|
||||
{
|
||||
braceCount--;
|
||||
pos++;
|
||||
}
|
||||
else
|
||||
{
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring SettingsLoader::GetSettingValue(const std::wstring& moduleName, const std::wstring& moduleDllPath, const std::wstring& key)
|
||||
{
|
||||
std::wstring settingsPath = FindSettingsFilePath(moduleName, moduleDllPath);
|
||||
if (settingsPath.empty()) return L"";
|
||||
|
||||
std::wstring settingsJson = ReadFileContents(settingsPath);
|
||||
if (settingsJson.empty()) return L"";
|
||||
|
||||
// Simple JSON parser to find the specific key
|
||||
std::string json(settingsJson.begin(), settingsJson.end());
|
||||
std::string searchKey(key.begin(), key.end());
|
||||
|
||||
// Look for "properties" -> key -> "value"
|
||||
std::string searchPattern = "\"" + searchKey + "\"";
|
||||
size_t keyPos = json.find(searchPattern);
|
||||
if (keyPos == std::string::npos) return L"";
|
||||
|
||||
// Find "value" within this property's object
|
||||
size_t objStart = json.find('{', keyPos);
|
||||
if (objStart == std::string::npos) return L"";
|
||||
|
||||
size_t valueKeyPos = json.find("\"value\"", objStart);
|
||||
if (valueKeyPos == std::string::npos) return L"";
|
||||
|
||||
// Find the colon and extract value
|
||||
size_t colonPos = json.find(':', valueKeyPos);
|
||||
if (colonPos == std::string::npos) return L"";
|
||||
|
||||
size_t valStart = colonPos + 1;
|
||||
while (valStart < json.size() && std::isspace(json[valStart])) valStart++;
|
||||
|
||||
std::string valueStr;
|
||||
if (json[valStart] == '"')
|
||||
{
|
||||
size_t valEnd = json.find('"', valStart + 1);
|
||||
if (valEnd != std::string::npos)
|
||||
{
|
||||
valueStr = json.substr(valStart + 1, valEnd - valStart - 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t valEnd = valStart;
|
||||
while (valEnd < json.size() && json[valEnd] != ',' && json[valEnd] != '}' && !std::isspace(json[valEnd]))
|
||||
{
|
||||
valEnd++;
|
||||
}
|
||||
valueStr = json.substr(valStart, valEnd - valStart);
|
||||
}
|
||||
|
||||
return std::wstring(valueStr.begin(), valueStr.end());
|
||||
}
|
||||
|
||||
bool SettingsLoader::SetSettingValue(const std::wstring& moduleName, const std::wstring& moduleDllPath, const std::wstring& key, const std::wstring& value)
|
||||
{
|
||||
std::wstring settingsPath = FindSettingsFilePath(moduleName, moduleDllPath);
|
||||
if (settingsPath.empty())
|
||||
{
|
||||
std::wcerr << L"Error: Settings file not found\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::wstring settingsJson = ReadFileContents(settingsPath);
|
||||
if (settingsJson.empty())
|
||||
{
|
||||
std::wcerr << L"Error: Unable to read settings file\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string json(settingsJson.begin(), settingsJson.end());
|
||||
std::string searchKey(key.begin(), key.end());
|
||||
std::string newValue(value.begin(), value.end());
|
||||
|
||||
// Find the property
|
||||
std::string searchPattern = "\"" + searchKey + "\"";
|
||||
size_t keyPos = json.find(searchPattern);
|
||||
if (keyPos == std::string::npos)
|
||||
{
|
||||
// Setting not found - prompt user to add it
|
||||
std::wcout << L"\033[1;33mWarning:\033[0m Setting '" << key << L"' not found in settings file.\n";
|
||||
std::wcout << L"This could be a new setting or a typo.\n\n";
|
||||
|
||||
if (PromptYesNo(L"Do you want to add this as a new setting?"))
|
||||
{
|
||||
std::string modifiedJson = AddNewProperty(json, searchKey, newValue);
|
||||
if (modifiedJson.empty())
|
||||
{
|
||||
std::wcerr << L"Error: Failed to add new property to settings file\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::wstring newJson(modifiedJson.begin(), modifiedJson.end());
|
||||
if (WriteFileContents(settingsPath, newJson))
|
||||
{
|
||||
std::wcout << L"\033[1;32m+\033[0m New setting '" << key << L"' added with value: " << value << L"\n";
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::wcerr << L"Error: Failed to write settings file\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::wcout << L"Operation cancelled.\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Find "value" within this property's object
|
||||
size_t objStart = json.find('{', keyPos);
|
||||
if (objStart == std::string::npos) return false;
|
||||
|
||||
size_t valueKeyPos = json.find("\"value\"", objStart);
|
||||
if (valueKeyPos == std::string::npos) return false;
|
||||
|
||||
// Find the colon and the existing value
|
||||
size_t colonPos = json.find(':', valueKeyPos);
|
||||
if (colonPos == std::string::npos) return false;
|
||||
|
||||
size_t valStart = colonPos + 1;
|
||||
while (valStart < json.size() && std::isspace(json[valStart])) valStart++;
|
||||
|
||||
size_t valEnd;
|
||||
bool isString = (json[valStart] == '"');
|
||||
|
||||
if (isString)
|
||||
{
|
||||
valEnd = json.find('"', valStart + 1);
|
||||
if (valEnd != std::string::npos) valEnd++; // Include closing quote
|
||||
}
|
||||
else
|
||||
{
|
||||
valEnd = valStart;
|
||||
while (valEnd < json.size() && json[valEnd] != ',' && json[valEnd] != '}' && !std::isspace(json[valEnd]))
|
||||
{
|
||||
valEnd++;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if new value should be quoted
|
||||
bool newValueNeedsQuotes = false;
|
||||
if (newValue != "true" && newValue != "false")
|
||||
{
|
||||
// Check if it's a number
|
||||
bool isNumber = !newValue.empty();
|
||||
for (char c : newValue)
|
||||
{
|
||||
if (!std::isdigit(c) && c != '.' && c != '-')
|
||||
{
|
||||
isNumber = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
newValueNeedsQuotes = !isNumber;
|
||||
}
|
||||
|
||||
std::string replacement;
|
||||
if (newValueNeedsQuotes)
|
||||
{
|
||||
replacement = "\"" + newValue + "\"";
|
||||
}
|
||||
else
|
||||
{
|
||||
replacement = newValue;
|
||||
}
|
||||
|
||||
// Replace the value
|
||||
json = json.substr(0, valStart) + replacement + json.substr(valEnd);
|
||||
|
||||
// Write back
|
||||
std::wstring newJson(json.begin(), json.end());
|
||||
if (WriteFileContents(settingsPath, newJson))
|
||||
{
|
||||
std::wcout << L"\033[1;32m?\033[0m Setting '" << key << L"' updated to: " << value << L"\n";
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::wcerr << L"Error: Failed to write settings file\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool SettingsLoader::WriteFileContents(const std::wstring& filePath, const std::wstring& contents) const
|
||||
{
|
||||
std::ofstream file(filePath, std::ios::binary);
|
||||
if (!file.is_open())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string utf8Contents(contents.begin(), contents.end());
|
||||
file << utf8Contents;
|
||||
file.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SettingsLoader::PromptYesNo(const std::wstring& prompt)
|
||||
{
|
||||
std::wcout << prompt << L" [y/N]: ";
|
||||
std::wcout.flush();
|
||||
|
||||
std::wstring input;
|
||||
std::getline(std::wcin, input);
|
||||
|
||||
// Trim whitespace
|
||||
while (!input.empty() && iswspace(input.front())) input.erase(input.begin());
|
||||
while (!input.empty() && iswspace(input.back())) input.pop_back();
|
||||
|
||||
// Check for yes responses
|
||||
return !input.empty() && (input[0] == L'y' || input[0] == L'Y');
|
||||
}
|
||||
|
||||
std::string SettingsLoader::AddNewProperty(const std::string& json, const std::string& key, const std::string& value)
|
||||
{
|
||||
// Find the "properties" section
|
||||
size_t propsPos = json.find("\"properties\"");
|
||||
if (propsPos == std::string::npos)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
// Find the opening brace of properties object
|
||||
size_t propsStart = json.find('{', propsPos);
|
||||
if (propsStart == std::string::npos)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
// Find the closing brace of properties object
|
||||
int braceCount = 1;
|
||||
size_t pos = propsStart + 1;
|
||||
size_t propsEnd = std::string::npos;
|
||||
|
||||
while (pos < json.size() && braceCount > 0)
|
||||
{
|
||||
if (json[pos] == '{') braceCount++;
|
||||
else if (json[pos] == '}')
|
||||
{
|
||||
braceCount--;
|
||||
if (braceCount == 0) propsEnd = pos;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
|
||||
if (propsEnd == std::string::npos)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
// Determine if new value should be quoted
|
||||
bool needsQuotes = false;
|
||||
if (value != "true" && value != "false")
|
||||
{
|
||||
bool isNumber = !value.empty();
|
||||
for (char c : value)
|
||||
{
|
||||
if (!std::isdigit(c) && c != '.' && c != '-')
|
||||
{
|
||||
isNumber = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
needsQuotes = !isNumber;
|
||||
}
|
||||
|
||||
// Build the new property JSON
|
||||
// Format: "key": { "value": ... }
|
||||
std::string valueJson = needsQuotes ? ("\"" + value + "\"") : value;
|
||||
std::string newProperty = ",\n \"" + key + "\": {\n \"value\": " + valueJson + "\n }";
|
||||
|
||||
// Check if properties object is empty (only whitespace between braces)
|
||||
std::string propsContent = json.substr(propsStart + 1, propsEnd - propsStart - 1);
|
||||
bool isEmpty = true;
|
||||
for (char c : propsContent)
|
||||
{
|
||||
if (!std::isspace(c))
|
||||
{
|
||||
isEmpty = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert the new property before the closing brace of properties
|
||||
std::string result;
|
||||
if (isEmpty)
|
||||
{
|
||||
// No leading comma for empty properties
|
||||
newProperty = "\n \"" + key + "\": {\n \"value\": " + valueJson + "\n }\n ";
|
||||
}
|
||||
|
||||
result = json.substr(0, propsEnd) + newProperty + json.substr(propsEnd);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool SettingsLoader::IsHotkeyObject(const std::string& json, size_t objStart)
|
||||
{
|
||||
// A hotkey object has "win", "alt", "ctrl", "shift", and "code" fields
|
||||
// Find the end of this object
|
||||
int braceCount = 1;
|
||||
size_t pos = objStart + 1;
|
||||
size_t objEnd = objStart;
|
||||
|
||||
while (pos < json.size() && braceCount > 0)
|
||||
{
|
||||
if (json[pos] == '{') braceCount++;
|
||||
else if (json[pos] == '}')
|
||||
{
|
||||
braceCount--;
|
||||
if (braceCount == 0) objEnd = pos;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
|
||||
if (objEnd <= objStart) return false;
|
||||
|
||||
std::string objContent = json.substr(objStart, objEnd - objStart + 1);
|
||||
|
||||
// Check for hotkey-specific fields
|
||||
return (objContent.find("\"win\"") != std::string::npos ||
|
||||
objContent.find("\"code\"") != std::string::npos) &&
|
||||
objContent.find("\"value\"") == std::string::npos;
|
||||
}
|
||||
|
||||
std::string SettingsLoader::ParseHotkeyObject(const std::string& json, size_t objStart, size_t& objEnd)
|
||||
{
|
||||
// Find the end of this object
|
||||
int braceCount = 1;
|
||||
size_t pos = objStart + 1;
|
||||
objEnd = objStart;
|
||||
|
||||
while (pos < json.size() && braceCount > 0)
|
||||
{
|
||||
if (json[pos] == '{') braceCount++;
|
||||
else if (json[pos] == '}')
|
||||
{
|
||||
braceCount--;
|
||||
if (braceCount == 0) objEnd = pos;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
|
||||
if (objEnd <= objStart) return "";
|
||||
|
||||
std::string objContent = json.substr(objStart, objEnd - objStart + 1);
|
||||
|
||||
// Parse hotkey fields
|
||||
bool win = false, ctrl = false, alt = false, shift = false;
|
||||
int code = 0;
|
||||
|
||||
// Helper to find boolean value
|
||||
auto findBool = [&objContent](const std::string& key) -> bool {
|
||||
size_t keyPos = objContent.find("\"" + key + "\"");
|
||||
if (keyPos == std::string::npos) return false;
|
||||
size_t colonPos = objContent.find(':', keyPos);
|
||||
if (colonPos == std::string::npos) return false;
|
||||
size_t valStart = colonPos + 1;
|
||||
while (valStart < objContent.size() && std::isspace(objContent[valStart])) valStart++;
|
||||
return objContent.substr(valStart, 4) == "true";
|
||||
};
|
||||
|
||||
// Helper to find integer value
|
||||
auto findInt = [&objContent](const std::string& key) -> int {
|
||||
size_t keyPos = objContent.find("\"" + key + "\"");
|
||||
if (keyPos == std::string::npos) return 0;
|
||||
size_t colonPos = objContent.find(':', keyPos);
|
||||
if (colonPos == std::string::npos) return 0;
|
||||
size_t valStart = colonPos + 1;
|
||||
while (valStart < objContent.size() && std::isspace(objContent[valStart])) valStart++;
|
||||
size_t valEnd = valStart;
|
||||
while (valEnd < objContent.size() && (std::isdigit(objContent[valEnd]) || objContent[valEnd] == '-'))
|
||||
valEnd++;
|
||||
if (valEnd > valStart)
|
||||
return std::stoi(objContent.substr(valStart, valEnd - valStart));
|
||||
return 0;
|
||||
};
|
||||
|
||||
win = findBool("win");
|
||||
ctrl = findBool("ctrl");
|
||||
alt = findBool("alt");
|
||||
shift = findBool("shift");
|
||||
code = findInt("code");
|
||||
|
||||
// Build hotkey string
|
||||
std::string result;
|
||||
if (win) result += "Win+";
|
||||
if (ctrl) result += "Ctrl+";
|
||||
if (alt) result += "Alt+";
|
||||
if (shift) result += "Shift+";
|
||||
|
||||
// Convert virtual key code to key name
|
||||
if (code > 0)
|
||||
{
|
||||
if (code >= 'A' && code <= 'Z')
|
||||
{
|
||||
result += static_cast<char>(code);
|
||||
}
|
||||
else if (code >= '0' && code <= '9')
|
||||
{
|
||||
result += static_cast<char>(code);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Common VK codes
|
||||
switch (code)
|
||||
{
|
||||
case 0x20: result += "Space"; break;
|
||||
case 0x0D: result += "Enter"; break;
|
||||
case 0x1B: result += "Escape"; break;
|
||||
case 0x09: result += "Tab"; break;
|
||||
case 0x08: result += "Backspace"; break;
|
||||
case 0x2E: result += "Delete"; break;
|
||||
case 0x24: result += "Home"; break;
|
||||
case 0x23: result += "End"; break;
|
||||
case 0x21: result += "PageUp"; break;
|
||||
case 0x22: result += "PageDown"; break;
|
||||
case 0x25: result += "Left"; break;
|
||||
case 0x26: result += "Up"; break;
|
||||
case 0x27: result += "Right"; break;
|
||||
case 0x28: result += "Down"; break;
|
||||
case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75:
|
||||
case 0x76: case 0x77: case 0x78: case 0x79: case 0x7A: case 0x7B:
|
||||
result += "F" + std::to_string(code - 0x70 + 1);
|
||||
break;
|
||||
case 0xC0: result += "`"; break;
|
||||
case 0xBD: result += "-"; break;
|
||||
case 0xBB: result += "="; break;
|
||||
case 0xDB: result += "["; break;
|
||||
case 0xDD: result += "]"; break;
|
||||
case 0xDC: result += "\\"; break;
|
||||
case 0xBA: result += ";"; break;
|
||||
case 0xDE: result += "'"; break;
|
||||
case 0xBC: result += ","; break;
|
||||
case 0xBE: result += "."; break;
|
||||
case 0xBF: result += "/"; break;
|
||||
default:
|
||||
result += "VK_0x" + std::to_string(code);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove trailing + if no key code
|
||||
if (!result.empty() && result.back() == '+')
|
||||
{
|
||||
result.pop_back();
|
||||
}
|
||||
|
||||
return result.empty() ? "(not set)" : result;
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
/// <summary>
|
||||
/// Utility class for discovering and loading PowerToy module settings
|
||||
/// </summary>
|
||||
class SettingsLoader
|
||||
{
|
||||
public:
|
||||
SettingsLoader();
|
||||
~SettingsLoader();
|
||||
|
||||
/// <summary>
|
||||
/// Load settings for a PowerToy module
|
||||
/// </summary>
|
||||
/// <param name="moduleName">Name of the module (e.g., "CursorWrap")</param>
|
||||
/// <param name="moduleDllPath">Full path to the module DLL (for checking local settings.json)</param>
|
||||
/// <returns>JSON settings string, or empty string if not found</returns>
|
||||
std::wstring LoadSettings(const std::wstring& moduleName, const std::wstring& moduleDllPath);
|
||||
|
||||
/// <summary>
|
||||
/// Get the settings file path for a module
|
||||
/// </summary>
|
||||
/// <param name="moduleName">Name of the module</param>
|
||||
/// <returns>Full path to the settings.json file</returns>
|
||||
std::wstring GetSettingsPath(const std::wstring& moduleName) const;
|
||||
|
||||
/// <summary>
|
||||
/// Display settings information for a module
|
||||
/// </summary>
|
||||
/// <param name="moduleName">Name of the module</param>
|
||||
/// <param name="moduleDllPath">Path to the module DLL</param>
|
||||
void DisplaySettingsInfo(const std::wstring& moduleName, const std::wstring& moduleDllPath);
|
||||
|
||||
/// <summary>
|
||||
/// Get a specific setting value
|
||||
/// </summary>
|
||||
/// <param name="moduleName">Name of the module</param>
|
||||
/// <param name="moduleDllPath">Path to the module DLL</param>
|
||||
/// <param name="key">Setting key to retrieve</param>
|
||||
/// <returns>Value as string, or empty if not found</returns>
|
||||
std::wstring GetSettingValue(const std::wstring& moduleName, const std::wstring& moduleDllPath, const std::wstring& key);
|
||||
|
||||
/// <summary>
|
||||
/// Set a specific setting value
|
||||
/// </summary>
|
||||
/// <param name="moduleName">Name of the module</param>
|
||||
/// <param name="moduleDllPath">Path to the module DLL</param>
|
||||
/// <param name="key">Setting key to set</param>
|
||||
/// <param name="value">Value to set</param>
|
||||
/// <returns>True if successful</returns>
|
||||
bool SetSettingValue(const std::wstring& moduleName, const std::wstring& moduleDllPath, const std::wstring& key, const std::wstring& value);
|
||||
|
||||
/// <summary>
|
||||
/// Find the actual settings file path (handles case-insensitivity)
|
||||
/// </summary>
|
||||
/// <param name="moduleName">Name of the module</param>
|
||||
/// <param name="moduleDllPath">Path to the module DLL</param>
|
||||
/// <returns>Actual path to settings.json, or empty if not found</returns>
|
||||
std::wstring FindSettingsFilePath(const std::wstring& moduleName, const std::wstring& moduleDllPath);
|
||||
|
||||
private:
|
||||
/// <summary>
|
||||
/// Get the PowerToys root settings directory
|
||||
/// </summary>
|
||||
/// <returns>Path to %LOCALAPPDATA%\Microsoft\PowerToys</returns>
|
||||
std::wstring GetPowerToysSettingsRoot() const;
|
||||
|
||||
/// <summary>
|
||||
/// Read a text file into a string
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to the file</param>
|
||||
/// <returns>File contents as a string</returns>
|
||||
std::wstring ReadFileContents(const std::wstring& filePath) const;
|
||||
|
||||
/// <summary>
|
||||
/// Write a string to a text file
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to the file</param>
|
||||
/// <param name="contents">Contents to write</param>
|
||||
/// <returns>True if successful</returns>
|
||||
bool WriteFileContents(const std::wstring& filePath, const std::wstring& contents) const;
|
||||
|
||||
/// <summary>
|
||||
/// Parse settings properties from JSON and display them
|
||||
/// </summary>
|
||||
/// <param name="settingsJson">JSON string containing settings</param>
|
||||
/// <param name="indent">Indentation level</param>
|
||||
void DisplayJsonProperties(const std::wstring& settingsJson, int indent = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Parse a hotkey object from JSON and format it as a string (e.g., "Win+Alt+U")
|
||||
/// </summary>
|
||||
/// <param name="json">JSON string</param>
|
||||
/// <param name="objStart">Start position of the hotkey object</param>
|
||||
/// <param name="objEnd">Output: end position of the hotkey object</param>
|
||||
/// <returns>Formatted hotkey string, or empty if not a valid hotkey</returns>
|
||||
std::string ParseHotkeyObject(const std::string& json, size_t objStart, size_t& objEnd);
|
||||
|
||||
/// <summary>
|
||||
/// Check if a JSON object appears to be a hotkey settings object
|
||||
/// </summary>
|
||||
/// <param name="json">JSON string</param>
|
||||
/// <param name="objStart">Start position of the object</param>
|
||||
/// <returns>True if this looks like a hotkey object</returns>
|
||||
bool IsHotkeyObject(const std::string& json, size_t objStart);
|
||||
|
||||
/// <summary>
|
||||
/// Prompt user for yes/no confirmation
|
||||
/// </summary>
|
||||
/// <param name="prompt">The question to ask</param>
|
||||
/// <returns>True if user answered yes</returns>
|
||||
bool PromptYesNo(const std::wstring& prompt);
|
||||
|
||||
/// <summary>
|
||||
/// Add a new property to the JSON settings file
|
||||
/// </summary>
|
||||
/// <param name="json">The JSON string to modify</param>
|
||||
/// <param name="key">The property key to add</param>
|
||||
/// <param name="value">The value to set</param>
|
||||
/// <returns>Modified JSON string, or empty if failed</returns>
|
||||
std::string AddNewProperty(const std::string& json, const std::string& key, const std::string& value);
|
||||
};
|
||||
@@ -0,0 +1,367 @@
|
||||
// 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.
|
||||
|
||||
#include <Windows.h>
|
||||
#include <Tlhelp32.h>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include "ModuleLoader.h"
|
||||
#include "SettingsLoader.h"
|
||||
#include "HotkeyManager.h"
|
||||
#include "ConsoleHost.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
void PrintUsage()
|
||||
{
|
||||
std::wcout << L"PowerToys Module Loader - Standalone utility for loading and testing PowerToy modules\n\n";
|
||||
std::wcout << L"Usage: ModuleLoader.exe <module_dll_path> [options]\n\n";
|
||||
std::wcout << L"Arguments:\n";
|
||||
std::wcout << L" module_dll_path Path to the PowerToy module DLL (e.g., CursorWrap.dll)\n\n";
|
||||
std::wcout << L"Options:\n";
|
||||
std::wcout << L" --info Display current module settings and exit\n";
|
||||
std::wcout << L" --get <key> Get a specific setting value and exit\n";
|
||||
std::wcout << L" --set <key>=<val> Set a setting value (can be used multiple times)\n";
|
||||
std::wcout << L" --no-run Apply settings changes without running the module\n";
|
||||
std::wcout << L" --help Show this help message\n\n";
|
||||
std::wcout << L"Behavior:\n";
|
||||
std::wcout << L" - Automatically discovers settings from %%LOCALAPPDATA%%\\Microsoft\\PowerToys\\<ModuleName>\\settings.json\n";
|
||||
std::wcout << L" - Loads and enables the module\n";
|
||||
std::wcout << L" - Registers module hotkeys\n";
|
||||
std::wcout << L" - Runs until Ctrl+C is pressed\n\n";
|
||||
std::wcout << L"Examples:\n";
|
||||
std::wcout << L" ModuleLoader.exe x64\\Debug\\modules\\CursorWrap.dll\n";
|
||||
std::wcout << L" ModuleLoader.exe CursorWrap.dll --info\n";
|
||||
std::wcout << L" ModuleLoader.exe CursorWrap.dll --get wrap_mode\n";
|
||||
std::wcout << L" ModuleLoader.exe CursorWrap.dll --set wrap_mode=1\n";
|
||||
std::wcout << L" ModuleLoader.exe CursorWrap.dll --set auto_activate=true --no-run\n\n";
|
||||
std::wcout << L"Notes:\n";
|
||||
std::wcout << L" - Only non-UI modules are supported\n";
|
||||
std::wcout << L" - Debug output is written to module's log directory\n";
|
||||
}
|
||||
|
||||
std::wstring ExtractModuleName(const std::wstring& dllPath)
|
||||
{
|
||||
std::filesystem::path path(dllPath);
|
||||
std::wstring filename = path.stem().wstring();
|
||||
|
||||
// Remove "PowerToys." prefix if present (case-insensitive)
|
||||
const std::wstring powerToysPrefix = L"PowerToys.";
|
||||
if (filename.length() >= powerToysPrefix.length())
|
||||
{
|
||||
// Check if filename starts with "PowerToys." (case-insensitive)
|
||||
if (_wcsnicmp(filename.c_str(), powerToysPrefix.c_str(), powerToysPrefix.length()) == 0)
|
||||
{
|
||||
filename = filename.substr(powerToysPrefix.length());
|
||||
}
|
||||
}
|
||||
|
||||
// Common PowerToys module naming patterns
|
||||
// Remove common suffixes if present
|
||||
const std::wstring suffixes[] = { L"Module", L"ModuleInterface", L"Interface" };
|
||||
for (const auto& suffix : suffixes)
|
||||
{
|
||||
if (filename.size() > suffix.size())
|
||||
{
|
||||
size_t pos = filename.rfind(suffix);
|
||||
if (pos != std::wstring::npos && pos + suffix.size() == filename.size())
|
||||
{
|
||||
filename = filename.substr(0, pos);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
struct CommandLineOptions
|
||||
{
|
||||
std::wstring dllPath;
|
||||
bool showInfo = false;
|
||||
bool showHelp = false;
|
||||
bool noRun = false;
|
||||
std::wstring getKey;
|
||||
std::vector<std::pair<std::wstring, std::wstring>> setValues;
|
||||
};
|
||||
|
||||
CommandLineOptions ParseCommandLine(int argc, wchar_t* argv[])
|
||||
{
|
||||
CommandLineOptions options;
|
||||
|
||||
for (int i = 1; i < argc; i++)
|
||||
{
|
||||
std::wstring arg = argv[i];
|
||||
|
||||
if (arg == L"--help" || arg == L"-h" || arg == L"/?")
|
||||
{
|
||||
options.showHelp = true;
|
||||
}
|
||||
else if (arg == L"--info")
|
||||
{
|
||||
options.showInfo = true;
|
||||
}
|
||||
else if (arg == L"--no-run")
|
||||
{
|
||||
options.noRun = true;
|
||||
}
|
||||
else if (arg == L"--get" && i + 1 < argc)
|
||||
{
|
||||
options.getKey = argv[++i];
|
||||
}
|
||||
else if (arg == L"--set" && i + 1 < argc)
|
||||
{
|
||||
std::wstring setValue = argv[++i];
|
||||
size_t eqPos = setValue.find(L'=');
|
||||
if (eqPos != std::wstring::npos)
|
||||
{
|
||||
std::wstring key = setValue.substr(0, eqPos);
|
||||
std::wstring value = setValue.substr(eqPos + 1);
|
||||
options.setValues.push_back({key, value});
|
||||
}
|
||||
else
|
||||
{
|
||||
std::wcerr << L"Warning: Invalid --set format. Use --set key=value\n";
|
||||
}
|
||||
}
|
||||
else if (arg[0] != L'-' && options.dllPath.empty())
|
||||
{
|
||||
options.dllPath = arg;
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
int wmain(int argc, wchar_t* argv[])
|
||||
{
|
||||
// Enable UTF-8 console output for box-drawing characters
|
||||
SetConsoleOutputCP(CP_UTF8);
|
||||
|
||||
// Enable virtual terminal processing for ANSI escape codes (colors)
|
||||
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
DWORD dwMode = 0;
|
||||
if (GetConsoleMode(hOut, &dwMode))
|
||||
{
|
||||
SetConsoleMode(hOut, dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
|
||||
}
|
||||
|
||||
std::wcout << L"PowerToys Module Loader v1.1\n";
|
||||
std::wcout << L"=============================\n\n";
|
||||
|
||||
// Parse command-line arguments
|
||||
auto options = ParseCommandLine(argc, argv);
|
||||
|
||||
if (options.showHelp)
|
||||
{
|
||||
PrintUsage();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (options.dllPath.empty())
|
||||
{
|
||||
std::wcerr << L"Error: Missing required argument <module_dll_path>\n\n";
|
||||
PrintUsage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Validate DLL exists
|
||||
if (!std::filesystem::exists(options.dllPath))
|
||||
{
|
||||
std::wcerr << L"Error: Module DLL not found: " << options.dllPath << L"\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Extract module name from DLL path
|
||||
std::wstring moduleName = ExtractModuleName(options.dllPath);
|
||||
|
||||
// Create settings loader
|
||||
SettingsLoader settingsLoader;
|
||||
|
||||
// Handle --info option
|
||||
if (options.showInfo)
|
||||
{
|
||||
settingsLoader.DisplaySettingsInfo(moduleName, options.dllPath);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Handle --get option
|
||||
if (!options.getKey.empty())
|
||||
{
|
||||
std::wstring value = settingsLoader.GetSettingValue(moduleName, options.dllPath, options.getKey);
|
||||
if (value.empty())
|
||||
{
|
||||
std::wcerr << L"Setting '" << options.getKey << L"' not found.\n";
|
||||
return 1;
|
||||
}
|
||||
std::wcout << options.getKey << L"=" << value << L"\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Handle --set options
|
||||
if (!options.setValues.empty())
|
||||
{
|
||||
bool allSuccess = true;
|
||||
for (const auto& [key, value] : options.setValues)
|
||||
{
|
||||
if (!settingsLoader.SetSettingValue(moduleName, options.dllPath, key, value))
|
||||
{
|
||||
allSuccess = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.noRun)
|
||||
{
|
||||
return allSuccess ? 0 : 1;
|
||||
}
|
||||
|
||||
std::wcout << L"\n";
|
||||
}
|
||||
|
||||
// Check if PowerToys.exe is running
|
||||
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (hSnapshot != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
PROCESSENTRY32W pe32;
|
||||
pe32.dwSize = sizeof(PROCESSENTRY32W);
|
||||
|
||||
bool powerToysRunning = false;
|
||||
if (Process32FirstW(hSnapshot, &pe32))
|
||||
{
|
||||
do
|
||||
{
|
||||
if (_wcsicmp(pe32.szExeFile, L"PowerToys.exe") == 0)
|
||||
{
|
||||
powerToysRunning = true;
|
||||
break;
|
||||
}
|
||||
} while (Process32NextW(hSnapshot, &pe32));
|
||||
}
|
||||
CloseHandle(hSnapshot);
|
||||
|
||||
if (powerToysRunning)
|
||||
{
|
||||
// Display warning with VT100 colors
|
||||
std::wcout << L"\033[1;43;30m WARNING \033[0m PowerToys.exe is currently running!\n\n";
|
||||
|
||||
std::wcout << L"\033[1;31m";
|
||||
std::wcout << L"Running ModuleLoader while PowerToys is active may cause conflicts:\n";
|
||||
std::wcout << L" - Duplicate hotkey registrations\n";
|
||||
std::wcout << L" - Conflicting module instances\n";
|
||||
std::wcout << L" - Unexpected behavior\n";
|
||||
std::wcout << L"\033[0m\n";
|
||||
|
||||
std::wcout << L"\033[1;36m";
|
||||
std::wcout << L"RECOMMENDATION: Exit PowerToys before continuing.\n";
|
||||
std::wcout << L"\033[0m\n";
|
||||
|
||||
std::wcout << L"\033[1;33m";
|
||||
std::wcout << L"Do you want to continue anyway? (y/N): ";
|
||||
std::wcout << L"\033[0m";
|
||||
|
||||
wchar_t response = L'\0';
|
||||
std::wcin >> response;
|
||||
|
||||
if (response != L'y' && response != L'Y')
|
||||
{
|
||||
std::wcout << L"\nExiting. Please close PowerToys and try again.\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::wcout << L"\n";
|
||||
}
|
||||
}
|
||||
|
||||
std::wcout << L"Loading module: " << options.dllPath << L"\n";
|
||||
std::wcout << L"Detected module name: " << moduleName << L"\n\n";
|
||||
|
||||
try
|
||||
{
|
||||
// Load settings for the module
|
||||
std::wcout << L"Loading settings...\n";
|
||||
std::wstring settingsJson = settingsLoader.LoadSettings(moduleName, options.dllPath);
|
||||
|
||||
if (settingsJson.empty())
|
||||
{
|
||||
std::wcerr << L"Error: Could not load settings for module '" << moduleName << L"'\n";
|
||||
std::wcerr << L"Expected location: %LOCALAPPDATA%\\Microsoft\\PowerToys\\" << moduleName << L"\\settings.json\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::wcout << L"Settings loaded successfully.\n\n";
|
||||
|
||||
// Load the module DLL
|
||||
std::wcout << L"Loading module DLL...\n";
|
||||
ModuleLoader moduleLoader;
|
||||
if (!moduleLoader.Load(options.dllPath))
|
||||
{
|
||||
std::wcerr << L"Error: Failed to load module DLL\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::wcout << L"Module DLL loaded successfully.\n";
|
||||
std::wcout << L"Module key: " << moduleLoader.GetModuleKey() << L"\n";
|
||||
std::wcout << L"Module name: " << moduleLoader.GetModuleName() << L"\n\n";
|
||||
|
||||
// Apply settings to the module
|
||||
std::wcout << L"Applying settings to module...\n";
|
||||
moduleLoader.SetConfig(settingsJson);
|
||||
std::wcout << L"Settings applied.\n\n";
|
||||
|
||||
// Register hotkeys
|
||||
std::wcout << L"Registering module hotkeys...\n";
|
||||
HotkeyManager hotkeyManager;
|
||||
if (!hotkeyManager.RegisterModuleHotkeys(moduleLoader))
|
||||
{
|
||||
std::wcerr << L"Warning: Failed to register some hotkeys\n";
|
||||
}
|
||||
std::wcout << L"Hotkeys registered: " << hotkeyManager.GetRegisteredCount() << L"\n\n";
|
||||
|
||||
// Enable the module
|
||||
std::wcout << L"Enabling module...\n";
|
||||
moduleLoader.Enable();
|
||||
std::wcout << L"Module enabled.\n\n";
|
||||
|
||||
// Display status
|
||||
std::wcout << L"=============================\n";
|
||||
std::wcout << L"Module is now running!\n";
|
||||
std::wcout << L"=============================\n\n";
|
||||
std::wcout << L"Module Status:\n";
|
||||
std::wcout << L" - Name: " << moduleLoader.GetModuleName() << L"\n";
|
||||
std::wcout << L" - Key: " << moduleLoader.GetModuleKey() << L"\n";
|
||||
std::wcout << L" - Enabled: " << (moduleLoader.IsEnabled() ? L"Yes" : L"No") << L"\n";
|
||||
std::wcout << L" - Hotkeys: " << hotkeyManager.GetRegisteredCount() << L" registered\n\n";
|
||||
|
||||
if (hotkeyManager.GetRegisteredCount() > 0)
|
||||
{
|
||||
std::wcout << L"Registered Hotkeys:\n";
|
||||
hotkeyManager.PrintHotkeys();
|
||||
std::wcout << L"\n";
|
||||
}
|
||||
|
||||
std::wcout << L"Press Ctrl+C to exit.\n";
|
||||
std::wcout << L"You can press the module's hotkey to toggle its functionality.\n\n";
|
||||
|
||||
// Run the message loop
|
||||
ConsoleHost consoleHost(moduleLoader, hotkeyManager);
|
||||
consoleHost.Run();
|
||||
|
||||
// Cleanup
|
||||
std::wcout << L"\nShutting down...\n";
|
||||
moduleLoader.Disable();
|
||||
hotkeyManager.UnregisterAll();
|
||||
|
||||
std::wcout << L"Module unloaded successfully.\n";
|
||||
return 0;
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
std::wcerr << L"Fatal error: " << ex.what() << L"\n";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user