I. Introduction

Aujourd'hui, de plus en plus de monde a son joystick posé sur le coin du bureau. Grâce aux efforts des designers, on peut dire que si on en n'a pas trop l'usage (tout le monde n'est pas passionné par Flight Simulator et ses congénères), ça fait presque « œuvre d'art ». Et dans un monde ou les PC sont taillés à la hache, c'est déjà pas mal ! Cependant, pour faire plaisir aux gestionnaires, on aimerait bien leur trouver une utilité supplémentaire (surtout pour que Mademoiselle notre copine arrête de nous dire « mais t'as encore acheté un truc inutile ?!? Ça va te servir à quoi ? T'en avais pas assez comme ça pour encombrer le bureau ?!"). Hé bien que diriez-vous si on en faisait une télécommande ? certes elle est a fil, mais c'est tout de même mieux que de se lever pour aller monter le volume du film quand c'est pas assez fort et inversement …

Je me propose donc d'étudier avec vous les possibilités que nous offre Windows pour la gestion des joysticks à travers la réalisation d'une télécommande.

II. Fonctionnement

Afin de mieux utiliser les fonctions associées au joystick, voyons tout d'abord comment il fonctionne. Tout d'abord, le joystick est un périphérique passif, c'est a dire que contrairement au clavier, il ne va pas déclencher d'évènements dans le système (interruption ou autre). C'est donc le système d'exploitation qui est chargé, via le pilote associé, d'aller interroger le joystick sur son état. Bien sûr, pour éviter de surcharger le système, cette interrogation est faite en temps réel, à la demande des programmes clients. Afin de différencier chacun des périphériques connectés à la machine, Windows leur attribue un numéro identifiant. Cet identifiant est celui affiché dans le panneau de contrôle des périphériques de jeu auquel il faut retrancher 1 (pourquoi faire simple quand on peut faire compliqué ?). Le système peu supporter jusqu'à 16 périphériques et fourni pour répondre aux besoins les plus courants deux constantes : JOYSTICKID1 et JOYSTICKID2 qui correspondent aux deux premiers périphériques (héritage de Windows 3.x et précédents).

Avant toute chose, on va devoir commencer par apprendre le joystick qu'on veut manipuler. En effet, comment gérer un joystick si on ne connaît pas ses caractéristiques ? Nombre de boutons ? Les types d'axes ? Y a-t-il un point de vue ? Nous allons donc commencer par nous renseigner sur les capacités du joystick, puis voir le fonctionnement de la capture avant de nous enfoncer dans les méandres de l'interrogation du joystick.

III. Obtenir les caractéristiques du joystick

III-A. Liste des périphériques connectés

Avant de rechercher les caractéristiques d'un joystick, il faut nous assurer qu'il est connecté. Pour cela nous allons utiliser la fonction joyGetPos() qui renvoie deux constantes : JOYERR_NOERROR si le joystick est connecté, ou JOYERR_UNPLUGGED si le joystick n'est pas connecté. Cependant, ce n'est pas parce que le joystick d'ID 0 n'est pas connecté qu'aucun ne l'est : il faut tester tous les IDs. Voyons un exemple de code qui affiche les IDs des joysticks connectés :

 
Sélectionnez
#include <windows.h>
#include <stdio.h>

 ...

void AfficherJoysConnectes()
{
    int i;
    JOYINFO structtmp;

    printf("Voici la liste des joysticks connectés :\n");
    for (i = JOYSTICKID1 ; i < (JOYSTICKID1 + 16) ; i++) //seuls 16 IDs sont possibles
    {
        if (joyGetPos(i,&structtmp) == JOYERR_NOERROR)
            printf("%d\n",i);
    }
}

III-B. Caractéristiques d'un périphérique

Maintenant qu'on sait comment détecter la présence d'un joystick, nous allons pouvoir nous pencher sur le problème de la récupération des caractéristiques. L'API Microsoft fournit a cet effet la fonction joyGetDevCaps() qui prends trois paramètres et renvoie JOYERR_NOERROR si tout se passe bien. Les trois paramètres sont l'ID du joystick à interroger, l'adresse vers une structure de type JOYCAPS où seront stockées les informations et la taille de cette structure afin que Windows puisse détecter la version utilisée par le programme. Voyons le contenu de la structure de caractéristiques :

WORD wMid ID du manufacteur
WORD wPid ID du produit
CHAR szPname[MAXPNAMELEN] nom du produit
UINT wXmin valeur minimale de l'axe X
UINT wXmax valeur maximale de l'axe X
UINT wYmin valeur minimale de l'axe Y
UINT wYmax valeur maximale de l'axe Y
UINT wZmin valeur minimale de l'axe Z (Gaz)
UINT wZmax valeur maximale de l'axe Z (Gaz)
UINT wNumButtons nombre de boutons du périphérique
UINT wPeriodMin Période minimale à utiliser lors de la capture
UINT wPeriodMax Période maximale à utiliser lors de la capture
UINT wRmin valeur minimale de l'axe R (palloniers)
UINT wRmax valeur maximale de l'axe R (palonniers)
UINT wUmin valeur minimale de l'axe U
UINT wUmax valeur maximale de l'axe U
UINT wVmin valeur minimale de l'axe V
UINT wVmax y'a pas idée d'avoir autant d'axes ! ;-)
UINT wCaps Zone de drapeaux indiquant les capacités du périphérique
UINT wMaxAxes Nombre d'axes que le joystick peut gérer
UINT wNumAxes Nombre d'axes actuellement utilisés
UINT wMaxButtons Nombre de boutons que le joystick peut gérer
CHAR szRegKey[MAXPNAMELEN] Clé du registre associée au joystick
CHAR szOEMVxD[MAX_JOYSTICKOEMVXDNAME] Nom du driver du joystick

wCaps utilise les drapeaux suivants :

  • JOYCAPS_HASZ : l'axe Z existe.
  • JOYCAPS_HASR : l'axe R existe.
  • JOYCAPS_HASU : l'axe U existe.
  • JOYCAPS_HASV : l'axe V existe.
  • JOYCAPS_HASPOV : le joystick peut fournir des informations de point de vue.
  • JOYCAPS_POV4DIR : le joystick ne reconnaît que les valeurs d'orientation discrète (haute, basse, gauche et droite).
  • JOYCAPS_POVCTS : le joystick reconnaît les valeurs d'orientation continues en degrés.

Voyons un exemple de programme qui affiche les capacités d'un périphérique :

 
Sélectionnez
#include <windows.h>
#include <stdio.h>

 ...

void AfficherCaracs(UINT JoyID)
{
    JOYCAPS InfosCaps;
    if (joyGetDevCaps(JoyID, &InfosCaps, sizeof(JOYCAPS)) == JOYERR_NOERROR)
    {
        printf("\nNom du périphérique : %s", InfoCaps.szPname);
        printf("Nombre d'axes possibles : %d, gérables : %d\n",
                InfoCaps.wNumAxes, InfoCaps.wMaxAxes);
        printf("Axe |  Min  |  Max\n");
        printf("----+-------+-------\n");
        printf(" X  | ] | ]\n", InfoCaps.wXMin, InfoCaps.wXMax);
        printf(" Y  | ] | ]\n", InfoCaps.wYMin, InfoCaps.wYMax);
        if (InfoCaps.wCaps & JOYCAPS_HASZ)
            printf(" Z  | ] | ]\n", InfoCaps.wZMin, InfoCaps.wZMax);
        else
            printf(" Z  |    Absent\n");
        if (InfoCaps.wCaps & JOYCAPS_HASR)
            printf(" R  | ] | ]\n", InfoCaps.wRMin, InfoCaps.wRMax);
        else
            printf(" R  |    Absent\n");
        if (InfoCaps.wCaps & JOYCAPS_HASU)
            printf(" U  | ] | ]\n", InfoCaps.wUMin, InfoCaps.wUMax);
        else
            printf(" U  |    Absent\n");
        if (InfoCaps.wCaps & JOYCAPS_HASV)
            printf(" V  | ] | ]\n", InfoCaps.wVMin, InfoCaps.wVMax);
        else
            printf(" V  |    Absent\n");
        printf("\nPoint de vue : ");
        if (InfoCaps.wCaps & JOYCAPS_HASPOV)
        {
            if (InfoCaps.wCaps & JOYCAPS_POV4DIR)
                printf("Quadridirectionnel\n");
            else
                printf("Continu\n");
        }
        else
            printf("Absent\n");
        printf("Nombre de boutons possibles : %d, gérables : %d\n", 
                InfoCaps.wNumButtons, InfoCaps.wMaxButtons);
    }
}

IV. La capture du Joystick

Afin d'éviter aux programmeurs d'avoir à réaliser un thread séparé s'occupant de la gestion du joystick afin de ne pas perturber l'application principale, Microsoft à mis à la disposition des programmeurs une fonctionnalité spécifique : la capture du joystick. La capture du joystick permet de se décharger de la gestion du joystick et de recevoir des messages associés au joystick. Comme tout, cela a également des désavantages : seule une application à la fois peu capturer un joystick, un maximum de deux joysticks peuvent être capturés au même moment, et les messages sont assez limités et ne permettent pas de contrôler complètement les joysticks actuels. Par ailleurs, il se peut qu'un message n'arrive pas jusqu'à l'application qui a capturé le joystick si une autre fait appel à joyGetPos() à peut près au même moment que le message est envoyé.

IV-A. La capture

Il y a deux types de captures possibles : la première envoie des messages à intervalles réguliers (entre wPeriodMin et wPeriodMax) et la seconde envoie des messages quand il y a un changement d'état. La capture est mise en place par la fonction joySetCapture() qui prends quatre paramètres et renvoie JOYERR_NOERROR si tout se passe bien. Les quatre paramètres sont : le handle de la fenêtre à laquelle Windows doit envoyer les messages, l'ID du joystick à capturer, la période de temps entre deux tests d'état et le type de capture (FALSE pour la première et TRUE pour la seconde). Dans le cas du second type de capture, l'application devrait également préciser l'amplitude minimale de mouvement sur un axe pour laquelle on considère qu'il y a changement d'état en faisant un appel à la fonction joySetThreshold(). Cette dernière prend deux paramètres : l'ID du joystick et la quantité de mouvement. Pour connaître la valeur actuelle on peut utiliser la fonction joyGetThreshold() qui prends en paramètres l'ID du joystick et l'adresse d'un entier non signé de type UINT. Les deux fonctions renvoient JOYERR_NOERROR quand tout se passe bien. Bien entendu, lorsque l'application n'a plus besoin du joystick, elle peut relâcher la capture en faisant un appel à la fonction joyReleaseCapture() qui prend en paramètre l'ID du joystick à relâcher et renvoie toujours la même constante si tout se passe bien. Voyons un exemple de capture du joystick basée sur le changement d'état (cet exemple utilise l'API pour créer une fenêtre, voyez ici pour un tutoriel de Bob sur l'utilisation de l'API).

