mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-03 09:46:54 +02:00
This PR adds new options for disabling wrap, updates the wrapping model, extends the simulator and cursor logging. <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [ ] Closes: #45116 - [ ] Closes: #44955 - [ ] Closes: #44827 - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [x] **Tests:** Added/updated and all pass - [x] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx The PR adds a new option for disabling cursor wrapping, exposing three options: None - wrapping is not disabled, Ctrl key - if this is pressed then wrapping is disabled, Shift key - if this is pressed then wrapping is disabled, this would enable a user to temporarily disable wrapping if they wanted to get close to a monitor edge without wrapping (auto-hide status bar for example). The cursor wrap edge model has been updated to mirror Windows monitor-to-monitor cursor movement, this should ensure there aren't any non-wrappable edges. A new test tool has been added 'CursorLog' this is a monitor aware, dpi/scaling aware Win32 application that captures mouse movement across monitors to a log file, the log contains one line per mouse movement which includes: Monitor, x, y, scale, dpi. The wrapping simulator has been updated to include the new wrapping model and support mouse cursor log playback. ## Validation Steps Performed The updated CursorWrap has been tested on a single monitor (laptop) and multi-monitor desktop PC with monitors being offset to test edge/wrapping behavior. --------- Co-authored-by: Mike Hall <mikehall@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: vanzue <vanzue@outlook.com>
368 lines
13 KiB
C++
368 lines
13 KiB
C++
// 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;
|
|
}
|
|
}
|