diff --git a/rpcs3/CMakeLists.txt b/rpcs3/CMakeLists.txt index ff02cc95c3..6d78ae237b 100644 --- a/rpcs3/CMakeLists.txt +++ b/rpcs3/CMakeLists.txt @@ -66,6 +66,7 @@ set(RPCS3_SRC Input/ds4_pad_handler.cpp Input/dualsense_pad_handler.cpp Input/evdev_joystick_handler.cpp + Input/evdev_gun_handler.cpp Input/hid_pad_handler.cpp Input/keyboard_pad_handler.cpp Input/mm_joystick_handler.cpp diff --git a/rpcs3/Emu/Cell/Modules/cellGem.cpp b/rpcs3/Emu/Cell/Modules/cellGem.cpp index 111c05cb1d..6af39c6e15 100644 --- a/rpcs3/Emu/Cell/Modules/cellGem.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGem.cpp @@ -11,6 +11,7 @@ #include "Emu/IdManager.h" #include "Emu/RSX/Overlays/overlay_cursor.h" #include "Input/pad_thread.h" +#include "Input/evdev_gun_handler.h" #include // for fmod @@ -214,6 +215,13 @@ public: connected_controllers = 1; break; } + case move_handler::gun: + { +#ifdef HAVE_LIBEVDEV + connected_controllers = evdev_gun_handler::getInstance()->getNumGuns(); +#endif + break; + } case move_handler::null: default: break; @@ -851,6 +859,63 @@ static void mouse_pos_to_gem_state(const u32 mouse_no, const gem_config::gem_con pos_to_gem_state(mouse_no, controller, gem_state, mouse.x_pos, mouse.y_pos, mouse.x_max, mouse.y_max); } +static bool gun_input_to_pad(const u32 gem_no, be_t& digital_buttons, be_t& analog_t) +{ + digital_buttons = 0; + analog_t = 0; + + if (!is_input_allowed()) { + return false; + } + + evdev_gun_handler *gh = evdev_gun_handler::getInstance(); + gh->pool(); + + digital_buttons = 0; + + if(gh->getButton(gem_no, EVDEV_GUN_BUTTON_LEFT) == 1) + digital_buttons |= CELL_GEM_CTRL_T; + + if(gh->getButton(gem_no, EVDEV_GUN_BUTTON_RIGHT) == 1) + digital_buttons |= CELL_GEM_CTRL_MOVE; + + if(gh->getButton(gem_no, EVDEV_GUN_BUTTON_MIDDLE) == 1) + digital_buttons |= CELL_GEM_CTRL_SELECT; + + if(gh->getButton(gem_no, EVDEV_GUN_BUTTON_BTN1) == 1) + digital_buttons |= CELL_GEM_CTRL_START; + + if(gh->getButton(gem_no, EVDEV_GUN_BUTTON_BTN2) == 1) + digital_buttons |= CELL_GEM_CTRL_CROSS; + + if(gh->getButton(gem_no, EVDEV_GUN_BUTTON_BTN3) == 1) + digital_buttons |= CELL_GEM_CTRL_CIRCLE; + + if(gh->getButton(gem_no, EVDEV_GUN_BUTTON_BTN4) == 1) + digital_buttons |= CELL_GEM_CTRL_SQUARE; + + if(gh->getButton(gem_no, EVDEV_GUN_BUTTON_BTN5) == 1) + digital_buttons |= CELL_GEM_CTRL_TRIANGLE; + + //analog_t = (mouse_data.buttons & CELL_MOUSE_BUTTON_1) ? 0xFFFF : 0; + return true; +} + +static void gun_pos_to_gem_state(const u32 gem_no, const gem_config::gem_controller& controller, vm::ptr& gem_state) +{ + if (!gem_state || !is_input_allowed()) { + return; + } + + evdev_gun_handler *gh = evdev_gun_handler::getInstance(); + int x_pos = gh->getAxisX(gem_no); + int y_pos = gh->getAxisY(gem_no); + int x_max = gh->getAxisXMax(gem_no); + int y_max = gh->getAxisYMax(gem_no); + + pos_to_gem_state(gem_no, controller, gem_state, x_pos, y_pos, x_max, y_max); +} + // ********************* // * cellGem functions * // ********************* @@ -1232,7 +1297,7 @@ error_code cellGemGetImageState(u32 gem_num, vm::ptr gem_imag *gem_image_state = {}; - if (g_cfg.io.move == move_handler::fake || g_cfg.io.move == move_handler::mouse) + if (g_cfg.io.move == move_handler::fake || g_cfg.io.move == move_handler::mouse || g_cfg.io.move == move_handler::gun) { auto& shared_data = g_fxo->get(); gem_image_state->frame_timestamp = shared_data.frame_timestamp.load(); @@ -1250,6 +1315,12 @@ error_code cellGemGetImageState(u32 gem_num, vm::ptr gem_imag { mouse_pos_to_gem_image_state(gem_num, gem.controllers[gem_num], gem_image_state); } + else if (g_cfg.io.move == move_handler::gun) + { +#ifdef HAVE_LIBEVDEV + gun_pos_to_gem_state(gem_num, gem.controllers[gem_num], gem_image_state); +#endif + } } return CELL_OK; @@ -1280,7 +1351,7 @@ error_code cellGemGetInertialState(u32 gem_num, u32 state_flag, u64 timestamp, v *inertial_state = {}; - if (g_cfg.io.move == move_handler::fake || g_cfg.io.move == move_handler::mouse) + if (g_cfg.io.move == move_handler::fake || g_cfg.io.move == move_handler::mouse || g_cfg.io.move == move_handler::gun) { ds3_input_to_ext(gem_num, gem.controllers[gem_num], inertial_state->ext); @@ -1296,6 +1367,12 @@ error_code cellGemGetInertialState(u32 gem_num, u32 state_flag, u64 timestamp, v { mouse_input_to_pad(gem_num, inertial_state->pad.digitalbuttons, inertial_state->pad.analog_T); } + else if (g_cfg.io.move == move_handler::gun) + { +#ifdef HAVE_LIBEVDEV + gun_input_to_pad(gem_num, inertial_state->pad.digitalbuttons, inertial_state->pad.analog_T); +#endif + } } return CELL_OK; @@ -1442,7 +1519,7 @@ error_code cellGemGetState(u32 gem_num, u32 flag, u64 time_parameter, vm::ptrext); @@ -1466,6 +1543,13 @@ error_code cellGemGetState(u32 gem_num, u32 flag, u64 time_parameter, vm::ptrpad.digitalbuttons, gem_state->pad.analog_T); mouse_pos_to_gem_state(gem_num, gem.controllers[gem_num], gem_state); } + else if (g_cfg.io.move == move_handler::gun) + { +#ifdef HAVE_LIBEVDEV + gun_input_to_pad(gem_num, gem_state->pad.digitalbuttons, gem_state->pad.analog_T); + gun_pos_to_gem_state(gem_num, gem.controllers[gem_num], gem_state); +#endif + } } if (false) // TODO: check if we are computing colors diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 9a88105dd8..d2090f3ac9 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -9,6 +9,7 @@ #include "Emu/perf_monitor.hpp" #include "Emu/vfs_config.h" #include "Emu/IPC_config.h" +#include "Input/evdev_gun_handler.h" #include "Emu/Cell/ErrorCodes.h" #include "Emu/Cell/PPUThread.h" @@ -2737,6 +2738,9 @@ void Emulator::CleanUp() { // Deinitialize object manager to prevent any hanging objects at program exit g_fxo->clear(); + + // deinitialize gun + evdev_gun_handler::shutdown(); } std::string Emulator::GetFormattedTitle(double fps) const diff --git a/rpcs3/Emu/system_config_types.cpp b/rpcs3/Emu/system_config_types.cpp index 4ae2a7a760..cc0fb3f434 100644 --- a/rpcs3/Emu/system_config_types.cpp +++ b/rpcs3/Emu/system_config_types.cpp @@ -412,6 +412,7 @@ void fmt_class_string::format(std::string& out, u64 arg) case move_handler::null: return "Null"; case move_handler::fake: return "Fake"; case move_handler::mouse: return "Mouse"; + case move_handler::gun: return "Gun"; } return unknown; diff --git a/rpcs3/Emu/system_config_types.h b/rpcs3/Emu/system_config_types.h index b9eda82a42..4df742ffe9 100644 --- a/rpcs3/Emu/system_config_types.h +++ b/rpcs3/Emu/system_config_types.h @@ -128,6 +128,7 @@ enum class move_handler null, fake, mouse, + gun }; enum class buzz_handler diff --git a/rpcs3/Input/evdev_gun_handler.cpp b/rpcs3/Input/evdev_gun_handler.cpp new file mode 100644 index 0000000000..e36d9de7ed --- /dev/null +++ b/rpcs3/Input/evdev_gun_handler.cpp @@ -0,0 +1,325 @@ +// This makes debugging on windows less painful +//#define HAVE_LIBEVDEV + +#ifdef HAVE_LIBEVDEV + +#include "evdev_gun_handler.h" +#include +#include +#include + +LOG_CHANNEL(evdev_log, "evdev"); + +evdev_gun_handler* evdev_gun_handler_instance = nullptr; + +evdev_gun_handler* evdev_gun_handler::getInstance() +{ + if (evdev_gun_handler_instance != nullptr) + return evdev_gun_handler_instance; + evdev_gun_handler_instance = new evdev_gun_handler(); + evdev_gun_handler_instance->init(); + return evdev_gun_handler_instance; +} + +struct event_udev_entry +{ + const char* devnode; + struct udev_list_entry* item; +}; + +int event_isNumber(const char* s) +{ + size_t n; + + if (strlen(s) == 0) + { + return 0; + } + + for (n = 0; n < strlen(s); n++) + { + if (!(s[n] == '0' || s[n] == '1' || s[n] == '2' || s[n] == '3' || s[n] == '4' || + s[n] == '5' || s[n] == '6' || s[n] == '7' || s[n] == '8' || s[n] == '9')) + return 0; + } + return 1; +} + +// compare /dev/input/eventX and /dev/input/eventY where X and Y are numbers +int event_strcmp_events(const char* x, const char* y) +{ + + // find a common string + int n, common, is_number; + int a, b; + + n = 0; + while (x[n] == y[n] && x[n] != '\0' && y[n] != '\0') + { + n++; + } + common = n; + + // check if remaining string is a number + is_number = 1; + if (event_isNumber(x + common) == 0) + is_number = 0; + if (event_isNumber(y + common) == 0) + is_number = 0; + + if (is_number == 1) + { + a = atoi(x + common); + b = atoi(y + common); + + if (a == b) + return 0; + if (a < b) + return -1; + return 1; + } + else + { + return strcmp(x, y); + } +} + +/* Used for sorting devnodes to appear in the correct order */ +static int sort_devnodes(const void* a, const void* b) +{ + const struct event_udev_entry* aa = static_cast(a); + const struct event_udev_entry* bb = static_cast(b); + return event_strcmp_events(aa->devnode, bb->devnode); +} + +evdev_gun_handler::evdev_gun_handler() +{ + m_is_init = false; + m_ndevices = 0; +} + +evdev_gun_handler::~evdev_gun_handler() +{ + for (int i = 0; i < m_ndevices; i++) + { + close(m_devices[i]); + } + if (m_udev != nullptr) + udev_unref(m_udev); + m_ndevices = 0; + m_is_init = false; + evdev_log.notice("Lightgun: Shutdown udev initialization"); +} + +int evdev_gun_handler::getButton(int gunno, int button) +{ + return m_devices_buttons[gunno][button]; +} + +int evdev_gun_handler::getAxisX(int gunno) +{ + return m_devices_axis[gunno][0][EVDEV_GUN_AXIS_VALS_CURRENT] - m_devices_axis[gunno][0][EVDEV_GUN_AXIS_VALS_MIN]; +} + +int evdev_gun_handler::getAxisY(int gunno) +{ + return m_devices_axis[gunno][1][EVDEV_GUN_AXIS_VALS_CURRENT] - m_devices_axis[gunno][1][EVDEV_GUN_AXIS_VALS_MIN]; +} + +int evdev_gun_handler::getAxisXMax(int gunno) +{ + return m_devices_axis[gunno][0][EVDEV_GUN_AXIS_VALS_MAX] - m_devices_axis[gunno][0][EVDEV_GUN_AXIS_VALS_MIN]; +} + +int evdev_gun_handler::getAxisYMax(int gunno) +{ + return m_devices_axis[gunno][1][EVDEV_GUN_AXIS_VALS_MAX] - m_devices_axis[gunno][1][EVDEV_GUN_AXIS_VALS_MIN]; +} + +void evdev_gun_handler::pool() +{ + struct input_event input_events[32]; + int j, len; + + for (int i = 0; i < m_ndevices; i++) + { + while ((len = read(m_devices[i], input_events, sizeof(input_events))) > 0) + { + len /= sizeof(*input_events); + for (j = 0; j < len; j++) + { + if (input_events[j].type == EV_KEY) + { + switch (input_events[j].code) + { + case BTN_LEFT: + m_devices_buttons[i][EVDEV_GUN_BUTTON_LEFT] = input_events[j].value; + break; + case BTN_RIGHT: + m_devices_buttons[i][EVDEV_GUN_BUTTON_RIGHT] = input_events[j].value; + break; + case BTN_MIDDLE: + m_devices_buttons[i][EVDEV_GUN_BUTTON_MIDDLE] = input_events[j].value; + break; + case BTN_1: + m_devices_buttons[i][EVDEV_GUN_BUTTON_BTN1] = input_events[j].value; + break; + case BTN_2: + m_devices_buttons[i][EVDEV_GUN_BUTTON_BTN2] = input_events[j].value; + break; + case BTN_3: + m_devices_buttons[i][EVDEV_GUN_BUTTON_BTN3] = input_events[j].value; + break; + case BTN_4: + m_devices_buttons[i][EVDEV_GUN_BUTTON_BTN4] = input_events[j].value; + break; + case BTN_5: + m_devices_buttons[i][EVDEV_GUN_BUTTON_BTN5] = input_events[j].value; + break; + } + } + else if (input_events[j].type == EV_ABS) + { + if (input_events[j].code == ABS_X) + { + m_devices_axis[i][0][EVDEV_GUN_AXIS_VALS_CURRENT] = input_events[j].value; + } + else if (input_events[j].code == ABS_Y) + { + m_devices_axis[i][1][EVDEV_GUN_AXIS_VALS_CURRENT] = input_events[j].value; + } + } + } + } + } +} + +int evdev_gun_handler::getNumGuns() +{ + return m_ndevices; +} + +void evdev_gun_handler::shutdown() +{ + if (evdev_gun_handler_instance != nullptr) + { + delete evdev_gun_handler_instance; + evdev_gun_handler_instance = nullptr; + } +} + +bool evdev_gun_handler::init() +{ + if (m_is_init) + return true; + + struct udev_enumerate* enumerate; + struct udev_list_entry* devs = nullptr; + struct udev_list_entry* item = nullptr; + unsigned sorted_count = 0; + struct event_udev_entry sorted[8]; // max devices + unsigned int i; + + evdev_log.notice("Lightgun: Begin udev initialization"); + + m_udev = udev_new(); + if (m_udev == nullptr) + return false; + + enumerate = udev_enumerate_new(m_udev); + + if (enumerate != nullptr) + { + udev_enumerate_add_match_property(enumerate, "ID_INPUT_MOUSE", "1"); + udev_enumerate_add_match_subsystem(enumerate, "input"); + udev_enumerate_scan_devices(enumerate); + devs = udev_enumerate_get_list_entry(enumerate); + + for (item = devs; item; item = udev_list_entry_get_next(item)) + { + const char* name = udev_list_entry_get_name(item); + struct udev_device* dev = udev_device_new_from_syspath(m_udev, name); + const char* devnode = udev_device_get_devnode(dev); + + if (devnode != nullptr && sorted_count < 8) + { + sorted[sorted_count].devnode = devnode; + sorted[sorted_count].item = item; + sorted_count++; + } + else + { + udev_device_unref(dev); + } + } + + /* Sort the udev entries by devnode name so that they are + * created in the proper order */ + qsort(sorted, sorted_count, + sizeof(struct event_udev_entry), sort_devnodes); + + for (i = 0; i < sorted_count; i++) + { + if (m_ndevices >= EVDEV_GUN_MAX_DEVICES) + break; + + const char* name = udev_list_entry_get_name(sorted[i].item); + /* Get the filename of the /sys entry for the device + * and create a udev_device object (dev) representing it. */ + struct udev_device* dev = udev_device_new_from_syspath(m_udev, name); + evdev_log.notice("Lightgun: found device %s", name); + const char* devnode = udev_device_get_devnode(dev); + + if (devnode) + { + struct input_absinfo absx, absy; + int valid = 0; + int fd = open(devnode, O_RDONLY | O_NONBLOCK); + if (fd != -1) + { + for (int b = 0; b < EVDEV_GUN_BUTTON_MAX; b++) + { + m_devices_buttons[m_ndevices][b] = 0; + } + for (int a = 0; a < 3; a++) + { + m_devices_axis[m_ndevices][0][a] = 0; + m_devices_axis[m_ndevices][1][a] = 0; + } + if (ioctl(fd, EVIOCGABS(ABS_X), &absx) >= 0) + { + if (ioctl(fd, EVIOCGABS(ABS_Y), &absy) >= 0) + { + evdev_log.notice("Lightgun: device %s, absx(%i, %i), absy(%i, %i)", name, absx.minimum, absx.maximum, absy.minimum, absy.maximum); + + m_devices_axis[m_ndevices][0][EVDEV_GUN_AXIS_VALS_MIN] = absx.minimum; + m_devices_axis[m_ndevices][0][EVDEV_GUN_AXIS_VALS_MAX] = absx.maximum; + m_devices_axis[m_ndevices][1][EVDEV_GUN_AXIS_VALS_MIN] = absy.minimum; + m_devices_axis[m_ndevices][1][EVDEV_GUN_AXIS_VALS_MAX] = absy.maximum; + valid = 1; + } + } + + if (valid == 1) + { + evdev_log.notice("Lightgun: device %s set as gun %i", name, m_ndevices); + m_devices[m_ndevices++] = fd; + } + else + { + evdev_log.notice("Lightgun: device %s not valid. abs_x and abs_y not found", name); + close(fd); + } + } + } + udev_device_unref(dev); + } + udev_enumerate_unref(enumerate); + return true; + } + + m_is_init = true; + return true; +} + +#endif diff --git a/rpcs3/Input/evdev_gun_handler.h b/rpcs3/Input/evdev_gun_handler.h new file mode 100644 index 0000000000..c07d6e95dd --- /dev/null +++ b/rpcs3/Input/evdev_gun_handler.h @@ -0,0 +1,60 @@ +#pragma once +#ifdef HAVE_LIBEVDEV + +#include "util/types.hpp" +#include "util/logs.hpp" + +#define EVDEV_GUN_MAX_DEVICES 8 + +enum evdev_gun_buttons +{ + EVDEV_GUN_BUTTON_LEFT, + EVDEV_GUN_BUTTON_RIGHT, + EVDEV_GUN_BUTTON_MIDDLE, + EVDEV_GUN_BUTTON_BTN1, + EVDEV_GUN_BUTTON_BTN2, + EVDEV_GUN_BUTTON_BTN3, + EVDEV_GUN_BUTTON_BTN4, + EVDEV_GUN_BUTTON_BTN5, + EVDEV_GUN_BUTTON_MAX, +}; + +enum evdev_gun_axis_vals +{ + EVDEV_GUN_AXIS_VALS_MIN, + EVDEV_GUN_AXIS_VALS_CURRENT, + EVDEV_GUN_AXIS_VALS_MAX, +}; + +#define EVDEV_GUN_BUTTON_LEFT 1 + +class evdev_gun_handler +{ +public: + evdev_gun_handler(); + ~evdev_gun_handler(); + + static evdev_gun_handler* getInstance(); + static void shutdown(); + + bool init(); + + int getNumGuns(); + int getButton(int gunno, int button); + int getAxisX(int gunno); + int getAxisY(int gunno); + int getAxisXMax(int gunno); + int getAxisYMax(int gunno); + + void pool(); + +private: + bool m_is_init; + struct udev* m_udev = nullptr; + int m_devices[EVDEV_GUN_MAX_DEVICES]; + int m_devices_buttons[EVDEV_GUN_MAX_DEVICES][EVDEV_GUN_BUTTON_MAX]; + int m_devices_axis[EVDEV_GUN_MAX_DEVICES][2][3]; + int m_ndevices; +}; + +#endif diff --git a/rpcs3/rpcs3qt/emu_settings.cpp b/rpcs3/rpcs3qt/emu_settings.cpp index a07ab57e32..d70c0406a9 100644 --- a/rpcs3/rpcs3qt/emu_settings.cpp +++ b/rpcs3/rpcs3qt/emu_settings.cpp @@ -1057,6 +1057,7 @@ QString emu_settings::GetLocalizedSetting(const QString& original, emu_settings_ case move_handler::null: return tr("Null", "Move handler"); case move_handler::fake: return tr("Fake", "Move handler"); case move_handler::mouse: return tr("Mouse", "Move handler"); + case move_handler::gun: return tr("Gun", "Gun handler"); } break; case emu_settings_type::Buzz: diff --git a/rpcs3/rpcs3qt/gs_frame.cpp b/rpcs3/rpcs3qt/gs_frame.cpp index 53a60e725c..03b20e5473 100644 --- a/rpcs3/rpcs3qt/gs_frame.cpp +++ b/rpcs3/rpcs3qt/gs_frame.cpp @@ -808,7 +808,7 @@ void gs_frame::take_screenshot(std::vector data, const u32 sshot_width, cons void gs_frame::mouseDoubleClickEvent(QMouseEvent* ev) { - if (m_disable_mouse || g_cfg.io.move == move_handler::mouse) return; + if (m_disable_mouse || g_cfg.io.move == move_handler::mouse || g_cfg.io.move == move_handler::gun) return; if (ev->button() == Qt::LeftButton) {