root/src/kits/device/Joystick.cpp
/*
 * Copyright 2002-2008, Marcus Overhagen, Stefano Ceccherini, Fredrik Modéen.
 * All rights reserved.
 * Distributed under the terms of the MIT License.
 */


#include <Joystick.h>
#include <JoystickTweaker.h>

#include <new>
#include <stdio.h>
#include <sys/ioctl.h>

#include <Debug.h>
#include <Directory.h>
#include <List.h>
#include <Path.h>
#include <String.h>


#if DEBUG
static FILE *sLogFile = NULL;

inline void
LOG(const char *fmt, ...)
{
        char buf[1024];
        va_list ap;
        va_start(ap, fmt);
        vsprintf(buf, fmt, ap);
        va_end(ap);
        fputs(buf, sLogFile); fflush(sLogFile);
}

#       define LOG_ERR(text...) LOG(text)

#else
#       define LOG(text...)
#       define LOG_ERR(text...) fprintf(stderr, text)
#endif

#define CALLED() LOG("%s\n", __PRETTY_FUNCTION__)


BJoystick::BJoystick()
        :
        // legacy members for standard mode
        timestamp(0),
        horizontal(0),
        vertical(0),
        button1(true),
        button2(true),

        fBeBoxMode(false),
        fFD(-1),
        fDevices(new(std::nothrow) BList),
        fJoystickInfo(new(std::nothrow) joystick_info),
        fJoystickData(new(std::nothrow) BList)
{
#if DEBUG
        sLogFile = fopen("/var/log/joystick.log", "a");
#endif

        if (fJoystickInfo != NULL) {
                memset(&fJoystickInfo->module_info, 0, sizeof(joystick_module_info));
                fJoystickInfo->calibration_enable = false;
                fJoystickInfo->max_latency = 0;
        }

        RescanDevices();
}


BJoystick::~BJoystick()
{
        if (fFD >= 0)
                close(fFD);

        if (fDevices != NULL) {
                for (int32 i = 0; i < fDevices->CountItems(); i++)
                        delete (BString *)fDevices->ItemAt(i);

                delete fDevices;
        }

        delete fJoystickInfo;

        if (fJoystickData != NULL) {
                for (int32 i = 0; i < fJoystickData->CountItems(); i++) {
                        variable_joystick *variableJoystick
                                = (variable_joystick *)fJoystickData->ItemAt(i);
                        if (variableJoystick == NULL)
                                continue;

                        free(variableJoystick->data);
                        delete variableJoystick;
                }

                delete fJoystickData;
        }
}


status_t
BJoystick::Open(const char *portName)
{
        CALLED();
        return Open(portName, true);
}