fichier entete : capture.h
Sélectionnez
#ifndef Unit1H
#define Unit1H

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

#endif
          fichier principal (WinMain) : capture.c
//---------------------------------------------------------------------------
#include <windows.h>

#include "Unit1.h"
//---------------------------------------------------------------------------
HINSTANCE hInst; // instance de l'application
LPCTSTR lpszAppName = "CAPTURE";
LPCTSTR lpszTitle = "Capture du joystick";
HWND ZoneTexte;

WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    MSG msg;
    HWND hWnd;
    WNDCLASSEX wc;
    MMRESULT ret;


    // Enregistrement des caractéristiques du class name de la fenêtre
    wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
    wc.lpfnWndProc = (WNDPROC)WndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = 0;
    wc.hIcon = LoadIcon(hInstance, lpszAppName);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW);
    wc.lpszMenuName = lpszAppName;
    wc.lpszClassName = lpszAppName;
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.hIconSm = NULL;

    if(!RegisterClassEx(&wc))
        return FALSE;

    hInst = hInstance;
    // Création de la fenêtre
    hWnd = CreateWindow(lpszAppName, lpszTitle, WS_OVERLAPPEDWINDOW,
                        0, 0, 300, 300, NULL, NULL, hInstance, NULL);

    if (!hWnd)
        return FALSE;

    // Affichage de la fenêtre
    ShowWindow(hWnd,nCmdShow);

    // Création d'une zone de texte dans la fenêtre
    ZoneTexte = CreateWindow("EDIT", "", WS_VISIBLE | WS_VSCROLL | WS_CHILD
                             | ES_AUTOVSCROLL | ES_MULTILINE , 1, 2, 290, 270,
                             hWnd, NULL, hInstance, NULL);
    UpdateWindow(hWnd);

    // Capture du joystick
    ret = joySetCapture(hWnd, // les messages seront envoyés à la fenêtre
                  JOYSTICKID1, // capture du premier joystick
                  50, // période de 50 ms. Si c'est hors bornes, Windows ajuste
                  TRUE); // Basée sur les changements d'état

    if (ret == JOYERR_NOERROR)
    {
        // ajuster les valeurs en fonction des caractéristiques du joystick
        // ici, c'est environ 3% pour le mien
        joySetThreshold(JOYSTICKID1, 2000);
    }
    else
        PostQuitMessage(0);

    // lancement de la boucle de traitement des messages
    while(GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (msg.wParam);
}

