Skip to content

Commit

Permalink
feat: add support for radio buttons
Browse files Browse the repository at this point in the history
  • Loading branch information
Curve committed May 5, 2024
1 parent 3eefd92 commit fb4564c
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 57 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ CPMFindPackage(

CPMFindPackage(
NAME lime
VERSION 3.0
VERSION 3.1
GIT_REPOSITORY "https://github.com/Curve/lime"
)

Expand Down
14 changes: 10 additions & 4 deletions include/checkbox.hpp → include/checkable.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,24 @@ namespace simplytest
using profuis::box_state;
using profuis::checkable_data;

class checkbox
enum class checkable_type
{
check,
radio,
};

class checkable
{
struct impl;

private:
std::unique_ptr<impl> m_impl;

private:
checkbox();
checkable();

public:
~checkbox();
~checkable();

public:
void toggle() const;
Expand All @@ -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
10 changes: 6 additions & 4 deletions include/uia.hpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#pragma once

#include "checkable.hpp"

#include <memory>
#include <uiautomation.h>

namespace simplytest
{
class checkbox;
class checkable;

class uia_core
{
Expand All @@ -23,18 +25,18 @@ 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;

private:
std::unique_ptr<impl> m_impl;

public:
virtual ~checkbox_uia();
virtual ~toggle_uia();

public:
checkbox_uia(std::shared_ptr<checkbox>);
toggle_uia(std::shared_ptr<checkable>, checkable_type);

public:
IFACEMETHODIMP_(ULONG) AddRef() override;
Expand Down
33 changes: 17 additions & 16 deletions src/checkbox.cpp → src/checkable.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include "checkbox.hpp"
#include "checkable.hpp"

#include "logger.hpp"
#include "uia.hpp"
Expand All @@ -11,9 +11,9 @@ namespace simplytest
{
using profuis::box_state;

struct checkbox::impl
struct checkable::impl
{
using map_t = std::unordered_map<HWND, std::shared_ptr<checkbox>>;
using map_t = std::unordered_map<HWND, std::shared_ptr<checkable>>;

public:
static inline lockpp::lock<map_t, std::recursive_mutex> instances{};
Expand All @@ -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();

Expand All @@ -44,39 +44,40 @@ 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;
}

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<impl>()) {}
checkable::checkable() : m_impl(std::make_unique<impl>()) {}

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();

Expand All @@ -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;
Expand All @@ -103,11 +104,11 @@ namespace simplytest
logger::get()->info(L"creating instance {:x} (content: '{}')", reinterpret_cast<std::uintptr_t>(hwnd),
data->text);

auto state = std::shared_ptr<checkbox>(new checkbox);
auto state = std::shared_ptr<checkable>(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);

Expand Down
48 changes: 35 additions & 13 deletions src/patch.cpp
Original file line number Diff line number Diff line change
@@ -1,40 +1,62 @@
#include "patch.hpp"

#include "checkbox.hpp"
#include "checkable.hpp"
#include "logger.hpp"

#include <lime/hooks/hook.hpp>

namespace simplytest
{
using render_hk_t = lime::hook<void(void *, void *, profuis::checkable_data *)>;
std::unique_ptr<render_hk_t> render_hook;
using check_render_hk_t = lime::hook<void(void *, void *, profuis::checkable_data *)>;
using radio_render_hk_t = lime::hook<void(void *, void *, profuis::checkable_data *)>;

void __attribute__((fastcall)) hk_render(void *thiz, void *cdc, profuis::checkable_data *data)
std::unique_ptr<check_render_hk_t> check_render_hook;
std::unique_ptr<radio_render_hk_t> 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<int>(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<int>(hook.error()));
logger::get()->error("failed to create radio hook: {}", static_cast<int>(radio_hook.error()));
return;
}

render_hook = std::move(hook.value());
radio_render_hook = std::move(radio_hook.value());
}
} // namespace simplytest
Loading

0 comments on commit fb4564c

Please sign in to comment.