mirror of
https://github.com/glfw/glfw.git
synced 2025-12-21 14:42:06 +00:00
418 lines
15 KiB
C
418 lines
15 KiB
C
//========================================================================
|
|
// GLFW 3.5 OGC - www.glfw.org
|
|
//------------------------------------------------------------------------
|
|
// Copyright (c) 2024 Alberto Mardegan <info@mardy.it>
|
|
//
|
|
// This software is provided 'as-is', without any express or implied
|
|
// warranty. In no event will the authors be held liable for any damages
|
|
// arising from the use of this software.
|
|
//
|
|
// Permission is granted to anyone to use this software for any purpose,
|
|
// including commercial applications, and to alter it and redistribute it
|
|
// freely, subject to the following restrictions:
|
|
//
|
|
// 1. The origin of this software must not be misrepresented; you must not
|
|
// claim that you wrote the original software. If you use this software
|
|
// in a product, an acknowledgment in the product documentation would
|
|
// be appreciated but is not required.
|
|
//
|
|
// 2. Altered source versions must be plainly marked as such, and must not
|
|
// be misrepresented as being the original software.
|
|
//
|
|
// 3. This notice may not be removed or altered from any source
|
|
// distribution.
|
|
//
|
|
//========================================================================
|
|
|
|
#include "internal.h"
|
|
|
|
#if defined(_GLFW_OGC)
|
|
|
|
#include <ogc/lwp_watchdog.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#ifdef __wii__
|
|
#include <wiiuse/wpad.h>
|
|
#endif
|
|
|
|
#define MAX_FAILED_GC_READS 10
|
|
|
|
#define MAX_GC_AXES 6
|
|
#define MAX_GC_BUTTONS 8
|
|
#define MAX_GC_HATS 1
|
|
|
|
/* PAD_ScanPads() returns 0 if no joystick is connected, but also if querying
|
|
* the hardware failed for whatever reason. This can occasionally happen at
|
|
* runtime; so, we want to ignore these events and only report the joystick
|
|
* disconnection if PAD_ScanPads() consistently returns 0.
|
|
*
|
|
* This function returns true if the scanPads value is reliable.
|
|
*/
|
|
static GLFWbool readGCJoysticks(u32 *scanPads)
|
|
{
|
|
static GLFWbool lastReadFailed = GLFW_FALSE;
|
|
static u64 firstDisconnectedTimestamp = 0;
|
|
u32 currScanPads;
|
|
u64 timestamp;
|
|
|
|
currScanPads = PAD_ScanPads();
|
|
if (currScanPads == 0) {
|
|
timestamp = gettime();
|
|
if (lastReadFailed) {
|
|
if (ticks_to_millisecs(timestamp - firstDisconnectedTimestamp) < 500)
|
|
return GLFW_FALSE;
|
|
} else {
|
|
firstDisconnectedTimestamp = timestamp;
|
|
lastReadFailed = GLFW_TRUE;
|
|
return GLFW_FALSE;
|
|
}
|
|
} else {
|
|
lastReadFailed = GLFW_FALSE;
|
|
}
|
|
|
|
*scanPads = currScanPads;
|
|
return GLFW_TRUE;
|
|
}
|
|
|
|
static void readGCJoystick(_GLFWjoystick* js)
|
|
{
|
|
u32 id = js->ogc.id;
|
|
u16 btns = PAD_ButtonsHeld(id);
|
|
js->buttons[0] = btns & PAD_BUTTON_A ? GLFW_PRESS : GLFW_RELEASE;
|
|
js->buttons[1] = btns & PAD_BUTTON_B ? GLFW_PRESS : GLFW_RELEASE;
|
|
js->buttons[2] = btns & PAD_BUTTON_X ? GLFW_PRESS : GLFW_RELEASE;
|
|
js->buttons[3] = btns & PAD_BUTTON_Y ? GLFW_PRESS : GLFW_RELEASE;
|
|
js->buttons[4] = btns & PAD_TRIGGER_L ? GLFW_PRESS : GLFW_RELEASE;
|
|
js->buttons[5] = btns & PAD_TRIGGER_R ? GLFW_PRESS : GLFW_RELEASE;
|
|
js->buttons[6] = btns & PAD_TRIGGER_Z ? GLFW_PRESS : GLFW_RELEASE;
|
|
js->buttons[7] = btns & PAD_BUTTON_START ? GLFW_PRESS : GLFW_RELEASE;
|
|
js->axes[0] = PAD_StickX(id) / 100.0;
|
|
js->axes[1] = PAD_StickY(id) / 100.0;
|
|
js->axes[2] = PAD_SubStickX(id) / 100.0;
|
|
js->axes[3] = PAD_SubStickY(id) / 100.0;
|
|
js->axes[4] = PAD_TriggerL(id) / 200.0;
|
|
js->axes[5] = PAD_TriggerR(id) / 200.0;
|
|
unsigned char hat = 0;
|
|
if (btns & PAD_BUTTON_UP) hat |= GLFW_HAT_UP;
|
|
if (btns & PAD_BUTTON_DOWN) hat |= GLFW_HAT_DOWN;
|
|
if (btns & PAD_BUTTON_LEFT) hat |= GLFW_HAT_LEFT;
|
|
if (btns & PAD_BUTTON_RIGHT) hat |= GLFW_HAT_RIGHT;
|
|
_glfwInputJoystickHat(js, 0, hat);
|
|
}
|
|
|
|
/* CRC algorithm used by SDL */
|
|
static uint16_t crc16_for_byte(uint8_t r)
|
|
{
|
|
uint16_t crc = 0;
|
|
int i;
|
|
for (i = 0; i < 8; ++i) {
|
|
crc = ((crc ^ r) & 1 ? 0xA001 : 0) ^ crc >> 1;
|
|
r >>= 1;
|
|
}
|
|
return crc;
|
|
}
|
|
|
|
uint16_t SDL_crc16(uint16_t crc, const void *data, size_t len)
|
|
{
|
|
/* As an optimization we can precalculate a 256 entry table for each byte */
|
|
size_t i;
|
|
for (i = 0; i < len; ++i) {
|
|
crc = crc16_for_byte((uint8_t)crc ^ ((const uint8_t *)data)[i]) ^ crc >> 8;
|
|
}
|
|
return crc;
|
|
}
|
|
|
|
static _GLFWjoystick* addJoystick(int deviceId,
|
|
const char *name, const char* guid,
|
|
int axisCount, int buttonCount, int hatCount)
|
|
{
|
|
fprintf(stderr, "Add controller %d %s\n", deviceId, name);
|
|
_GLFWjoystick* js =
|
|
_glfwAllocJoystick(name, guid,
|
|
axisCount, buttonCount, hatCount);
|
|
js->ogc.id = deviceId;
|
|
_glfw.ogcjs.joystickIndex[deviceId] = js - _glfw.joysticks;
|
|
_glfwInputJoystick(js, GLFW_CONNECTED);
|
|
return js;
|
|
}
|
|
|
|
static void addGCJoystick(int deviceId)
|
|
{
|
|
char name[16], guid[33];
|
|
uint16_t nameCrc;
|
|
|
|
sprintf(name, "Gamecube %d", deviceId);
|
|
nameCrc = SDL_crc16(0, name, strlen(name));
|
|
sprintf(guid, "0000%02x%02x7e050000000%d000001000000",
|
|
nameCrc & 0xff, nameCrc >> 8, deviceId + 1);
|
|
addJoystick(deviceId, name, guid,
|
|
MAX_GC_AXES, MAX_GC_BUTTONS, MAX_GC_HATS);
|
|
}
|
|
|
|
#ifdef __wii__
|
|
static inline float readAxis(uint8_t pos, uint8_t center, uint8_t min, uint8_t max)
|
|
{
|
|
if (pos < center) {
|
|
return (pos - center) / (float)(center - min);
|
|
} else {
|
|
return (pos - center) / (float)(max - center);
|
|
}
|
|
}
|
|
|
|
static void readJoystickAxes(_GLFWjoystick* js, int index, const joystick_t *data)
|
|
{
|
|
js->axes[index] = readAxis(data->pos.x, data->center.x, data->min.x, data->max.x);
|
|
js->axes[index + 1] = readAxis(data->pos.y, data->center.y, data->min.y, data->max.y);
|
|
}
|
|
|
|
static inline float clampUnity(float value)
|
|
{
|
|
if (value < -1.0f) return -1.0f;
|
|
if (value > 1.0f) return 1.0f;
|
|
return value;
|
|
}
|
|
|
|
static void readOrientationAxes(_GLFWjoystick* js, int index, const orient_t *data)
|
|
{
|
|
/* Orientation fields range from -180 to 180 (they are measured in
|
|
* degrees), but in order to use them as joystick axes it's more reasonable
|
|
* to limit their range to -90/90. */
|
|
js->axes[index] = clampUnity(-data->pitch / 90.0f);
|
|
js->axes[index + 1] = clampUnity(data->roll / 90.0f);
|
|
js->axes[index + 2] = clampUnity(data->yaw / 90.0f);
|
|
}
|
|
|
|
static void readWiimote(_GLFWjoystick* js)
|
|
{
|
|
u32 id = js->ogc.id - MAX_GC_JOYSTICKS;
|
|
WPADData *data = WPAD_Data(id);
|
|
u32 btns = data->btns_h | data->btns_d;
|
|
unsigned char hat = 0;
|
|
|
|
GLFWbool readWiimote = GLFW_FALSE;
|
|
GLFWbool wiimotePointer = GLFW_FALSE;
|
|
int wiimoteFirstAxis = 0;
|
|
if (js->ogc.expansion == EXP_NUNCHUK) {
|
|
js->buttons[7] = btns & WPAD_NUNCHUK_BUTTON_Z ? GLFW_PRESS : GLFW_RELEASE;
|
|
js->buttons[8] = btns & WPAD_NUNCHUK_BUTTON_C ? GLFW_PRESS : GLFW_RELEASE;
|
|
/* When a nunchuk is connected, we report its joystick as the first two axes,
|
|
* followed by three axes from the wiimote orientation, followed by
|
|
* three axes from the nunchuk orientation. */
|
|
readJoystickAxes(js, 0, &data->exp.nunchuk.js);
|
|
readWiimote = GLFW_TRUE;
|
|
wiimotePointer = GLFW_TRUE;
|
|
wiimoteFirstAxis = 2;
|
|
readOrientationAxes(js, wiimoteFirstAxis + 3, &data->exp.nunchuk.orient);
|
|
} else if (js->ogc.expansion == EXP_CLASSIC) {
|
|
if (btns & WPAD_CLASSIC_BUTTON_LEFT) hat |= GLFW_HAT_LEFT;
|
|
if (btns & WPAD_CLASSIC_BUTTON_RIGHT) hat |= GLFW_HAT_RIGHT;
|
|
if (btns & WPAD_CLASSIC_BUTTON_UP) hat |= GLFW_HAT_UP;
|
|
if (btns & WPAD_CLASSIC_BUTTON_DOWN) hat |= GLFW_HAT_DOWN;
|
|
js->buttons[0] = btns & WPAD_CLASSIC_BUTTON_A ? GLFW_PRESS : GLFW_RELEASE;
|
|
js->buttons[1] = btns & WPAD_CLASSIC_BUTTON_B ? GLFW_PRESS : GLFW_RELEASE;
|
|
js->buttons[2] = btns & WPAD_CLASSIC_BUTTON_X ? GLFW_PRESS : GLFW_RELEASE;
|
|
js->buttons[3] = btns & WPAD_CLASSIC_BUTTON_Y ? GLFW_PRESS : GLFW_RELEASE;
|
|
js->buttons[4] = btns & WPAD_CLASSIC_BUTTON_FULL_L ? GLFW_PRESS : GLFW_RELEASE;
|
|
js->buttons[5] = btns & WPAD_CLASSIC_BUTTON_FULL_R ? GLFW_PRESS : GLFW_RELEASE;
|
|
js->buttons[6] = btns & WPAD_CLASSIC_BUTTON_ZL ? GLFW_PRESS : GLFW_RELEASE;
|
|
js->buttons[7] = btns & WPAD_CLASSIC_BUTTON_ZR ? GLFW_PRESS : GLFW_RELEASE;
|
|
js->buttons[8] = btns & WPAD_CLASSIC_BUTTON_MINUS ? GLFW_PRESS : GLFW_RELEASE;
|
|
js->buttons[9] = btns & WPAD_CLASSIC_BUTTON_PLUS ? GLFW_PRESS : GLFW_RELEASE;
|
|
js->buttons[10] = btns & WPAD_CLASSIC_BUTTON_HOME ? GLFW_PRESS : GLFW_RELEASE;
|
|
readJoystickAxes(js, 0, &data->exp.classic.ljs);
|
|
readJoystickAxes(js, 2, &data->exp.classic.rjs);
|
|
} else if (js->ogc.expansion == EXP_NONE) {
|
|
readWiimote = GLFW_TRUE;
|
|
wiimotePointer = data->ir.valid;
|
|
}
|
|
|
|
if (readWiimote) {
|
|
if (wiimotePointer) {
|
|
if (btns & WPAD_BUTTON_LEFT) hat |= GLFW_HAT_LEFT;
|
|
if (btns & WPAD_BUTTON_RIGHT) hat |= GLFW_HAT_RIGHT;
|
|
if (btns & WPAD_BUTTON_UP) hat |= GLFW_HAT_UP;
|
|
if (btns & WPAD_BUTTON_DOWN) hat |= GLFW_HAT_DOWN;
|
|
} else {
|
|
if (btns & WPAD_BUTTON_LEFT) hat |= GLFW_HAT_DOWN;
|
|
if (btns & WPAD_BUTTON_RIGHT) hat |= GLFW_HAT_UP;
|
|
if (btns & WPAD_BUTTON_UP) hat |= GLFW_HAT_LEFT;
|
|
if (btns & WPAD_BUTTON_DOWN) hat |= GLFW_HAT_RIGHT;
|
|
}
|
|
|
|
js->buttons[0] = btns & WPAD_BUTTON_1 ? GLFW_PRESS : GLFW_RELEASE;
|
|
js->buttons[1] = btns & WPAD_BUTTON_2 ? GLFW_PRESS : GLFW_RELEASE;
|
|
js->buttons[2] = btns & WPAD_BUTTON_A ? GLFW_PRESS : GLFW_RELEASE;
|
|
js->buttons[3] = btns & WPAD_BUTTON_B ? GLFW_PRESS : GLFW_RELEASE;
|
|
js->buttons[4] = btns & WPAD_BUTTON_MINUS ? GLFW_PRESS : GLFW_RELEASE;
|
|
js->buttons[5] = btns & WPAD_BUTTON_PLUS ? GLFW_PRESS : GLFW_RELEASE;
|
|
js->buttons[6] = btns & WPAD_BUTTON_HOME ? GLFW_PRESS : GLFW_RELEASE;
|
|
readOrientationAxes(js, wiimoteFirstAxis, &data->orient);
|
|
}
|
|
|
|
_glfwInputJoystickHat(js, 0, hat);
|
|
|
|
/* On the Wii, let's close the window when the HOME button is pressed,
|
|
* since that's the default behaviour for homebrew applications. The close
|
|
* event can anyway be overridden in the application, if this behaviour is
|
|
* not desired. */
|
|
if (btns & WPAD_BUTTON_HOME && _glfw.windowListHead) {
|
|
_glfwInputWindowCloseRequest(_glfw.windowListHead);
|
|
}
|
|
}
|
|
|
|
static void addWiimote(int deviceId, uint8_t expansion)
|
|
{
|
|
char name[64], guid[40];
|
|
uint16_t nameCrc;
|
|
int axisCount, buttonCount, hatCount;
|
|
|
|
/* These counters are for the bare Wiimote */
|
|
axisCount = 3;
|
|
buttonCount = 7;
|
|
hatCount = 1;
|
|
char *name_ptr = name;
|
|
name_ptr += sprintf(name_ptr, "Wiimote %d", deviceId - MAX_GC_JOYSTICKS);
|
|
switch (expansion) {
|
|
case WPAD_EXP_NUNCHUK:
|
|
strcpy(name_ptr, " + Nunchuk");
|
|
axisCount += 5;
|
|
buttonCount += 2;
|
|
break;
|
|
case WPAD_EXP_CLASSIC:
|
|
strcpy(name_ptr, " + Classic");
|
|
axisCount = 4;
|
|
buttonCount = 11;
|
|
hatCount = 1;
|
|
break;
|
|
case WPAD_EXP_GUITARHERO3:
|
|
strcpy(name_ptr, " + Guitar Hero 3");
|
|
break;
|
|
case WPAD_EXP_WIIBOARD:
|
|
strcpy(name_ptr, " + Balance board");
|
|
break;
|
|
}
|
|
|
|
nameCrc = SDL_crc16(0, name, strlen(name));
|
|
sprintf(guid, "0500%02x%02x7e0500000%d0%d000001000000",
|
|
nameCrc & 0xff, nameCrc >> 8, expansion + 1, deviceId + 1);
|
|
_GLFWjoystick *js = addJoystick(deviceId, name, guid,
|
|
axisCount, buttonCount, hatCount);
|
|
js->ogc.expansion = expansion;
|
|
}
|
|
#endif /* __wii__ */
|
|
|
|
static void removeJoystick(int deviceId)
|
|
{
|
|
_GLFWjoystick* js = JOYSTICK_FROM_DEVICE(deviceId);
|
|
_glfwInputJoystick(js, GLFW_DISCONNECTED);
|
|
_glfwFreeJoystick(js);
|
|
_glfw.ogcjs.joystickIndex[deviceId] = -1;
|
|
fprintf(stderr, "Removed controller %d\n", deviceId);
|
|
}
|
|
|
|
static GLFWbool updateJoysticks()
|
|
{
|
|
u32 scanPads = 0;
|
|
|
|
if (_glfw.joysticksInitialized && readGCJoysticks(&scanPads)) {
|
|
for (int i = 0; i < MAX_GC_JOYSTICKS; i++) {
|
|
GLFWbool connected = !!(scanPads & (1 << i));
|
|
int joystickIndex = JOYSTICK_INDEX_FROM_DEVICE(i);
|
|
GLFWbool wasConnected = joystickIndex >= 0 ?
|
|
_glfw.joysticks[joystickIndex].connected : GLFW_FALSE;
|
|
if (connected != wasConnected) {
|
|
if (connected) {
|
|
addGCJoystick(i);
|
|
joystickIndex = JOYSTICK_INDEX_FROM_DEVICE(i);
|
|
} else {
|
|
removeJoystick(i);
|
|
}
|
|
}
|
|
if (connected)
|
|
readGCJoystick(&_glfw.joysticks[joystickIndex]);
|
|
}
|
|
}
|
|
|
|
#ifdef __wii__
|
|
/* The WPAD data is also used for the mouse, so we read it even if
|
|
* joysticks haven't been initialized */
|
|
WPAD_ReadPending(WPAD_CHAN_ALL, NULL);
|
|
if (_glfw.joysticksInitialized) {
|
|
for (int i = 0; i < MAX_WIIMOTES; i++) {
|
|
int deviceId = i + MAX_GC_JOYSTICKS;
|
|
int joystickIndex = JOYSTICK_INDEX_FROM_DEVICE(deviceId);
|
|
WPADData *data = WPAD_Data(i);
|
|
/* Ignore all reads where an error occurred */
|
|
if (data->err != WPAD_ERR_NONE) continue;
|
|
GLFWbool connected = data->data_present != 0;
|
|
uint8_t expansion = data->data_present & WPAD_DATA_EXPANSION ?
|
|
data->exp.type : EXP_NONE;
|
|
GLFWbool wasConnected = joystickIndex >= 0 ?
|
|
_glfw.joysticks[joystickIndex].connected : GLFW_FALSE;
|
|
uint8_t hadExpansion = joystickIndex >= 0 ?
|
|
_glfw.joysticks[joystickIndex].ogc.expansion : EXP_NONE;
|
|
if (connected != wasConnected || expansion != hadExpansion) {
|
|
if (wasConnected) {
|
|
removeJoystick(deviceId);
|
|
}
|
|
if (connected) {
|
|
addWiimote(deviceId, expansion);
|
|
joystickIndex = JOYSTICK_INDEX_FROM_DEVICE(deviceId);
|
|
}
|
|
}
|
|
if (connected)
|
|
readWiimote(&_glfw.joysticks[joystickIndex]);
|
|
}
|
|
}
|
|
#endif
|
|
return GLFW_TRUE;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
////// GLFW internal API //////
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
GLFWbool _glfwPollJoysticksOgc(void)
|
|
{
|
|
return updateJoysticks();
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
////// GLFW platform API //////
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
GLFWbool _glfwInitJoysticksOgc(void)
|
|
{
|
|
/* Hardware initialization done in ogc_init.c, since we want it to happen
|
|
* ASAP */
|
|
for (int i = 0; i < MAX_JOYSTICKS; i++) {
|
|
_glfw.ogcjs.joystickIndex[i] = -1;
|
|
}
|
|
return GLFW_TRUE;
|
|
}
|
|
|
|
void _glfwTerminateJoysticksOgc(void)
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
GLFWbool _glfwPollJoystickOgc(_GLFWjoystick* js, int mode)
|
|
{
|
|
updateJoysticks();
|
|
return js->connected;
|
|
}
|
|
|
|
const char* _glfwGetMappingNameOgc(void)
|
|
{
|
|
return "Ogc";
|
|
}
|
|
|
|
void _glfwUpdateGamepadGUIDOgc(char* guid)
|
|
{
|
|
}
|
|
|
|
#endif // _GLFW_OGC
|
|
|