IV-B. Les messages associés à la capture

Voyons maintenant quels sont les messages que Windows envoie à l'application qui a capturé un joystick. Tout d'abord, afin de différencier les joysticks Windows préfixe le nom des constantes de messages par MM_JOY1 ou MM_JOY2. Il y a quatre types de messages :

MM_JOY1BUTTONDOWN Un bouton a été pressé
MM_JOY1BUTTONUP Un bouton a été relaché
MM_JOY1MOVE La position de l'axe X ou Y a changé
MM_JOY1ZMOVE La position de l'axe Z a changé

Pour les deux premiers messages, wParam contient des drapeaux pour indiquer les changements d'état et les boutons enfoncés à l'aide des constantes JOY_BUTTONxCHG et JOY_BUTTONx (x compris entre 1 et 4). Pour les deux autres, wParam ne contient que les boutons enfoncés et seules les constantes JOY_BUTTONx sont utilisées.

Pour les trois premiers messages, LOWORD(lParam) contient la coordonnée X et HIWORD(lParam) la coordonnée Y par rapport au coin supérieur gauche de la fenêtre. Le dernier message ne complète que LOWORD(lParam) avec la coordonnée Z.

Voyons un exemple de code qui va modifier le contenu de la zone de texte créée précédemment en fonction du message reçu (suite du fichier principal : capture.c):

 
Sélectionnez
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    UINT wID;
    HWND hwndCtrl;
    char Texte[1000];
    char b1,b2,b3,b4;

    switch (uMsg)
    {
        case MM_JOY1BUTTONDOWN :
            b1 = (wParam & JOY_BUTTON1) ? 'O' : 'N';
            b2 = (wParam & JOY_BUTTON2) ? 'O' : 'N';
            b3 = (wParam & JOY_BUTTON3) ? 'O' : 'N';
            b4 = (wParam & JOY_BUTTON4) ? 'O' : 'N';
            sprintf(Texte,"Boutons effoncés :\r\n1 : %c\r\n2 : %c\r\n3 : %c\r\n4 : %c",
                    b1, b2, b3, b4);
            break;
        case MM_JOY1BUTTONUP :
            b1 = (wParam & JOY_BUTTON1) ? 'O' : 'N';
            b2 = (wParam & JOY_BUTTON2) ? 'O' : 'N';
            b3 = (wParam & JOY_BUTTON3) ? 'O' : 'N';
            b4 = (wParam & JOY_BUTTON4) ? 'O' : 'N';
            sprintf(Texte,"Boutons effoncés :\r\n1 : %c\r\n2 : %c\r\n3 : %c\r\n4 : %c",
                    b1, b2, b3, b4);
            break;
        case MM_JOY1MOVE :
            sprintf(Texte,"Axe X : %d\r\nAxe Y %d",LOWORD(lParam), HIWORD(lParam));
            break;
        case MM_JOY1ZMOVE :
            sprintf(Texte,"Axe Z : %d",LOWORD(lParam);
            break;
        case WM_DESTROY :
            PostQuitMessage(0);
            break;
        default :
            return (DefWindowProc(hWnd, uMsg, wParam, lParam));
    }
    SetWindowText(ZoneTexte,Texte);
    return 0L;
}

