/*************************************************************************/ /* joypad_openbsd.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ /* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ /* "Software"), to deal in the Software without restriction, including */ /* without limitation the rights to use, copy, modify, merge, publish, */ /* distribute, sublicense, and/or sell copies of the Software, and to */ /* permit persons to whom the Software is furnished to do so, subject to */ /* the following conditions: */ /* */ /* The above copyright notice and this permission notice shall be */ /* included in all copies or substantial portions of the Software. */ /* */ /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ //author: Thomas Frohwein #ifdef JOYDEV_ENABLED #include "joypad_openbsd.h" #include #include #include #include extern "C" { #include #include #include } #define HUG_DPAD_UP 0x90 #define HUG_DPAD_DOWN 0x91 #define HUG_DPAD_RIGHT 0x92 #define HUG_DPAD_LEFT 0x93 #define HAT_CENTERED 0x00 #define HAT_UP 0x01 #define HAT_RIGHT 0x02 #define HAT_DOWN 0x04 #define HAT_LEFT 0x08 #define HAT_RIGHTUP (HAT_RIGHT|HAT_UP) #define HAT_RIGHTDOWN (HAT_RIGHT|HAT_DOWN) #define HAT_LEFTUP (HAT_LEFT|HAT_UP) #define HAT_LEFTDOWN (HAT_LEFT|HAT_DOWN) #define REP_BUF_DATA(rep) ((rep)->buf->ucr_data) static struct { int uhid_report; hid_kind_t kind; const char *name; } const repinfo[] = { {UHID_INPUT_REPORT, hid_input, "input"}, {UHID_OUTPUT_REPORT, hid_output, "output"}, {UHID_FEATURE_REPORT, hid_feature, "feature"} }; enum { REPORT_INPUT, REPORT_OUTPUT, REPORT_FEATURE }; enum { JOYAXE_X, JOYAXE_Y, JOYAXE_Z, JOYAXE_SLIDER, JOYAXE_WHEEL, JOYAXE_RX, JOYAXE_RY, JOYAXE_RZ, JOYAXE_count }; static int report_alloc(struct report *r, struct report_desc *rd, int repind) { int len; len = hid_report_size(rd, repinfo[repind].kind, r->rid); if (len < 0) { ERR_PRINT("Negative HID report siz"); return ERR_PARAMETER_RANGE_ERROR; } r->size = len; if (r->size > 0) { r->buf = (usb_ctl_report *)malloc(sizeof(*r->buf) - sizeof(REP_BUF_DATA(r)) + r->size); if (r->buf == NULL) return ERR_OUT_OF_MEMORY; } else { r->buf = NULL; } r->status = report::SREPORT_CLEAN; return OK; } static int usage_to_joyaxe(unsigned usage) { switch (usage) { case HUG_X: return JOYAXE_X; case HUG_Y: return JOYAXE_Y; case HUG_Z: return JOYAXE_Z; case HUG_SLIDER: return JOYAXE_SLIDER; case HUG_WHEEL: return JOYAXE_WHEEL; case HUG_RX: return JOYAXE_RX; case HUG_RY: return JOYAXE_RY; case HUG_RZ: return JOYAXE_RZ; default: return -1; } } static unsigned hatval_conversion(int hatval) { static const unsigned hat_dir_map[8] = { HAT_UP, HAT_RIGHTUP, HAT_RIGHT, HAT_RIGHTDOWN, HAT_DOWN, HAT_LEFTDOWN, HAT_LEFT, HAT_LEFTUP }; if ((hatval & 7) == hatval) return hat_dir_map[hatval]; else return HAT_CENTERED; } /* calculate the value from the state of the dpad */ int dpad_conversion(int *dpad) { if (dpad[2]) { if (dpad[0]) return HAT_RIGHTUP; else if (dpad[1]) return HAT_RIGHTDOWN; else return HAT_RIGHT; } else if (dpad[3]) { if (dpad[0]) return HAT_LEFTUP; else if (dpad[1]) return HAT_LEFTDOWN; else return HAT_LEFT; } else if (dpad[0]) { return HAT_UP; } else if (dpad[1]) { return HAT_DOWN; } return HAT_CENTERED; } JoypadOpenBSD::Joypad::Joypad() { fd = -1; dpad = 0; devpath = ""; } JoypadOpenBSD::Joypad::~Joypad() { } void JoypadOpenBSD::Joypad::reset() { dpad = 0; fd = -1; for (int i = 0; i < MAX_ABS; ++i) { abs_map[i] = -1; curr_axis[i] = 0; } } JoypadOpenBSD::JoypadOpenBSD(InputDefault *in) { input = in; joy_thread.start(joy_thread_func, this); hid_init(NULL); } JoypadOpenBSD::~JoypadOpenBSD() { exit_monitor.set(); joy_thread.wait_to_finish(); close_joypad(); } void JoypadOpenBSD::joy_thread_func(void *p_user) { if (p_user) { JoypadOpenBSD *joy = (JoypadOpenBSD *)p_user; joy->run_joypad_thread(); } } void JoypadOpenBSD::run_joypad_thread() { monitor_joypads(); } void JoypadOpenBSD::monitor_joypads() { while (!exit_monitor.is_set()) { joy_mutex.lock(); for (int i = 0; i < 32; i++) { char fname[64]; sprintf(fname, "/dev/ujoy/%d", i); if (attached_devices.find(fname) == -1) { open_joypad(fname); } } joy_mutex.unlock(); usleep(1000000); // 1s } } int JoypadOpenBSD::get_joy_from_path(String p_path) const { for (int i = 0; i < JOYPADS_MAX; i++) { if (joypads[i].devpath == p_path) { return i; } } return -2; } void JoypadOpenBSD::close_joypad(int p_id) { if (p_id == -1) { for (int i = 0; i < JOYPADS_MAX; i++) { close_joypad(i); }; return; } else if (p_id < 0) return; Joypad &joy = joypads[p_id]; if (joy.fd != -1) { close(joy.fd); joy.fd = -1; attached_devices.remove(attached_devices.find(joy.devpath)); input->joy_connection_changed(p_id, false, ""); }; } void JoypadOpenBSD::setup_joypad_properties(int p_id) { Error err; Joypad *joy = &joypads[p_id]; struct hid_item hitem; struct hid_data *hdata; struct report *rep = NULL; int i; int ax; for (ax = 0; ax < JOYAXE_count; ax++) joy->axis_map[ax] = -1; joy->type = Joypad::BSDJOY_UHID; // TODO: hardcoded; later check if /dev/joyX or /dev/ujoy/X joy->repdesc = hid_get_report_desc(joy->fd); if (joy->repdesc == NULL) { ERR_PRINT("getting USB report descriptor"); } rep = &joy->inreport; if (ioctl(joy->fd, USB_GET_REPORT_ID, &rep->rid) < 0) { rep->rid = -1; /* XXX */ } err = (Error)report_alloc(rep, joy->repdesc, REPORT_INPUT); if (err != OK) { ERR_PRINT("allocating report descriptor"); } if (rep->size <= 0) { ERR_PRINT("input report descriptor has invalid length"); } hdata = hid_start_parse(joy->repdesc, 1 << hid_input, rep->rid); if (hdata == NULL) { ERR_PRINT("cannot start HID parser"); } int num_buttons = 0; int num_axes = 0; int num_hats = 0; while (hid_get_item(hdata, &hitem)) { switch (hitem.kind) { case hid_input: switch (HID_PAGE(hitem.usage)) { case HUP_GENERIC_DESKTOP: { unsigned usage = HID_USAGE(hitem.usage); int joyaxe = usage_to_joyaxe(usage); if (joyaxe >= 0) { joy->axis_map[joyaxe] = 1; } else if (usage == HUG_HAT_SWITCH || usage == HUG_DPAD_UP) { num_hats++; } break; } case HUP_BUTTON: num_buttons++; break; default: break; } default: break; } } hid_end_parse(hdata); for (i = 0; i < JOYAXE_count; i++) if (joy->axis_map[i] > 0) joy->axis_map[i] = num_axes++; if (num_axes == 0 && num_buttons == 0 && num_hats == 0) { ERR_PRINT("Not a joystick!"); } else { printf("joypad %d: %d axes %d buttons %d hats\n", p_id, num_axes, num_buttons, num_hats); } joy->force_feedback = false; joy->ff_effect_timestamp = 0; } void JoypadOpenBSD::open_joypad(const char *p_path) { int joy_num = input->get_unused_joy_id(); if (joy_num < 0) { ERR_PRINT("no ID available to assign to joypad device"); return; } int fd = open(p_path, O_RDONLY | O_NONBLOCK); if (fd != -1 && joy_num != -1) { // add to attached devices so we don't try to open it again attached_devices.push_back(String(p_path)); String name = ""; joypads[joy_num].reset(); Joypad &joy = joypads[joy_num]; joy.fd = fd; joy.devpath = String(p_path); setup_joypad_properties(joy_num); String uidname = "00"; input->joy_connection_changed(joy_num, true, name, uidname); } } void JoypadOpenBSD::joypad_vibration_start(int p_id, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp) { /* not supported */ } void JoypadOpenBSD::joypad_vibration_stop(int p_id, uint64_t p_timestamp) { /* not supported */ } float JoypadOpenBSD::axis_correct(int min, int max, int p_value) const { // Convert to a value between -1.0 and 1.0f. return 2.0f * (p_value - min) / (max - min) - 1.0f; } void JoypadOpenBSD::process_joypads() { struct hid_item hitem; struct hid_data *hdata; struct report *rep; int nbutton, naxe = -1; int v; int dpad[4] = { 0, 0, 0, 0 }; int actualbutton; if (joy_mutex.try_lock() != OK) { return; } for (int i = 0; i < JOYPADS_MAX; i++) { if (joypads[i].fd == -1) continue; Joypad *joy = &joypads[i]; rep = &joy->inreport; while (true) { ssize_t r = read(joy->fd, REP_BUF_DATA(rep), rep->size); if (r < 0 || (size_t)r != rep->size) { break; } hdata = hid_start_parse(joy->repdesc, 1 << hid_input, rep->rid); if (hdata == NULL) { continue; } for (nbutton = 0; hid_get_item(hdata, &hitem) > 0;) { (void)nbutton; switch (hitem.kind) { case hid_input: unsigned usage; int joyaxe; switch (HID_PAGE(hitem.usage)) { case HUP_GENERIC_DESKTOP: usage = HID_USAGE(hitem.usage); joyaxe = usage_to_joyaxe(usage); if (joyaxe >= 0) { naxe = joy->axis_map[joyaxe]; (void)naxe; v = hid_get_data(REP_BUF_DATA(rep), &hitem); /* XInput controllermapping relies on inverted Y axes. * These devices have a 16bit signed space, as opposed * to older DInput devices (8bit unsigned), so * hitem.logical_maximum can be used to differentiate them. */ if ((joyaxe == JOYAXE_Y || joyaxe == JOYAXE_RY) && hitem.logical_maximum > 255) { if (v != 0) v = ~v; } input->joy_axis(i, joyaxe, axis_correct(hitem.logical_minimum, hitem.logical_maximum, v)); break; } else if (usage == HUG_HAT_SWITCH) { v = hid_get_data(REP_BUF_DATA(rep), &hitem); joy->dpad = hatval_conversion(v); } else if (usage == HUG_DPAD_UP) { dpad[0] = hid_get_data(REP_BUF_DATA(rep), &hitem); joy->dpad = dpad_conversion(dpad); } else if (usage == HUG_DPAD_DOWN) { dpad[1] = hid_get_data(REP_BUF_DATA(rep), &hitem); joy->dpad = dpad_conversion(dpad); } else if (usage == HUG_DPAD_RIGHT) { dpad[2] = hid_get_data(REP_BUF_DATA(rep), &hitem); joy->dpad = dpad_conversion(dpad); } else if (usage == HUG_DPAD_LEFT) { dpad[3] = hid_get_data(REP_BUF_DATA(rep), &hitem); joy->dpad = dpad_conversion(dpad); } input->joy_hat(i, joy->dpad); break; case HUP_BUTTON: v = hid_get_data(REP_BUF_DATA(rep), &hitem); actualbutton = HID_USAGE(hitem.usage) - 1; // buttons are zero-based input->joy_button(i, actualbutton, v); default: continue; } break; default: break; } } hid_end_parse(hdata); } } joy_mutex.unlock(); } #endif