status_t
BJoystick::Open(const char *portName, bool enhanced)
{
        CALLED();

        if (portName == NULL)
                return B_BAD_VALUE;

        if (fJoystickInfo == NULL || fJoystickData == NULL)
                return B_NO_INIT;

        fBeBoxMode = !enhanced;

        char nameBuffer[64];
        if (portName[0] != '/') {
                snprintf(nameBuffer, sizeof(nameBuffer), DEVICE_BASE_PATH"/%s",
                        portName);
        } else
                snprintf(nameBuffer, sizeof(nameBuffer), "%s", portName);

        if (fFD >= 0)
                close(fFD);

        // TODO: BeOS don't use O_EXCL, and this seems to lead to some issues. I
        // added this flag having read some comments by Marco Nelissen on the
        // annotated BeBook. I think BeOS uses O_RDWR | O_NONBLOCK here.
        fFD = open(nameBuffer, O_RDWR | O_NONBLOCK | O_EXCL);
        if (fFD < 0)
                return B_ERROR;

        // read the Joystick Description file for this port/joystick
        _BJoystickTweaker joystickTweaker(*this);
        joystickTweaker.GetInfo(fJoystickInfo, portName);

        // signal that we support variable reads
        fJoystickInfo->module_info.flags |= js_flag_variable_size_reads;

        LOG("ioctl - %d\n", fJoystickInfo->module_info.num_buttons);
        ioctl(fFD, B_JOYSTICK_SET_DEVICE_MODULE, &fJoystickInfo->module_info,
                sizeof(joystick_module_info));
        ioctl(fFD, B_JOYSTICK_GET_DEVICE_MODULE, &fJoystickInfo->module_info,
                sizeof(joystick_module_info));
        LOG("ioctl - %d\n", fJoystickInfo->module_info.num_buttons);

        // Allocate the variable_joystick structures to hold the info for each
        // "stick". Note that the whole num_sticks thing seems a bit bogus, as
        // all sticks would be required to have exactly the same attributes,
        // i.e. axis, hat and button counts, since there is only one global
        // joystick_info for the whole device. What's implemented here is a
        // "best guess", using the read position in Update() to select the
        // stick for which data shall be returned.
        bool supportsVariable
                = (fJoystickInfo->module_info.flags & js_flag_variable_size_reads) != 0;
        for (uint16 i = 0; i < fJoystickInfo->module_info.num_sticks; i++) {
                variable_joystick *variableJoystick
                        = new(std::nothrow) variable_joystick;
                if (variableJoystick == NULL)
                        return B_NO_MEMORY;

                status_t result;
                if (supportsVariable) {
                        // The driver supports arbitrary controls.
                        result = variableJoystick->initialize(
                                fJoystickInfo->module_info.num_axes,
                                fJoystickInfo->module_info.num_hats,
                                fJoystickInfo->module_info.num_buttons);
                } else {
                        // The driver doesn't support our variable requests so we construct
                        // a data structure that is compatible with extended_joystick and
                        // just use that in reads. This allows us to use a single data
                        // format internally but be compatible with both inputs.
                        result = variableJoystick->initialize_to_extended_joystick();

                        // Also ensure that we don't read over those boundaries.
                        if (fJoystickInfo->module_info.num_axes > MAX_AXES)
                                fJoystickInfo->module_info.num_axes = MAX_AXES;
                        if (fJoystickInfo->module_info.num_hats > MAX_HATS)
                                fJoystickInfo->module_info.num_hats = MAX_HATS;
                        if (fJoystickInfo->module_info.num_buttons > MAX_BUTTONS)
                                fJoystickInfo->module_info.num_buttons = MAX_BUTTONS;
                }

                if (result != B_OK) {
                        delete variableJoystick;
                        return result;
                }

                if (!fJoystickData->AddItem(variableJoystick)) {
                        free(variableJoystick->data);
                        delete variableJoystick;
                        return B_NO_MEMORY;
                }
        }

        return fFD;
}


void
BJoystick::Close(void)
{
        CALLED();
        if (fFD >= 0) {
                close(fFD);
                fFD = -1;
        }
}


status_t
BJoystick::Update()
{
        CALLED();
        if (fJoystickInfo == NULL || fJoystickData == NULL || fFD < 0)
                return B_NO_INIT;

        for (uint16 i = 0; i < fJoystickInfo->module_info.num_sticks; i++) {
                variable_joystick *values
                        = (variable_joystick *)fJoystickData->ItemAt(i);
                if (values == NULL)
                        return B_NO_INIT;

                ssize_t result = read_pos(fFD, i, values->data,
                        values->data_size);
                if (result < 0)
                        return result;

                if ((size_t)result != values->data_size)
                        return B_ERROR;

                if (i > 0)
                        continue;

                // fill in the legacy values for the first stick
                timestamp = *values->timestamp;

                if (values->axis_count >= 1)
                        horizontal = values->axes[0];
                else
                        horizontal = 0;

                if (values->axis_count >= 2)
                        vertical = values->axes[1];
                else
                        vertical = 0;

                if (values->button_blocks > 0) {
                        button1 = (*values->buttons & 1) == 0;
                        button2 = (*values->buttons & 2) == 0;
                } else {
                        button1 = true;
                        button2 = true;
                }
        }

        return B_OK;
}


