#pragma once #ifdef HAVE_LIBEVDEV #include "util/types.hpp" #include "util/File.h" #include "Emu/Io/PadHandler.h" #include #include #include #include #include #include struct positive_axis : cfg::node { const std::string cfg_name = fs::get_config_dir() + "/evdev_positive_axis.yml"; cfg::_bool abs_x{this, "ABS_X", false}; cfg::_bool abs_y{this, "ABS_Y", false}; cfg::_bool abs_z{this, "ABS_Z", false}; cfg::_bool abs_rx{this, "ABS_RX", false}; cfg::_bool abs_ry{this, "ABS_RY", false}; cfg::_bool abs_rz{this, "ABS_RZ", false}; cfg::_bool abs_throttle{this, "ABS_THROTTLE", false}; cfg::_bool abs_rudder{this, "ABS_RUDDER", false}; cfg::_bool abs_wheel{this, "ABS_WHEEL", false}; cfg::_bool abs_gas{this, "ABS_GAS", false}; cfg::_bool abs_brake{this, "ABS_BRAKE", false}; cfg::_bool abs_hat0x{this, "ABS_HAT0X", false}; cfg::_bool abs_hat0y{this, "ABS_HAT0Y", false}; cfg::_bool abs_hat1x{this, "ABS_HAT1X", false}; cfg::_bool abs_hat1y{this, "ABS_HAT1Y", false}; cfg::_bool abs_hat2x{this, "ABS_HAT2X", false}; cfg::_bool abs_hat2y{this, "ABS_HAT2Y", false}; cfg::_bool abs_hat3x{this, "ABS_HAT3X", false}; cfg::_bool abs_hat3y{this, "ABS_HAT3Y", false}; cfg::_bool abs_pressure{this, "ABS_PRESSURE", false}; cfg::_bool abs_distance{this, "ABS_DISTANCE", false}; cfg::_bool abs_tilt_x{this, "ABS_TILT_X", false}; cfg::_bool abs_tilt_y{this, "ABS_TILT_Y", false}; cfg::_bool abs_tool_width{this, "ABS_TOOL_WIDTH", false}; cfg::_bool abs_volume{this, "ABS_VOLUME", false}; cfg::_bool abs_misc{this, "ABS_MISC", false}; cfg::_bool abs_mt_slot{this, "ABS_MT_SLOT", false}; cfg::_bool abs_mt_touch_major{this, "ABS_MT_TOUCH_MAJOR", false}; cfg::_bool abs_mt_touch_minor{this, "ABS_MT_TOUCH_MINOR", false}; cfg::_bool abs_mt_width_major{this, "ABS_MT_WIDTH_MAJOR", false}; cfg::_bool abs_mt_width_minor{this, "ABS_MT_WIDTH_MINOR", false}; cfg::_bool abs_mt_orientation{this, "ABS_MT_ORIENTATION", false}; cfg::_bool abs_mt_position_x{this, "ABS_MT_POSITION_X", false}; cfg::_bool abs_mt_position_y{this, "ABS_MT_POSITION_Y", false}; cfg::_bool abs_mt_tool_type{this, "ABS_MT_TOOL_TYPE", false}; cfg::_bool abs_mt_blob_id{this, "ABS_MT_BLOB_ID", false}; cfg::_bool abs_mt_tracking_id{this, "ABS_MT_TRACKING_ID", false}; cfg::_bool abs_mt_pressure{this, "ABS_MT_PRESSURE", false}; cfg::_bool abs_mt_distance{this, "ABS_MT_DISTANCE", false}; cfg::_bool abs_mt_tool_x{this, "ABS_MT_TOOL_X", false}; cfg::_bool abs_mt_tool_y{this, "ABS_MT_TOOL_Y", false}; bool load() { if (fs::file cfg_file{cfg_name, fs::read}) { return from_string(cfg_file.to_string()); } return false; } void save() { fs::pending_file file(cfg_name); if (file.file) { file.file.write(to_string()); file.commit(); } } bool exist() { return fs::is_file(cfg_name); } }; class evdev_joystick_handler final : public PadHandlerBase { static constexpr u32 NO_BUTTON = u32{umax}; // Some event code that doesn't exist in evdev (code type is U16) // Unique button names for the config files and our pad settings dialog const std::unordered_map button_list = { {NO_BUTTON, ""}, // Xbox One S Controller returns some buttons as key when connected through bluetooth {KEY_BACK, "Back Key"}, {KEY_HOMEPAGE, "Homepage Key"}, // Wii/Wii U Controller drivers use the following few keys {KEY_UP, "Up Key"}, {KEY_LEFT, "Left Key"}, {KEY_RIGHT, "Right Key"}, {KEY_DOWN, "Down Key"}, {KEY_NEXT, "Next Key"}, {KEY_PREVIOUS, "Previous Key"}, //{ BTN_MISC , "Misc" }, same as BTN_0 {BTN_0, "0"}, {BTN_1, "1"}, {BTN_2, "2"}, {BTN_3, "3"}, {BTN_4, "4"}, {BTN_5, "5"}, {BTN_6, "6"}, {BTN_7, "7"}, {BTN_8, "8"}, {BTN_9, "9"}, {0x10a, "0x10a"}, {0x10b, "0x10b"}, {0x10c, "0x10c"}, {0x10d, "0x10d"}, {0x10e, "0x10e"}, {0x10f, "0x10f"}, //{ BTN_MOUSE , "Mouse" }, same as BTN_LEFT {BTN_LEFT, "Left"}, {BTN_RIGHT, "Right"}, {BTN_MIDDLE, "Middle"}, {BTN_SIDE, "Side"}, {BTN_EXTRA, "Extra"}, {BTN_FORWARD, "Forward"}, {BTN_BACK, "Back"}, {BTN_TASK, "Task"}, {0x118, "0x118"}, {0x119, "0x119"}, {0x11a, "0x11a"}, {0x11b, "0x11b"}, {0x11c, "0x11c"}, {0x11d, "0x11d"}, {0x11e, "0x11e"}, {0x11f, "0x11f"}, //{ BTN_JOYSTICK , "Joystick" }, same as BTN_TRIGGER {BTN_TRIGGER, "Trigger"}, {BTN_THUMB, "Thumb"}, {BTN_THUMB2, "Thumb 2"}, {BTN_TOP, "Top"}, {BTN_TOP2, "Top 2"}, {BTN_PINKIE, "Pinkie"}, {BTN_BASE, "Base"}, {BTN_BASE2, "Base 2"}, {BTN_BASE3, "Base 3"}, {BTN_BASE4, "Base 4"}, {BTN_BASE5, "Base 5"}, {BTN_BASE6, "Base 6"}, {0x12c, "0x12c"}, {0x12d, "0x12d"}, {0x12e, "0x12e"}, {BTN_DEAD, "Dead"}, //{ BTN_GAMEPAD , "Gamepad" }, same as BTN_A //{ BTN_SOUTH , "South" }, same as BTN_A {BTN_A, "A"}, //{ BTN_EAST , "South" }, same as BTN_B {BTN_B, "B"}, {BTN_C, "C"}, //{ BTN_NORTH , "North" }, same as BTN_X {BTN_X, "X"}, //{ BTN_WEST , "West" }, same as BTN_Y {BTN_Y, "Y"}, {BTN_Z, "Z"}, {BTN_TL, "TL"}, {BTN_TR, "TR"}, {BTN_TL2, "TL 2"}, {BTN_TR2, "TR 2"}, {BTN_SELECT, "Select"}, {BTN_START, "Start"}, {BTN_MODE, "Mode"}, {BTN_THUMBL, "Thumb L"}, {BTN_THUMBR, "Thumb R"}, {0x13f, "0x13f"}, //{ BTN_DIGI , "Digi" }, same as BTN_TOOL_PEN {BTN_TOOL_PEN, "Pen"}, {BTN_TOOL_RUBBER, "Rubber"}, {BTN_TOOL_BRUSH, "Brush"}, {BTN_TOOL_PENCIL, "Pencil"}, {BTN_TOOL_AIRBRUSH, "Airbrush"}, {BTN_TOOL_FINGER, "Finger"}, {BTN_TOOL_MOUSE, "Mouse"}, {BTN_TOOL_LENS, "Lense"}, {BTN_TOOL_QUINTTAP, "Quinttap"}, {0x149, "0x149"}, {BTN_TOUCH, "Touch"}, {BTN_STYLUS, "Stylus"}, {BTN_STYLUS2, "Stylus 2"}, {BTN_TOOL_DOUBLETAP, "Doubletap"}, {BTN_TOOL_TRIPLETAP, "Tripletap"}, {BTN_TOOL_QUADTAP, "Quadtap"}, //{ BTN_WHEEL , "Wheel" }, same as BTN_GEAR_DOWN {BTN_GEAR_DOWN, "Gear Up"}, {BTN_GEAR_UP, "Gear Down"}, {BTN_DPAD_UP, "D-Pad Up"}, {BTN_DPAD_DOWN, "D-Pad Down"}, {BTN_DPAD_LEFT, "D-Pad Left"}, {BTN_DPAD_RIGHT, "D-Pad Right"}, {BTN_TRIGGER_HAPPY, "Happy"}, {BTN_TRIGGER_HAPPY1, "Happy 1"}, {BTN_TRIGGER_HAPPY2, "Happy 2"}, {BTN_TRIGGER_HAPPY3, "Happy 3"}, {BTN_TRIGGER_HAPPY4, "Happy 4"}, {BTN_TRIGGER_HAPPY5, "Happy 5"}, {BTN_TRIGGER_HAPPY6, "Happy 6"}, {BTN_TRIGGER_HAPPY7, "Happy 7"}, {BTN_TRIGGER_HAPPY8, "Happy 8"}, {BTN_TRIGGER_HAPPY9, "Happy 9"}, {BTN_TRIGGER_HAPPY10, "Happy 10"}, {BTN_TRIGGER_HAPPY11, "Happy 11"}, {BTN_TRIGGER_HAPPY12, "Happy 12"}, {BTN_TRIGGER_HAPPY13, "Happy 13"}, {BTN_TRIGGER_HAPPY14, "Happy 14"}, {BTN_TRIGGER_HAPPY15, "Happy 15"}, {BTN_TRIGGER_HAPPY16, "Happy 16"}, {BTN_TRIGGER_HAPPY17, "Happy 17"}, {BTN_TRIGGER_HAPPY18, "Happy 18"}, {BTN_TRIGGER_HAPPY19, "Happy 19"}, {BTN_TRIGGER_HAPPY20, "Happy 20"}, {BTN_TRIGGER_HAPPY21, "Happy 21"}, {BTN_TRIGGER_HAPPY22, "Happy 22"}, {BTN_TRIGGER_HAPPY23, "Happy 23"}, {BTN_TRIGGER_HAPPY24, "Happy 24"}, {BTN_TRIGGER_HAPPY25, "Happy 25"}, {BTN_TRIGGER_HAPPY26, "Happy 26"}, {BTN_TRIGGER_HAPPY27, "Happy 27"}, {BTN_TRIGGER_HAPPY28, "Happy 28"}, {BTN_TRIGGER_HAPPY29, "Happy 29"}, {BTN_TRIGGER_HAPPY30, "Happy 30"}, {BTN_TRIGGER_HAPPY31, "Happy 31"}, {BTN_TRIGGER_HAPPY32, "Happy 32"}, {BTN_TRIGGER_HAPPY33, "Happy 33"}, {BTN_TRIGGER_HAPPY34, "Happy 34"}, {BTN_TRIGGER_HAPPY35, "Happy 35"}, {BTN_TRIGGER_HAPPY36, "Happy 36"}, {BTN_TRIGGER_HAPPY37, "Happy 37"}, {BTN_TRIGGER_HAPPY38, "Happy 38"}, {BTN_TRIGGER_HAPPY39, "Happy 39"}, {BTN_TRIGGER_HAPPY40, "Happy 40"}}; // Unique positive axis names for the config files and our pad settings dialog const std::unordered_map axis_list = { {ABS_X, "LX+"}, {ABS_Y, "LY+"}, {ABS_Z, "LZ+"}, {ABS_RX, "RX+"}, {ABS_RY, "RY+"}, {ABS_RZ, "RZ+"}, {ABS_THROTTLE, "Throttle+"}, {ABS_RUDDER, "Rudder+"}, {ABS_WHEEL, "Wheel+"}, {ABS_GAS, "Gas+"}, {ABS_BRAKE, "Brake+"}, {ABS_HAT0X, "Hat0 X+"}, {ABS_HAT0Y, "Hat0 Y+"}, {ABS_HAT1X, "Hat1 X+"}, {ABS_HAT1Y, "Hat1 Y+"}, {ABS_HAT2X, "Hat2 X+"}, {ABS_HAT2Y, "Hat2 Y+"}, {ABS_HAT3X, "Hat3 X+"}, {ABS_HAT3Y, "Hat3 Y+"}, {ABS_PRESSURE, "Pressure+"}, {ABS_DISTANCE, "Distance+"}, {ABS_TILT_X, "Tilt X+"}, {ABS_TILT_Y, "Tilt Y+"}, {ABS_TOOL_WIDTH, "Width+"}, {ABS_VOLUME, "Volume+"}, {ABS_MISC, "Misc+"}, {ABS_MT_SLOT, "Slot+"}, {ABS_MT_TOUCH_MAJOR, "MT TMaj+"}, {ABS_MT_TOUCH_MINOR, "MT TMin+"}, {ABS_MT_WIDTH_MAJOR, "MT WMaj+"}, {ABS_MT_WIDTH_MINOR, "MT WMin+"}, {ABS_MT_ORIENTATION, "MT Orient+"}, {ABS_MT_POSITION_X, "MT PosX+"}, {ABS_MT_POSITION_Y, "MT PosY+"}, {ABS_MT_TOOL_TYPE, "MT TType+"}, {ABS_MT_BLOB_ID, "MT Blob ID+"}, {ABS_MT_TRACKING_ID, "MT Track ID+"}, {ABS_MT_PRESSURE, "MT Pressure+"}, {ABS_MT_DISTANCE, "MT Distance+"}, {ABS_MT_TOOL_X, "MT Tool X+"}, {ABS_MT_TOOL_Y, "MT Tool Y+"}, }; // Unique negative axis names for the config files and our pad settings dialog const std::unordered_map rev_axis_list = { {ABS_X, "LX-"}, {ABS_Y, "LY-"}, {ABS_Z, "LZ-"}, {ABS_RX, "RX-"}, {ABS_RY, "RY-"}, {ABS_RZ, "RZ-"}, {ABS_THROTTLE, "Throttle-"}, {ABS_RUDDER, "Rudder-"}, {ABS_WHEEL, "Wheel-"}, {ABS_GAS, "Gas-"}, {ABS_BRAKE, "Brake-"}, {ABS_HAT0X, "Hat0 X-"}, {ABS_HAT0Y, "Hat0 Y-"}, {ABS_HAT1X, "Hat1 X-"}, {ABS_HAT1Y, "Hat1 Y-"}, {ABS_HAT2X, "Hat2 X-"}, {ABS_HAT2Y, "Hat2 Y-"}, {ABS_HAT3X, "Hat3 X-"}, {ABS_HAT3Y, "Hat3 Y-"}, {ABS_PRESSURE, "Pressure-"}, {ABS_DISTANCE, "Distance-"}, {ABS_TILT_X, "Tilt X-"}, {ABS_TILT_Y, "Tilt Y-"}, {ABS_TOOL_WIDTH, "Width-"}, {ABS_VOLUME, "Volume-"}, {ABS_MISC, "Misc-"}, {ABS_MT_SLOT, "Slot-"}, {ABS_MT_TOUCH_MAJOR, "MT TMaj-"}, {ABS_MT_TOUCH_MINOR, "MT TMin-"}, {ABS_MT_WIDTH_MAJOR, "MT WMaj-"}, {ABS_MT_WIDTH_MINOR, "MT WMin-"}, {ABS_MT_ORIENTATION, "MT Orient-"}, {ABS_MT_POSITION_X, "MT PosX-"}, {ABS_MT_POSITION_Y, "MT PosY-"}, {ABS_MT_TOOL_TYPE, "MT TType-"}, {ABS_MT_BLOB_ID, "MT Blob ID-"}, {ABS_MT_TRACKING_ID, "MT Track ID-"}, {ABS_MT_PRESSURE, "MT Pressure-"}, {ABS_MT_DISTANCE, "MT Distance-"}, {ABS_MT_TOOL_X, "MT Tool X-"}, {ABS_MT_TOOL_Y, "MT Tool Y-"}, }; // Unique motion axis names for the config files and our pad settings dialog const std::unordered_map motion_axis_list = { {ABS_X, "X"}, {ABS_Y, "Y"}, {ABS_Z, "Z"}, {ABS_RX, "RX"}, {ABS_RY, "RY"}, {ABS_RZ, "RZ"}, }; struct EvdevButton { u32 code = 0; // key code of our button or axis int dir = 0; // dir is -1 in case of a button, 0 in case of a regular axis and 1 in case of a reverse axis int type = 0; // EV_KEY or EV_ABS }; struct evdev_sensor : public EvdevButton { bool mirrored = false; s32 shift = 0; }; struct input_event_wrapper { int type{}; // EV_KEY or EV_ABS bool is_initialized{}; }; struct key_event_wrapper : public input_event_wrapper { key_event_wrapper() { type = EV_KEY; } u16 value{}; }; struct axis_event_wrapper : public input_event_wrapper { axis_event_wrapper() { type = EV_ABS; } std::map values{}; // direction (negative = true) bool is_trigger{}; int min{}; int max{}; int flat{}; }; struct EvdevDevice : public PadDevice { libevdev* device{nullptr}; std::string path; std::vector all_buttons; std::set trigger_left{}; std::set trigger_right{}; std::array, 4> axis_left{}; std::array, 4> axis_right{}; std::array axis_motion{}; std::map> events_by_code; int cur_dir = 0; int cur_type = 0; int effect_id = -1; bool has_rumble = false; bool has_motion = false; }; public: evdev_joystick_handler(); ~evdev_joystick_handler(); void init_config(cfg_pad* cfg) override; bool Init() override; std::vector list_devices() override; bool bindPadToDevice(std::shared_ptr pad) override; connection get_next_button_press(const std::string& padId, const pad_callback& callback, const pad_fail_callback& fail_callback, gui_call_type call_type, const std::vector& buttons) override; void get_motion_sensors(const std::string& padId, const motion_callback& callback, const motion_fail_callback& fail_callback, motion_preview_values preview_values, const std::array& sensors) override; std::unordered_map get_motion_axis_list() const override; void SetPadData(const std::string& padId, u8 player_id, u8 large_motor, u8 small_motor, s32 r, s32 g, s32 b, bool player_led, bool battery_led, u32 battery_led_brightness) override; private: void close_devices(); std::shared_ptr get_evdev_device(const std::string& device); std::string get_device_name(const libevdev* dev); bool update_device(const std::shared_ptr& device); std::shared_ptr add_device(const std::string& device, bool in_settings = false); std::shared_ptr add_motion_device(const std::string& device, bool in_settings); std::unordered_map> GetButtonValues(const std::shared_ptr& device); void SetRumble(EvdevDevice* device, u8 large, u8 small); positive_axis m_pos_axis_config; std::set m_positive_axis; std::set m_blacklist; std::unordered_map m_min_button_values; std::unordered_map> m_settings_added; std::unordered_map> m_motion_settings_added; std::shared_ptr m_dev; bool check_button_set(const std::set& indices, const u32 code); bool check_button_sets(const std::array, 4>& sets, const u32 code); void apply_input_events(const std::shared_ptr& pad); u16 get_sensor_value(const libevdev* dev, const AnalogSensor& sensor, const input_event& evt) const; protected: PadHandlerBase::connection update_connection(const std::shared_ptr& device) override; void get_mapping(const pad_ensemble& binding) override; void get_extended_info(const pad_ensemble& binding) override; void apply_pad_data(const pad_ensemble& binding) override; bool get_is_left_trigger(const std::shared_ptr& device, u64 keyCode) override; bool get_is_right_trigger(const std::shared_ptr& device, u64 keyCode) override; bool get_is_left_stick(const std::shared_ptr& device, u64 keyCode) override; bool get_is_right_stick(const std::shared_ptr& device, u64 keyCode) override; }; #endif