Utiliser IOKit pour simuler une frappe au clavier

letofletof Membre
13:41 modifié dans Vos applications #1
Bonjour,

Je souhaiterai émettre une chaà®ne de caractères en saisie de l'application qui a le focus utilisateur.

Une solution existe avec appleScript, en envoyant un ordre keystroke à  un certain processus, mais elle ne me convient pas, car elle n'est valable qu'avec les applications scriptables !

Je me suis donc tourné vers IOKit.
J'ai réussi à  envoyer un keyCode, qui est parfaitement reçu par l'application courante. Mais ce keyCode ne correspond pas à  un caractère, mais à  une touche du clavier.

Or l'exemple d'apple (en fichier joint) m'a fait croire le contraire. De plus, il me semble qu'il ne marche pas, du moins chez moi ( Mac OS 10.4.9 ) car je reçu toujours le même caractère q ( keyCode 0 )

Aurais je oublié quelque chose ?
Y a-t-il eu des modifications depuis les précédentes versions de l'OS ?
Y a-t-il d'autres CharSet à  spécifier ?

Si quelqu'un avait une expérience sur ce problème à  me faire partager, ce serait gentil.
Merci de votre aide,

letof.

Pour ceux que ça interresse, le code du fichier joint se compile et s'execute comme ceci dans le Terminal :
<br />mv testHID.txt testHID.cc<br />g++ testHID.cc -o testHID -framework IOKit<br /><br />sleep 4 ; ./testHID<br />

sleep 4 vous donne le temps de vous placer sur TextEdit pour observer l'effet.

PS j'ai trouvé un post récent d'un autre forum, sur l'utilisation de IOHIDPostEvent pour un événement souris qui fonctionne très bien.
PS2 j'ai moi même parfaitement controlé la touche eject lors de mes tests.
PS3 pour contourner mon problème, j'ai dû 'mappé' les caractères pour en obtenir la correspondance keyCode, mais je suis limité aux minuscules, et à  la moitié des caractères. Cette solution reste du bricolage, que je souhaite temporaire.