status_t
BJoystick::SetMaxLatency(bigtime_t maxLatency)
{
        CALLED();
        if (fJoystickInfo == NULL || fFD < 0)
                return B_NO_INIT;

        status_t result = ioctl(fFD, B_JOYSTICK_SET_MAX_LATENCY, &maxLatency,
                sizeof(maxLatency));
        if (result == B_OK)
                fJoystickInfo->max_latency = maxLatency;

        return result;
}


int32
BJoystick::CountDevices()
{
        CALLED();

        if (fDevices == NULL)
                return 0;

        int32 count = fDevices->CountItems();

        LOG("Count = %d\n", count);
        return count;
}


status_t
BJoystick::GetDeviceName(int32 index, char *name, size_t bufSize)
{
        CALLED();
        if (fDevices == NULL)
                return B_NO_INIT;

        if (index >= fDevices->CountItems())
                return B_BAD_INDEX;

        if (name == NULL)
                return B_BAD_VALUE;

        BString *deviceName = (BString *)fDevices->ItemAt(index);
        if (deviceName->Length() > (int32)bufSize)
                return B_NAME_TOO_LONG;

        strlcpy(name, deviceName->String(), bufSize);
        LOG("Device Name = %s\n", name);
        return B_OK;
}


status_t
BJoystick::RescanDevices()
{
        CALLED();

        if (fDevices == NULL)
                return B_NO_INIT;

        ScanDevices(true);
        return B_OK;
}


bool
BJoystick::EnterEnhancedMode(const entry_ref *ref)
{
        CALLED();
        fBeBoxMode = false;
        return !fBeBoxMode;
}


int32
BJoystick::CountSticks()
{
        CALLED();
        if (fJoystickInfo == NULL)
                return 0;

        return fJoystickInfo->module_info.num_sticks;
}


int32
BJoystick::CountAxes()
{
        CALLED();
        if (fJoystickInfo == NULL)
                return 0;

        return fJoystickInfo->module_info.num_axes;
}


status_t
BJoystick::GetAxisValues(int16 *outValues, int32 forStick)
{
        CALLED();

        if (fJoystickInfo == NULL || fJoystickData == NULL)
                return B_NO_INIT;

        if (forStick < 0
                || forStick >= (int32)fJoystickInfo->module_info.num_sticks)
                return B_BAD_INDEX;

        variable_joystick *variableJoystick
                = (variable_joystick *)fJoystickData->ItemAt(forStick);
        if (variableJoystick == NULL)
                return B_NO_INIT;

        memcpy(outValues, variableJoystick->axes,
                fJoystickInfo->module_info.num_axes * sizeof(uint16));
        return B_OK;
}


status_t
BJoystick::GetAxisNameAt(int32 index, BString *outName)
{
        CALLED();

        if (index >= CountAxes())
                return B_BAD_INDEX;

        if (outName == NULL)
                return B_BAD_VALUE;

        // TODO: actually retrieve the name from the driver (via a new ioctl)
        *outName = "Axis ";
        *outName << index;
        return B_OK;
}


int32
BJoystick::CountHats()
{
        CALLED();
        if (fJoystickInfo == NULL)
                return 0;

        return fJoystickInfo->module_info.num_hats;
}


status_t
BJoystick::GetHatValues(uint8 *outHats, int32 forStick)
{
        CALLED();

        if (fJoystickInfo == NULL || fJoystickData == NULL)
                return B_NO_INIT;

        if (forStick < 0
                || forStick >= (int32)fJoystickInfo->module_info.num_sticks)
                return B_BAD_INDEX;

        variable_joystick *variableJoystick
                = (variable_joystick *)fJoystickData->ItemAt(forStick);
        if (variableJoystick == NULL)
                return B_NO_INIT;

        memcpy(outHats, variableJoystick->hats,
                fJoystickInfo->module_info.num_hats);
        return B_OK;
}


status_t
BJoystick::GetHatNameAt(int32 index, BString *outName)
{
        CALLED();

        if (index >= CountHats())
                return B_BAD_INDEX;

        if (outName == NULL)
                return B_BAD_VALUE;

        // TODO: actually retrieve the name from the driver (via a new ioctl)
        *outName = "Hat ";
        *outName << index;
        return B_OK;
}


