From 754804a1b69ae018e237309c1bce0b166b775b91 Mon Sep 17 00:00:00 2001 From: Chili Date: Wed, 21 May 2025 14:17:28 +0900 Subject: [PATCH 1/2] hotkey moved to core, working in kproc --- IntelPresentMon/Core/Core.vcxproj | 2 + IntelPresentMon/Core/Core.vcxproj.filters | 2 + IntelPresentMon/Core/source/infra/Logging.h | 1 + .../Core/source/win/HotkeyListener.cpp | 253 ++++++++++++++++++ .../Core/source/win/HotkeyListener.h | 55 ++++ 5 files changed, 313 insertions(+) create mode 100644 IntelPresentMon/Core/source/win/HotkeyListener.cpp create mode 100644 IntelPresentMon/Core/source/win/HotkeyListener.h diff --git a/IntelPresentMon/Core/Core.vcxproj b/IntelPresentMon/Core/Core.vcxproj index 979b91ef8..b866f46b1 100644 --- a/IntelPresentMon/Core/Core.vcxproj +++ b/IntelPresentMon/Core/Core.vcxproj @@ -85,6 +85,7 @@ + @@ -147,6 +148,7 @@ + diff --git a/IntelPresentMon/Core/Core.vcxproj.filters b/IntelPresentMon/Core/Core.vcxproj.filters index 0701ec39a..fed77fa84 100644 --- a/IntelPresentMon/Core/Core.vcxproj.filters +++ b/IntelPresentMon/Core/Core.vcxproj.filters @@ -91,6 +91,7 @@ + @@ -150,6 +151,7 @@ + diff --git a/IntelPresentMon/Core/source/infra/Logging.h b/IntelPresentMon/Core/source/infra/Logging.h index 1a8bb8d20..8016b9ca5 100644 --- a/IntelPresentMon/Core/source/infra/Logging.h +++ b/IntelPresentMon/Core/source/infra/Logging.h @@ -26,4 +26,5 @@ namespace p2c::v #else inline constexpr bool metric = true; #endif + inline constexpr bool hotkey2 = false; } \ No newline at end of file diff --git a/IntelPresentMon/Core/source/win/HotkeyListener.cpp b/IntelPresentMon/Core/source/win/HotkeyListener.cpp new file mode 100644 index 000000000..b01ca2b03 --- /dev/null +++ b/IntelPresentMon/Core/source/win/HotkeyListener.cpp @@ -0,0 +1,253 @@ +// Copyright (C) 2022 Intel Corporation +// SPDX-License-Identifier: MIT +#include "HotkeyListener.h" +#include +#include +#include +#include + +namespace rn = std::ranges; +namespace vi = rn::views; +using namespace pmon::util; + +// TODO: general logging in this codebase (winapi calls like PostThreadMessage etc.) + +namespace p2c::win +{ + Hotkeys::Hotkeys() + : + thread_{ "hotkey", &Hotkeys::Kernel_, this } + { + startupSemaphore_.acquire(); + pmlog_verb(v::hotkey2)("Hotkey process ctor complete"); + } + Hotkeys::~Hotkeys() + { + pmlog_verb(v::hotkey2)("Destroying hotkey processor"); + PostThreadMessageA(threadId_, WM_QUIT, 0, 0); + } + void Hotkeys::Kernel_() noexcept + { + try { + pmlog_verb(v::hotkey2)("Hotkey processor kernel start"); + + // capture thread id + threadId_ = GetCurrentThreadId(); + + // create message window class + const WNDCLASSEXA wx = { + .cbSize = sizeof(WNDCLASSEX), + .lpfnWndProc = DefWindowProc, + .hInstance = GetModuleHandle(nullptr), + .lpszClassName = "$PresentmonMessageWindowClass$", + }; + const auto atom = RegisterClassExA(&wx); + if (!atom) { + pmlog_error().hr(); + return; + } + + pmlog_verb(v::hotkey2)(std::format("Hotkey processor wndclass registered: {:X}", atom)); + + // create message window + messageWindowHandle_ = CreateWindowExW( + 0, MAKEINTATOM(atom), L"$PresentmonMessageWindow$", + 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, nullptr, nullptr + ); + if (!messageWindowHandle_) { + pmlog_error().hr(); + return; + } + + pmlog_verb(v::hotkey2)(std::format("Hotkey processor wnd created: {:X}", + reinterpret_cast(messageWindowHandle_))); + + // register to receive raw keyboard input + const RAWINPUTDEVICE rid{ + .usUsagePage = 0x01, + .usUsage = 0x06, + .dwFlags = RIDEV_INPUTSINK, + .hwndTarget = static_cast(messageWindowHandle_), + }; + if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) { + pmlog_error().hr(); + return; + } + + pmlog_verb(v::hotkey2)("Raw input registered"); + + // signal that constuction is complete + startupSemaphore_.release(); + + MSG msg; + while (GetMessageA(&msg, nullptr, 0, 0)) { + if (msg.message == WM_INPUT) { + pmlog_verb(v::hotkey2)("WM_INPUT received"); + + auto& lParam = msg.lParam; + // allocate memory for raw input data + UINT dwSize{}; + if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, nullptr, &dwSize, sizeof(RAWINPUTHEADER))) { + pmlog_error().hr(); + return; + } + const auto inputBackingBuffer = std::make_unique(dwSize); + + // read in raw input data + if (const auto size = GetRawInputData((HRAWINPUT)lParam, RID_INPUT, inputBackingBuffer.get(), + &dwSize, sizeof(RAWINPUTHEADER)); size != dwSize) { + pmlog_error().hr(); + return; + } + const auto& input = reinterpret_cast(*inputBackingBuffer.get()); + + // handle raw input data for keyboard device + if (input.header.dwType == RIM_TYPEKEYBOARD) + { + const auto& keyboard = input.data.keyboard; + + pmlog_verb(v::hotkey2)(std::format("KBD| vk:{} msg:{}", keyboard.VKey, keyboard.Message)); + + // check for keycode outside of range of our table + if (keyboard.VKey >= win::Key::virtualKeyTableSize) { + pmlog_verb(v::hotkey2)("KBD| vk out of range"); + continue; + } + // key presses + if (keyboard.Message == WM_KEYDOWN || keyboard.Message == WM_SYSKEYDOWN) { + pmlog_verb(v::hotkey2)("key press"); + // don't handle autorepeat presses + if (!pressedKeys_[keyboard.VKey]) { + // mark key as in down state + pressedKeys_[keyboard.VKey] = true; + // if key is not modifier + if (const auto key = win::Key::FromPlatformCode(keyboard.VKey)) { + if (!key->IsModifierKey()) { + // gather currently pressed modifiers + const auto mods = GatherModifiers_(); + // check for matching registered hotkey action + std::lock_guard lk{ mtx_ }; + if (auto i = registeredHotkeys_.find({ *key, mods }); + i != registeredHotkeys_.cend()) { + // if match found dispatch action on renderer thread + pmlog_verb(v::hotkey2)("hotkey dispatching"); + DispatchHotkey_(i->second); + } + } + } + } + else { + pmlog_verb(v::hotkey2)("key repeat"); + } + } + // key releases + else if (keyboard.Message == WM_KEYUP || keyboard.Message == WM_SYSKEYUP) { + pmlog_verb(v::hotkey2)("Key up"); + pressedKeys_[keyboard.VKey] = false; + } + } + else { + pmlog_warn("Unknown raw input type"); + } + } + } + + if (!DestroyWindow(static_cast(messageWindowHandle_))) { + pmlog_warn("failed window destroy").hr(); + } + if (!UnregisterClass(MAKEINTATOM(atom), GetModuleHandle(nullptr))) { + pmlog_warn("failed unreg class").hr(); + } + + pmlog_verb(v::hotkey2)(); + } + catch (...) { + pmlog_error(ReportException()); + } + } + void Hotkeys::DispatchHotkey_(int action) const + { + if (Handler_) { + pmlog_dbg("hotkey action dispatched to handler").pmwatch(action); + Handler_(action); + } + else { + pmlog_warn("Hotkey handler not set"); + } + } + void Hotkeys::SetHandler(std::function handler) + { + std::lock_guard lk{ mtx_ }; + pmlog_verb(v::hotkey2)("Hotkey handler set"); + Handler_ = std::move(handler); + } + bool Hotkeys::BindAction(int action, win::Key key, win::ModSet mods) + { + pmlog_verb(v::hotkey2)("Hotkey action binding"); + std::lock_guard lk{ mtx_ }; + // if action is already bound, remove it + if (const auto i = rn::find_if(registeredHotkeys_, [action](const auto& i) { + return i.second == action; + }); i != registeredHotkeys_.cend()) { + pmlog_verb(v::hotkey2)("Hotkey action binding remove existing action"); + registeredHotkeys_.erase(i); + } + // if hotkey combination is already bound, remove it + registeredHotkeys_.erase({ key, mods }); + // bind action to new hotkey combination + registeredHotkeys_.emplace(std::piecewise_construct, + std::forward_as_tuple(key, mods), + std::forward_as_tuple(action) + ); + // signal success + return true; + } + bool Hotkeys::ClearAction(int action) + { + pmlog_verb(v::hotkey2)("Hotkey action clearing"); + std::lock_guard lk{ mtx_ }; + // if action is bound, remove it + if (const auto i = rn::find_if(registeredHotkeys_, [action](const auto& i) { + return i.second == action; + }); i != registeredHotkeys_.cend()) { + registeredHotkeys_.erase(i); + } + else { + pmlog_warn("Attempted to clear unregistered hotkey"); + } + // signal success + return true; + } + win::ModSet Hotkeys::GatherModifiers_() const + { + win::ModSet mods; + if (pressedKeys_[VK_SHIFT]) { + mods = mods | win::Mod::Shift; + } + if (pressedKeys_[VK_CONTROL]) { + mods = mods | win::Mod::Ctrl; + } + if (pressedKeys_[VK_MENU]) { + mods = mods | win::Mod::Alt; + } + if (pressedKeys_[VK_LWIN]) { + mods = mods | win::Mod::Win; + } + return mods; + } + + + Hotkeys::Hotkey::Hotkey(win::Key key, win::ModSet mods) + : + key{ key }, + mods{ mods } + {} + bool Hotkeys::Hotkey::operator<(const Hotkey& rhs) const + { + return key == rhs.key ? (mods < rhs.mods) : (key < rhs.key); + } + bool Hotkeys::Hotkey::operator==(const Hotkey& rhs) const + { + return key == rhs.key && mods == rhs.mods; + } +} diff --git a/IntelPresentMon/Core/source/win/HotkeyListener.h b/IntelPresentMon/Core/source/win/HotkeyListener.h new file mode 100644 index 000000000..1420a71d2 --- /dev/null +++ b/IntelPresentMon/Core/source/win/HotkeyListener.h @@ -0,0 +1,55 @@ +// Copyright (C) 2022 Intel Corporation +// SPDX-License-Identifier: MIT +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace p2c::win +{ + class Hotkeys + { + public: + Hotkeys(); + Hotkeys(const Hotkeys&) = delete; + Hotkeys& operator=(const Hotkeys&) = delete; + ~Hotkeys(); + bool BindAction(int action, win::Key key, win::ModSet mods); + bool ClearAction(int action); + void SetHandler(std::function handler); + private: + // types + struct Hotkey + { + public: + Hotkey(win::Key key, win::ModSet mods); + bool operator<(const Hotkey& rhs) const; + bool operator==(const Hotkey& rhs) const; + private: + win::Key key; + win::ModSet mods; + }; + // functions + void Kernel_() noexcept; + void DispatchHotkey_(int action) const; + win::ModSet GatherModifiers_() const; + // data + std::binary_semaphore startupSemaphore_{ 0 }; + ::pmon::util::mt::Thread thread_; + unsigned long threadId_{}; + void* messageWindowHandle_ = nullptr; + std::bitset pressedKeys_; + // control access to concurrent memory for key map / handler + mutable std::mutex mtx_; + std::function Handler_; + std::map registeredHotkeys_; + }; +} \ No newline at end of file From 6bc4b1f06ef92e5b775ea902e028700d50a36e3b Mon Sep 17 00:00:00 2001 From: Chili Date: Wed, 21 May 2025 18:17:25 +0900 Subject: [PATCH 2/2] move hotkey to kproc for uiaccess and marshall between cef via actions --- IntelPresentMon/AppCef/CefNano.vcxproj | 5 +- .../AppCef/ipm-ui-vue/src/core/api.ts | 7 +- .../AppCef/source/DataBindAccessor.cpp | 47 +--- .../AppCef/source/DataBindAccessor.h | 2 - .../source/util/AsyncEndpointCollection.cpp | 4 - .../AppCef/source/util/HotkeyListener.cpp | 256 ------------------ .../AppCef/source/util/HotkeyListener.h | 56 ---- .../source/util/KernelActionRegistration.cpp | 2 + .../AppCef/source/util/KernelWrapper.h | 2 - .../AppCef/source/util/async/BindHotkey.h | 24 -- .../AppCef/source/util/async/ClearHotkey.h | 24 -- .../AppCef/source/util/async/EnumerateKeys.h | 2 + .../source/util/async/EnumerateProcesses.h | 4 + .../util/cact/CefActionRegistration.cpp | 3 +- .../source/util/cact/HotkeyFiredAction.h | 59 ++++ IntelPresentMon/Core/source/win/ModSet.cpp | 9 + IntelPresentMon/Core/source/win/ModSet.h | 1 + .../KernelProcess/KernelProcess.vcxproj | 2 + .../KernelProcess.vcxproj.filters | 6 + .../KernelProcess/kact/AllActions.h | 4 +- .../KernelProcess/kact/BindHotkey.h | 60 ++++ .../KernelProcess/kact/ClearHotkey.h | 52 ++++ .../kact/KernelExecutionContext.h | 2 + IntelPresentMon/KernelProcess/winmain.cpp | 11 +- 24 files changed, 218 insertions(+), 426 deletions(-) delete mode 100644 IntelPresentMon/AppCef/source/util/HotkeyListener.cpp delete mode 100644 IntelPresentMon/AppCef/source/util/HotkeyListener.h delete mode 100644 IntelPresentMon/AppCef/source/util/async/BindHotkey.h delete mode 100644 IntelPresentMon/AppCef/source/util/async/ClearHotkey.h create mode 100644 IntelPresentMon/AppCef/source/util/cact/HotkeyFiredAction.h create mode 100644 IntelPresentMon/KernelProcess/kact/BindHotkey.h create mode 100644 IntelPresentMon/KernelProcess/kact/ClearHotkey.h diff --git a/IntelPresentMon/AppCef/CefNano.vcxproj b/IntelPresentMon/AppCef/CefNano.vcxproj index 835029e34..8464054f2 100644 --- a/IntelPresentMon/AppCef/CefNano.vcxproj +++ b/IntelPresentMon/AppCef/CefNano.vcxproj @@ -23,7 +23,6 @@ - @@ -44,6 +43,7 @@ + @@ -60,8 +60,6 @@ - - @@ -75,7 +73,6 @@ - diff --git a/IntelPresentMon/AppCef/ipm-ui-vue/src/core/api.ts b/IntelPresentMon/AppCef/ipm-ui-vue/src/core/api.ts index b9ad66dc8..c00fe5ead 100644 --- a/IntelPresentMon/AppCef/ipm-ui-vue/src/core/api.ts +++ b/IntelPresentMon/AppCef/ipm-ui-vue/src/core/api.ts @@ -81,13 +81,10 @@ export class Api { return adapters; } static async bindHotkey(binding: Binding): Promise { - await this.invokeEndpointFuture('bindHotkey', binding); + await this.invokeEndpointFuture('BindHotkey', binding); } static async clearHotkey(action: Action): Promise { - await this.invokeEndpointFuture('clearHotkey', {action}); - } - static async launchKernel(): Promise { - await this.invokeEndpointFuture('launchKernel', {}); + await this.invokeEndpointFuture('ClearHotkey', {action}); } static async pushSpecification(spec: Spec): Promise { await this.invokeEndpointFuture('PushSpecification', spec); diff --git a/IntelPresentMon/AppCef/source/DataBindAccessor.cpp b/IntelPresentMon/AppCef/source/DataBindAccessor.cpp index abe7bb5fd..0812d7802 100644 --- a/IntelPresentMon/AppCef/source/DataBindAccessor.cpp +++ b/IntelPresentMon/AppCef/source/DataBindAccessor.cpp @@ -20,14 +20,7 @@ namespace p2c::client::cef : pBrowser{ std::move(pBrowser) }, pKernelWrapper{ pKernelWrapper_ } - { - pKernelWrapper->pHotkeys = std::make_unique(); - // set the hotkey listener component to call hotkey signal on the signal manager when a hotkey chord is detected - pKernelWrapper->pHotkeys->SetHandler([this](Action action) { - CefPostTask(TID_RENDERER, base::BindOnce(&util::SignalManager::SignalHotkeyFired, - base::Unretained(&pKernelWrapper->signals), uint32_t(action))); - }); - } + {} bool DataBindAccessor::Execute(const CefString& name, CefRefPtr object, const CefV8ValueList& arguments, CefRefPtr& retval, CefString& exception) { @@ -88,44 +81,6 @@ namespace p2c::client::cef } } - bool DataBindAccessor::BindHotkey(CefValue& pArgObj) - { - // {action:int, combination: {modifiers:[], key:int}} - - std::shared_lock lk{ kernelMtx }; - if (pKernelWrapper) { - const auto payload = pArgObj.GetDictionary(); - const auto comboJs = payload->GetDictionary("combination"); - const auto modsJs = comboJs->GetList("modifiers"); - - auto mods = win::Mod::Null; - for (int i = 0; i < modsJs->GetSize(); i++) { - const auto modCode = modsJs->GetValue(i)->GetInt(); - mods = mods | *win::ModSet::SingleModFromCode(modCode); - } - - return pKernelWrapper->pHotkeys->BindAction( - (Action)payload->GetInt("action"), - win::Key{ (win::Key::Code)comboJs->GetInt("key") }, - mods - ); - } - return false; - } - - bool DataBindAccessor::ClearHotkey(CefValue& pArgObj) - { - // {action:int} - - std::shared_lock lk{ kernelMtx }; - if (pKernelWrapper) { - return pKernelWrapper->pHotkeys->ClearAction( - (Action)pArgObj.GetDictionary()->GetInt("action") - ); - } - return false; - } - void DataBindAccessor::ClearKernelWrapper() { std::lock_guard lk{ kernelMtx }; diff --git a/IntelPresentMon/AppCef/source/DataBindAccessor.h b/IntelPresentMon/AppCef/source/DataBindAccessor.h index f2584a476..ced6059fe 100644 --- a/IntelPresentMon/AppCef/source/DataBindAccessor.h +++ b/IntelPresentMon/AppCef/source/DataBindAccessor.h @@ -19,8 +19,6 @@ namespace p2c::client::cef CefRefPtr& retval, CefString& exception) override; void ResolveAsyncEndpoint(uint64_t uid, bool success, CefRefPtr pArgs); - bool BindHotkey(CefValue& pArgObj); - bool ClearHotkey(CefValue& pArgObj); void ClearKernelWrapper(); private: // data diff --git a/IntelPresentMon/AppCef/source/util/AsyncEndpointCollection.cpp b/IntelPresentMon/AppCef/source/util/AsyncEndpointCollection.cpp index ebcfa3ace..bc638ca16 100644 --- a/IntelPresentMon/AppCef/source/util/AsyncEndpointCollection.cpp +++ b/IntelPresentMon/AppCef/source/util/AsyncEndpointCollection.cpp @@ -6,8 +6,6 @@ #include "async/BrowseReadSpec.h" #include "async/BrowseStoreSpec.h" -#include "async/BindHotkey.h" -#include "async/ClearHotkey.h" #include "async/EnumerateProcesses.h" #include "async/EnumerateKeys.h" #include "async/EnumerateModifiers.h" @@ -34,8 +32,6 @@ namespace p2c::client::util using namespace async; AddEndpoint(); AddEndpoint(); - AddEndpoint(); - AddEndpoint(); AddEndpoint(); AddEndpoint(); AddEndpoint(); diff --git a/IntelPresentMon/AppCef/source/util/HotkeyListener.cpp b/IntelPresentMon/AppCef/source/util/HotkeyListener.cpp deleted file mode 100644 index 5dec741c5..000000000 --- a/IntelPresentMon/AppCef/source/util/HotkeyListener.cpp +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright (C) 2022 Intel Corporation -// SPDX-License-Identifier: MIT -#include "HotkeyListener.h" -#include "Logging.h" -#include -#include -#include -#include -#include "include/base/cef_callback.h" -#include "include/wrapper/cef_closure_task.h" - -namespace rn = std::ranges; -namespace vi = rn::views; -using namespace pmon::util; - -// TODO: general logging in this codebase (winapi calls like PostThreadMessage etc.) - -namespace p2c::client::util -{ - Hotkeys::Hotkeys() - : - thread_{ "hotkey", & Hotkeys::Kernel_, this} - { - startupSemaphore_.acquire(); - pmlog_verb(v::hotkey)("Hotkey process ctor complete"); - } - Hotkeys::~Hotkeys() - { - pmlog_verb(v::hotkey)("Destroying hotkey processor"); - PostThreadMessageA(threadId_, WM_QUIT, 0, 0); - } - void Hotkeys::Kernel_() noexcept - { - try { - pmlog_verb(v::hotkey)("Hotkey processor kernel start"); - - // capture thread id - threadId_ = GetCurrentThreadId(); - - // create message window class - const WNDCLASSEXA wx = { - .cbSize = sizeof(WNDCLASSEX), - .lpfnWndProc = DefWindowProc, - .hInstance = GetModuleHandle(nullptr), - .lpszClassName = "$PresentmonMessageWindowClass$", - }; - const auto atom = RegisterClassEx(&wx); - if (!atom) { - pmlog_error().hr(); - return; - } - - pmlog_verb(v::hotkey)(std::format("Hotkey processor wndclass registered: {:X}", atom)); - - // create message window - messageWindowHandle_ = CreateWindowExA( - 0, MAKEINTATOM(atom), "$PresentmonMessageWindow$", - 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, nullptr, nullptr - ); - if (!messageWindowHandle_) { - pmlog_error().hr(); - return; - } - - pmlog_verb(v::hotkey)(std::format("Hotkey processor wnd created: {:X}", - reinterpret_cast(messageWindowHandle_))); - - // register to receive raw keyboard input - const RAWINPUTDEVICE rid{ - .usUsagePage = 0x01, - .usUsage = 0x06, - .dwFlags = RIDEV_INPUTSINK, - .hwndTarget = static_cast(messageWindowHandle_), - }; - if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) { - pmlog_error().hr(); - return; - } - - pmlog_verb(v::hotkey)("Raw input registered"); - - // signal that constuction is complete - startupSemaphore_.release(); - - MSG msg; - while (GetMessageA(&msg, nullptr, 0, 0)) { - if (msg.message == WM_INPUT) { - pmlog_verb(v::hotkey)("WM_INPUT received"); - - auto& lParam = msg.lParam; - // allocate memory for raw input data - UINT dwSize{}; - if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, nullptr, &dwSize, sizeof(RAWINPUTHEADER))) { - pmlog_error().hr(); - return; - } - const auto inputBackingBuffer = std::make_unique(dwSize); - - // read in raw input data - if (const auto size = GetRawInputData((HRAWINPUT)lParam, RID_INPUT, inputBackingBuffer.get(), - &dwSize, sizeof(RAWINPUTHEADER)); size != dwSize) { - pmlog_error().hr(); - return; - } - const auto& input = reinterpret_cast(*inputBackingBuffer.get()); - - // handle raw input data for keyboard device - if (input.header.dwType == RIM_TYPEKEYBOARD) - { - const auto& keyboard = input.data.keyboard; - - pmlog_verb(v::hotkey)(std::format("KBD| vk:{} msg:{}", keyboard.VKey, keyboard.Message)); - - // check for keycode outside of range of our table - if (keyboard.VKey >= win::Key::virtualKeyTableSize) { - pmlog_verb(v::hotkey)("KBD| vk out of range"); - continue; - } - // key presses - if (keyboard.Message == WM_KEYDOWN || keyboard.Message == WM_SYSKEYDOWN) { - pmlog_verb(v::hotkey)("key press"); - // don't handle autorepeat presses - if (!pressedKeys_[keyboard.VKey]) { - // mark key as in down state - pressedKeys_[keyboard.VKey] = true; - // if key is not modifier - if (const auto key = win::Key::FromPlatformCode(keyboard.VKey)) { - if (!key->IsModifierKey()) { - // gather currently pressed modifiers - const auto mods = GatherModifiers_(); - // check for matching registered hotkey action - std::lock_guard lk{ mtx_ }; - if (auto i = registeredHotkeys_.find({ *key, mods }); - i != registeredHotkeys_.cend()) { - // if match found dispatch action on renderer thread - pmlog_verb(v::hotkey)("hotkey dispatching"); - DispatchHotkey_(i->second); - } - } - } - } - else { - pmlog_verb(v::hotkey)("key repeat"); - } - } - // key releases - else if (keyboard.Message == WM_KEYUP || keyboard.Message == WM_SYSKEYUP) { - pmlog_verb(v::hotkey)("Key up"); - pressedKeys_[keyboard.VKey] = false; - } - } - else { - pmlog_warn("Unknown raw input type"); - } - } - } - - if (!DestroyWindow(static_cast(messageWindowHandle_))) { - pmlog_warn("failed window destroy").hr(); - } - if (!UnregisterClass(MAKEINTATOM(atom), GetModuleHandle(nullptr))) { - pmlog_warn("failed unreg class").hr(); - } - - pmlog_verb(v::hotkey)(); - } - catch (...) { - pmlog_error(ReportException()); - } - } - void Hotkeys::DispatchHotkey_(Action action) const - { - if (Handler_) { - pmlog_dbg("hotkey action dispatched to handler").pmwatch(reflect::enum_name(action)); - Handler_(action); - } - else { - pmlog_warn("Hotkey handler not set"); - } - } - void Hotkeys::SetHandler(std::function handler) - { - std::lock_guard lk{ mtx_ }; - pmlog_verb(v::hotkey)("Hotkey handler set"); - Handler_ = std::move(handler); - } - bool Hotkeys::BindAction(Action action, win::Key key, win::ModSet mods) - { - pmlog_verb(v::hotkey)("Hotkey action binding"); - std::lock_guard lk{ mtx_ }; - // if action is already bound, remove it - if (const auto i = rn::find_if(registeredHotkeys_, [action](const auto& i) { - return i.second == action; - }); i != registeredHotkeys_.cend()) { - pmlog_verb(v::hotkey)("Hotkey action binding remove existing action"); - registeredHotkeys_.erase(i); - } - // if hotkey combination is already bound, remove it - registeredHotkeys_.erase({ key, mods }); - // bind action to new hotkey combination - registeredHotkeys_.emplace(std::piecewise_construct, - std::forward_as_tuple(key, mods), - std::forward_as_tuple(action) - ); - // signal success - return true; - } - bool Hotkeys::ClearAction(Action action) - { - pmlog_verb(v::hotkey)("Hotkey action clearing"); - std::lock_guard lk{ mtx_ }; - // if action is bound, remove it - if (const auto i = rn::find_if(registeredHotkeys_, [action](const auto& i) { - return i.second == action; - }); i != registeredHotkeys_.cend()) { - registeredHotkeys_.erase(i); - } - else { - pmlog_warn("Attempted to clear unregistered hotkey"); - } - // signal success - return true; - } - win::ModSet Hotkeys::GatherModifiers_() const - { - win::ModSet mods; - if (pressedKeys_[VK_SHIFT]) { - mods = mods | win::Mod::Shift; - } - if (pressedKeys_[VK_CONTROL]) { - mods = mods | win::Mod::Ctrl; - } - if (pressedKeys_[VK_MENU]) { - mods = mods | win::Mod::Alt; - } - if (pressedKeys_[VK_LWIN]) { - mods = mods | win::Mod::Win; - } - return mods; - } - - - Hotkeys::Hotkey::Hotkey(win::Key key, win::ModSet mods) - : - key{ key }, - mods{ mods } - {} - bool Hotkeys::Hotkey::operator<(const Hotkey& rhs) const - { - return key == rhs.key ? (mods < rhs.mods) : (key < rhs.key); - } - bool Hotkeys::Hotkey::operator==(const Hotkey& rhs) const - { - return key == rhs.key && mods == rhs.mods; - } -} diff --git a/IntelPresentMon/AppCef/source/util/HotkeyListener.h b/IntelPresentMon/AppCef/source/util/HotkeyListener.h deleted file mode 100644 index 10caf0365..000000000 --- a/IntelPresentMon/AppCef/source/util/HotkeyListener.h +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (C) 2022 Intel Corporation -// SPDX-License-Identifier: MIT -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "../Action.h" - -namespace p2c::client::util -{ - class Hotkeys - { - public: - Hotkeys(); - Hotkeys(const Hotkeys&) = delete; - Hotkeys& operator=(const Hotkeys&) = delete; - ~Hotkeys(); - bool BindAction(Action action, win::Key key, win::ModSet mods); - bool ClearAction(Action action); - void SetHandler(std::function handler); - private: - // types - struct Hotkey - { - public: - Hotkey(win::Key key, win::ModSet mods); - bool operator<(const Hotkey& rhs) const; - bool operator==(const Hotkey& rhs) const; - private: - win::Key key; - win::ModSet mods; - }; - // functions - void Kernel_() noexcept; - void DispatchHotkey_(Action action) const; - win::ModSet GatherModifiers_() const; - // data - std::binary_semaphore startupSemaphore_{ 0 }; - ::pmon::util::mt::Thread thread_; - unsigned long threadId_{}; - void* messageWindowHandle_ = nullptr; - std::bitset pressedKeys_; - // control access to concurrent memory for key map / handler - mutable std::mutex mtx_; - std::function Handler_; - std::map registeredHotkeys_; - }; -} \ No newline at end of file diff --git a/IntelPresentMon/AppCef/source/util/KernelActionRegistration.cpp b/IntelPresentMon/AppCef/source/util/KernelActionRegistration.cpp index 2b5ba9f03..55e8ca22e 100644 --- a/IntelPresentMon/AppCef/source/util/KernelActionRegistration.cpp +++ b/IntelPresentMon/AppCef/source/util/KernelActionRegistration.cpp @@ -11,5 +11,7 @@ namespace p2c::client::util::kact { IpcActionRegistrator reg_ibind_PushSpecification_; IpcActionRegistrator reg_ibind_SetAdapter_; IpcActionRegistrator reg_ibind_SetCapture_; + IpcActionRegistrator reg_ibind_BindHotkey_; + IpcActionRegistrator reg_ibind_ClearHotkey_; } diff --git a/IntelPresentMon/AppCef/source/util/KernelWrapper.h b/IntelPresentMon/AppCef/source/util/KernelWrapper.h index 7f997c64b..874c86fb2 100644 --- a/IntelPresentMon/AppCef/source/util/KernelWrapper.h +++ b/IntelPresentMon/AppCef/source/util/KernelWrapper.h @@ -1,6 +1,5 @@ #pragma once #include -#include "HotkeyListener.h" #include "SignalManager.h" #include "AsyncEndpointManager.h" #include "ActionClientServer.h" @@ -11,7 +10,6 @@ namespace p2c::client::util { struct KernelWrapper { - std::unique_ptr pHotkeys; util::SignalManager signals; util::AsyncEndpointManager asyncEndpoints; std::unique_ptr pClient; diff --git a/IntelPresentMon/AppCef/source/util/async/BindHotkey.h b/IntelPresentMon/AppCef/source/util/async/BindHotkey.h deleted file mode 100644 index 5f835e449..000000000 --- a/IntelPresentMon/AppCef/source/util/async/BindHotkey.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (C) 2022 Intel Corporation -// SPDX-License-Identifier: MIT -#pragma once -#include "../AsyncEndpoint.h" -#include -#include "include/base/cef_callback.h" -#include "include/wrapper/cef_closure_task.h" -#include "../../DataBindAccessor.h" -#include "../CefValues.h" - -namespace p2c::client::util::async -{ - class BindHotkey : public AsyncEndpoint - { - public: - static constexpr std::string GetKey() { return "bindHotkey"; } - BindHotkey() : AsyncEndpoint{ AsyncEndpoint::Environment::RenderProcess } {} - // {combination: {modifiers:[], hotkey:int}, action:int} => null - Result ExecuteOnRenderer(uint64_t uid, CefRefPtr pArgObj, cef::DataBindAccessor& accessor) const override - { - return Result{ accessor.BindHotkey(*pArgObj), CefValueNull() }; - } - }; -} \ No newline at end of file diff --git a/IntelPresentMon/AppCef/source/util/async/ClearHotkey.h b/IntelPresentMon/AppCef/source/util/async/ClearHotkey.h deleted file mode 100644 index 53f12d9b8..000000000 --- a/IntelPresentMon/AppCef/source/util/async/ClearHotkey.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (C) 2022 Intel Corporation -// SPDX-License-Identifier: MIT -#pragma once -#include "../AsyncEndpoint.h" -#include -#include "include/base/cef_callback.h" -#include "include/wrapper/cef_closure_task.h" -#include "../../DataBindAccessor.h" -#include "../CefValues.h" - -namespace p2c::client::util::async -{ - class ClearHotkey : public AsyncEndpoint - { - public: - static constexpr std::string GetKey() { return "clearHotkey"; } - ClearHotkey() : AsyncEndpoint{ AsyncEndpoint::Environment::RenderProcess } {} - // {action:int} => null - Result ExecuteOnRenderer(uint64_t uid, CefRefPtr pArgObj, cef::DataBindAccessor& accessor) const override - { - return Result{ accessor.ClearHotkey(*pArgObj), CefValueNull() }; - } - }; -} \ No newline at end of file diff --git a/IntelPresentMon/AppCef/source/util/async/EnumerateKeys.h b/IntelPresentMon/AppCef/source/util/async/EnumerateKeys.h index 28e23a5d3..bcd2b40c1 100644 --- a/IntelPresentMon/AppCef/source/util/async/EnumerateKeys.h +++ b/IntelPresentMon/AppCef/source/util/async/EnumerateKeys.h @@ -3,6 +3,8 @@ #pragma once #include "../AsyncEndpoint.h" #include "../CefValues.h" +#include +#include namespace p2c::client::util::async { diff --git a/IntelPresentMon/AppCef/source/util/async/EnumerateProcesses.h b/IntelPresentMon/AppCef/source/util/async/EnumerateProcesses.h index 1cfa04ba4..fd86304d6 100644 --- a/IntelPresentMon/AppCef/source/util/async/EnumerateProcesses.h +++ b/IntelPresentMon/AppCef/source/util/async/EnumerateProcesses.h @@ -2,8 +2,11 @@ // SPDX-License-Identifier: MIT #pragma once #include +#include +#include #include "../AsyncEndpoint.h" #include "../CefValues.h" +#include namespace p2c::client::util::async { @@ -15,6 +18,7 @@ namespace p2c::client::util::async // {} => {processes: [{name: string, pid: uint}]} Result ExecuteOnRenderer(uint64_t uid, CefRefPtr pArgObj, cef::DataBindAccessor&) const override { + namespace vi = std::views; // enumerate processes on system win::ProcessMapBuilder builder; builder.FillWindowHandles(); diff --git a/IntelPresentMon/AppCef/source/util/cact/CefActionRegistration.cpp b/IntelPresentMon/AppCef/source/util/cact/CefActionRegistration.cpp index 78e44eda6..51b019791 100644 --- a/IntelPresentMon/AppCef/source/util/cact/CefActionRegistration.cpp +++ b/IntelPresentMon/AppCef/source/util/cact/CefActionRegistration.cpp @@ -2,4 +2,5 @@ #include "OverlayDiedAction.h" #include "PresentmonInitFailedAction.h" #include "StalePidAction.h" -#include "TargetLostAction.h" \ No newline at end of file +#include "TargetLostAction.h" +#include "HotkeyFiredAction.h" \ No newline at end of file diff --git a/IntelPresentMon/AppCef/source/util/cact/HotkeyFiredAction.h b/IntelPresentMon/AppCef/source/util/cact/HotkeyFiredAction.h new file mode 100644 index 000000000..1d6edd037 --- /dev/null +++ b/IntelPresentMon/AppCef/source/util/cact/HotkeyFiredAction.h @@ -0,0 +1,59 @@ +#pragma once +#include +#include "CefExecutionContext.h" +#include "../../Action.h" +#include + +#define ACT_NAME HotkeyFiredAction +#define ACT_EXEC_CTX CefExecutionContext +#define ACT_TYPE AsyncEventActionBase_ +#define ACT_NS ::p2c::client::util::cact + +namespace p2c::client::util::cact +{ + using namespace ::pmon::ipc::act; + + class ACT_NAME : public ACT_TYPE + { + public: + static constexpr const char* Identifier = STRINGIFY(ACT_NAME); + struct Params + { + int32_t actionId; + + template void serialize(A& ar) { + ar(actionId); + } + }; + private: + friend class ACT_TYPE; + static void Execute_(const ACT_EXEC_CTX& ctx, SessionContext& stx, Params&& in); + }; +} + +#ifdef PM_ASYNC_ACTION_REGISTRATION_ +#include +#include +#include +#include "../SignalManager.h" +namespace p2c::client::util::cact +{ + ACTION_REG(); + // need to put this function definition only in the cef-server-side since it contains + // references to CEF types we do not want to pollute other projects with + // TODO: consider a better approach to maintaining single-file actions like this + // while also enabling dependency decoupling more easily + void ACT_NAME::Execute_(const ACT_EXEC_CTX& ctx, SessionContext& stx, Params&& in) + { + CefPostTask(TID_RENDERER, base::BindOnce(&SignalManager::SignalHotkeyFired, + base::Unretained(ctx.pSignalManager), in.actionId)); + } +} +#endif + +ACTION_TRAITS_DEF(); + +#undef ACT_NAME +#undef ACT_EXEC_CTX +#undef ACT_NS +#undef ACT_TYPE \ No newline at end of file diff --git a/IntelPresentMon/Core/source/win/ModSet.cpp b/IntelPresentMon/Core/source/win/ModSet.cpp index 413254e45..39743310f 100644 --- a/IntelPresentMon/Core/source/win/ModSet.cpp +++ b/IntelPresentMon/Core/source/win/ModSet.cpp @@ -140,4 +140,13 @@ namespace p2c::win { return Registry::Get().EnumerateMods(); } + + ModSet ModSet::FromCodes(const std::vector& codes) + { + uint32_t combined = 0; + for (auto& code : codes) { + combined |= code; + } + return { combined }; + } } \ No newline at end of file diff --git a/IntelPresentMon/Core/source/win/ModSet.h b/IntelPresentMon/Core/source/win/ModSet.h index e91505218..07f2db885 100644 --- a/IntelPresentMon/Core/source/win/ModSet.h +++ b/IntelPresentMon/Core/source/win/ModSet.h @@ -30,6 +30,7 @@ namespace p2c::win std::vector GetModStrings(); static std::optional FromString(const std::string& s); static std::optional SingleModFromCode(uint32_t p); + static ModSet FromCodes(const std::vector& codes); static std::vector EnumerateMods(); private: // types diff --git a/IntelPresentMon/KernelProcess/KernelProcess.vcxproj b/IntelPresentMon/KernelProcess/KernelProcess.vcxproj index ea9ca4985..eab27aae7 100644 --- a/IntelPresentMon/KernelProcess/KernelProcess.vcxproj +++ b/IntelPresentMon/KernelProcess/KernelProcess.vcxproj @@ -158,6 +158,8 @@ + + diff --git a/IntelPresentMon/KernelProcess/KernelProcess.vcxproj.filters b/IntelPresentMon/KernelProcess/KernelProcess.vcxproj.filters index cfbbb7c13..9a313d175 100644 --- a/IntelPresentMon/KernelProcess/KernelProcess.vcxproj.filters +++ b/IntelPresentMon/KernelProcess/KernelProcess.vcxproj.filters @@ -56,6 +56,12 @@ Header Files + + Header Files + + + Header Files + diff --git a/IntelPresentMon/KernelProcess/kact/AllActions.h b/IntelPresentMon/KernelProcess/kact/AllActions.h index f4a4c95ad..f3baf7df0 100644 --- a/IntelPresentMon/KernelProcess/kact/AllActions.h +++ b/IntelPresentMon/KernelProcess/kact/AllActions.h @@ -4,4 +4,6 @@ #include "SetCapture.h" #include "EnumerateAdapters.h" #include "Introspect.h" -#include "PushSpecification.h" \ No newline at end of file +#include "PushSpecification.h" +#include "BindHotkey.h" +#include "ClearHotkey.h" \ No newline at end of file diff --git a/IntelPresentMon/KernelProcess/kact/BindHotkey.h b/IntelPresentMon/KernelProcess/kact/BindHotkey.h new file mode 100644 index 000000000..ccfcc0483 --- /dev/null +++ b/IntelPresentMon/KernelProcess/kact/BindHotkey.h @@ -0,0 +1,60 @@ +#pragma once +#include "../../Interprocess/source/act/ActionHelper.h" +#include "KernelExecutionContext.h" +#include +#include "../../Core/source/kernel/Kernel.h" + +#define ACT_NAME BindHotkey +#define ACT_EXEC_CTX KernelExecutionContext +#define ACT_TYPE AsyncActionBase_ +#define ACT_NS kproc::kact + +namespace ACT_NS +{ + using namespace ::pmon::ipc::act; + + class ACT_NAME : public ACT_TYPE + { + public: + static constexpr const char* Identifier = STRINGIFY(ACT_NAME); + struct Params + { + struct Combination { + uint32_t key; + std::vector modifiers; + template void serialize(A& ar) { + ar(key, modifiers); + } + } combination; + int action; + + template void serialize(A& ar) { + ar(combination, action); + } + }; + struct Response { + template void serialize(A& ar) { + } + }; + private: + friend class ACT_TYPE; + static Response Execute_(const ACT_EXEC_CTX& ctx, SessionContext& stx, Params&& in) + { + ctx.pHotkeys->BindAction(in.action, + p2c::win::Key::Code(in.combination.key), + p2c::win::ModSet::FromCodes(in.combination.modifiers)); + return {}; + } + }; + +#ifdef PM_ASYNC_ACTION_REGISTRATION_ + ACTION_REG(); +#endif +} + +ACTION_TRAITS_DEF(); + +#undef ACT_NAME +#undef ACT_EXEC_CTX +#undef ACT_NS +#undef ACT_TYPE \ No newline at end of file diff --git a/IntelPresentMon/KernelProcess/kact/ClearHotkey.h b/IntelPresentMon/KernelProcess/kact/ClearHotkey.h new file mode 100644 index 000000000..3041ba705 --- /dev/null +++ b/IntelPresentMon/KernelProcess/kact/ClearHotkey.h @@ -0,0 +1,52 @@ +#pragma once +#include "../../Interprocess/source/act/ActionHelper.h" +#include "KernelExecutionContext.h" +#include +#include "../../Core/source/kernel/Kernel.h" + +#define ACT_NAME ClearHotkey +#define ACT_EXEC_CTX KernelExecutionContext +#define ACT_TYPE AsyncActionBase_ +#define ACT_NS kproc::kact + +namespace ACT_NS +{ + using namespace ::pmon::ipc::act; + + class ACT_NAME : public ACT_TYPE + { + public: + static constexpr const char* Identifier = STRINGIFY(ACT_NAME); + + struct Params + { + int action; + + template void serialize(A& ar) { + ar(action); + } + }; + struct Response { + template void serialize(A& ar) { + } + }; + private: + friend class ACT_TYPE; + static Response Execute_(const ACT_EXEC_CTX& ctx, SessionContext& stx, Params&& in) + { + ctx.pHotkeys->ClearAction(in.action); + return {}; + } + }; + +#ifdef PM_ASYNC_ACTION_REGISTRATION_ + ACTION_REG(); +#endif +} + +ACTION_TRAITS_DEF(); + +#undef ACT_NAME +#undef ACT_EXEC_CTX +#undef ACT_NS +#undef ACT_TYPE \ No newline at end of file diff --git a/IntelPresentMon/KernelProcess/kact/KernelExecutionContext.h b/IntelPresentMon/KernelProcess/kact/KernelExecutionContext.h index 995b0d6c9..b3abcdcad 100644 --- a/IntelPresentMon/KernelProcess/kact/KernelExecutionContext.h +++ b/IntelPresentMon/KernelProcess/kact/KernelExecutionContext.h @@ -9,6 +9,7 @@ #include #include #include "../../Core/source/kernel/Kernel.h" +#include "../../Core/source/win/HotkeyListener.h" namespace kproc::kact { @@ -32,5 +33,6 @@ namespace kproc::kact std::optional responseWriteTimeoutMs; p2c::kern::Kernel** ppKernel = nullptr; + p2c::win::Hotkeys* pHotkeys = nullptr; }; } \ No newline at end of file diff --git a/IntelPresentMon/KernelProcess/winmain.cpp b/IntelPresentMon/KernelProcess/winmain.cpp index b75b1fdf1..2c202f5b6 100644 --- a/IntelPresentMon/KernelProcess/winmain.cpp +++ b/IntelPresentMon/KernelProcess/winmain.cpp @@ -7,6 +7,7 @@ #include "../AppCef/source/util/cact/OverlayDiedAction.h" #include "../AppCef/source/util/cact/PresentmonInitFailedAction.h" #include "../AppCef/source/util/cact/StalePidAction.h" +#include "../AppCef/source/util/cact/HotkeyFiredAction.h" #include #include #include @@ -149,9 +150,17 @@ int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLi // this pointer serves as a way to set the kernel on the server exec context after the server is created p2c::kern::Kernel* pKernel = nullptr; + // active object that creates a window and sinks raw input messages to listen for hotkey presses + p2c::win::Hotkeys hotkeys; // this server receives a connection from the CEF render process const auto actName = std::format(R"(\\.\pipe\ipm-cef-channel-{})", GetCurrentProcessId()); - KernelServer server{ kact::KernelExecutionContext{.ppKernel = &pKernel }, actName, 1, "D:(A;;GA;;;WD)S:(ML;;NW;;;ME)" }; + KernelServer server{ kact::KernelExecutionContext{ .ppKernel = &pKernel, .pHotkeys = &hotkeys }, + actName, 1, "D:(A;;GA;;;WD)S:(ML;;NW;;;ME)" }; + // set the hotkey manager to send notifications via the action server + hotkeys.SetHandler([&](int action) { + pmlog_info("hey hot"); + server.DispatchDetached(p2c::client::util::cact::HotkeyFiredAction::Params{ .actionId = action }); + }); // this handler receives events from the kernel and transmits them to the render process via the server KernelHandler kernHandler{ server }; // the kernel manages the PresentMon data collection and the overlay rendering