Suivez-nous sur X
|
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
ALL
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z
|
|
A propos d'Obligement
|
|
David Brunet
|
|
|
|
Programmation : Assembleur - input.device
(Article écrit par Frédéric Delacroix et extrait d'Amiga News - janvier 1996)
|
|
Tout d'abord, je me permets de vous souhaiter une bonne année, riche en programmation, sur Amiga bien sûr. Ce mois-ci, nous
laissons AmigaGuide de côté et nous abordons un des piliers centraux du système, plus par son rôle que par sa complexité ;
j'ai nommé l'input.device.
Présentation
L'input.device est un périphérique Exec se trouvant en ROM. Son rôle est de gérer une chaîne centralisant tous les événements
agissant sur l'environnement de l'Amiga : touches du clavier, déplacement de la souris, insertion d'une disquette, mais aussi
activation d'une fenêtre ou choix d'un menu. Ces événements sont "distribués" tour à tour aux applications qui en font la
demande et celles-ci peuvent y réagir.
Ainsi, pour attendre que l'utilisateur clique sur le bouton de la souris, on ne pioche pas directement dans les registres
matériels comme on le voit encore trop souvent, mais on demande à l'input.device de nous prévenir quand cela arrive. Cette
organisation permet une grande souplesse, indispensable d'ailleurs à tout système d'exploitation qui se respecte (suivez
mon regard) : on peut non seulement imaginer de nouveaux types d'événements, mais ceux-ci peuvent être pris en compte par
un nombre quelconque d'applications à la fois.
On comprend alors comment fonctionne le système : lorsque vous appuyez sur le bouton gauche de la souris, cela est détecté
par le gameport.device, qui injecte un événement dans l'input.device. Cet événement est alors détecté (entre autres) par
l'intuition.library qui teste la position du pointeur de souris. Si par exemple celui-ci se trouve au-dessus du gadget de
fermeture d'une fenêtre, Intuition produit un nouvel événement, de type fermeture de fenêtre cette fois, et l'injecte dans
l'input.device. Celui-ci est alors reçu, "au tour suivant" par les différents gestionnaires, dont... celui d'Intuition,
c'est-à-dire celui qui a envoyé cet événement ! A la réception de celui-ci, Intuition détermine si le programme possédant
cette fenêtre a demandé à recevoir les messages IDCMP (eh oui, voilà leur origine) de type IDCMP_CLOSEWINDOW.
Si c'est le cas, un message IDCMP lui est envoyé et l'événement est retiré de la chaîne. Si ce n'est pas le cas, l'événement
poursuit sa route et peut alors être intercepté par le console.device (cas des fenêtres Shell de type CLOSE par exemple) ou
d'autres personnes, ce qui n'aurait pas été possible si Intuition avait réagi "directement" sans repasser par l'input.device.
La structure INPUTEVENT
L'input.device possède sa propre tâche, originalement nommée input.device, mais attention, ce n'est pas un processus (donc pas
question d'appeler le DOS !). Les événements gérés par l'input.device sont structurés en InputEvents, qu'on retrouve d'ailleurs
un peu partout, depuis certains paramètres fournis à Intuition à ceux utilisés par la commodities.library. La structure
InputEvent est définie dans le fichier d'inclusion "devices/inputevent.(h|i)" :
struct InputEvent {
struct InputEvent *ie_NextEvent;
UBYTE ie_Class;
UBYTE ie_SubClass;
UWORD ie_Code;
UWORD ie_Qualifier;
union {
struct {
WORD ie_x;
WORD ie_y;
} ie_xy;
APTR ie_addr;
struct {
UBYTE ie_prev1DownCode;
UBYTE ie_prev1DownQual;
UBYTE ie_prev2DownCode;
UBYTE ie_prev2DownQual;
} ie_dead;
} ie_position;
struct timeval ie_TimeStamp;
};
#define ie_X ie_position.ie_xy.ie_x
#define ie_Y ie_position.ie_xy.ie_y
#define ie_EventAddress ie_position.ie_addr
#define ie_Prev1DownCode ie_position.ie_dead.ie_prev1DownCode
#define ie_Prev1DownQual ie_position.ie_dead.ie_prev1DownQual
#define ie_Prev2DownCode ie_position.ie_dead.ie_prev2DownCode
#define ie_Prev2DownQual ie_position.ie_dead.ie_prev2DownQual
|
Ne vous laissez pas impressionner par sa complexité car elle n'est qu'apparente. Le champ ie_NextEvent pointe sur la structure
InputEvent suivante (elles sont toujours chaînées par ordre chronologique, bien entendu), l'octet ie_Class indique la classe
de l'événement. Les valeurs possibles sont définies dans le fichier d'inclusion, elles vont de l'appui sur une touche
IECLASS_RAWKEY à la modification de la position et de la taille d'une fenêtre IECLASS_CHANGEWINDOW. Le champ ie_SubClass
peut servir à préciser ce type, ses valeurs possibles dépendant entièrement de ie_Class. ie_Code contient un code spécifique,
par exemple le code de la touche sur laquelle vous avez appuyé. ie_Qualifier contient toujours l'état des touches qualificateurs (Shift,
Alt, Amiga, Ctrl, Caps Lock), mais aussi d'autres valeurs plus inattendues et fort utiles comme REPEAT (cas d'une touche répétée)
ou NUMERICPAD pour le pavé numérique.
Vient ensuite une grosse union. C'est la façon compliquée du C de dire que les données à cet endroit dépendent de la classe
de l'événement. Selon cette classe, on peut y trouver les coordonnées du pointeur de la souris, les codes des touches mortes
(accent circonflexe, tréma) pressées auparavant, l'adresse d'une structure plus grande... Rien de bien méchant.
Enfin, le champ ie_TimeStamp contient la date à laquelle l'événement s'est produit. C'est utile pour tester un double-clic par
exemple.
Le périphérique lui-même
Comme je l'ai dit plus haut, le périphérique est écrit autour d'une tâche, qui gère une chaîne d'InputEvents. Elle gère aussi
une liste de "clients", les InputHandlers. Il s'agit de routines d'applications qui se sont déclarées auprès de l'input.device
afin de recevoir les InputEvents. Il y en a trois en standard dans l'Amiga : celui d'Intuition, celui de la commodities.library
et celui du console.device.
L'ouverture du périphérique se fait comme d'habitude par la fonction OpenDevice(), il n'y a rien de particulier à ce sujet
(les paramètres unité et drapeaux (flags) sont à mettre à 0). Signalons tout de même que l'input.device utilise une structure IOStdReq.
Voyons donc les commandes intéressantes de l'input.device :
- IND_WRITEEVENT : insère un nouvel événement dans la chaîne. Celui-ci sera traité par tous les clients. Le champ io_Length doit
contenir la taille de la structure InputEvent, et io_Data pointe sur la structure InputEvent elle-même. Dans celle-ci, le
champ ie_NextEvent sera ignoré et la structure TimeStamp sera mise à l'heure courante (uniquement à partir du Kickstart 2.0).
Il est à noter que le contenu de cette structure est détruit.
- IND_ADDHANDLER : cette commande ajoute un nouveau gestionnaire à la liste de l'input.device. Il est décrit par une structure
Interrupt pointée par le champ io_Data de la structure IOStdReq. La priorité (champ ln_Pri) doit être initialisée, elle
conditionne l'ordre dans lequel les gestionnaires sont appelés. Le gestionnaire d'Intuition a une priorité de 50, celui du
console.device une priorité de 0. La routine pointée par le champ is_Code de la structure Interrupt est appelée à chaque
fois qu'un nouvel événement apparaît. Dans ce cas, le registre A0 contient l'adresse du premier événement, le chaînage avec
les autres étant fait par le champ ie_NextEvent. Le registre A1 contiendra alors ce que vous avez cru bon de mettre dans le
champ is_Data de la structure Interrupt. En retour, la fonction doit retourner (en D0) l'adresse des structures InputEvent
qui remplaceront celles reçues en paramètre.
- IND_REMHANDLER : vous vous en doutiez, cette commande retire la structure Interrupt ajoutée par IND_ADDHANDLER de la liste du
périphérique. La documentation officielle précise que cette commande n'est pas immédiate (le drapeau IOF_QUICK sera alors mis
à 0), sans doute à cause du fait qu'il faut que ce soit la tâche du périphérique (qui peut être occupée à autre chose) qui
enlève la structure. IND_ADDHANDLER n'est d'ailleurs pas immédiate non plus.
Un exemple
Après toutes ces explications théoriques, nous allons voir un exemple simple d'utilisation de l'input.device. Il utilise une
classe qui a été introduite avec la version 36 (Kickstart 2.0) : IECLASS_NEWPOINTERPOS. Elle permet (notamment) le positionnement
du pointeur de souris à un endroit précis d'un écran Intuition. Cet événement est d'ailleurs pris en charge par l'InputHandler
d'Intuition. Dans le cas de cette classe, le champ ie_EventAddress contient un pointeur sur une structure IEPointerPixel.
Je vous renvoie aux commentaires du listage pour de plus amples informations, en précisant que ce programme prend en argument
les coordonnées du pointeur en pixels et le nom d'un écran public (facultatif), par exemple: 200 200 DEVPAC.1.
include exec/exec.i
include exec/exec_lib.i
include intuition/intuition_lib.i
include devices/input.i
include devices/inputevent.i
include dos/dos_lib.i
move.l 4.w,a6
moveq #20,d7 ; code de retour: FAIL
move.l a6,Exec.Base
lea DOS.Name(pc),a1 ; ouvre le DOS
moveq #37,d0 ; Kickstart 2.04 minimum
jsr _LVOOpenLibrary(a6)
move.l d0,DOS.Base
beq exit
lea Intuition.Name(pc),a1 ; ouvre Intuition
moveq #37,d0
jsr _LVOOpenLibrary(a6)
move.l d0,Intuition.Base
beq CloseDOS
jsr _LVOCreateMsgPort(a6) ; crée un port
move.l d0,Input.Port ; de réponse
move.l d0,a0
moveq #IOSTD_SIZE,d0 ; crée une struct
jsr _LVOCreateIORequest(a6) ; IOStdReq
move.l d0,Input.IO
beq FreeIO
move.l d0,a1
lea Input.Name(pc),a0
moveq #0,d0
move.l d0,d1 ; ouvre l'input.device
jsr _LVOOpenDevice(a6)
tst.b d0
bne FreeIO
move.l #Args.Template,d1 ; lit les arguments
move.l #Args.Array,d2
moveq #0,d3
move.l DOS.Base(pc),a6
jsr _LVOReadArgs(a6)
move.l d0,Args.RDArgs
beq.s CloseInput
moveq #10,d7 ; code de retour: ERROR
lea Input.Event(pc),a3 ; struct InputEvent
lea IE.PointerPixel(pc),a4 ; struct IEPointerPixel
move.b #IECLASS_NEWPOINTERPOS,ie_Class(a3) ; classe
move.b #IESUBCLASS_PIXEL,ie_SubClass(a3) ; sous-classe
move.l a4,ie_EventAddress(a3) ; lie les 2 structs
move.l PubScreen.Name(pc),a0 ; verrouille l'écran
move.l Intuition.Base(pc),a6 ; public demandé
jsr _LVOLockPubScreen(a6)
move.l d0,iepp_Screen(a4)
beq.s FreeArgs
move.l d0,a0
jsr _LVOScreenToFront(a6) ; l'amène en avant
move.l Pointer.X(pc),a0 ; position du pointeur
move.w 2(a0),iepp_PositionX(a4)
move.l Pointer.Y(pc),a0
move.w 2(a0),iepp_PositionY(a4)
move.l Input.IO(pc),a1
move.l a3,IO_DATA(a1) ; struct InputEvent à ajouter
move.l #ie_SIZEOF,IO_LENGTH(a1)
move.w #IND_WRITEEVENT,IO_COMMAND(a1) ; commande: écrire
move.l Exec.Base(pc),a6
jsr _LVODoIO(a6)
moveq #0,d7 ; code de retour: OK
UnlockScreen
suba.l a0,a0 ; déverrouille l'écran
move.l iepp_Screen(a4),a1
move.l Intuition.Base(pc),a6
jsr _LVOUnlockPubScreen(a6)
FreeArgs
move.l Args.RDArgs(pc),d1 ; libère les arguments
move.l DOS.Base(pc),a6
jsr _LVOFreeArgs(a6)
CloseInput
move.l Input.IO(pc),a1 ; ferme l'input.device
move.l Exec.Base(pc),a6
jsr _LVOCloseDevice(a6)
FreeIO move.l Input.IO(pc),a0 ; peut être nul
jsr _LVODeleteIORequest(a6) ; libère l'IOStdReq
move.l Input.Port(pc),a0 ; libère le port
jsr _LVODeleteMsgPort(a6)
CloseIntuition
move.l Intuition.Base(pc),a1 ; ferme Intuition
jsr _LVOCloseLibrary(a6)
CloseDOS
move.l DOS.Base(pc),a1 ; ferme le DOS
jsr _LVOCloseLibrary(a6)
exit move.l d7,d0
rts
Exec.Base dc.l 0
DOS.Base dc.l 0
Intuition.Base dc.l 0
Input.Port dc.l 0
Input.IO dc.l 0
Args.Array
Pointer.X dc.l 0
Pointer.Y dc.l 0
PubScreen.Name dc.l 0
Args.RDArgs dc.l 0
Input.Event dcb.b ie_SIZEOF,0
IE.PointerPixel dcb.b IEPointerPixel_SIZEOF,0
DOS.Name dc.b 'dos.library',0
Intuition.Name dc.b 'intuition.library',0
Args.Template dc.b 'X/N/A,Y/N/A,PUBSCREEN',0
Input.Name dc.b 'input.device',0
|
|