Key bindings: pass keys to text fields that are on screen

Character creation and the save window were not detected (the UI manager's
dialog slot held a hidden busy dialog); bound keys were swallowed or posted
their hotkey into the field. A text field now counts while it is the focused
edit control and was drawn in the last 250 ms.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
khallaandClaude Opus 5.5 committed 2026-10-18 00:57:10 +02:00
1 parent 075f9e2c8a
commit 432cac2ccf
3 files changed
+61 -14

No files matched your search

+41 -14
View File
@@ -229,29 +229,36 @@ void Startup() {
}
RE_STARTUP(InputStartup, Startup);
// The text field that has the keyboard, as the game decides it: the UI
// manager hands key events to its dialog (+0x144) while that is shown
// (+0x10 bit 0; cUI_Manager::ReceiveEvent 0x7573ba), and the dialog's focused
// control (+0x148) is an edit control without the read-only bit (+0x10 bit
// 16). The focus pointer alone stays set after the dialog closed (measured:
// the save window's 'DEFAULT_EDIT' after closing it), which switched the
// bindings off.
constexpr size_t kUiDialog = 0x144;
// The text field that has the keyboard: the UI manager's focused control
// (+0x148, set by UI event 0x10 in cUI_Manager::ReceiveEvent 0x757120), when
// it is an edit control without the read-only bit (+0x10 bit 16) that is on
// screen. Neither the focus nor the control's own flags change when its window
// closes (measured: flags 0x8005 and the focus kept after closing the save
// window; the control has no parent at +0x50), so "on screen" is measured:
// cUI_EditControl::Render (0x737ae0) drew it in the last kDrawnRecentlyMs. (The manager's dialog slot +0x144 is not it either: it
// held a hidden cUI_BusyDlg while typing in character creation and the save
// window.)
constexpr size_t kUiFocus = 0x148;
constexpr size_t kControlFlags = 0x10;
constexpr uint32_t kControlVisible = 1;
constexpr uint32_t kEditReadOnly = 1u << 16;
constexpr DWORD kDrawnRecentlyMs = 250;
std::atomic<void*> g_drawnEdit{nullptr};
std::atomic<DWORD> g_drawnEditTick{0};
uint32_t ControlFlags(const uint8_t* control) {
return *reinterpret_cast<const uint32_t*>(control + kControlFlags);
}
uint8_t* FocusedTextField() {
auto* ui = *reinterpret_cast<uint8_t**>(kUiManager);
auto* dialog = ui ? *reinterpret_cast<uint8_t**>(ui + kUiDialog) : nullptr;
if (!dialog || (*reinterpret_cast<uint32_t*>(dialog + kControlFlags) & kControlVisible) == 0) {
void* focus = ui ? *reinterpret_cast<void**>(ui + kUiFocus) : nullptr;
if (!focus || focus != g_drawnEdit.load() || GetTickCount() - g_drawnEditTick.load() > kDrawnRecentlyMs) {
return nullptr;
}
void* focus = *reinterpret_cast<void**>(ui + kUiFocus);
auto* edit = static_cast<uint8_t*>(focus ? game::DynamicCast(focus, game::kTypeControl, game::kTypeEditControl)
: nullptr);
if (!edit || (*reinterpret_cast<uint32_t*>(edit + kControlFlags) & kEditReadOnly) != 0) {
auto* edit = static_cast<uint8_t*>(game::DynamicCast(focus, game::kTypeControl, game::kTypeEditControl));
if (!edit || (ControlFlags(edit) & kEditReadOnly) != 0) {
return nullptr;
}
return edit;
@@ -259,6 +266,17 @@ uint8_t* FocusedTextField() {
} // namespace
// Only the focused control counts: a window draws several edit controls per
// frame (measured: the save window), the last one drawn is not the focus.
void NoteEditDrawn(void* edit) {
auto* ui = *reinterpret_cast<uint8_t**>(kUiManager);
if (ui && edit == *reinterpret_cast<void**>(ui + kUiFocus) &&
(ControlFlags(static_cast<uint8_t*>(edit)) & kControlVisible) != 0) {
g_drawnEdit = edit;
g_drawnEditTick = GetTickCount();
}
}
bool TypingIntoTextField() {
return FocusedTextField() != nullptr;
}
@@ -327,4 +345,13 @@ LRESULT CALLBACK GameWindowProc(HWND window, UINT message, WPARAM wParam, LPARAM
return GameWindowProc_original(window, message, wParam, lParam);
}
// cUI_EditControl::Render (vf14, thiscall (renderer), ret 4; returns right
// away while the control is hidden).
void __fastcall EditControl_Render(void* self, void* edx, void* renderer);
RE_HOOK(0x00737ae0, EditControl_Render);
void __fastcall EditControl_Render(void* self, void* edx, void* renderer) {
mods::input::NoteEditDrawn(self);
EditControl_Render_original(self, edx, renderer);
}
} // namespace game