int32
BJoystick::CountButtons()
{
        CALLED();
        if (fJoystickInfo == NULL)
                return 0;

        return fJoystickInfo->module_info.num_buttons;
}


uint32
BJoystick::ButtonValues(int32 forStick)
{
        CALLED();

        if (fJoystickInfo == NULL || fJoystickData == NULL)
                return 0;

        if (forStick < 0
                || forStick >= (int32)fJoystickInfo->module_info.num_sticks)
                return 0;

        variable_joystick *variableJoystick
                = (variable_joystick *)fJoystickData->ItemAt(forStick);
        if (variableJoystick == NULL || variableJoystick->button_blocks == 0)
                return 0;

        return *variableJoystick->buttons;
}


status_t
BJoystick::GetButtonValues(bool *outButtons, int32 forStick)
{
        CALLED();

        if (fJoystickInfo == NULL || fJoystickData == NULL)
                return B_NO_INIT;

        if (forStick < 0
                || forStick >= (int32)fJoystickInfo->module_info.num_sticks)
                return B_BAD_INDEX;

        variable_joystick *variableJoystick
                = (variable_joystick *)fJoystickData->ItemAt(forStick);
        if (variableJoystick == NULL)
                return B_NO_INIT;

        int16 buttonCount = fJoystickInfo->module_info.num_buttons;
        for (int16 i = 0; i < buttonCount; i++) {
                outButtons[i]
                        = (variableJoystick->buttons[i / 32] & (1 << (i % 32))) != 0;
        }

        return B_OK;
}


status_t
BJoystick::GetButtonNameAt(int32 index, BString *outName)
{
        CALLED();

        if (index >= CountButtons())
                return B_BAD_INDEX;

        if (outName == NULL)
                return B_BAD_VALUE;

        // TODO: actually retrieve the name from the driver (via a new ioctl)
        *outName = "Button ";
        *outName << index;
        return B_OK;
}


status_t
BJoystick::GetControllerModule(BString *outName)
{
        CALLED();
        if (fJoystickInfo == NULL || fFD < 0)
                return B_NO_INIT;

        if (outName == NULL)
                return B_BAD_VALUE;

        outName->SetTo(fJoystickInfo->module_info.module_name);
        return B_OK;
}


status_t
BJoystick::GetControllerName(BString *outName)
{
        CALLED();
        if (fJoystickInfo == NULL || fFD < 0)
                return B_NO_INIT;

        if (outName == NULL)
                return B_BAD_VALUE;

        outName->SetTo(fJoystickInfo->module_info.device_name);
        return B_OK;
}


bool
BJoystick::IsCalibrationEnabled()
{
        CALLED();
        if (fJoystickInfo == NULL)
                return false;

        return fJoystickInfo->calibration_enable;
}


status_t
BJoystick::EnableCalibration(bool calibrates)
{
        CALLED();
        if (fJoystickInfo == NULL || fFD < 0)
                return B_NO_INIT;

        status_t result = ioctl(fFD, B_JOYSTICK_SET_RAW_MODE, &calibrates,
                sizeof(calibrates));
        if (result == B_OK)
                fJoystickInfo->calibration_enable = calibrates;

        return result;
}


void
BJoystick::Calibrate(struct _extended_joystick *reading)
{
        CALLED();
}


void
BJoystick::ScanDevices(bool useDisabled)
{
        CALLED();
        if (useDisabled) {
                _BJoystickTweaker joystickTweaker(*this);
                joystickTweaker.scan_including_disabled();
        }
}


//      #pragma mark - FBC protection


void BJoystick::_ReservedJoystick1() {}
void BJoystick::_ReservedJoystick2() {}
void BJoystick::_ReservedJoystick3() {}
status_t BJoystick::_Reserved_Joystick_4(void*, ...) { return B_ERROR; }
status_t BJoystick::_Reserved_Joystick_5(void*, ...) { return B_ERROR; }
status_t BJoystick::_Reserved_Joystick_6(void*, ...) { return B_ERROR; }