From fb4564c78ecb8c5e9d6b65fc55cf33e982c75b93 Mon Sep 17 00:00:00 2001 From: Curve Date: Sun, 5 May 2024 22:47:11 +0200 Subject: [PATCH] feat: add support for radio buttons --- CMakeLists.txt | 2 +- include/{checkbox.hpp => checkable.hpp} | 14 +++++--- include/uia.hpp | 10 +++--- src/{checkbox.cpp => checkable.cpp} | 33 ++++++++--------- src/patch.cpp | 48 ++++++++++++++++++------- src/uia.cpp | 41 +++++++++++---------- 6 files changed, 91 insertions(+), 57 deletions(-) rename include/{checkbox.hpp => checkable.hpp} (72%) rename src/{checkbox.cpp => checkable.cpp} (77%) diff --git a/CMakeLists.txt b/CMakeLists.txt index fb818f5..e8d314e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,7 +53,7 @@ CPMFindPackage( CPMFindPackage( NAME lime - VERSION 3.0 + VERSION 3.1 GIT_REPOSITORY "https://github.com/Curve/lime" ) diff --git a/include/checkbox.hpp b/include/checkable.hpp similarity index 72% rename from include/checkbox.hpp rename to include/checkable.hpp index 4b7268c..ff8a369 100644 --- a/include/checkbox.hpp +++ b/include/checkable.hpp @@ -10,7 +10,13 @@ namespace simplytest using profuis::box_state; using profuis::checkable_data; - class checkbox + enum class checkable_type + { + check, + radio, + }; + + class checkable { struct impl; @@ -18,10 +24,10 @@ namespace simplytest std::unique_ptr m_impl; private: - checkbox(); + checkable(); public: - ~checkbox(); + ~checkable(); public: void toggle() const; @@ -32,6 +38,6 @@ namespace simplytest [[nodiscard]] box_state state() const; public: - static void update(checkable_data *); + static void update(checkable_data *, checkable_type); }; } // namespace simplytest diff --git a/include/uia.hpp b/include/uia.hpp index f186526..651f3fe 100644 --- a/include/uia.hpp +++ b/include/uia.hpp @@ -1,11 +1,13 @@ #pragma once +#include "checkable.hpp" + #include #include namespace simplytest { - class checkbox; + class checkable; class uia_core { @@ -23,7 +25,7 @@ namespace simplytest static uia_core &get(); }; - class checkbox_uia : public IRawElementProviderSimple, public IUIAutomationTogglePattern, public IToggleProvider + class toggle_uia : public IRawElementProviderSimple, public IUIAutomationTogglePattern, public IToggleProvider { struct impl; @@ -31,10 +33,10 @@ namespace simplytest std::unique_ptr m_impl; public: - virtual ~checkbox_uia(); + virtual ~toggle_uia(); public: - checkbox_uia(std::shared_ptr); + toggle_uia(std::shared_ptr, checkable_type); public: IFACEMETHODIMP_(ULONG) AddRef() override; diff --git a/src/checkbox.cpp b/src/checkable.cpp similarity index 77% rename from src/checkbox.cpp rename to src/checkable.cpp index 6c0e4b9..e890c01 100644 --- a/src/checkbox.cpp +++ b/src/checkable.cpp @@ -1,4 +1,4 @@ -#include "checkbox.hpp" +#include "checkable.hpp" #include "logger.hpp" #include "uia.hpp" @@ -11,9 +11,9 @@ namespace simplytest { using profuis::box_state; - struct checkbox::impl + struct checkable::impl { - using map_t = std::unordered_map>; + using map_t = std::unordered_map>; public: static inline lockpp::lock instances{}; @@ -23,14 +23,14 @@ namespace simplytest HWND hwnd; public: - checkbox_uia *provider; + toggle_uia *provider; WNDPROC wnd_proc; public: static LRESULT hk_wndproc(HWND, UINT, WPARAM, LPARAM); }; - LRESULT checkbox::impl::hk_wndproc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) + LRESULT checkable::impl::hk_wndproc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { auto locked = instances.write(); @@ -44,7 +44,7 @@ namespace simplytest if (message == WM_DESTROY) { - logger::get()->trace("[{:x}] received WM_DESTROY", addr); + logger::get()->trace("[{:x}] received destroy signal", addr); locked->erase(hwnd); return 0; @@ -52,31 +52,32 @@ namespace simplytest if (message == WM_GETOBJECT) { - logger::get()->trace("[{:x}] received WM_GETOBJECT", addr); + logger::get()->trace("[{:x}] received accessibility request", addr); return uia_core::get().UiaReturnRawElementProvider(hwnd, wparam, lparam, self->provider); } return CallWindowProcW(self->wnd_proc, hwnd, message, wparam, lparam); } - checkbox::checkbox() : m_impl(std::make_unique()) {} + checkable::checkable() : m_impl(std::make_unique()) {} - checkbox::~checkbox() + checkable::~checkable() { m_impl->provider->Release(); } - void checkbox::toggle() const + void checkable::toggle() const { + logger::get()->debug("[{:x}] received toggle request"); SendMessageW(m_impl->hwnd, BM_SETCHECK, checked() ? BST_UNCHECKED : BST_CHECKED, 0); } - HWND checkbox::hwnd() const + HWND checkable::hwnd() const { return m_impl->hwnd; } - bool checkbox::checked() const + bool checkable::checked() const { auto state = m_impl->state.copy(); @@ -88,12 +89,12 @@ namespace simplytest state == box_state::BOX_MOUSE_HOVER_CHECKED; } - box_state checkbox::state() const + box_state checkable::state() const { return m_impl->state.copy(); } - void checkbox::update(checkable_data *data) + void checkable::update(checkable_data *data, checkable_type type) { auto instances = impl::instances.write(); auto *hwnd = data->object->hwnd; @@ -103,11 +104,11 @@ namespace simplytest logger::get()->info(L"creating instance {:x} (content: '{}')", reinterpret_cast(hwnd), data->text); - auto state = std::shared_ptr(new checkbox); + auto state = std::shared_ptr(new checkable); state->m_impl->hwnd = hwnd; state->m_impl->state.assign(data->state); - state->m_impl->provider = new checkbox_uia(state); + state->m_impl->provider = new toggle_uia(state, type); auto wnd_proc = GetWindowLongPtrW(hwnd, GWLP_WNDPROC); diff --git a/src/patch.cpp b/src/patch.cpp index 94442bc..b340d0f 100644 --- a/src/patch.cpp +++ b/src/patch.cpp @@ -1,40 +1,62 @@ #include "patch.hpp" -#include "checkbox.hpp" +#include "checkable.hpp" #include "logger.hpp" #include namespace simplytest { - using render_hk_t = lime::hook; - std::unique_ptr render_hook; + using check_render_hk_t = lime::hook; + using radio_render_hk_t = lime::hook; - void __attribute__((fastcall)) hk_render(void *thiz, void *cdc, profuis::checkable_data *data) + std::unique_ptr check_render_hook; + std::unique_ptr radio_render_hook; + + void __attribute__((fastcall)) hk_check_render(void *thiz, void *cdc, profuis::checkable_data *data) + { + checkable::update(data, checkable_type::check); + check_render_hook->original()(thiz, cdc, data); + } + + void __attribute__((fastcall)) hk_radio_render(void *thiz, void *cdc, profuis::checkable_data *data) { - checkbox::update(data); - render_hook->original()(thiz, cdc, data); + checkable::update(data, checkable_type::radio); + radio_render_hook->original()(thiz, cdc, data); } void patch(const lime::module &profuis) { auto paint_check = profuis.find_symbol("?PaintCheckButton@CExtPaintManager"); + auto paint_radio = profuis.find_symbol("?PaintRadioButton@CExtPaintManager"); - if (!paint_check) + if (!paint_check || !paint_radio) { - logger::get()->error("could not find `?PaintCheckButton@CExtPaintManager`"); + logger::get()->error("could not find check or radio renderer"); return; } - logger::get()->info("paint_check is {:x}", paint_check.value()); - auto hook = render_hk_t::create(paint_check.value(), hk_render); + logger::get()->info("check_paint is {:x}", paint_check.value()); + logger::get()->info("check_radio is {:x}", paint_radio.value()); + + auto check_hook = check_render_hk_t::create(paint_check.value(), hk_check_render); + + if (!check_hook) + { + logger::get()->error("failed to create check hook: {}", static_cast(check_hook.error())); + return; + } + + check_render_hook = std::move(check_hook.value()); + + auto radio_hook = radio_render_hk_t::create(paint_radio.value(), hk_radio_render); - if (!hook) + if (!radio_hook) { - logger::get()->error("failed to create hook: {}", static_cast(hook.error())); + logger::get()->error("failed to create radio hook: {}", static_cast(radio_hook.error())); return; } - render_hook = std::move(hook.value()); + radio_render_hook = std::move(radio_hook.value()); } } // namespace simplytest diff --git a/src/uia.cpp b/src/uia.cpp index 3442a00..f45b8e5 100644 --- a/src/uia.cpp +++ b/src/uia.cpp @@ -1,8 +1,7 @@ #include "uia.hpp" -#include "checkbox.hpp" -#include #include +#include namespace simplytest { @@ -37,30 +36,33 @@ namespace simplytest return *instance; } - struct checkbox_uia::impl + struct toggle_uia::impl { - std::shared_ptr parent; + std::shared_ptr parent; + checkable_type type; public: ToggleState cached; std::atomic_size_t ref_count; }; - checkbox_uia::~checkbox_uia() = default; + toggle_uia::~toggle_uia() = default; - checkbox_uia::checkbox_uia(std::shared_ptr parent) : m_impl(std::make_unique()) + toggle_uia::toggle_uia(std::shared_ptr parent, checkable_type type) : m_impl(std::make_unique()) { m_impl->parent = std::move(parent); + m_impl->type = type; + get_CurrentToggleState(&m_impl->cached); } - IFACEMETHODIMP_(ULONG) checkbox_uia::AddRef() + IFACEMETHODIMP_(ULONG) toggle_uia::AddRef() { m_impl->ref_count++; return S_OK; } - IFACEMETHODIMP_(ULONG) checkbox_uia::Release() + IFACEMETHODIMP_(ULONG) toggle_uia::Release() { auto count = m_impl->ref_count.fetch_sub(1); @@ -72,7 +74,7 @@ namespace simplytest return count; } - IFACEMETHODIMP checkbox_uia::QueryInterface(REFIID id, void **ret) + IFACEMETHODIMP toggle_uia::QueryInterface(REFIID id, void **ret) { if (id == __uuidof(IUnknown) || id == __uuidof(IRawElementProviderSimple)) { @@ -97,24 +99,24 @@ namespace simplytest return S_OK; } - HRESULT checkbox_uia::Toggle() + HRESULT toggle_uia::Toggle() { m_impl->parent->toggle(); return S_OK; } - HRESULT checkbox_uia::get_ToggleState(ToggleState *ret) + HRESULT toggle_uia::get_ToggleState(ToggleState *ret) { return get_CurrentToggleState(ret); } - HRESULT checkbox_uia::get_CachedToggleState(ToggleState *ret) + HRESULT toggle_uia::get_CachedToggleState(ToggleState *ret) { *ret = m_impl->cached; return S_OK; } - HRESULT checkbox_uia::get_CurrentToggleState(ToggleState *ret) + HRESULT toggle_uia::get_CurrentToggleState(ToggleState *ret) { m_impl->cached = m_impl->parent->checked() ? ToggleState_On : ToggleState_Off; *ret = m_impl->cached; @@ -122,7 +124,7 @@ namespace simplytest return S_OK; } - IFACEMETHODIMP checkbox_uia::GetPropertyValue(PROPERTYID id, VARIANT *ret) + IFACEMETHODIMP toggle_uia::GetPropertyValue(PROPERTYID id, VARIANT *ret) { ret->vt = VT_EMPTY; @@ -138,14 +140,15 @@ namespace simplytest } else if (id == UIA_ControlTypePropertyId) { - ret->lVal = UIA_CheckBoxControlTypeId; - ret->vt = VT_I4; + ret->lVal = + m_impl->type == checkable_type::check ? UIA_CheckBoxControlTypeId : UIA_RadioButtonControlTypeId; + ret->vt = VT_I4; } return S_OK; } - IFACEMETHODIMP checkbox_uia::GetPatternProvider(PATTERNID id, IUnknown **ret) + IFACEMETHODIMP toggle_uia::GetPatternProvider(PATTERNID id, IUnknown **ret) { *ret = nullptr; @@ -157,13 +160,13 @@ namespace simplytest return S_OK; } - IFACEMETHODIMP checkbox_uia::get_ProviderOptions(ProviderOptions *ret) + IFACEMETHODIMP toggle_uia::get_ProviderOptions(ProviderOptions *ret) { *ret = static_cast(ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading); return S_OK; } - IFACEMETHODIMP checkbox_uia::get_HostRawElementProvider(IRawElementProviderSimple **ret) + IFACEMETHODIMP toggle_uia::get_HostRawElementProvider(IRawElementProviderSimple **ret) { return uia_core::get().UiaHostProviderFromHwnd(m_impl->parent->hwnd(), ret); }