Adding new features: joystick.

I’ve been looking at the SDL sources now and come to the conclusion that writing a joystick Python extension (module) is quite easy. At least for Linux and BSD, those are the systems I’m familiar with.

Since I don’t use Windows or have access to it I’m asking someone with programming experience on Windows to help out.

The plan would be to write a Python module in C++ (just as with Panda3D in general), follow those guidelines and include joystick/joypad support in Panda3D.

Why not use PyGame? Well, for the simple reason that it’s huge and the joystick part is miniscule. Besides, Panda3D is a game engine and as such it should include support for the most common types of controllers.
It would be nice to write a joystick module adapted for OO entirely, one that can be easily intergrated with the callback system and task manager.

Any Windows hackers out there who are interrested? If not, it fails since Panda3D requires support for both linux and win at this stage. (hopefully osx, bsd and whatever in the future)

Not sure if this help any more, but here is a short hack I used to query the joystick on Windows. The small extension uses winmm (comes with PlatformSDK for Windows XP). Oh, and LGPL license of course. Tell me if you need binaries for Pytrhon 2.4 or Python 2.5.

enn0x

2007/07/07 EDIT: fixed a problem in method getButton. Now you can use getButton( 0 ) to getButton( n-1 ) where n is the number of buttons.

/*
    Copyright (c) 2007 Ralf Pfrogner (ralf.pfrogner@gmx.net)

    This file is part of "Joystick Python Extension".

    This software is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public License as
    published by the Free Software Foundation; either version 2.1 of the
    License, or (at your option) any later version. The text of the
    GNU Lesser General Public License is included with this library in
    the file license.txt.

    This software is distributed in the hope that it will be
    useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the file
    license.txt for more details.
*/

// Python headers
//
#include <Python.h>
#include "structmember.h"

// Windows MMS headers
//
#include <Windows.h>



// ____________________________________________________________________________
// JOYSTICK TYPE

typedef struct {
    PyObject_HEAD

    int joyID;
    JOYINFOEX info;
    JOYCAPS caps;

} PyJoystickObject;

extern PyTypeObject PyJoystickType;

// _____UTILS_____

static float parametric( int x0 )
{
    int x;

    // integer range is asymetic around zero,
    // so we have to cheat a little to get float ranges from -1.0 to 1.0

    if ( x0 < 32767 ) x = x0 - 32768;
    else              x = x0 - 32767;

    return (float)x / 32768.0f;
}

// _____METHOD : UPDATE_____

static PyObject* PyJoystickObject_update( PyJoystickObject *self, PyObject *args )
{
    int success;

    success = joyGetPosEx( self->joyID, &(self->info) );

    return Py_BuildValue( "O", ( success == JOYERR_NOERROR ) ? Py_True : Py_False );
}

// _____METHOD : GET X_____

static PyObject* PyJoystickObject_getX( PyJoystickObject *self, PyObject *args )
{
    return Py_BuildValue( "f", parametric( self->info.dwXpos ) );
}

// _____METHOD : GET Y_____

static PyObject* PyJoystickObject_getY( PyJoystickObject *self, PyObject *args )
{
    return Py_BuildValue( "f", parametric( self->info.dwYpos ) );
}

// _____METHOD : GET Z_____

static PyObject* PyJoystickObject_getZ( PyJoystickObject *self, PyObject *args )
{
    return Py_BuildValue( "f", parametric( self->info.dwZpos ) );
}

// _____METHOD : GET R_____

static PyObject* PyJoystickObject_getR( PyJoystickObject *self, PyObject *args )
{
    return Py_BuildValue( "f", parametric( self->info.dwRpos ) );
}

// _____METHOD : GET U_____

static PyObject* PyJoystickObject_getU( PyJoystickObject *self, PyObject *args )
{
    return Py_BuildValue( "f", parametric( self->info.dwUpos ) );
}

// _____METHOD : GET V_____

static PyObject* PyJoystickObject_getV( PyJoystickObject *self, PyObject *args )
{
    return Py_BuildValue( "f", parametric( self->info.dwVpos ) );
}

// _____METHOD : NUM AXES_____

static PyObject* PyJoystickObject_numAxes( PyJoystickObject *self, PyObject *args )
{
    return Py_BuildValue( "i", self->caps.wNumAxes );
}

// _____METHOD : NUM BUTTONS_____

static PyObject* PyJoystickObject_numButtons( PyJoystickObject *self, PyObject *args )
{
    return Py_BuildValue( "i", self->caps.wNumButtons );
}

// _____METHOD : NAME_____

static PyObject* PyJoystickObject_name( PyJoystickObject *self, PyObject *args )
{
    return Py_BuildValue( "s", self->caps.szPname );
}

// _____METHOD : GET BUTTON_____

static PyObject* PyJoystickObject_getButton( PyJoystickObject *self, PyObject *args )
{
    int btn;
    bool state;

    if ( ! PyArg_ParseTuple( args, "i", &btn ) )
        return NULL;

    state = self->info.dwButtons & ( 1 << btn );

    return Py_BuildValue( "O", state ? Py_True : Py_False );
}

// _____METHOD : GET POV_____

static PyObject* PyJoystickObject_getPOV( PyJoystickObject *self, PyObject *args )
{
    return Py_BuildValue( "i", self->info.dwPOV / 100 );
}




// _____METHODS_____

