Monday, November 01, 2021

Re: games/godot: add initial gamecontroller support

Thomas Frohwein <tfrohwein@fastmail.com> writes:

> Hi,
>
> This diff adds gamecontroller support to Godot. The code is largely
> based on sdl2 and fit into Godot's framework.

Hello :)

As you know, I can't really do a runtime test (my ps3 gamepad is not
supported :/) but the least I can do is provide some feedbacks on the
diff.

> [...]
>
> feedback and ok's welcome. I think in terms of functionality this is
> at an okay point to add to the port and hopefully more widespread use
> will help to figure out the remaining bits.

of course I'm OK with this! (but read below)

> Ultimately I will be working probably with Omar on upstreaming both
> sndio and gamecontroller support. It seems that upstream is deferring
> such updates to the eventual 4.0 release which is still ways out.

I forgot to tell you the other day, the way upstream works (if I've
understood correctly) is to merge improvements in the main branch
(i.e. the future godot 4.0) and then eventually backport the changes to
3.X. So there's still hope for the sndio patch :D

Also, if you have some questions on the godot internal there's an
instance of rocketchat where we can ask godot devs.

I have some comments on the diff (but keep in mind that i'm not a c++
guy, take everything with a grain of salt)

> [...]
> Index: files/joypad_openbsd/joypad_openbsd.cpp
> ===================================================================
> RCS file: files/joypad_openbsd/joypad_openbsd.cpp
> diff -N files/joypad_openbsd/joypad_openbsd.cpp
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ files/joypad_openbsd/joypad_openbsd.cpp 1 Nov 2021 06:49:42 -0000
> @@ -0,0 +1,516 @@
> +/*************************************************************************/
> +/* joypad_openbsd.cpp */
> +/*************************************************************************/
> +/* This file is part of: */
> +/* GODOT ENGINE */
> +/* https://godotengine.org */
> +/*************************************************************************/
> +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
> +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
> +/* Copyright (c) 2021 Thomas Frohwein */
> +/* */
> +/* 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. */
> +/*************************************************************************/
> +
> +#ifdef JOYDEV_ENABLED
> +
> +#include "joypad_openbsd.h"
> +
> +#include <sys/param.h>
> +
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <unistd.h>
> +
> +extern "C" {
> + #include <dev/usb/usb.h>
> + #include <dev/usb/usbhid.h>
> + #include <usbhid.h>
> +}

at first I was confused why the extern C is needed here, then I looked
up those headers and they don't have the (usual) __BEGIN_DECL/__END_DECL
that makes them usable from C++.

You know the situation better, but maybe it would be possible to make
those headers usable from C++ in the first place? I don't really know
whether is usual or not for system stuff to wrap declarations in
__BEGIN_DECL for these low-level headers.

> +#define LONG_BITS (sizeof(long) * 8)
> +#define test_bit(nr, addr) (((1UL << ((nr) % LONG_BITS)) & ((addr)[(nr) / LONG_BITS])) != 0)
> +#define NBITS(x) ((((x)-1) / LONG_BITS) + 1)

this group of defines seems to be unused, isn't it?

> +#define MAX_UHID_JOYS 64
> +#define MAX_JOY_JOYS 2
> +#define MAX_JOYS (MAX_UHID_JOYS + MAX_JOY_JOYS)

this one too, copy-paste? :D

> +#define DEV_USB 3 /* needed to get GUID from USB_GET_DEVICEINFO */
> +#define GUID_LEN 32 /* GUID string has length 32 */

and these too. Are they meant to be used in the future?

> +#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)
> +
> +extern "C" {

this extern doesn't really seems to be necessarly. The struct and enum
declarations are fine without, and the functions declared are only
called from C++ (i.e. they don't need to be available from C or being
called from C.)

> +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 = 0,
> + REPORT_OUTPUT = 1,
> + REPORT_FEATURE = 2
> +};

I would probably drop the explicit numbering here, unless you plan to
extend it in the future.

> +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 *, struct report_desc *, int);

and I'd also move the report_alloc here instead of forward-prototyping
it.

