Files
jpolo1224 1eb63137e3 ARMSX3: Android port of RPCS3, proof of concept
Adds an Android build of the RPCS3 core plus a Compose UI, and fixes several
things that stopped it working on ARM64.

Renderer:
- Emit concrete bounds for runtime sized arrays in uniform blocks when
  VK_EXT_shader_uniform_buffer_unsized_array is missing. Adreno does not have
  the extension, so every game pipeline failed with VK_ERROR_UNKNOWN and only
  overlays drew.
- Probe and request that extension properly instead of chaining its feature
  struct unconditionally.
- Hand VMA the Vulkan function pointers it needs under VK_NO_PROTOTYPES.
- Rebuild the surface and swapchain when the window is lost instead of killing
  the RSX thread.
- Only create a GLES context when the GL renderer is actually selected.

SPU:
- Sum instead of taking an absolute difference in the ARM64 block verification
  checksum. The difference collides on the near identical job binaries an SPU
  job manager streams through one local store address, so a cached block could
  run against another job's code.

Threading:
- Implement thread affinity on Android using sched_setaffinity.
- Add an ARM big.LITTLE core arrangement so SPU and RSX threads land on the
  fast cores.

Misc:
- Detect the host CPU for the LLVM JIT instead of pinning cortex-a34.
- Fall back to the default audio device when cubeb cannot enumerate.
2026-08-06 08:59:52 -04:00

959 lines
23 KiB
C++