Réponses

  • BruBru Membre
    13:41 modifié #2
    dans 1200645867:

    PS3 pour contourner mon problème, j'ai dû 'mappé' les caractères pour en obtenir la correspondance keyCode, mais je suis limité aux minuscules, et à  la moitié des caractères.


    Pour les minuscules/majuscules, tu dois poster un événement clavier "touche shift pressée" en premier, suivi de l'événement clavier "touche normale".

    Idem pour les touches mortes (cà d envoyer les caractères accentués, surtout ceux avec ^ et ¨), où tu dois d'abord émettre l'événement de la touche morte puis celui de la touche du caractère à  accentué.

    .
  • BruBru Membre
    13:41 modifié #3
    Pour envoyer des événements clavier aux applis, il est préférable de passer par Quartz Event Services.
    Notamment, depuis 10.4 (ou 10.5, je ne sais plus), tu as la possibilité d'envoyer des chaines de caractères (unicode) en tant qu'événement clavier, ce qui simplifie notablement les choses.

    .
  • letofletof Membre
    13:41 modifié #4
    Merci Bru,

    Avec IOKit ça devient 'de la bidouille', car je dois remplacer chaque caractère par sa séquence clavier.
    C'est pas glop :(

    Je vais de ce pas me tourner vers Quartz

    Merci encore.
  • olofolof Membre
    13:41 modifié #5
    En plus, en envoyant des séquences clavier, tu deviens tributaire du "type" de clavier (français, suisse-romand, américain), non ???
  • letofletof Membre
    13:41 modifié #6
    Oui olof, il m'avait semblé, mais faute de mieux...

    Je suis en train de regarder en utilisant Quartz Event Service, et il me semble que je suis toujours tributaire du type de clavier.
    J'ai tapé ça pour l'instant, mais n'étant pas sur mac au travail :( ... je ne le testerai que ce Week end.

    Enfin si ça tente quelqu'un d'essayer :

    <br />#include &lt;CoreFoundation/CoreFoundation.h&gt;<br />#include &lt;ApplicationServices/ApplicationServices.h&gt;<br /><br />int main(int argc, char *argv&#91;])<br />{<br />	CFStringRef testStr = CFSTR(&quot;Hello World !&quot;) ;<br /><br />	CGEventRef eventRef = NULL ;<br />	<br />	static CGEventSourceRef mySource = NULL ;<br />	<br />	// CFRelease(CGEventCreate(NULL)); // astuce ? pour contourner un bug d&#39;initialisation ?? j&#39;ai lu ça quelque part.<br />	if( mySource == NULL ) // inutile pour l&#39;instant, mais quand ce sera intégré dans un objet cocoa ...<br />	{<br />		CGEventSourceKeyboardType keybID = LMGetKbdType() ;<br />		mySource = CGEventSourceCreate( kCGEventSourceStateCombinedSystemState ) ;<br />		CGEventSourceSetKeyboardType(mySource, keybID ) ;<br />	}<br /><br />	if( ( eventRef = CGEventCreateKeyboardEvent( mySource, (CGKeyCode)56, true ) ) != NULL )<br /><br />		CGEventKeyboardSetUnicodeString( eventRef, CFStringGetLength(testStr), CFStringGetCharactersPtr(testStr) ) ; <br /><br />	CFRelease( eventRef ) ;<br /><br />	return( 0 );<br /><br />
    


    Merci de votre intérêt.
  • letofletof Membre
    13:41 modifié #7
    ça manque peut-être à  la fin :
    <br />	CFRelease( mySource ) ;<br />
    
  • BruBru Membre
    13:41 modifié #8
    Dans ton exemple (non testé), il te manque la chose la plus importante :
    poster l'événement dans le window server !

    Ceci se fait par CGEventPost (c'est le système qui se chargera d'envoyer ces événements à  l'appli qui va bien - celle au premier plan -), ou par CGEventPostToPSN pour n'envoyer les événements qu'à  une appli précise (même si elle n'est pas active - au premier plan-).

    Je testerai ton code ce soir.

    D'autre part, faut essayer la fonction CGEventKeyboardSetUnicodeString qui, théoriquement, permet de préciser l'envoi des événements clavier à  partir d'une chaine de caractère au lieu de passer par les codes clavier.

    .
  • letofletof Membre
    13:41 modifié #9
    Merci Bru

    CGEventPost logique ! :)beta:

    par contre CGEventKeyboardSetUnicodeString prend un événement en paramétre. Cette événement doit être un événement clavier. Comme créer cet événement autrement qu'avec CGEventCreateKeyboardEvent ??

    J'avoue que la doc Apple est plus que succincte.

    Merci.
  • BruBru Membre
    13:41 modifié #10
    Je n'ai jamais joué avec CGEventKeyboardSetUnicodeString.

    Au pire, j'ai dans mes archives un bout de code que j'avais écrit d'antan jadis pour convertir un caractère ascii en une séquence de codes clavier pour l'obtenir.
    J'utilisais alors la ressource KCHR (aujourd'hui, ce serait plutôt uchr) de manière inversée.

    Je ne sais pas si aujourd'hui, c'est toujours d'actualité, d'autant plus que 10.5 a rendu beaucoup de fonctions carbon deprecated...

    .
  • schlumschlum Membre
    13:41 modifié #11
    Et pour cause, apparemment ils ont changé tout le fonctionnement interne de NSString et CFString  :(
  • letofletof Membre
    13:41 modifié #12
    Bonjour,
    Je reviens sur ce que j'ai fait ce week-end,
    Le code que j'ai mis ici est la base de tout ce que j'ai essayé.

    Création d'un événement clavier (keyCode 6 comme dans les exemples : CGEventKeyboardGetUnicodeString me rend bien 'w' comme chaine), puis affectation d'une chaine par CGEventKeyboardSetUnicodeString. => La chaine est bien enregistrée quelque part (CGEventKeyboardGetUnicodeString me réponds bien ma chaine), mais au CGPostEvent, j'ai toujours mon keyCode 6 ('w' clavier francais)

    J'ai essayé aussi de partir d'un événement vide (CGCreateEvent) puis de lui affecté le type clavier, est enfin d'y affecter une chaine, mais rien n'y fait, au CGPostEvent, je vois apparaitre 'q' (keyCode 0 du clavier francais)

    Donc je suppose qu'il doit y avoir une routine autre que CGPostEvent à  appeler pour générer les séquences clavier correspondantes à  la chaine Unicode. Mais laquelle ?

    Je n'ai pas essayé avec CGEventPostToPSN, mais il ne me semble pas qu'elle soit différente de  CGPostEvent, à  part le process en paramètre.

    Je recherche une autre solution, et si je la trouve, je vous la transmets.

    A suivre...
  • BruBru Membre
    13:41 modifié #13
    J'ai aussi fait un peu joujou avec ça ce we.

    A priori, CGEventKeyboardSetUnicodeString est buggé sous 10.4, et ne fonctionne donc pas correctement.
    Il faut donc passer par un classique CGEventCreateKeyboardEvent.
    N'ayant pas encore installé 10.5, je ne peux pas savoir si c'est ok sous cette version.

    Concernant cette dernière fonction, 2 problèmes se posent :
    - utiliser des les codes clavier.
    - attacher le "bon clavier".

    Pour le second point, il faut surtout ne pas oublier de mettre le bon id de clavier dans l'événement, afin que le système puisse faire correctement le mappage.
    Ceci se fait en utilisant la fonction CGEventSourceSetKeyboardType.
    Si tu ne précises pas ça, c'est le clavier "par défaut" qui est utilisé, donc le clavier US (voir la console au démarrage).

    Pour le premier point, je suis en train de construire la fonction inverse à  UCKeyTranslate, cà d à  partir d'un caractère unicode, retrouver la séquence de code clavier à  générer.
    Ceci se fait en passant par les tables de mappage prséentes dans la ressource uchr.

    .
  • letofletof Membre
    13:41 modifié #14
    J'avais aussi remarqué la fonction UCKeyTranslate, et je suis bien embêté qu'il n'y ai pas d'inverse dans l'API.

    Je me pose quand même une question : Quel est l'intérêt d'une fonction buggé ? Elle a été introduite pour répondre à  un besoin. Donc certaines personnes doivent vouloir l'utiliser (j'ai trouvé une question identique à  la mienne sur list.apple.com, mais la réponse ne me convenait pas car sans explication, il était proposé de passer par les keyCodes 1 par 1) Un bug non résolu sur 10 mises à  jour majeures, signifierai que la fonction n'est finalement utile à  pas grand monde. Donc pourquoi l'avoir introduite ?

    Donc j'en reviens à  l'existence d'une routine (peut être cachée) qui permet à  partir d'un événement clavier affublé d'une chaine Unicode, d'être convertie en événement clavier.

    Sinon, pour le type de clavier, il ne m'est pas nécessaire d'avoir le clavier Français ; il me suffit de générer les keyCodes avec le même type de clavier que celui qui servira à  les interpréter. Un clavier qui me permette les lettres accentuées.

    Merci Bru de ton aide, mais transmets moi juste les infos que tu as sur uchr, je m'en contenterai pour une fonction UCKeyTranslateInverse.
  • letofletof Membre
    13:41 modifié #15
    J'ai trouvé ma réponse :

    La doc apple précise bien ( mais comme c'est en anglais, ça ne m'a pas sauté aux yeux ) que CGEventKeyboardSetUnicodeString ASSOCIE une chaine à  un événement, mais que celle-ci est ignoré par le traitement de l'événement, au profit du keyCode. Donc ce n'est pas adapté à  mon problème.

    On en revient donc à  UCKeyTranslateInverse.
  • letofletof Membre
    13:41 modifié #16
    Bonjour,

    Après quelques essais infructueux, mais une recherche intensive, j'ai trouver ça :

    http://svn.python.org/projects/external/tk8.4.12/macosx/tkMacOSXKeyEvent.c

    Dès fois que ce soit utile pour d'autres.
    Moi je vais de ce pas tester tout ça.

    A bientôt.
  • BruBru Membre
    13:41 modifié #17
    De mon côté, j'ai retrouvé mon code...
    Je suis en train de le ré-écrire pour prendre en compte la ressource uchr (au lieu de  KCHR).

    .
  • AliGatorAliGator Membre, Modérateur
    13:41 modifié #18
    dans 1201090225:

    De mon côté, j'ai retrouvé mon code...
    Je suis en train de le ré-écrire pour prendre en compte la ressource uchr (au lieu de  KCHR).

    .
    Juste pour info, ça marche comment cet aspect "ressource uchr" ? Je veux dire je croyais que la mode sous OSX était à  l'abandon des "resource forks", non ? l'aspect "ressource" existe encore ?? Ca n'a pas été remplacé par d'autres types de fichiers contenant les mêmes infos ?

    (Bon j'avoue que je suis pas non plus allé chercher sur mon mac -- que je n'ai d'ailleurs pas sous la main en ce moment -- comment était foutus les fichiers de clavier)
  • BruBru Membre
    13:41 modifié #19
    dans 1201094408:

    Juste pour info, ça marche comment cet aspect "ressource uchr" ? Je veux dire je croyais que la mode sous OSX était à  l'abandon des "resource forks", non ? l'aspect "ressource" existe encore ?? Ca n'a pas été remplacé par d'autres types de fichiers contenant les mêmes infos ?


    Les ressources keyboard layout peuvent être sous forme de ressources traditionnelles, ou sous forme de fichier xml à  mettre dans Library/Keyboard.

    L'API de gestion des claviers de OS X fournit par chance une fonction haut-niveau qui permet de récupérer le contenu de ladite ressource, quelque soit son format/emplacement.

    Donc, pas besoin de se prendre le chou.

    .
  • AliGatorAliGator Membre, Modérateur
    13:41 modifié #20
    dans 1201104843:

    dans 1201094408:

    Juste pour info, ça marche comment cet aspect "ressource uchr" ? Je veux dire je croyais que la mode sous OSX était à  l'abandon des "resource forks", non ? l'aspect "ressource" existe encore ?? Ca n'a pas été remplacé par d'autres types de fichiers contenant les mêmes infos ?


    Les ressources keyboard layout peuvent être sous forme de ressources traditionnelles, ou sous forme de fichier xml à  mettre dans Library/Keyboard.

    L'API de gestion des claviers de OS X fournit par chance une fonction haut-niveau qui permet de récupérer le contenu de ladite ressource, quelque soit son format/emplacement.

    Donc, pas besoin de se prendre le chou.

    .
    Ok merci  ;)
  • letofletof Membre
    13:41 modifié #21
    Salut, Bru !

    D'après ce que j'ai lu, les ressources KCHR ne serait pas complètement abandonnées. Sans doute pour ne les pas réécrire pour les anciens claviers. Dans tous les codes que j'ai récupéré, les 2 types de ressources sont gérées. Alors je ne sais pas encore si c'est utile. En tout cas, j'ai un clavier d'iMac G3 émeraude (existe-t-il plus vieux sur le marché ?), et j'ai bien un uchr renseigné.
    Par contre, je n'ai pas encore cerné la structure interne d'uchr. J'y ai trouvé des caractères ASCII rangé par ordre de keyCodes, mais pas de caractères étendus, ni les combinaisons de touches.

    Qu'arrives tu à  y décrypter ?
  • BruBru Membre
    janvier 2008 modifié #22
    Bon, sous 10.4, clavier français, c'est toujours un KCHR qui est utilisé.

    J'ai retrouvé une partie du code que j'avais écrit pour faire la conversion ASCII vers KEYCODE.
    Je l'ai adapté à  10.4 (il était écrit à  l'époque de Système 7) et je l'ai commenté :
    <br />BOOL Ascii2Virtual(char pcar, BOOL *pshift, BOOL *palt, char *pkeycode)<br />{<br /> &nbsp; KeyboardLayoutRef clavier;<br /> &nbsp; const void *dtacla;<br /> &nbsp; UInt16 nbblocs;<br /> &nbsp; char *modblocs, *blocs, *deadkeys;<br /> &nbsp; int ix, ifin, numbloc, keycode;<br /><br /> &nbsp; BOOL shift, alt;<br /><br /> &nbsp; // récupération du clavier courant<br /> &nbsp; if (KLGetCurrentKeyboardLayout(&amp;clavier)) return NO;<br /><br /> &nbsp; // récupération de la description (keyboard layout) du clavier courant<br /> &nbsp; if (KLGetKeyboardLayoutProperty(clavier, kKLKCHRData, &amp;dtacla)) return NO;<br /><br /> &nbsp; // récupération du pointeur de début des numéros de blocs pour chaque combinaison de modifiers<br /> &nbsp; modblocs=((char *)dtacla)+2;<br /><br /> &nbsp; // récupération de nombre de blocs keycode-&gt;ascii<br /> &nbsp; nbblocs=*((UInt16 *)(dtacla+258));<br /><br /> &nbsp; // récupération du pointeur de début des blocs keycode-&gt;ascii<br /> &nbsp; blocs=((char *)dtacla)+260;<br /><br /> &nbsp; // on détermine la taille de toutes les tables keycode-&gt;ascii à  scanner<br /> &nbsp; ifin=nbblocs*128;<br /><br /> &nbsp; // on détermine le pointeur de début de la tables des dead keys<br /> &nbsp; deadkeys=blocs+ifin;<br /><br /> &nbsp; // maintenant on parcourt les blocs keycode-&gt;ascii pour retrouver le car ascii<br /> &nbsp; for (ix=0; ix&lt;ifin; ix++)<br /> &nbsp; {<br />      if (blocs[ix]==pcar)<br />      {<br />       &nbsp; // car ascii trouvé : il faut déterminer dans quel bloc (numéro du bloc) il se trouve<br />       &nbsp; keycode=ix&amp;0x7f;<br />       &nbsp; numbloc=ix&gt;&gt;7;<br />       &nbsp; break;<br />      }<br /> &nbsp; }<br /><br /> &nbsp; // car non trouvé : on termine (avec erreur)<br /> &nbsp; if (ix&gt;=ifin) return NO;<br /><br /> &nbsp; // à  partir du numéro de bloc, il faut retrouver la combinaison de modifiers utilisant ce bloc<br /> &nbsp; for (ix=0; ix&lt;15; ix++)<br /> &nbsp; {<br />      // on ne traite pas si les modifiers ne sont pas &quot;majuscule&quot; et &quot;option&quot;<br />      if (ix&amp;1 || ix&amp;4) continue;<br /><br />      // combinaison de modifiers trouvée pour le bloc<br />      if (modblocs[ix]==numbloc)<br />      {<br />       &nbsp; shift=(ix&amp;2)?YES:NO;<br />       &nbsp; alt=(ix&amp;8)?YES:NO;<br />       &nbsp; break;       &nbsp; <br />      }<br /> &nbsp; }<br /><br /> &nbsp; // combinaison modifiers non trouvé : on termine (avec erreur)<br /> &nbsp; if (ix&gt;=15) return NO;<br /><br /> &nbsp; // mise à  jour des paramètres<br /> &nbsp; *pkeycode=keycode;<br /> &nbsp; *pshift=shift;<br /> &nbsp; *palt=alt;<br /><br /> &nbsp; return YES;<br />}<br />
    


    La fonction retourne YES si la conversion a eu lieu, sinon NO (code ASCII inconnu dans l'encodage Mac OS Roman, ou ASCII formé à  partir des touches mortes).

    Le premier paramètre est le caractère ASCII en Mac OS Roman, et les 3 suivants sont les pointeurs vers le key-code retourné et l'état des touches modificatrices pour obtenir le caractère.

    Pour les touches modificatrices, j'avais une seconde fonction... Je ne la retrouve pas pour le moment pour le moment.

    Enjoy.

    .
  • BruBru Membre
    13:41 modifié #23
    Bon, j'ai fais à  la va-vite un prog  de test :
    <br />void CreateAndPostEvent(CGEventSourceRef source, ProcessSerialNumber psn, char keycode, int flags);<br /><br />@implementation UneClasseQuelconque<br /><br />- (void)SendKeyEvent<br />{<br />&nbsp;  ProcessSerialNumber psn;<br />&nbsp;  CGEventSourceRef source;<br />&nbsp;  NSString *s=@&quot;Chaine de test !&quot;;<br />&nbsp;  NSData *strdat;<br />&nbsp;  int ix, siz;<br />&nbsp;  char *cars;<br />&nbsp;  BOOL ok, shift, alt;<br />&nbsp;  char keycode;<br />&nbsp;  int flags;<br /><br />&nbsp;  GetProcessForPID(1163, &amp;psn);<br />&nbsp;  source=CGEventSourceCreate(kCGEventSourceStateCombinedSessionState);<br /><br />&nbsp;  strdat=[s dataUsingEncoding:NSMacOSRomanStringEncoding];<br />&nbsp;  siz=[strdat length];<br />&nbsp;  cars=(char *)[strdat bytes];<br />&nbsp;  for (ix=0; ix&lt;siz; ix++)<br />&nbsp;  {<br />&nbsp; &nbsp; &nbsp; ok=Ascii2Virtual(cars[ix], &amp;shift, &amp;alt, &amp;keycode);<br />&nbsp; &nbsp; &nbsp; if (ok)<br />&nbsp; &nbsp; &nbsp; {<br />&nbsp; &nbsp; &nbsp; &nbsp;  flags=(shift?kCGEventFlagMaskShift:0)|(alt?kCGEventFlagMaskAlternate:0);<br />&nbsp; &nbsp; &nbsp; &nbsp;  CreateAndPostEvent(source, psn, keycode, flags);<br />&nbsp; &nbsp; &nbsp; }<br />&nbsp;  }<br />&nbsp;  CFRelease(source);<br />}<br /><br />@end<br /><br />void CreateAndPostEvent(CGEventSourceRef source, ProcessSerialNumber psn, char keycode, int flags)<br />{<br />&nbsp;  CGEventRef event;<br /><br />&nbsp;  event=CGEventCreateKeyboardEvent(source, keycode, true);<br />&nbsp;  CGEventSetFlags(event, flags);<br />&nbsp;  CGEventPostToPSN(&amp;psn, event);<br />&nbsp;  CFRelease(event);<br />&nbsp;  event=CGEventCreateKeyboardEvent(source, keycode, false);<br />&nbsp;  CGEventSetFlags(event, flags);<br />&nbsp;  CGEventPostToPSN(&amp;psn, event);<br />&nbsp;  CFRelease(event);<br />}<br />
    


    Ici, j'envoie les événements "key" à  l'appli dont le pid est 1163 (il s'agit de TextEdit pour mon test).

    .
Connectez-vous ou Inscrivez-vous pour répondre.