> +static int
> +usage_to_joyaxe(unsigned usage)
> +{
> + int joyaxe;
> + switch (usage) {
> + case HUG_X:
> + joyaxe = JOYAXE_X;
> + break;
> + case HUG_Y:
> + joyaxe = JOYAXE_Y;
> + break;
> + case HUG_Z:
> + joyaxe = JOYAXE_Z;
> + break;
> + case HUG_SLIDER:
> + joyaxe = JOYAXE_SLIDER;
> + break;
> + case HUG_WHEEL:
> + joyaxe = JOYAXE_WHEEL;
> + break;
> + case HUG_RX:
> + joyaxe = JOYAXE_RX;
> + break;
> + case HUG_RY:
> + joyaxe = JOYAXE_RY;
> + break;
> + case HUG_RZ:
> + joyaxe = JOYAXE_RZ;
> + break;
> + default:
> + joyaxe = -1;
> + }
> + return joyaxe;

nitpicking/style: I would avoid declaring a variable and instead doing a
plain return in each case. Of course, unless you plan to do further
checks between the switch and the return.

> +}
> +
> +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
> + };
> + unsigned result;
> + if ((hatval & 7) == hatval)
> + result = hat_dir_map[hatval];
> + else
> + result = HAT_CENTERED;
> + return result;

here too

> +}
> +
> +/* 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;
> +}
> +
> +} // extern "C"
> +
> +JoypadOpenBSD::Joypad::Joypad() {
> + fd = -1;
> + dpad = 0;
> + devpath = "";
> +}
> +
> +JoypadOpenBSD::Joypad::~Joypad() {
> +}
> +
> +void JoypadOpenBSD::Joypad::reset() {
> + dpad = 0;
> + fd = -1;
> +
> + InputDefault::JoyAxis jx;
> + jx.min = -1;
> + jx.value = 0.0f;

this seems like dead code, or do the assignments cause some
side-effects?

> +}
> +
> [...]
> +
> +void JoypadOpenBSD::setup_joypad_properties(int p_id) {
> + 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;

indentation :P

> + joy->type = Joypad::BSDJOY_UHID; // TODO: hardcoded; later should check if '/dev/joyX' or '/dev/ujoyX'
> +
> + joy->repdesc = hid_get_report_desc(joy->fd);
> + if (joy->repdesc == NULL) {
> + printf("ERROR getting USB report descriptor\n");
> + // TODO: break/abort
> + }
> + rep = &joy->inreport;
> + if (ioctl(joy->fd, USB_GET_REPORT_ID, &rep->rid) < 0) {
> + rep->rid = -1; /* XXX */
> + }
> + if (report_alloc(rep, joy->repdesc, REPORT_INPUT) < 0) {
> + printf("ERROR allocating report descriptor\n");
> + }
> + if (rep->size <= 0) {
> + printf("ERROR: input report descriptor has invalid length\n");
> + }
> + hdata = hid_start_parse(joy->repdesc, 1 << hid_input, rep->rid);
> + if (hdata == NULL) {
> + printf("ERROR: Cannot start HID parser\n");
> + }
> +
> + 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:
> + {

nitpicking again (sorry) the extra indentation level is not needed.
(saving you some time hopefully, if you open a pr on github there's a
bot that flags stuff like this.)

> + 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;
> + }
> + }
> + 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) {
> + printf("ERROR: Not a joystick!\n");
> + } else {
> + printf("joypad %d: %d axes %d buttons %d hats\n", p_id, num_axes, num_buttons, num_hats);
> + }
> + hid_end_parse(hdata);
> + 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();
> + 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));
> +
> + char uid[128];
> + 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 = uid;
> + int uidlen = MIN(name.length(), 11);

this variable is not used