V. L'interrogation du Joystick

Voyons maintenant comment faire lorsque la capture ne permet pas de résoudre nos problèmes ou qu'il est préférable de contrôler le déroulement des opérations par nous même. En effet, la capture ne permet que de gérer quatre boutons et trois axes ce qui est limité si on veut transformer notre merveilleux petit outil en télécommande.

Windows fournit une fonction avancée pour interroger le joystick de manière plus complète que ce que permet la capture. Pour l'interrogation, il y a les fonctions joyGetPos() et joyGetPosEx() ; nous ne nous occuperons pas de la première puisqu'elle n'apporte rien par rapport à la capture, nous ne verrons donc que la seconde. Cette fonction prend en paramètres l'ID du joystick à interroger ainsi que l'adresse d'une structure JOYINFOEX et renvoie JOYERR_NOERROR quand tout se passe bien. Voyons en détail le contenu de la structure JOYINFOEX :

DWORD dwSize Taille de la structure (nécessaire pour détecter la version utilisée)
DWORD dwFlags Drapeaux indiquant les données qu'on souhaite obtenir
DWORD dwXpos Positions des différents axes du joystick
DWORD dwYpos
DWORD dwZpos
DWORD dwRpos
DWORD dwUpos
DWORD dwVpos
DWORD dwButtons Etat des boutons (jusqu'à 32 boutons)
DWORD dwButtonNumber Nombre de boutons pressés
DWORD dwPOV Position du point de vue
DWORD dwReserved1 Réservé (pourquoi faire, je ne sais pas … ), ne pas utiliser
DWORD dwReserved2

À la lumière de ces informations, on peut déjà se réjouir en imaginant ce qu'on va pouvoir faire faire à notre manette de jeu. Que dis-je notre baguette magique spéciale PC ;-). Voyons maintenant un peu plus en détail les quelques spécificités de cette structure. Tout d'abord dwButtons : pour connaître l'état d'un bouton, il suffit de lire l'état du bit correspondant : au bouton 1 correspond le bit de poids le plus faible et ainsi de suite jusqu'au 32e bouton auquel correspond le bit de poids le plus fort. Ensuite, dwPOV : cette variable contient l'orientation du point de vue en centièmes de degrés (90° <=> 9000). Windows fournit cinq constantes correspondant aux positions primaires : JOY_POVCENTERED (-1 : centre), JOY_POVBACKWARD (18000 : arrière), JOY_POVFORWARD (0 : avant), JOY_POVLEFT (27000 : gauche) et JOYPOVRIGHT (9000 : droite). Finalement, il ne nous reste plus qu'à indiquer à Windows quelles sont les informations dont on a besoin, et c'est le rôle de dwFlags. Voici un tableau récapitulatif des drapeaux utilisables.

JOY_RETURNALL Équivalent à mettre tous les bits JOY_RETURN à l'exception de JOY_RETURNRAWDATA.
JOY_RETURNBUTTONS dwButtons contient des informations valides à propos de chaque bouton.
JOY_RETURNCENTERED Centre la position neutre du joystick au milieu de chaque axe.
JOY_RETURNPOV dwPOV contient des informations valides a propos du point de vue exprimées à l'aide des constantes énoncées ci-dessus (utile pour les applications qui ne gèrent que les positions discrètes quand le joystick fournit des informations continues).
JOY_RETURNPOVCTS dwPOV contient des informations valides a propos du point de vue exprimées en valeurs discrètes.
JOY_RETURNR dwRpos, dwUpos, dwVpos, dwXpos, dwYpos et/ou dwZpos contiennent des informations valides sur les positions des axes
JOY_RETURNU
JOY_RETURNV
JOY_RETURNX
JOY_RETURNY
JOY_RETURNZ
JOY_USEDEADZONE Etend la zone morte du joystick. Le driver renvoie une valeur constante tant qu'il est dans la zone morte.
JOY_RETURNRAWDATA Les données contenues dans la structure sont des données brutes (non calibrées)

Les drapeaux suivants fournissent des informations pour calibrer le joystick. Je ne les ai jamais utilisées, alors n'hésitez pas à me faire part de vos expériences afin de compléter ce tutoriel.

JOY_CAL_READ3 Lit les données brutes X, Y et Z et les fournit par l'intermédiaire de dwXpos et ses consœurs.
JOY_CAL_READ4 Lit les données brutes X, Y, Z et R et les fournit par l'intermédiaire de dwXpos et ses consœurs.
JOY_CAL_READ5 Lit les données brutes X, Y, Z, R et U et les fournit par l'intermédiaire de dwXpos et ses consœurs.
JOY_CAL_READ6 Lit les données brutes X, Y, Z, R, U et V et les fournit par l'intermédiaire de dwXpos et ses consœurs.
JOY_CAL_READALWAYS Lit les données du joystick même si le driver indique qu'il n'est pas présent.
JOY_CAL_READRONLY Lit uniquement les données brutes associées à(aux) l'axe(s) indiqués.
JOY_CAL_READXONLY
JOY_CAL_READXYONLY
JOY_CAL_READYONLY
JOY_CAL_READZONLY
JOY_CAL_READUONLY
JOY_CAL_READVONLY

Nous allons maintenant mettre ces informations en œuvre pour utiliser le joystick à la place de la souris. Pour plus d'informations, je vous oriente vers mon tutoriel sur le contrôle du clavier et de la souris. Dans cet exemple, je ne ferais aucune vérification quant aux caractéristiques du joystick : je considère que le joystick à contrôler est connu et qu'il n'y aura aucun problème. Si votre périphérique ne prends pas en charge l'une de ces caractéristiques, vous n'aurez qu'à la mettre en commentaire.

 
Sélectionnez
#include <windows.h>

#define BOUTON1 1
#define BOUTON2 2
#define BOUTON3 4
#define BOUTON4 8

int CalculerDeplacement(UINT Max, UINT Min, UINT Val);

WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    JOYINFOEX ActualPos; // Etat actuel et dernier état du joystick
    DWORD dwBoutons; // Masque pour récupérer l'état des boutons
    UINT uMax[2], uMin[2]; // Maximums et minimums de chacun des axes
    UINT uPoolPeriod; // Temps entre deux intérogations du joystick
    JOYCAPS InfosCaps; // Infos sur les capacités du joystick
    BOOL bStop; // Test d'arrêt de la boucle (bouton 4)
    POINT Curseur; // Curseur de la souris

    // initialisation du temps d'attente avec un minimum de 20 millisecondes
    uPoolPeriod = 20;

    if (joyGetDevCaps(JOYSTICKID1, &InfosCaps, sizeof(JOYCAPS)) != JOYERR_NOERROR)
        return 0;

    // Initialisation de la structure informationnelle avec sa taille
    ActualPos.dwSize = sizeof(JOYINFOEX);

    // Initialisation des autres membres que la taille de la structure à 0
    // (Plus rapide que d'affecter 0 à chaque élément. En effet, il est
    // obligatoire que tous les membres soient nuls sous peine d'échec)
    memset(&(ActualPos.dwFlags),0,sizeof(JOYINFOEX) - sizeof(DWORD));
    // Initialisation des variables d'état actuel avant de lancer la boucle
    // de gestion du joystick
    ActualPos.dwFlags = JOY_RETURNALL;
    joyGetPosEx(JOYSTICKID1,&ActualPos);
    dwBoutons = ActualPos.dwButtons;

    bStop = FALSE;

    // Lancement de la gestion du joystick
    while (!bStop)
    {
        // on réinitialise ActualPos avant d'appeller l'API
        // pour savoir la position actuelle du joystick
        memset(&(ActualPos.dwFlags),0,sizeof(JOYINFOEX) - sizeof(DWORD));
        ActualPos.dwFlags = JOY_RETURNALL;
        joyGetPosEx(JOYSTICKID1,&ActualPos);

        // Position du curseur
        GetCursorPos(&Curseur);

        // Gestion de l'axe X
        Curseur.x += CalculerDeplacement(InfosCaps.wXmin, InfosCaps.wXmax, ActualPos.dwXpos);

        // Gestion de l'axe Y
        Curseur.y += CalculerDeplacement(InfosCaps.wYmin, InfosCaps.wYmax, ActualPos.dwYpos);

        // Déplacement du curseur
        SetCursorPos(Curseur.x, Curseur.y);

        // Gestion de la molette avec le point de vue
        if (ActualPos.dwPOV == JOY_POVFORWARD)
            mouse_event(MOUSEEVENTF_WHEEL,Curseur.x,Curseur.y, 120, 0);
        else if (ActualPos.dwPOV == JOY_POVBACKWARD)
            mouse_event(MOUSEEVENTF_WHEEL,Curseur.x,Curseur.y, -120, 0);

        // Test de l'état des boutons
        // on teste l'état du bouton et on agit uniquement
        // si il y a eu un changement d'état
        if ((ActualPos.dwButtons & BOUTON1) && !(dwBoutons & BOUTON1))
            mouse_event(MOUSEEVENTF_LEFTDOWN,Curseur.x,Curseur.y, 0, 0);
        else if (!(ActualPos.dwButtons & BOUTON1) && (dwBoutons & BOUTON1))
            mouse_event(MOUSEEVENTF_LEFTUP,Curseur.x,Curseur.y, 0, 0);

        if ((ActualPos.dwButtons & BOUTON2) && !(dwBoutons & BOUTON2))
            mouse_event(MOUSEEVENTF_MIDDLEDOWN,Curseur.x,Curseur.y, 0, 0);
        else if (!(ActualPos.dwButtons & BOUTON2) && (dwBoutons & BOUTON2))
            mouse_event(MOUSEEVENTF_MIDDLEUP,Curseur.x,Curseur.y, 0, 0);

        if ((ActualPos.dwButtons & BOUTON3) && !(dwBoutons & BOUTON3))
            mouse_event(MOUSEEVENTF_RIGHTDOWN,Curseur.x,Curseur.y, 0, 0);
        else if (!(ActualPos.dwButtons & BOUTON3) && (dwBoutons & BOUTON3))
            mouse_event(MOUSEEVENTF_RIGHTUP,Curseur.x,Curseur.y, 0, 0);

        if (ActualPos.dwButtons & BOUTON4)
            bStop = TRUE;

        dwBoutons = ActualPos.dwButtons;

        // On patiente le avant de retester la position du joystick
        Sleep(uPoolPeriod);
    }
    return 0;
}
//---------------------------------------------------------------------------

int CalculerDeplacement(UINT Max, UINT Min, UINT Val)
{
    // On calcule la position actuelle en pourcentage et on en déduit le mouvement
    float PCent = (float)(Val) / (float)(Min - Max) * 100.0;
    PCent -= 50.0;
    PCent /= 10.0;
    if ((PCent < 1) && (PCent > -1))
        PCent = 0.0;

    return (int)(PCent);
}