Customize system menu items through dedicated API (#677)

Document new interface changes.
This commit is contained in:
vldmr11080
2019-11-12 11:48:14 +01:00
committed by Enrico Giordani
parent 9f78af29bf
commit be86cd4028
17 changed files with 350 additions and 1 deletions

View File

@@ -20,5 +20,6 @@ PowertoyModule load_powertoy(const std::wstring& filename) {
FreeLibrary(handle);
winrt::throw_last_error();
}
module->register_system_menu_helper(&SystemMenuHelperInstace());
return PowertoyModule(module, handle);
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include "powertoys_events.h"
#include "system_menu_helper.h"
#include <interface/powertoy_module_interface.h>
#include <string>
#include <memory>
@@ -12,6 +13,7 @@ class PowertoyModule;
struct PowertoyModuleDeleter {
void operator()(PowertoyModuleIface* module) const {
if (module) {
powertoys_events().unregister_system_menu_action(module);
powertoys_events().unregister_receiver(module);
module->destroy();
}
@@ -38,6 +40,9 @@ public:
powertoys_events().register_receiver(*want_signals, module);
}
}
if (SystemMenuHelperInstace().HasCustomConfig(module)) {
powertoys_events().register_system_menu_action(module);
}
}
const std::wstring& get_name() const {

View File

@@ -2,6 +2,7 @@
#include "powertoys_events.h"
#include "lowlevel_keyboard_event.h"
#include "win_hook_event.h"
#include "system_menu_helper.h"
void first_subscribed(const std::wstring& event) {
if (event == ll_keyboard)
@@ -41,6 +42,37 @@ void PowertoysEvents::unregister_receiver(PowertoyModuleIface* module) {
}
}
void PowertoysEvents::register_system_menu_action(PowertoyModuleIface* module) {
std::unique_lock lock(mutex);
system_menu_receivers.insert(module);
}
void PowertoysEvents::unregister_system_menu_action(PowertoyModuleIface* module) {
std::unique_lock lock(mutex);
auto it = system_menu_receivers.find(module);
if (it != system_menu_receivers.end()) {
SystemMenuHelperInstace().Reset(module);
system_menu_receivers.erase(it);
}
}
void PowertoysEvents::handle_system_menu_action(const WinHookEvent& data) {
if (data.event == EVENT_SYSTEM_MENUSTART) {
for (auto& module : system_menu_receivers) {
SystemMenuHelperInstace().Customize(module, data.hwnd);
}
}
else if (data.event == EVENT_OBJECT_INVOKED) {
if (PowertoyModuleIface* module{ SystemMenuHelperInstace().ModuleFromItemId(data.idChild) }) {
std::wstring itemName = SystemMenuHelperInstace().ItemNameFromItemId(data.idChild);
// Process event on specified system menu item by responsible module.
module->signal_system_menu_action(itemName.c_str());
// Process event on specified system menu item by system menu helper (check/uncheck if needed).
SystemMenuHelperInstace().ProcessSelectedItem(module, GetForegroundWindow(), itemName.c_str());
}
}
}
intptr_t PowertoysEvents::signal_event(const std::wstring & event, intptr_t data) {
intptr_t rvalue = 0;
std::shared_lock lock(mutex);

View File

@@ -1,15 +1,23 @@
#pragma once
#include <interface/powertoy_module_interface.h>
#include <interface/win_hook_event_data.h>
#include <string>
class PowertoysEvents {
public:
void register_receiver(const std::wstring& event, PowertoyModuleIface* module);
void unregister_receiver(PowertoyModuleIface* module);
void register_system_menu_action(PowertoyModuleIface* module);
void unregister_system_menu_action(PowertoyModuleIface* module);
void handle_system_menu_action(const WinHookEvent& data);
intptr_t signal_event(const std::wstring& event, intptr_t data);
private:
std::shared_mutex mutex;
std::unordered_map<std::wstring, std::vector<PowertoyModuleIface*>> receivers;
std::unordered_set<PowertoyModuleIface*> system_menu_receivers;
};
PowertoysEvents& powertoys_events();

View File

@@ -107,6 +107,7 @@
<ClCompile Include="powertoy_module.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="settings_window.cpp" />
<ClCompile Include="system_menu_helper.cpp" />
<ClCompile Include="trace.cpp" />
<ClCompile Include="tray_icon.cpp" />
<ClCompile Include="unhandled_exception_handler.cpp" />
@@ -121,6 +122,7 @@
<ClInclude Include="powertoy_module.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="settings_window.h" />
<ClInclude Include="system_menu_helper.h" />
<ClInclude Include="trace.h" />
<ClInclude Include="tray_icon.h" />
<ClInclude Include="unhandled_exception_handler.h" />

View File

@@ -33,6 +33,9 @@
<ClCompile Include="win_hook_event.cpp">
<Filter>Events</Filter>
</ClCompile>
<ClCompile Include="system_menu_helper.cpp">
<Filter>Utils</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
@@ -67,6 +70,9 @@
<ClInclude Include="win_hook_event.h">
<Filter>Events</Filter>
</ClInclude>
<ClInclude Include="system_menu_helper.h">
<Filter>Utils</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="Utils">

View File

@@ -0,0 +1,129 @@
#include "pch.h"
#include "system_menu_helper.h"
#include <interface/powertoy_module_interface.h>
namespace {
constexpr int KSeparatorPos = 1;
constexpr int KNewItemPos = 2;
unsigned int GenerateItemId() {
static unsigned int generator = 0x70777479;
return ++generator;
}
}
SystemMenuHelper& SystemMenuHelperInstace() {
static SystemMenuHelper instance;
return instance;
}
void SystemMenuHelper::SetConfiguration(PowertoyModuleIface* module, const std::vector<ItemInfo>& config) {
Reset(module);
Configurations[module] = config;
for (auto& [window, modules] : ProcessedModules) {
// Unregister module. After system menu is opened again, new configuration will be applied.
modules.erase(std::remove(std::begin(modules), std::end(modules), module), std::end(modules));
}
}
void SystemMenuHelper::ProcessSelectedItem(PowertoyModuleIface* module, HWND window, const wchar_t* itemName) {
for (const auto& item : Configurations[module]) {
if (itemName == item.name && item.checkBox) {
// Handle check/uncheck action only if specified by module configuration.
for (const auto& [id, data] : IdMappings) {
if (data.second == itemName) {
HMENU systemMenu = GetSystemMenu(window, false);
int state = (GetMenuState(systemMenu, id, MF_BYCOMMAND) == MF_CHECKED) ? MF_UNCHECKED : MF_CHECKED;
CheckMenuItem(systemMenu, id, MF_BYCOMMAND | state);
break;
}
}
break;
}
}
}
bool SystemMenuHelper::Customize(PowertoyModuleIface* module, HWND window) {
auto& modules = ProcessedModules[window];
for (const auto& m : modules) {
if (module == m) {
return false;
}
}
AddSeparator(module, window);
for (const auto& info : Configurations[module]) {
AddItem(module, window, info.name, info.enable);
}
modules.push_back(module);
return true;
}
void SystemMenuHelper::Reset(PowertoyModuleIface* module) {
for (auto& [window, modules] : ProcessedModules) {
if (HMENU systemMenu{ GetSystemMenu(window, false) }) {
for (auto& [id, data] : IdMappings) {
if (data.first == module) {
DeleteMenu(systemMenu, id, MF_BYCOMMAND);
}
}
}
}
}
bool SystemMenuHelper::HasCustomConfig(PowertoyModuleIface* module) {
return Configurations.find(module) != Configurations.end();
}
bool SystemMenuHelper::AddItem(PowertoyModuleIface* module, HWND window, const std::wstring& name, const bool enable) {
if (HMENU systemMenu{ GetSystemMenu(window, false) }) {
MENUITEMINFO item;
item.cbSize = sizeof(item);
item.fMask = MIIM_ID | MIIM_STRING | MIIM_STATE;
item.fState = MF_UNCHECKED | MF_DISABLED; // Item is disabled by default.
item.wID = GenerateItemId();
item.dwTypeData = const_cast<WCHAR*>(name.c_str());
item.cch = name.size() + 1;
if (InsertMenuItem(systemMenu, GetMenuItemCount(systemMenu) - KNewItemPos, true, &item)) {
IdMappings[item.wID] = { module, name };
if (enable) {
EnableMenuItem(systemMenu, item.wID, MF_BYCOMMAND | MF_ENABLED);
}
return true;
}
}
return false;
}
bool SystemMenuHelper::AddSeparator(PowertoyModuleIface* module, HWND window) {
if (HMENU systemMenu{ GetSystemMenu(window, false) }) {
MENUITEMINFO separator;
separator.cbSize = sizeof(separator);
separator.fMask = MIIM_ID | MIIM_FTYPE;
separator.fType = MFT_SEPARATOR;
separator.wID = GenerateItemId();
if (InsertMenuItem(systemMenu, GetMenuItemCount(systemMenu) - KSeparatorPos, true, &separator)) {
IdMappings[separator.wID] = { module, L"sepparator_dummy_name" };
return true;
}
}
return false;
}
PowertoyModuleIface* SystemMenuHelper::ModuleFromItemId(const int& id) {
auto it = IdMappings.find(id);
if (it != IdMappings.end()) {
return it->second.first;
}
return nullptr;
}
const std::wstring SystemMenuHelper::ItemNameFromItemId(const int& id) {
auto itemIt = IdMappings.find(id);
if (itemIt != IdMappings.end()) {
return itemIt->second.second;
}
return std::wstring{};
}

View File

@@ -0,0 +1,42 @@
#pragma once
#pragma once
#include <interface/powertoy_system_menu.h>
#include <windows.h>
#include <string>
#include <vector>
#include <unordered_map>
class PowertoyModuleIface;
class SystemMenuHelper : public PowertoySystemMenuIface {
public:
// PowertoySystemMenuIface
virtual void SetConfiguration(PowertoyModuleIface* module, const std::vector<ItemInfo>& config) override;
virtual void ProcessSelectedItem(PowertoyModuleIface* module, HWND window, const wchar_t* itemName) override;
bool Customize(PowertoyModuleIface* module, HWND window);
void Reset(PowertoyModuleIface* module);
bool HasCustomConfig(PowertoyModuleIface* module);
PowertoyModuleIface* ModuleFromItemId(const int& id);
const std::wstring ItemNameFromItemId(const int& id);
private:
bool AddItem(PowertoyModuleIface* module, HWND window, const std::wstring& name, const bool enable);
bool AddSeparator(PowertoyModuleIface* module, HWND window);
// Store processed modules per window to avoid handling it multiple times.
std::unordered_map<HWND, std::vector<PowertoyModuleIface*>> ProcessedModules{};
// Keep mappings form item id to the module who created it and item name for faster processing later.
std::unordered_map<int, std::pair<PowertoyModuleIface*, std::wstring>> IdMappings{};
// Store configurations provided by module.
// This will be used to create custom system menu items and to handle updates.
std::unordered_map<PowertoyModuleIface*, std::vector<ItemInfo>> Configurations{};
};
SystemMenuHelper& SystemMenuHelperInstace();

View File

@@ -9,6 +9,8 @@ static std::mutex mutex;
static std::deque<WinHookEvent> hook_events;
static std::condition_variable dispatch_cv;
void intercept_system_menu_action(intptr_t);
static void CALLBACK win_hook_event_proc(HWINEVENTHOOK winEventHook,
DWORD event,
HWND window,
@@ -39,7 +41,9 @@ static void dispatch_thread_proc() {
auto event = hook_events.front();
hook_events.pop_front();
lock.unlock();
powertoys_events().signal_event(win_hook_event, reinterpret_cast<intptr_t>(&event));
intptr_t data = reinterpret_cast<intptr_t>(&event);
intercept_system_menu_action(data);
powertoys_events().signal_event(win_hook_event, data);
lock.lock();
}
}
@@ -70,3 +74,9 @@ void stop_win_hook_event() {
hook_events.shrink_to_fit();
}
void intercept_system_menu_action(intptr_t data) {
WinHookEvent* evt = reinterpret_cast<WinHookEvent*>(data);
if (evt->event == EVENT_SYSTEM_MENUSTART || evt->event == EVENT_OBJECT_INVOKED) {
powertoys_events().handle_system_menu_action(*evt);
}
}