#include "stdafx.h"
#include "pad_thread.h"
#include "product_info.h"
#include "ds3_pad_handler.h"
#include "ds4_pad_handler.h"
#include "dualsense_pad_handler.h"
#include "skateboard_pad_handler.h"
#include "ps_move_handler.h"
#ifdef __ANDROID__
#include "virtual_pad_handler.h"
#endif
#ifdef _WIN32
#include "xinput_pad_handler.h"
#include "mm_joystick_handler.h"
#elif HAVE_LIBEVDEV
#include "evdev_joystick_handler.h"
#endif
#ifdef HAVE_SDL3
#include "sdl_pad_handler.h"
#endif
#ifndef ANDROID
#include "keyboard_pad_handler.h"
#endif
#include "Emu/Io/Null/NullPadHandler.h"
#include "Emu/Io/interception.h"
#include "Emu/Io/PadHandler.h"
#include "Emu/Io/pad_config.h"
#include "Emu/System.h"
#include "Emu/system_config.h"
#include "Emu/RSX/Overlays/HomeMenu/overlay_home_menu.h"
#include "Emu/RSX/Overlays/overlay_message.h"
#include "Emu/Cell/lv2/sys_usbd.h"
#include "Emu/Cell/Modules/cellGem.h"
#include "Emu/Cell/timers.hpp"
#include "Utilities/Thread.h"
#include "util/atomic.hpp"
LOG_CHANNEL(sys_log, "SYS");
extern void pad_state_notify_state_change(usz index, u32 state);
extern bool is_input_allowed();
extern std::string g_input_config_override;
namespace pad
{
atomic_t<pad_thread*> g_pad_thread = nullptr;
shared_mutex g_pad_mutex;
std::string g_title_id;
atomic_t<bool> g_started{false};
atomic_t<bool> g_reset{false};
atomic_t<bool> g_enabled{true};
atomic_t<bool> g_home_menu_requested{false};
}
namespace rsx
{
void set_native_ui_flip();
}
struct pad_setting
{
u32 port_status = 0;
u32 device_capability = 0;
u32 device_type = 0;
u32 class_type = 0;
u16 vendor_id = 0;
u16 product_id = 0;
bool is_ldd_pad = false;
};
pad_thread::pad_thread(void* curthread, void* curwindow, std::string_view title_id) : m_curthread(curthread), m_curwindow(curwindow)
{
pad::g_title_id = title_id;
pad::g_pad_thread = this;
pad::g_started = false;
}
pad_thread::~pad_thread()
{
pad::g_pad_thread = nullptr;
}
void pad_thread::Init()
{
std::lock_guard lock(pad::g_pad_mutex);
// Reset mouse-based gyro state
m_mouse_gyro.set_enabled(g_cfg.io.mouse_based_gyro_enabled.get());
// Cache old settings if possible
std::array<pad_setting, CELL_PAD_MAX_PORT_NUM> pad_settings;
for (u32 i = 0; i < CELL_PAD_MAX_PORT_NUM; i++) // max 7 pads
{
if (m_pads[i])
{
pad_settings[i] =
{
m_pads[i]->m_port_status,
m_pads[i]->m_device_capability,
m_pads[i]->m_device_type,
m_pads[i]->m_class_type,
m_pads[i]->m_vendor_id,
m_pads[i]->m_product_id,
m_pads[i]->ldd
};
}
else
{
pad_settings[i] =
{
CELL_PAD_STATUS_DISCONNECTED,
CELL_PAD_CAPABILITY_PS3_CONFORMITY | CELL_PAD_CAPABILITY_PRESS_MODE | CELL_PAD_CAPABILITY_ACTUATOR,
CELL_PAD_DEV_TYPE_STANDARD,
CELL_PAD_PCLASS_TYPE_STANDARD,
0,
0,
false
};
}
}
num_ldd_pad = 0;
m_info.now_connect = 0;
m_handlers.clear();
g_cfg_input_configs.load();
std::string active_config = g_cfg_input_configs.active_configs.get_value(pad::g_title_id);
if (active_config.empty())
{
active_config = g_cfg_input_configs.active_configs.get_value(g_cfg_input_configs.global_key);
}
input_log.notice("Using input configuration: '%s' (override='%s')", active_config, g_input_config_override);
// Load in order to get the pad handlers
if (!g_cfg_input.load(pad::g_title_id, active_config))
{
input_log.notice("Loaded empty pad config");
}
// Adjust to the different pad handlers
for (usz i = 0; i < g_cfg_input.player.size(); i++)
{
std::shared_ptr<PadHandlerBase> handler;
pad_thread::InitPadConfig(g_cfg_input.player[i]->config, g_cfg_input.player[i]->handler, handler);
}
// Reload with proper defaults
if (!g_cfg_input.load(pad::g_title_id, active_config))
{
input_log.notice("Reloaded empty pad config");
}
input_log.trace("Using pad config:\n%s", g_cfg_input);
#ifndef ANDROID
std::shared_ptr<keyboard_pad_handler> keyptr;
#endif
// Always have a Null Pad Handler
std::shared_ptr<NullPadHandler> nullpad = std::make_shared<NullPadHandler>();
m_handlers.emplace(pad_handler::null, nullpad);
for (u32 i = 0; i < CELL_PAD_MAX_PORT_NUM; i++) // max 7 pads
{
cfg_player* cfg = g_cfg_input.player[i];
std::shared_ptr<PadHandlerBase> cur_pad_handler;
const pad_handler handler_type = pad_settings[i].is_ldd_pad ? pad_handler::null : cfg->handler.get();
if (m_handlers.contains(handler_type))
{
cur_pad_handler = m_handlers[handler_type];
}
else
{
if (handler_type == pad_handler::keyboard)
{
#ifndef ANDROID
keyptr = std::make_shared<keyboard_pad_handler>();
keyptr->moveToThread(static_cast<QThread*>(m_curthread));
keyptr->SetTargetWindow(static_cast<QWindow*>(m_curwindow));
cur_pad_handler = keyptr;
#else
cur_pad_handler = nullpad;
#endif
}
else
{
cur_pad_handler = GetHandler(handler_type);
}
m_handlers.emplace(handler_type, cur_pad_handler);
}
cur_pad_handler->Init();
std::shared_ptr<Pad> pad = std::make_shared<Pad>(handler_type, i, CELL_PAD_STATUS_DISCONNECTED, pad_settings[i].device_capability, pad_settings[i].device_type);
m_pads[i] = pad;
if (pad_settings[i].is_ldd_pad)
{
InitLddPad(i, &pad_settings[i].port_status);
}
else
{
if (!cur_pad_handler->bindPadToDevice(pad))
{
// Failed to bind the device to cur_pad_handler so binds to NullPadHandler
input_log.error("Failed to bind device '%s' to handler %s. Falling back to NullPadHandler.", cfg->device.to_string(), handler_type);
nullpad->bindPadToDevice(pad);
}
input_log.notice("Pad %d: device='%s', handler=%s, VID=0x%x, PID=0x%x, class_type=0x%x, class_profile=0x%x",
i, cfg->device.to_string(), pad->m_pad_handler, pad->m_vendor_id, pad->m_product_id, pad->m_class_type, pad->m_class_profile);
if (pad->m_pad_handler != pad_handler::null)
{
input_log.notice("Pad %d: config=\n%s", i, cfg->to_string());
}
// If the user changes the emulated controller, then simulate unplugging and plugging in a new controller
if (m_pads_connected[i] && (pad_settings[i].class_type != pad->m_class_type || pad_settings[i].vendor_id != pad->m_vendor_id || pad_settings[i].product_id != pad->m_product_id))
{
pad->m_disconnection_timer = get_system_time() + 30'000ull;
}
}
pad->is_fake_pad = ((g_cfg.io.move == move_handler::real || g_cfg.io.move == move_handler::fake) && i >= (static_cast<u32>(CELL_PAD_MAX_PORT_NUM) - static_cast<u32>(CELL_GEM_MAX_NUM)))
|| (pad->m_class_type >= CELL_PAD_FAKE_TYPE_FIRST && pad->m_class_type < CELL_PAD_FAKE_TYPE_LAST);
connect_usb_controller(i, input::get_product_by_vid_pid(pad->m_vendor_id, pad->m_product_id));
}
// Set copilots
for (usz i = 0; i < m_pads.size(); i++)
{
auto& pad = m_pads[i];
if (!pad)
continue;
pad->copilots.clear();
if (pad->is_copilot())
continue;
for (usz j = 0; j < m_pads.size(); j++)
{
auto& other = m_pads[j];
if (i == j || !other || other->copilot_player() != i)
continue;
pad->copilots.push_back(other);
}
}
// Initialize active mouse and keyboard. Activate pad handler if one exists.
input::set_mouse_and_keyboard(m_handlers.contains(pad_handler::keyboard) ? input::active_mouse_and_keyboard::pad : input::active_mouse_and_keyboard::emulated);
}
void pad_thread::SetRumble(u32 pad, u8 large_motor, u8 small_motor)
{
if (pad >= m_pads.size() || !m_pads[pad])
return;
const u64 now_us = get_system_time();
m_pads[pad]->m_last_rumble_time_us = now_us;
m_pads[pad]->m_vibrate_motors[0].value = large_motor;
m_pads[pad]->m_vibrate_motors[1].value = small_motor;
// Rumble copilots as well
for (const auto& copilot : m_pads[pad]->copilots)
{
if (copilot && copilot->is_connected())
{
copilot->m_last_rumble_time_us = now_us;
copilot->m_vibrate_motors[0].value = large_motor;
copilot->m_vibrate_motors[1].value = small_motor;
}
}
}
void pad_thread::SetIntercepted(bool intercepted)
{
if (intercepted)
{
m_info.system_info |= CELL_PAD_INFO_INTERCEPTED;
m_info.ignore_input = true;
}
else
{
m_info.system_info &= ~CELL_PAD_INFO_INTERCEPTED;
}
}
void pad_thread::apply_copilots()
{
const auto normalize = [](s32 value)
{
return (value - 128) / 127.0f;
};
std::lock_guard lock(pad::g_pad_mutex);
for (auto& pad : m_pads)
{
if (!pad || !pad->is_connected())
{
continue;
}
pad->m_buttons_external.resize(pad->m_buttons.size());
for (usz i = 0; i < pad->m_buttons.size(); i++)
{
const Button& src = pad->m_buttons[i];
ButtonExternal& dst = pad->m_buttons_external[i];
dst.m_offset = src.m_offset;
dst.m_outKeyCode = src.m_outKeyCode;
dst.m_value = src.m_value;
dst.m_pressed = src.m_pressed;
}
for (usz i = 0; i < pad->m_sticks.size(); i++)
{
const AnalogStick& src = pad->m_sticks[i];
AnalogStickExternal& dst = pad->m_sticks_external[i];
dst.m_offset = src.m_offset;
dst.m_value = src.m_value;
}
if (pad->copilots.empty() || pad->is_copilot())
{
continue;
}
// Merge buttons
for (const auto& copilot : pad->copilots)
{
if (!copilot || !copilot->is_connected())
{
continue;
}
for (ButtonExternal& button : pad->m_buttons_external)
{
for (const Button& other : copilot->m_buttons)
{
if (button.m_offset == other.m_offset && button.m_outKeyCode == other.m_outKeyCode)
{
if (other.m_pressed)
{
button.m_pressed = true;
if (button.m_value < other.m_value)
{
button.m_value = other.m_value;
}
}
break;
}
}
}
}
// Merge sticks
for (AnalogStickExternal& stick : pad->m_sticks_external)
{
f32 accumulated_value = normalize(stick.m_value);
for (const auto& copilot : pad->copilots)
{
if (!copilot || !copilot->is_connected())
{
continue;
}
for (const AnalogStick& other : copilot->m_sticks)
{
if (stick.m_offset == other.m_offset)
{
accumulated_value += normalize(other.m_value);
break;
}
}
}
stick.m_value = static_cast<u16>(std::round(std::clamp(accumulated_value * 127.0f + 128.0f, 0.0f, 255.0f)));
}
}
}
void pad_thread::update_pad_states()
{
for (usz i = 0; i < m_pads.size(); i++)
{
const auto& pad = m_pads[i];
// Simulate unplugging and plugging in a new controller
if (pad && pad->m_disconnection_timer > 0)
{
const bool is_connected = pad->is_connected();
const u64 now = get_system_time();
if (is_connected && now < pad->m_disconnection_timer)
{
pad->m_port_status &= ~CELL_PAD_STATUS_CONNECTED;
pad->m_port_status |= CELL_PAD_STATUS_ASSIGN_CHANGES;
}
else if (!is_connected && now >= pad->m_disconnection_timer)
{
pad->m_port_status |= CELL_PAD_STATUS_CONNECTED + CELL_PAD_STATUS_ASSIGN_CHANGES;
pad->m_disconnection_timer = 0;
}
}
const bool connected = pad && !pad->is_fake_pad && pad->is_connected();
if (m_pads_connected[i] == connected)
continue;
pad_state_notify_state_change(i, connected ? CELL_PAD_STATUS_CONNECTED : CELL_PAD_STATUS_DISCONNECTED);
m_pads_connected[i] = connected;
}
}
void pad_thread::operator()()
{
Init();
const bool is_vsh = Emu.IsVsh();
pad::g_reset = false;
pad::g_started = true;
bool mode_changed = true;
atomic_t<pad_handler_mode> pad_mode{g_cfg.io.pad_mode.get()};
std::vector<std::unique_ptr<named_thread<std::function<void()>>>> threads;
const auto stop_threads = [&threads]()
{
input_log.notice("Stopping pad threads...");
for (auto& thread : threads)
{
// Join thread (ordered explicitly)
thread.reset();
}
threads.clear();
input_log.notice("Pad threads stopped");
};
const auto start_threads = [this, &threads, &pad_mode]()
{
if (pad_mode == pad_handler_mode::single_threaded)
{
return;
}
input_log.notice("Starting pad threads...");
#if defined(__APPLE__)
// Let's keep hid handlers on the same thread
std::vector<std::shared_ptr<PadHandlerBase>> hid_handlers;
std::vector<std::shared_ptr<PadHandlerBase>> handlers;
for (const auto& [type, handler] : m_handlers)
{
switch (type)
{
case pad_handler::null:
break;
case pad_handler::ds3:
case pad_handler::ds4:
case pad_handler::dualsense:
case pad_handler::skateboard:
case pad_handler::move:
hid_handlers.push_back(handler);
break;
default:
handlers.push_back(handler);
break;
}
}
if (!hid_handlers.empty())
{
threads.push_back(std::make_unique<named_thread<std::function<void()>>>("HID Thread", [handlers = std::move(hid_handlers)]()
{
while (thread_ctrl::state() != thread_state::aborting)
{
if (!pad::g_enabled || !is_input_allowed())
{
thread_ctrl::wait_for(30'000);
continue;
}
for (auto& handler : handlers)
{
handler->process();
}
u64 pad_sleep = g_cfg.io.pad_sleep;
if (Emu.IsPaused())
{
pad_sleep = std::max<u64>(pad_sleep, 30'000);
}
thread_ctrl::wait_for(pad_sleep);
}
}));
}
for (const auto& handler : handlers)
{
#else
for (const auto& [type, handler] : m_handlers)
{
if (type == pad_handler::null) continue;
#endif
threads.push_back(std::make_unique<named_thread<std::function<void()>>>(fmt::format("%s Thread", handler->m_type), [handler]()
{
while (thread_ctrl::state() != thread_state::aborting)
{
if (!pad::g_enabled || !is_input_allowed())
{
thread_ctrl::wait_for(30'000);
continue;
}
handler->process();
u64 pad_sleep = g_cfg.io.pad_sleep;
if (Emu.IsPaused())
{
pad_sleep = std::max<u64>(pad_sleep, 30'000);
}
thread_ctrl::wait_for(pad_sleep);
}
}));
}
input_log.notice("Pad threads started");
};
while (thread_ctrl::state() != thread_state::aborting)
{
if (!pad::g_enabled || !is_input_allowed())
{
m_resume_emulation_flag = false;
m_mask_start_press_to_resume = 0;
thread_ctrl::wait_for(30'000);
continue;
}
// Update variables
const bool needs_reset = pad::g_reset && pad::g_reset.exchange(false);
const pad_handler_mode new_pad_mode = g_cfg.io.pad_mode.get();
mode_changed |= new_pad_mode != pad_mode.exchange(new_pad_mode);
// Reset pad handlers if necessary
if (needs_reset || mode_changed)
{
mode_changed = false;
stop_threads();
if (needs_reset)
{
Init();
}
else
{
input_log.success("The pad mode was changed to %s", pad_mode.load());
}
start_threads();
}
u32 connected_devices = 0;
if (pad_mode == pad_handler_mode::single_threaded)
{
for (auto& handler : m_handlers)
{
handler.second->process();
connected_devices += handler.second->connected_devices;
}
}
else
{
for (auto& handler : m_handlers)
{
connected_devices += handler.second->connected_devices;
}
}
apply_copilots();
if (Emu.IsRunning())
{
update_pad_states();
// Apply mouse-based gyro emulation.
// Intentionally bound to Player 1 only.
m_mouse_gyro.apply_gyro(m_pads[0]);
}
m_info.now_connect = connected_devices + num_ldd_pad;
// The ignore_input section is only reached when a dialog was closed and the pads are still intercepted.
// As long as any of the listed buttons is pressed, cellPadGetData will ignore all input (needed for Hotline Miami).
// ignore_input was added because if we keep the pads intercepted, then some games will enter the menu due to unexpected system interception (tested with Ninja Gaiden Sigma).
if (m_info.ignore_input && !(m_info.system_info & CELL_PAD_INFO_INTERCEPTED))
{
bool any_button_pressed = false;
for (usz i = 0; i < m_pads.size() && !any_button_pressed; i++)
{
const auto& pad = m_pads[i];
if (!pad->is_connected())
continue;
for (const Button& button : pad->m_buttons)
{
if (button.m_pressed && (
(button.m_offset == CELL_PAD_BTN_OFFSET_DIGITAL1 && (
button.m_outKeyCode == CELL_PAD_CTRL_START ||
button.m_outKeyCode == CELL_PAD_CTRL_SELECT)) ||
(button.m_offset == CELL_PAD_BTN_OFFSET_DIGITAL2 && (
button.m_outKeyCode == CELL_PAD_CTRL_CROSS ||
button.m_outKeyCode == CELL_PAD_CTRL_CIRCLE ||
button.m_outKeyCode == CELL_PAD_CTRL_TRIANGLE ||
button.m_outKeyCode == CELL_PAD_CTRL_SQUARE))
))
{
any_button_pressed = true;
break;
}
}
}
if (!any_button_pressed)
{
m_info.ignore_input = false;
}
}
// Handle home menu if requested
if (!is_vsh && !m_home_menu_open && Emu.IsRunning())
{
bool ps_button_pressed = false;
for (usz i = 0; i < m_pads.size() && !ps_button_pressed; i++)
{
if (i > 0 && g_cfg.io.lock_overlay_input_to_player_one)
break;
const auto& pad = m_pads[i];
if (!pad->is_connected())
continue;
// Check if an LDD pad pressed the PS button (bit 0 of the first button)
// NOTE: Rock Band 3 doesn't seem to care about the len. It's always 0.
if (pad->ldd /*&& pad->ldd_data.len >= 1 */&& !!(pad->ldd_data.button[0] & CELL_PAD_CTRL_LDD_PS))
{
ps_button_pressed = true;
break;
}
for (const Button& button : pad->m_buttons)
{
if (button.m_offset == CELL_PAD_BTN_OFFSET_DIGITAL1 && button.m_outKeyCode == CELL_PAD_CTRL_PS && button.m_pressed)
{
ps_button_pressed = true;
break;
}
}
}
// Make sure we call this function only once per button press
if ((ps_button_pressed && !m_ps_button_pressed) || pad::g_home_menu_requested.exchange(false))
{
open_home_menu();
}
m_ps_button_pressed = ps_button_pressed;
}
// Handle paused emulation (if triggered by home menu).
if (m_home_menu_open && g_cfg.misc.pause_during_home_menu)
{
// Reset resume control if the home menu is open
m_resume_emulation_flag = false;
m_mask_start_press_to_resume = 0;
m_track_start_press_begin_timestamp = 0;
// Update UI
rsx::set_native_ui_flip();
thread_ctrl::wait_for(33'000);
continue;
}
if (m_resume_emulation_flag)
{
m_resume_emulation_flag = false;
Emu.BlockingCallFromMainThread([]()
{
Emu.Resume();
});
}
u64 pad_sleep = g_cfg.io.pad_sleep;
if (Emu.GetStatus(false) == system_state::paused)
{
pad_sleep = std::max<u64>(pad_sleep, 30'000);
u64 timestamp = get_system_time();
u32 pressed_mask = 0;
for (usz i = 0; i < m_pads.size(); i++)
{
const auto& pad = m_pads[i];
if (!pad->is_connected())
continue;
for (const Button& button : pad->m_buttons)
{
if (button.m_offset == CELL_PAD_BTN_OFFSET_DIGITAL1 && button.m_outKeyCode == CELL_PAD_CTRL_START && button.m_pressed)
{
pressed_mask |= 1u << i;
break;
}
}
}
m_mask_start_press_to_resume &= pressed_mask;
if (!pressed_mask || timestamp - m_track_start_press_begin_timestamp >= 700'000)
{
m_track_start_press_begin_timestamp = timestamp;
if (std::exchange(m_mask_start_press_to_resume, u32{umax}))
{
m_mask_start_press_to_resume = 0;
m_track_start_press_begin_timestamp = 0;
sys_log.success("Resuming emulation using the START button in a few seconds...");
auto msg_ref = std::make_shared<atomic_t<u32>>(1);
rsx::overlays::queue_message(localized_string_id::EMULATION_RESUMING, 2'000'000, msg_ref);
m_resume_emulation_flag = true;
for (u32 i = 0; i < 40; i++)
{
if (Emu.GetStatus(false) != system_state::paused)
{
// Abort if emulation has been resumed by other means
m_resume_emulation_flag = false;
msg_ref->release(0);
break;
}
thread_ctrl::wait_for(50'000);
rsx::set_native_ui_flip();
}
}
}
}
else
{
// Reset resume control if caught a state of unpaused emulation
m_mask_start_press_to_resume = 0;
m_track_start_press_begin_timestamp = 0;
}
thread_ctrl::wait_for(pad_sleep);
}
stop_threads();
}
void pad_thread::InitLddPad(u32 handle, const u32* port_status)
{
if (handle >= m_pads.size())
{
return;
}
static const input::product_info product = input::get_product_info(input::product_type::playstation_3_controller);
auto& pad = m_pads[handle];
pad->ldd = true;
pad->Init
(
port_status ? *port_status : CELL_PAD_STATUS_CONNECTED | CELL_PAD_STATUS_ASSIGN_CHANGES | CELL_PAD_STATUS_CUSTOM_CONTROLLER,
CELL_PAD_CAPABILITY_PS3_CONFORMITY,
CELL_PAD_DEV_TYPE_LDD,
CELL_PAD_PCLASS_TYPE_STANDARD,
product.pclass_profile,
product.vendor_id,
product.product_id,
50
);
input_log.notice("Pad %d: LDD, VID=0x%x, PID=0x%x, class_type=0x%x, class_profile=0x%x",
handle, pad->m_vendor_id, pad->m_product_id, pad->m_class_type, pad->m_class_profile);
num_ldd_pad++;
}
s32 pad_thread::AddLddPad()
{
// Look for first null pad
for (u32 i = 0; i < CELL_PAD_MAX_PORT_NUM; i++)
{
if (g_cfg_input.player[i]->handler == pad_handler::null && !m_pads[i]->ldd)
{
InitLddPad(i, nullptr);
return i;
}
}
return -1;
}
void pad_thread::UnregisterLddPad(u32 handle)
{
auto& pad = ::at32(m_pads, handle);
pad->ldd = false;
pad->m_port_status &= ~CELL_PAD_STATUS_CONNECTED;
pad->m_port_status |= CELL_PAD_STATUS_ASSIGN_CHANGES;
num_ldd_pad--;
}
std::shared_ptr<PadHandlerBase> pad_thread::GetHandler(pad_handler type)
{
switch (type)
{
case pad_handler::null:
return std::make_shared<NullPadHandler>();
case pad_handler::keyboard:
#ifdef ANDROID
return std::make_shared<NullPadHandler>();
#else
return std::make_shared<keyboard_pad_handler>();
#endif
case pad_handler::ds3:
return std::make_shared<ds3_pad_handler>();
case pad_handler::ds4:
return std::make_shared<ds4_pad_handler>();
case pad_handler::dualsense:
return std::make_shared<dualsense_pad_handler>();
case pad_handler::skateboard:
return std::make_shared<skateboard_pad_handler>();
case pad_handler::move:
return std::make_shared<ps_move_handler>();
#ifdef _WIN32
case pad_handler::xinput:
return std::make_shared<xinput_pad_handler>();
case pad_handler::mm:
return std::make_shared<mm_joystick_handler>();
#endif
#ifdef HAVE_SDL3
case pad_handler::sdl:
return std::make_shared<sdl_pad_handler>();
#endif
#ifdef HAVE_LIBEVDEV
case pad_handler::evdev:
return std::make_shared<evdev_joystick_handler>();
#endif
#ifdef __ANDROID__
// ARMSX3: on-screen touch controls. Guard must match the enum value in
// Emu/Io/pad_config_types.h and the fmt_class_string case beside it.
case pad_handler::virtual_pad:
return std::make_shared<virtual_pad_handler>();
#endif
}
return nullptr;
}
void pad_thread::InitPadConfig(cfg_pad& cfg, pad_handler type, std::shared_ptr<PadHandlerBase>& handler)
{
// We need to restore the original defaults first.
cfg.restore_defaults();
if (!handler)
{
handler = GetHandler(type);
}
ensure(!!handler);
// Set and apply actual defaults depending on pad handler
handler->init_config(&cfg);
}
extern bool send_open_home_menu_cmds();
extern void send_close_home_menu_cmds();
extern bool close_osk_from_ps_button();
void pad_thread::open_home_menu()
{
// Check if the OSK is open and can be closed
if (!close_osk_from_ps_button())
{
rsx::overlays::queue_message(get_localized_string(localized_string_id::CELL_OSK_DIALOG_BUSY));
return;
}
if (auto manager = g_fxo->try_get<rsx::overlays::display_manager>())
{
if (m_home_menu_open.exchange(true))
{
return;
}
if (!send_open_home_menu_cmds())
{
m_home_menu_open = false;
return;
}
input_log.notice("opening home menu...");
const error_code result = manager->create<rsx::overlays::home_menu_dialog>()->show([this](s32 status)
{
input_log.notice("closing home menu with status %d", status);
m_home_menu_open = false;
send_close_home_menu_cmds();
});
(result ? input_log.error : input_log.notice)("opened home menu with result %d", s32{result});
}
}