> kb_listeners;
build_rebind_list = [&]() {
Rml::Element* list = doc->GetElementById("rebind-list");
if (!list) return;
const int n = psx_keybinds_button_count();
std::string html;
for (int b = 0; b < n; b += 2) { // two (label, chip) pairs per row
html += "";
for (int k = b; k < b + 2 && k < n; k++) {
html += "";
html += rml_escape(psx_keybinds_button_label(k));
html += "";
}
html += "
";
}
list->SetInnerRML(html); // destroys prior chips...
kb_listeners.clear(); // ...so dropping their listeners is safe
for (int k = 0; k < n; k++) {
const std::string id = std::string("kb-") + psx_keybinds_button_name(k);
if (Rml::Element* e = doc->GetElementById(id)) {
auto lis = std::make_unique();
lis->on_click = [&, k, id]() { begin_scan(k, id); };
e->AddEventListener(Rml::EventId::Click, lis.get());
kb_listeners.push_back(std::move(lis));
}
}
};
build_rebind_list();
// ---- Main loop ----
Result result = Result::Quit;
bool running = true;
while (running) {
SDL_Event ev;
while (SDL_PollEvent(&ev)) {
// While a rebind scan is armed, swallow keyboard input (the next
// keydown resolves it; Esc cancels) so it can't leak into RmlUi
// controls.
if (scan_kind &&
(ev.type == SDL_KEYDOWN || ev.type == SDL_KEYUP || ev.type == SDL_TEXTINPUT)) {
if (ev.type == SDL_KEYDOWN) handle_scan_key(ev.key);
continue;
}
switch (ev.type) {
case SDL_QUIT:
m.quit_requested = true;
break;
case SDL_WINDOWEVENT:
if (ev.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
SDL_GL_GetDrawableSize(window, &win_w, &win_h);
render_interface.SetViewport(win_w, win_h);
context->SetDimensions(Rml::Vector2i(win_w, win_h));
}
RmlSDL::InputEventHandler(context, ev);
break;
default:
RmlSDL::InputEventHandler(context, ev);
break;
}
}
if (m.launch_requested) { result = Result::Launch; running = false; }
if (m.quit_requested) { result = Result::Quit; running = false; }
// Deferred chip-list rebuild (set from chip handlers / scan capture /
// player switch / reset — never rebuild a list from inside its own
// listener's dispatch).
if (rebuild_pending) { rebuild_pending = false; build_rebind_list(); }
context->Update();
render_interface.Clear();
render_interface.BeginFrame();
context->Render();
render_interface.EndFrame();
SDL_GL_SwapWindow(window);
}
// ---- Commit choices on launch ----
if (result == Result::Launch) {
io.renderer = m.renderer; io.has_renderer = true;
io.supersampling = m.supersampling; io.has_supersampling = true;
io.antialiasing = m.antialiasing; io.has_antialiasing = true;
io.texture_filter = m.texture_filter; io.has_texture_filter = true;
io.screen_kind = m.crt; io.has_screen_kind = true;
io.auto_skip_fmv = m.auto_skip_fmv; io.has_auto_skip_fmv = true;
io.turbo_loads = m.turbo_loads; io.has_turbo_loads = true;
io.fullscreen = m.fullscreen; io.has_fullscreen = true;
io.frame_interpolation = m.frame_interpolation;
io.has_frame_interpolation = true;
io.frame_interpolation_fps = m.frame_interpolation_fps;
io.has_frame_interpolation_fps = true;
io.skip_launcher = m.skip_launcher; io.has_skip_launcher = true;
io.spu_hq = m.spu_hq; io.has_spu_hq = true;
io.aspect_num = kAspects[m.aspect_index][0];
io.aspect_den = kAspects[m.aspect_index][1];
io.has_aspect_ratio = true;
io.window_width = m.window_width; io.has_window_width = true;
if (!m.bios_path.empty()) { io.bios_path = fs::path(std::string(m.bios_path)); io.has_bios_path = true; }
if (!m.disc_path.empty()) { io.disc_path = fs::path(std::string(m.disc_path)); io.has_disc_path = true; }
io.memcard1_enabled = m.mc1_enabled; io.has_memcard1_enabled = true;
io.memcard2_enabled = m.mc2_enabled; io.has_memcard2_enabled = true;
if (!m.mc1_path.empty()) { io.memcard1_path = fs::path(std::string(m.mc1_path)); io.has_memcard1_path = true; }
if (!m.mc2_path.empty()) { io.memcard2_path = fs::path(std::string(m.mc2_path)); io.has_memcard2_path = true; }
const int i1 = (m.p1_dev_index >= 0 && m.p1_dev_index < (int)dev_opts.size()) ? m.p1_dev_index : 0;
const int i2 = (m.p2_dev_index >= 0 && m.p2_dev_index < (int)dev_opts.size()) ? m.p2_dev_index : 0;
io.p1_device = device_string(dev_opts[i1]); io.has_p1_device = true;
io.p2_device = device_string(dev_opts[i2]); io.has_p2_device = true;
io.p1_mode = m.p1_mode; io.has_p1_mode = true;
io.p2_mode = m.p2_mode; io.has_p2_mode = true;
io.deadzone = m.deadzone_pct * 32767 / 100; io.has_deadzone = true;
// Localization: persist the chosen language code (only meaningful when the
// game declared a menu; otherwise leave io.language untouched).
if (m.lang_menu && m.lang_index >= 0 &&
m.lang_index < (int)game.languages.size()) {
io.language = game.languages[m.lang_index].code;
io.has_language = true;
}
}
Rml::Shutdown();
RmlGL3::Shutdown();
return result;
}
} // namespace psx_launcher