static PyMethodDef PyJoystickObject_methods[ ] = {
    { "numAxes", ( PyCFunction ) PyJoystickObject_numAxes, METH_VARARGS, "" },
    { "numButtons", ( PyCFunction ) PyJoystickObject_numButtons, METH_VARARGS, "" },
    { "name", ( PyCFunction ) PyJoystickObject_name, METH_VARARGS, "" },
    { "update", ( PyCFunction ) PyJoystickObject_update, METH_VARARGS, "" },
    { "getButton", ( PyCFunction ) PyJoystickObject_getButton, METH_VARARGS, "" },
    { "getPOV", ( PyCFunction ) PyJoystickObject_getPOV, METH_VARARGS, "" },
    { "getX", ( PyCFunction ) PyJoystickObject_getX, METH_VARARGS, "" },
    { "getY", ( PyCFunction ) PyJoystickObject_getY, METH_VARARGS, "" },
    { "getZ", ( PyCFunction ) PyJoystickObject_getZ, METH_VARARGS, "" },
    { "getR", ( PyCFunction ) PyJoystickObject_getR, METH_VARARGS, "" },
    { "getU", ( PyCFunction ) PyJoystickObject_getU, METH_VARARGS, "" },
    { "getV", ( PyCFunction ) PyJoystickObject_getV, METH_VARARGS, "" },

    { NULL }
};

// _____MEMBERS_____

static PyMemberDef PyJoystickObject_members[ ] = {
    { NULL }
};

// _____NEW_____

static PyObject* PyJoystickObject_new( PyTypeObject *type, PyObject *args, PyObject *kwds )
{
    PyJoystickObject *self;

    self = ( PyJoystickObject* ) type->tp_alloc( type, 0 );
    if ( self != NULL ) { }

    return (PyObject *) self;
}

// _____INIT_____

static int PyJoystickObject_init( PyJoystickObject* self, PyObject *args, PyObject *kwds )
{
    if ( ! PyArg_ParseTuple( args, "i", &(self->joyID) ) )
        return NULL;

    self->info.dwFlags = JOY_RETURNALL;
    self->info.dwSize = sizeof( self->info );

    joyGetDevCaps( self->joyID, &(self->caps), sizeof( self->caps ) );
    joyGetPosEx( self->joyID, &(self->info) );

    return 0;
}

// _____DEALLOC_____

static void PyJoystickObject_dealloc( PyJoystickObject *self )
{
    //delete self->m_ptr;

    self->ob_type->tp_free( (PyObject *) self );
}

// ______TYPE_____

extern PyTypeObject PyJoystickType = {
    PyObject_HEAD_INIT( NULL )
    0,                                         /* ob_size */
    "Joystick.Joystick",                       /* tp_name */
    sizeof( PyJoystickObject ),                /* tp_basicsize */
    0,                                         /* tp_itemsize */
    ( destructor ) PyJoystickObject_dealloc,   /* tp_dealloc */
    0,                                         /* tp_print */
    0,                                         /* tp_getattr */
    0,                                         /* tp_setattr */
    0,                                         /* tp_compare */
    0,                                         /* tp_repr */
    0,                                         /* tp_as_number */
    0,                                         /* tp_as_sequence */
    0,                                         /* tp_as_mapping */
    0,                                         /* tp_hash */
    0,                                         /* tp_call */
    0,                                         /* tp_str */
    0,                                         /* tp_getattro */
    0,                                         /* tp_setattro */
    0,                                         /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT,                        /* tp_flags */
    0,                                         /* tp_doc */
    0,                                         /* tp_traverse */
    0,                                         /* tp_clear */
    0,                                         /* tp_richcompare */
    0,                                         /* tp_weaklistoffset */
    0,                                         /* tp_iter */
    0,                                         /* tp_iternext */
    PyJoystickObject_methods,                  /* tp_methods */
    PyJoystickObject_members,                  /* tp_members */
    0,                                         /* tp_getset */
    0,                                         /* tp_base */
    0,                                         /* tp_dict */
    0,                                         /* tp_descr_get */
    0,                                         /* tp_descr_set */
    0,                                         /* tp_dictoffset */
    ( initproc ) PyJoystickObject_init,        /* tp_init */
    0,                                         /* tp_alloc */
    PyJoystickObject_new,                      /* tp_new */
};



// ____________________________________________________________________________
// MODULE

// _____METHOD : NUM JOYSTICKS _____

static PyObject* Joystick_NumJoysticks( PyObject *self, PyObject *args )
{
    unsigned int i, n, nmax;
    JOYINFO info;

    nmax = joyGetNumDevs( );

    n = 0;

    for ( i=0; i < nmax; i++ ) {
        if ( joyGetPos( i, &info ) == JOYERR_NOERROR ) n++;
    }

    return Py_BuildValue( "i", n );
}

// _____METHODS_____

static PyMethodDef Pymethods[ ] = {
    { "NumJoysticks", ( PyCFunction ) Joystick_NumJoysticks, METH_VARARGS, "" },

    { NULL }
};

// ____INIT MODULE_____

PyMODINIT_FUNC initJoystick( void )
{
    PyObject* m;

    m = Py_InitModule3( "Joystick", Pymethods, "" );

    // Constants
    PyObject_SetAttrString( m, "__version__", Py_BuildValue( "s", "0.1" ) );

    //PyObject_SetAttrString( m, "ID1", PyInt_FromLong( JOYSTICKID1 ) );

    // Joystick
    if ( PyType_Ready( &PyJoystickType ) < 0 )
        return;

    Py_INCREF( &PyJoystickType );
    PyModule_AddObject( m, "Joystick", (PyObject*) &PyJoystickType );
}