> + 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 */
> +}
> +
> +InputDefault::JoyAxis JoypadOpenBSD::axis_correct(int min, int max, int p_value) const {
> + InputDefault::JoyAxis jx;
> +
> + if (min < 0) {
> + jx.min = -1;
> + if (p_value < 0) {
> + jx.value = (float)-p_value / min;
> + } else {
> + jx.value = (float)p_value / max;
> + }
> + } else if (min == 0) {
> + jx.min = 0;
> + jx.value = 0.0f + (float)p_value / max;
> + }
> +
> + return jx;
> +}
> +
> +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 (read(joy->fd, REP_BUF_DATA(rep), rep->size) ==
> rep->size) {

it warns about the comparison of integers of different signs: ssize_t
for read and size_t for rep->size.

Also, I'd check for a possible read error here. I rewrote the loop as
follows:

while (true) {
ssize_t r = read(joy->fd, REP_BUF_DATA(rep), rep->size);
if (r < 0) {
ERR_PRINTS("ujoy: can't read")
break;
}

if ((size_t)r != rep->size) {
break;
}

/* ... */
}

but we may want to abort if read fails...

> + hdata = hid_start_parse(joy->repdesc, 1 << hid_input, rep->rid);
> + if (hdata == NULL) {
> + continue;
> + }
> +
> + for (nbutton = 0; hid_get_item(hdata, &hitem) > 0;) {
> + switch (hitem.kind) {
> + case hid_input:
> + switch (HID_PAGE(hitem.usage)) {
> + case HUP_GENERIC_DESKTOP:
> + {

same nitpicking here

> [...]
> + joy_mutex.unlock();
> +}
> +
> +extern "C" {

this extern is redundant too

> +static int
> +report_alloc(struct report *r, struct report_desc *rd, int repind)
> +{
> + int len = hid_report_size(rd, repinfo[repind].kind, r->rid);
> + if (len < 0) {
> + printf("ERROR: Negative HID report size\n"); //
> SDL_SetError("Negative HID report size");

We could change report_alloc to return an Error on failure or an OK on
success, it would fit more in the godot' existing codebase. We could
also turn all those functions into (private) methods of the class, I
guess it would be more C++-esque, but I don't know if it would drift the
code away from the original SDL patch.

(I've also converted some printfs to ERR_PRINTS by the way)

> [...]
> Index: patches/patch-platform_x11_detect_py
> ===================================================================
> RCS file: /cvs/ports/games/godot/patches/patch-platform_x11_detect_py,v
> retrieving revision 1.6
> diff -u -p -r1.6 patch-platform_x11_detect_py
> --- patches/patch-platform_x11_detect_py 22 Aug 2021 01:43:49 -0000 1.6
> +++ patches/patch-platform_x11_detect_py 1 Nov 2021 06:49:42 -0000
> [...]
> ++
> ++ if platform.system() == "OpenBSD":
> ++ env.Append(CPPDEFINES=["JOYDEV_ENABLED"])

something tells me that you haven't updated scons :P

there's a mix of spaces (in the if line) and tabs (in the env.Append
line) which python3 doesn't really like.

> [...]

I'm attaching a patch with these point addressed. I've compiled it and
runs, but can't really ensure I haven't broke anything by accident.

Thanks,

Omar Polo


Index: Makefile
===================================================================
RCS file: /home/cvs/ports/games/godot/Makefile,v
retrieving revision 1.28
diff -u -p -r1.28 Makefile
--- Makefile 29 Oct 2021 21:07:02 -0000 1.28
+++ Makefile 1 Nov 2021 08:02:20 -0000
@@ -11,7 +11,7 @@ V = 3.3.4
GODOTSTEAM_V = g333-s151-g397
DISTNAME = godot-${V}-stable
PKGNAME = godot-${V}
-REVISION = 0
+REVISION = 1
CATEGORIES = games
HOMEPAGE = https://godotengine.org/
MAINTAINER = Omar Polo <op@omarpolo.com>
@@ -23,7 +23,7 @@ WANTLIB += ${COMPILER_LIBCXX}
WANTLIB += GL X11 Xau Xcursor Xdmcp Xext Xfixes Xi Xinerama Xrandr
WANTLIB += Xrender c enet execinfo freetype intl m mbedtls mbedcrypto
WANTLIB += mbedx509 mpcdec ogg opus opusfile png sndio steam_api theora
-WANTLIB += theoradec vorbis vorbisfile webp xcb z pcre2-32 vpx zstd
+WANTLIB += theoradec usbhid vorbis vorbisfile webp xcb z pcre2-32 vpx zstd

# C++14
COMPILER = base-clang ports-gcc
@@ -43,7 +43,7 @@ MODSCONS_FLAGS = CC="${CC}" \
CXX="${CXX}" \
CFLAGS="${CFLAGS} -I${LOCALBASE}/include/goldberg_emulator/sdk_includes" \
CXXFLAGS="${CXXFLAGS} -Wno-deprecated-register" \
- LINKFLAGS="${LDFLAGS} -lintl -lmpcdec" \
+ LINKFLAGS="${LDFLAGS} -lintl -lmpcdec -lusbhid" \
builtin_enet=no \
builtin_freetype=no \
builtin_glew=no \
@@ -91,6 +91,8 @@ WANTLIB += atomic

post-extract:
cp -R ${FILESDIR}/sndio ${WRKDIST}/drivers
+ cp ${FILESDIR}/joypad_openbsd/joypad_openbsd.{cpp,h} \
+ ${WRKDIST}/platform/x11/

pre-configure:
${SUBST_CMD} ${WRKSRC}/drivers/unix/os_unix.cpp
Index: files/joypad_openbsd/joypad_openbsd.cpp
===================================================================
RCS file: files/joypad_openbsd/joypad_openbsd.cpp
diff -N files/joypad_openbsd/joypad_openbsd.cpp
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ files/joypad_openbsd/joypad_openbsd.cpp 1 Nov 2021 10:42:56 -0000
@@ -0,0 +1,495 @@
+/*************************************************************************/
+/* joypad_openbsd.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2021 Thomas Frohwein */
+/* */
+/* 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. */
+/*************************************************************************/
+
+#ifdef JOYDEV_ENABLED
+
+#include "joypad_openbsd.h"
+
+#include <sys/param.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+extern "C" {
+ #include <dev/usb/usb.h>
+ #include <dev/usb/usbhid.h>
+ #include <usbhid.h>
+}
+
+#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 Error
+report_alloc(struct report *r, struct report_desc *rd, int repind)
+{
+ int len = hid_report_size(rd, repinfo[repind].kind, r->rid);
+ if (len < 0) {
+ ERR_PRINTS("Negative HID report size");
+ 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;
+
+ InputDefault::JoyAxis jx;
+ jx.min = -1;
+ jx.value = 0.0f;
+}
+
+JoypadOpenBSD::JoypadOpenBSD(InputDefault *in) {
+ exit_ujoy = false;
+ input = in;
+
+ joy_thread.start(joy_thread_func, this);
+ hid_init(NULL);
+}
+
+JoypadOpenBSD::~JoypadOpenBSD() {
+ exit_ujoy = true;
+ 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_ujoy) {
+ 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 should check if '/dev/joyX' or '/dev/ujoyX'
+
+ joy->repdesc = hid_get_report_desc(joy->fd);
+ if (joy->repdesc == NULL) {
+ ERR_PRINTS("getting USB report descriptor");
+ // TODO: break/abort
+ }
+ rep = &joy->inreport;
+ if (ioctl(joy->fd, USB_GET_REPORT_ID, &rep->rid) < 0) {
+ rep->rid = -1; /* XXX */
+ }
+
+ err = report_alloc(rep, joy->repdesc, REPORT_INPUT);
+ if (err != OK) {
+ ERR_PRINTS("allocating report descriptor");
+ }
+ if (rep->size <= 0) {
+ ERR_PRINTS("input report descriptor has invalid length");
+ }
+ hdata = hid_start_parse(joy->repdesc, 1 << hid_input, rep->rid);
+ if (hdata == NULL) {
+ ERR_PRINTS("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;
+ }
+ }
+ 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_PRINTS("Not a joystick!\n");
+ } else {
+ printf("joypad %d: %d axes %d buttons %d hats", p_id, num_axes, num_buttons, num_hats);
+ }
+ hid_end_parse(hdata);
+ 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();
+ 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));
+
+ char uid[128];
+ 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 = uid;
+ 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 */
+}
+
+InputDefault::JoyAxis JoypadOpenBSD::axis_correct(int min, int max, int p_value) const {
+ InputDefault::JoyAxis jx;
+
+ if (min < 0) {
+ jx.min = -1;
+ if (p_value < 0) {
+ jx.value = (float)-p_value / min;
+ } else {
+ jx.value = (float)p_value / max;
+ }
+ } else if (min == 0) {
+ jx.min = 0;
+ jx.value = 0.0f + (float)p_value / max;
+ }
+
+ return jx;
+}
+
+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) {
+ ERR_PRINTS("ujoy: can't read");
+ break;
+ }
+
+ if ((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;) {
+ 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) {
+ naxe = joy->axis_map[joyaxe];
+ 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;
+ }
+
+ //InputDefault::JoyAxis value = axis_correct(hitem.logical_minimum, hitem.logical_maximum, v);
+ //joy->curr_axis[joyaxe] = value;
+ //input->joy_axis(i, joyaxe, value);
+ 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);
+ nbutton++;
+ break;
+ default:
+ continue;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ hid_end_parse(hdata);
+ }
+ }
+ joy_mutex.unlock();
+}
+
+

No comments:

Post a Comment