mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-23 19:49:43 +01:00
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request This PR implements functionality to ignore specific hotkey conflicts in PowerToys settings. The primary purpose is to allow users to suppress individual shortcut conflict warnings if they find their configurations work correctly despite the detected conflicts. <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [x] Closes: #41544 - [x] **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 <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments - Added hotkey conflict ignore functionality with user-controllable settings - Updated shortcut control UI to support ignore states and clearer conflict messaging - Enhanced conflict detection to respect ignored shortcuts when counting conflicts <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed --------- Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com> Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com> Signed-off-by: Shawn Yuan (from Dev Box) <shuaiyuan@microsoft.com> Co-authored-by: Niels Laute <niels.laute@live.nl>
301 lines
8.3 KiB
C++
301 lines
8.3 KiB
C++
#include "pch.h"
|
|
|
|
#include <interface/powertoy_module_interface.h>
|
|
|
|
#include <common/logger/logger.h>
|
|
#include <common/utils/resources.h>
|
|
#include <common/utils/winapi_error.h>
|
|
|
|
#include <AlwaysOnTop/trace.h>
|
|
#include <AlwaysOnTop/ModuleConstants.h>
|
|
|
|
#include <shellapi.h>
|
|
#include <common/SettingsAPI/settings_objects.h>
|
|
#include <common/interop/shared_constants.h>
|
|
|
|
namespace NonLocalizable
|
|
{
|
|
const wchar_t ModulePath[] = L"PowerToys.AlwaysOnTop.exe";
|
|
}
|
|
|
|
namespace
|
|
{
|
|
const wchar_t JSON_KEY_PROPERTIES[] = L"properties";
|
|
const wchar_t JSON_KEY_WIN[] = L"win";
|
|
const wchar_t JSON_KEY_ALT[] = L"alt";
|
|
const wchar_t JSON_KEY_CTRL[] = L"ctrl";
|
|
const wchar_t JSON_KEY_SHIFT[] = L"shift";
|
|
const wchar_t JSON_KEY_CODE[] = L"code";
|
|
const wchar_t JSON_KEY_HOTKEY[] = L"hotkey";
|
|
const wchar_t JSON_KEY_VALUE[] = L"value";
|
|
}
|
|
|
|
BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/)
|
|
{
|
|
switch (ul_reason_for_call)
|
|
{
|
|
case DLL_PROCESS_ATTACH:
|
|
Trace::AlwaysOnTop::RegisterProvider();
|
|
break;
|
|
|
|
case DLL_THREAD_ATTACH:
|
|
case DLL_THREAD_DETACH:
|
|
break;
|
|
|
|
case DLL_PROCESS_DETACH:
|
|
Trace::AlwaysOnTop::UnregisterProvider();
|
|
break;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
class AlwaysOnTopModuleInterface : public PowertoyModuleIface
|
|
{
|
|
public:
|
|
// Return the localized display name of the powertoy
|
|
virtual PCWSTR get_name() override
|
|
{
|
|
return app_name.c_str();
|
|
}
|
|
|
|
// Return the non localized key of the powertoy, this will be cached by the runner
|
|
virtual const wchar_t* get_key() override
|
|
{
|
|
return app_key.c_str();
|
|
}
|
|
|
|
// Return the configured status for the gpo policy for the module
|
|
virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override
|
|
{
|
|
return powertoys_gpo::getConfiguredAlwaysOnTopEnabledValue();
|
|
}
|
|
|
|
// Return JSON with the configuration options.
|
|
// These are the settings shown on the settings page along with their current values.
|
|
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
|
|
{
|
|
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
|
|
|
|
// Create a Settings object.
|
|
PowerToysSettings::Settings settings(hinstance, get_name());
|
|
|
|
return settings.serialize_to_buffer(buffer, buffer_size);
|
|
}
|
|
|
|
// Passes JSON with the configuration settings for the powertoy.
|
|
// This is called when the user hits Save on the settings page.
|
|
virtual void set_config(const wchar_t* config) override
|
|
{
|
|
try
|
|
{
|
|
// Parse the input JSON string.
|
|
PowerToysSettings::PowerToyValues values =
|
|
PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
|
|
|
|
parse_hotkey(values);
|
|
// If you don't need to do any custom processing of the settings, proceed
|
|
// to persists the values calling:
|
|
values.save_to_settings_file();
|
|
// Otherwise call a custom function to process the settings before saving them to disk:
|
|
// save_settings();
|
|
}
|
|
catch (std::exception&)
|
|
{
|
|
// Improper JSON.
|
|
}
|
|
}
|
|
|
|
virtual bool on_hotkey(size_t /*hotkeyId*/) override
|
|
{
|
|
if (m_enabled)
|
|
{
|
|
Logger::trace(L"AlwaysOnTop hotkey pressed");
|
|
if (!is_process_running())
|
|
{
|
|
Enable();
|
|
}
|
|
|
|
SetEvent(m_hPinEvent);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
|
|
{
|
|
if (m_hotkey.key)
|
|
{
|
|
if (hotkeys && buffer_size >= 1)
|
|
{
|
|
hotkeys[0] = m_hotkey;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Enable the powertoy
|
|
virtual void enable()
|
|
{
|
|
Logger::info("AlwaysOnTop enabling");
|
|
|
|
Enable();
|
|
}
|
|
|
|
// Disable the powertoy
|
|
virtual void disable()
|
|
{
|
|
Logger::info("AlwaysOnTop disabling");
|
|
|
|
Disable(true);
|
|
}
|
|
|
|
// Returns if the powertoy is enabled
|
|
virtual bool is_enabled() override
|
|
{
|
|
return m_enabled;
|
|
}
|
|
|
|
// Destroy the powertoy and free memory
|
|
virtual void destroy() override
|
|
{
|
|
Disable(false);
|
|
delete this;
|
|
}
|
|
|
|
AlwaysOnTopModuleInterface()
|
|
{
|
|
app_name = L"AlwaysOnTop"; //TODO: localize
|
|
app_key = NonLocalizable::ModuleKey;
|
|
m_hPinEvent = CreateDefaultEvent(CommonSharedConstants::ALWAYS_ON_TOP_PIN_EVENT);
|
|
m_hTerminateEvent = CreateDefaultEvent(CommonSharedConstants::ALWAYS_ON_TOP_TERMINATE_EVENT);
|
|
init_settings();
|
|
}
|
|
|
|
private:
|
|
void Enable()
|
|
{
|
|
m_enabled = true;
|
|
|
|
// Log telemetry
|
|
Trace::AlwaysOnTop::Enable(true);
|
|
|
|
unsigned long powertoys_pid = GetCurrentProcessId();
|
|
std::wstring executable_args = L"";
|
|
executable_args.append(std::to_wstring(powertoys_pid));
|
|
ResetEvent(m_hPinEvent);
|
|
|
|
SHELLEXECUTEINFOW sei{ sizeof(sei) };
|
|
sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
|
|
sei.lpFile = NonLocalizable::ModulePath;
|
|
sei.nShow = SW_SHOWNORMAL;
|
|
sei.lpParameters = executable_args.data();
|
|
if (ShellExecuteExW(&sei) == false)
|
|
{
|
|
Logger::error(L"Failed to start AlwaysOnTop");
|
|
auto message = get_last_error_message(GetLastError());
|
|
if (message.has_value())
|
|
{
|
|
Logger::error(message.value());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_hProcess = sei.hProcess;
|
|
}
|
|
}
|
|
|
|
void Disable(bool const traceEvent)
|
|
{
|
|
m_enabled = false;
|
|
ResetEvent(m_hPinEvent);
|
|
|
|
// Log telemetry
|
|
if (traceEvent)
|
|
{
|
|
Trace::AlwaysOnTop::Enable(false);
|
|
}
|
|
|
|
SetEvent(m_hTerminateEvent);
|
|
|
|
// Wait for 1.5 seconds for the process to end correctly and stop etw tracer
|
|
WaitForSingleObject(m_hProcess, 1500);
|
|
|
|
// If process is still running, terminate it
|
|
if (m_hProcess)
|
|
{
|
|
TerminateProcess(m_hProcess, 0);
|
|
m_hProcess = nullptr;
|
|
}
|
|
}
|
|
|
|
void parse_hotkey(PowerToysSettings::PowerToyValues& settings)
|
|
{
|
|
auto settingsObject = settings.get_raw_json();
|
|
if (settingsObject.GetView().Size())
|
|
{
|
|
try
|
|
{
|
|
auto jsonHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_HOTKEY).GetNamedObject(JSON_KEY_VALUE);
|
|
m_hotkey.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN);
|
|
m_hotkey.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT);
|
|
m_hotkey.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
|
|
m_hotkey.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
|
|
m_hotkey.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
|
|
}
|
|
catch (...)
|
|
{
|
|
Logger::error("Failed to initialize AlwaysOnTop start shortcut");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Logger::info("AlwaysOnTop settings are empty");
|
|
}
|
|
}
|
|
|
|
bool is_process_running()
|
|
{
|
|
return WaitForSingleObject(m_hProcess, 0) == WAIT_TIMEOUT;
|
|
}
|
|
|
|
void init_settings()
|
|
{
|
|
try
|
|
{
|
|
// Load and parse the settings file for this PowerToy.
|
|
PowerToysSettings::PowerToyValues settings =
|
|
PowerToysSettings::PowerToyValues::load_from_settings_file(get_key());
|
|
|
|
parse_hotkey(settings);
|
|
}
|
|
catch (std::exception&)
|
|
{
|
|
Logger::warn(L"An exception occurred while loading the settings file");
|
|
// Error while loading from the settings file. Let default values stay as they are.
|
|
}
|
|
}
|
|
|
|
std::wstring app_name;
|
|
std::wstring app_key; //contains the non localized key of the powertoy
|
|
|
|
bool m_enabled = false;
|
|
HANDLE m_hProcess = nullptr;
|
|
Hotkey m_hotkey;
|
|
|
|
// Handle to event used to pin/unpin windows
|
|
HANDLE m_hPinEvent;
|
|
HANDLE m_hTerminateEvent;
|
|
};
|
|
|
|
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
|
|
{
|
|
return new AlwaysOnTopModuleInterface();
|
|
}
|