Comment remapper les touches du clavier en fonction de la durée pendant laquelle vous maintenez la touche

9

Je voudrais remapper les touches de mon pavé numérique afin qu'elles se comportent différemment selon la durée pendant laquelle la touche est enfoncée. Voici un exemple:

Si je maintiens la touche du pavé numérique 9 enfoncée pendant moins de 300 ms, il enverra le raccourci clavier "onglet précédent" Ctrl+Tab

Si je maintiens la touche du pavé numérique 9 enfoncée pendant 300 à 599 ms, il enverra le raccourci clavier "nouvel onglet" Ctrl+T

Si je maintiens la touche du pavé numérique 9 enfoncée pendant 600 à 899 ms, il enverra le raccourci clavier "Fermer l'onglet / fenêtre" Ctrl+W

Si je maintiens la touche du pavé numérique 9 enfoncée pendant plus de 899 ms, cela ne fait rien au cas où j'aurais raté la fenêtre de temps que je voulais.

Sous Windows, je pourrais le faire avec AutoHotKey et sous OS XI avec le ControllerMate, mais je ne trouve pas d'outil sous UNIX / Linux qui permette le remappage des clés en fonction de la durée de conservation d'une clé.

Si vous connaissez un outil qui peut résoudre mon problème, assurez-vous de fournir un script ou un exemple de code qui illustre le comportement de durée de conservation de clé conditionnelle que j'ai décrit ci-dessus. Il n'a pas besoin d'être le code complet pour résoudre mon exemple, mais il devrait être suffisant pour moi de le réutiliser pour mon exemple.

Kanoko
la source
C'est une chose tellement bizarre à faire. Comment allez-vous chronométrer votre presse de 600 millisecondes? : D +1 pour une idée folle.
Wildcard
Juste pour ajouter du piment à votre vie, vous devez ajouter une fenêtre de temps de 347 à 350 ms qui forcera l'arrêt de votre ordinateur. ;)
Wildcard
@Wildcard J'utilise en fait le pavé numérique de mon Razer Naga pour cela et lorsque j'ai implémenté l'idée avec AutoHotKey sur Windows, j'ai utilisé des fenêtres temporelles de 300 à 400 ms, mais maintenant que j'utilise ce système depuis un certain temps, j'utilise fenêtres de temps à environ 200 ms d'intervalle, et je peux obtenir la fenêtre de temps souhaitée environ 99% du temps. C'est très similaire à la façon dont vous communiquez avec le code morse.
kanoko

Réponses:

7

Je viens d'écrire ceci en C :

#include <stdio.h>
#include <curses.h>
#include <time.h> //time(0)
#include <sys/time.h>                // gettimeofday()
#include <stdlib.h>

void waitFor (unsigned int secs) {
    //credit: http://stackoverflow.com/a/3930477/1074998
    unsigned int retTime = time(0) + secs;   // Get finishing time.
    while (time(0) < retTime);               // Loop until it arrives.
}

int
main(void) {

    struct timeval t0, t1, t2, t3;
    double elapsedTime;

    clock_t elapsed_t = 0;
    int c = 0x35;

    initscr();
    cbreak();
    noecho();
    keypad(stdscr, TRUE);

    halfdelay(5); //increae the number if not working //adjust below `if (elapsedTime <= 0.n)` if this changed
    printf("\nSTART again\n");

    elapsed_t = 0;
    gettimeofday(&t0, NULL);

    float diff;

    int first = 1;
    int atleast_one = 0;

      while( getch() == c) { //while repeating same char, else(ffff ffff in my system) break

            int atleast_one = 1;

            if (first == 1) {
                gettimeofday(&t1, NULL);
                first = 0;
            }

            //printf("DEBUG 1 %x!\n", c);
            gettimeofday(&t2, NULL);
            elapsedTime = (t2.tv_sec - t1.tv_sec) + ((t2.tv_usec - t1.tv_usec)/1000000.0); 

            if (elapsedTime > 1) { //hit max time

                printf("Hit Max, quit now. %f\n", elapsedTime);
                system("gnome-terminal");
                //waitFor(4);

                int cdd;
                while ((cdd = getch()) != '\n' && cdd != EOF);
                endwin();

                exit(0);
            }

            if(halfdelay(1) == ERR) { //increae the number if not working
                //printf("DEBUG 2\n");
                //waitFor(4);
                break; 
                }
            else {
                //printf("DEBUG 3\n");
                }
        }

    if (atleast_one == 0) {
            //gettimeofday(&t1, NULL);
            t1 = t0;
    }

    gettimeofday(&t3, NULL);
    elapsedTime = (t3.tv_sec - t1.tv_sec) + ((t3.tv_usec - t1.tv_usec)/1000000.0); 
    printf("Normal quit %f\n", elapsedTime);
    if (elapsedTime > 0.6) { //this number based on halfdelay above
        system("gedit &");
        //system("xdotool key shift+left &");
        //system("mplayer -vo caca -quiet 'video.mp4' &");
        //waitFor(4);
    }
    else if (elapsedTime <= 0.6) {
        system("xdotool key ctrl+shift+t &");
        //waitFor(4);
    }

    int cdd;
    while ( (cdd = getch() ) != '\n' && cdd != EOF);
    endwin();
    return 0; 

}

Utilisez showkey -apour obtenir le code clé de liaison:

xb@dnxb:/tmp$ sudo showkey -a

Press any keys - Ctrl-D will terminate this program

^[[24~   27 0033 0x1b #pressed F12
         91 0133 0x5b
         50 0062 0x32
         52 0064 0x34
        126 0176 0x7e
5        53 0065 0x35 #pressed Numpad 5, 5 is the keycode used in `bind`
^C        3 0003 0x03
^D        4 0004 0x04
xb@dnxb:/tmp$ 

Placez le code clé de liaison 5 et sa commande (par exemple, exécuter /tmp/.a.out) dans ~ / .bashrc:

bind '"5":"/tmp/a.out\n"'

Notez que le code clé pertinent doit également être modifié dans le code source (la valeur hexadécimale peut également être obtenue par le sudo showkey -ahaut):

int c = 0x35;

Compiler avec (sortie /tmp/a.outdans mon exemple):

cc filename.c -lcurses

Manifestation:

Pavé numérique 5, appuyez brièvement sur ouvrir un nouvel onglet, appuyez légèrement sur gedit ouvert et appuyez longuement sur gnome-terminal.

entrez la description de l'image ici

Ce n'est pas directement applicable dans aucune fenêtre du gestionnaire de bureau gnome, mais je pense que cela devrait vous donner une idée de la façon (difficile) de le mettre en œuvre. Il fonctionne également dans la console virtuelle (Ctrl + Alt + N) et fonctionne dans certains émulateurs de terminaux (par exemple konsole, gnome-terminal, xterm).

p / s: je ne suis pas programmeur ac, alors pardonnez-moi si ce code n'est pas optimisé.

[MISE À JOUR]

La réponse précédente ne fonctionne que dans le shell et le focus requis, donc je pense que l'analyse du / dev / input / eventX est la solution pour travailler dans toute la session X.

Je ne veux pas réinventer la roue. Je joue avec l' evtestutilitaire et j'ai modifié la partie inférieure d' evtest.c avec mon propre code:

int onHold = 0;
struct timeval t0;
double elapsedTime;
int hitMax = 0;

while (1) {
    rd = read(fd, ev, sizeof(struct input_event) * 64);

    if (rd < (int) sizeof(struct input_event)) {
        perror("\nevtest: error reading");
        return 1;
    }

    system("echo 'running' >/tmp/l_is_running 2>/tmp/l_isrunning_E &");
    for (i = 0; i < rd / sizeof(struct input_event); i++) {

        //system("date >/tmp/l_date 2>/tmp/l_dateE &");

        if (ev[i].type == EV_KEY) {
            if ( (ev[i].code == 76) ) {

                if (!onHold) {
                    onHold = 1;
                    t0 = ev[i].time;
                    hitMax = 0;
                }
                if (!hitMax) { //to avoid hitMax still do the time checking instruction, you can remove hitMax checking if you think it's overkill, but still hitMax itself is necessary to avoid every (max) 2 seconds will repeatly system();
                    elapsedTime = (ev[i].time.tv_sec - t0.tv_sec) + ((ev[i].time.tv_usec - t0.tv_usec)/1000000.0);
                    printf("elapsedTime: %f\n", elapsedTime);
                    if (elapsedTime > 2) {
                        hitMax = 1;
                        printf("perform max time action\n");
                        system("su - xiaobai -c 'export DISPLAY=:0; gedit &'");
                    }
                }

                if (ev[i].value == 0)  {
                    printf("reseted ...... %d\n", ev[i].value);
                    onHold = 0;
                    if (!hitMax) {
                        if (elapsedTime > 1) { //just ensure lower than max 2 seconds
                            system("su - xiaobai -c 'export DISPLAY=:0; gnome-terminal &'");
                        } else if (elapsedTime > 0.5) { 
                            system("su - xiaobai -c \"export DISPLAY=:0; vlc '/home/xiaobai/Downloads/videos/test/Pokémon Red_Blue_Yellow Gym Leader Battle Theme Remix-CbJTkx7QUJU.mp4' &\"");
                        } else if  (elapsedTime > 0.2) {
                            system("su - xiaobai -c 'export DISPLAY=:0; nautilus &'");
                        }
                    } else { //else's max system() already perform
                        hitMax = 0;
                    }
                }
            }
        }
    }
}

Notez que vous devez changer la partie nom d'utilisateur ( xiaobai est mon nom d'utilisateur). Et aussi if ( (ev[i].code == 76) ) {est mon code clavier Numpad 5, vous devrez peut-être imprimer manuellement le code ev [i]. Pour confirmer deux fois. Et bien sûr, vous devez également changer le chemin de la vidéo :)

Compilez-le et testez-le directement avec (la partie `` est pour obtenir le bon /dev/input/eventN):

$ gcc /home/put_your_path/my_long_press.c -o /home/put_your_path/my_long_press; sudo /home/put_your_path/my_long_press `ls -la /dev/input/by-path/* | grep kbd |  echo "/dev/input/""$(awk -F'/' '{print $NF}')" ` &

Notez que /by-id/cela ne fonctionne pas dans Fedora 24, donc je le change en / by-path /. Kali pas un tel problème.

Mon gestionnaire de bureau est gdm3:

$ cat /etc/X11/default-display-manager 
/usr/sbin/gdm3

Donc, j'ai mis cette ligne /etc/gdm3/PostLogin/Defaultpour exécuter cette commande en tant que root au démarrage de gdm ( /etc/X11/Xsession.d/*ne fonctionne pas):

/home/put_your_path/my_long_press `ls -la /dev/input/by-id/* | grep kbd |  echo "/dev/input/""$(awk -F'/' '{print $NF}')" 2>/tmp/l_gdm` 2>/tmp/l_gdmE &

Pour une raison inconnue / etc/gdm/PostLogin/Defaultne fonctionne pas sur Fedora 24 'gdm qui me donne " Autorisation refusée " lors de la vérification du /tmp/l_gdmEjournal. Exécuter manuellement aucun problème cependant.

Manifestation:

Pavé numérique 5, appui instantané (<= 0,2 seconde) sera ignoré, appui court (0,2 à 0,5 seconde) ouvert nautilus, appui moyen (0,5 à 1 seconde) ouvert vlcpour lire la vidéo, appui long (1 à 2 secondes) ouvert gnome-terminalet timeout-appuyez sur (2 secondes) pour ouvrir gedit.

entrez la description de l'image ici

J'ai téléchargé le code complet (un seul fichier) ici .

[MISE À JOUR à nouveau]

[1] Ajout d'un flux de clés multiples et correction d'un notify-sendéchec par define DBUS_SESSION_BUS_ADDRESS. [2] Ajouté XDG_CURRENT_DESKTOPet GNOME_DESKTOP_SESSION_IDpour s'assurer que konsole utilise le thème gnome gui (Modifiez-le si vous n'utilisez pas gnome).

J'ai mis à jour mon code ici .

Notez que ce code ne gère pas le flux de clés combinées, par exemple Ctrl+ t.

MISE À JOUR:

Il existe plusieurs interfaces de périphérique dont la séquence d'entrées / dev / input / by-path / XXX-eventN est aléatoire. Je change donc la commande /etc/gdm3/PostLogin/Defaultcomme ci-dessous ( Chesenc'est mon nom de clavier, pour votre cas, vous devriez le changer à la grep Razerplace):

/your_path/my_long_press "$(cat /proc/bus/input/devices | grep -i Chesen -A 4 | grep -P '^(?=.*sysrq)(?=.*leds)' |  tr ' ' '\n' | ls /dev/input/`grep event`)" 2>/tmp/l_gdmE &

Vous pouvez essayer l'extrait eventN de cat /proc/bus/input/devices | grep -i Razer -A 4:

$ cat /proc/bus/input/devices | grep -i Razer -A 4
N: Name="Razer Razer Naga Chroma"
P: Phys=usb-0000:00:14.0-1.3/input0
S: Sysfs=/devices/pci0000:00/0000:00:14.0/usb3/3-1/3-1.3/3-1.3:1.0/0003:1532:0053.0003/input/input6
U: Uniq=
H: Handlers=mouse2 event5 
--
N: Name="Razer Razer Naga Chroma"
P: Phys=usb-0000:00:14.0-1.3/input1
S: Sysfs=/devices/pci0000:00/0000:00:14.0/usb3/3-1/3-1.3/3-1.3:1.1/0003:1532:0053.0004/input/input7
U: Uniq=
H: Handlers=sysrq kbd event6 
--
N: Name="Razer Razer Naga Chroma"
P: Phys=usb-0000:00:14.0-1.3/input2
S: Sysfs=/devices/pci0000:00/0000:00:14.0/usb3/3-1/3-1.3/3-1.3:1.2/0003:1532:0053.0005/input/input8
U: Uniq=
H: Handlers=sysrq kbd leds event7 
$ 

Dans cet exemple ci-dessus, seule sudo cat /dev/input/event7l'impression bizarre sera imprimée lorsque vous cliquerez sur les 12 chiffres de la souris Razer, qui a le modèle «sysrq kbd leds event7» à utiliser grep -P '^(?=.*sysrq)(?=.*leds)'ci-dessus (votre modèle peut varier). sudo cat /dev/input/event6imprime une sortie bizarre uniquement lorsque vous cliquez sur la touche centrale haut / bas. While sudo cat /dev/input/event5imprimera une sortie bizarre lorsque vous déplacez votre souris et faites défiler la molette.

[Mise à jour: prise en charge du câble du clavier pour recharger le programme]

Ce qui suit devrait être une explication:

$ lsusb #to know my keyboard is idVendor 0a81 and idProduct 0101
...
Bus 001 Device 003: ID 0a81:0101 Chesen Electronics Corp. Keyboard

$ cat /etc/udev/rules.d/52-hole-keyboard.rules #add this line with your idVendor and idProduct above in custom udev rules file
ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="0a81", ATTR{idProduct}=="0101", MODE="0666", GROUP="plugdev", RUN+="/bin/bash -c 'echo 1 > /tmp/chesen_plugged'"

$ cat /usr/local/bin/inotifyChesenPlugged #A long run listener script to listen for modification of /tmp/chesen_plugged #Ensures `inotifywait` has been installed first.
touch /tmp/chesen_plugged
while inotifywait -q -e modify /tmp/chesen_plugged >/dev/null; do
        killall -9 my_long_press
        /usr/local/bin/startLongPress &
done

$ cat /usr/local/bin/startLongPress #the executable script run the long press executable #Change with your pattern as explained above.
#!/bin/bash
<YOUR_DIR>/my_long_press "$(cat /proc/bus/input/devices | grep -i Chesen -A 4 | grep -P '^(?=.*sysrq)(?=.*leds)' |  tr ' ' '\n' | ls /dev/input/`grep event`)" 2>/tmp/l_gdmE) & disown

$ cat /etc/gdm3/PostLogin/Default #the executable startup script run listener and long press script
/usr/local/bin/inotifyChesenPlugged &
/usr/local/bin/startLongPress &
林果 皞
la source
Je suppose que cette méthode nécessite qu'une fenêtre de terminal soit mise au point lors des pressions de touches? Y a-t-il un moyen de contourner ceci?
kanoko
@kanoko J'ai mis à jour la solution.
林果 皞
merci, j'apprécie vraiment l'effort que vous y avez consacré. Je vais essayer ça. pensez-vous que cette solution aura un impact notable sur l'utilisation du processeur si je la configure avec 12 raccourcis clavier différents?
Kanoko
@kanoko J'ai de nouveau mis à jour le code pour jouer avec plusieurs touches. À mon humble avis, je ne pense pas que cela ait un impact notable sur le processeur, car 10+ si-sinon est trop subtil, et il n'exécute la vérification qu'après lecture (fd, ev, sizeof (struct input_event) * 64); , c'est-à-dire qu'il n'exécute que if-elsechaque pression de touche, tandis que j'ai également ajouté if (currCode >= 59) && (currCode <= 81)pour limiter la plage avant if-else.
林果 皞
1
vous êtes incroyable!!! Merci beaucoup pour toute votre aide. si jamais vous avez la chance d'essayer cela avec une souris numérique MMO comme Razer Naga, je vous jure que cela changera votre vie. Je peux vous montrer mes mappages clés si vous êtes intéressé.
kanoko
1

Vous trouverez peut-être un outil qui fonctionne avec un ensemble particulier de programmes, mais il n'y aura pas d'outil utilisable globalement car le comportement lié au temps est effectué dans les applications dans X, plutôt que par le système de fenêtrage.

Thomas Dickey
la source
0

avez-vous vérifié le Xmodmap?

xmodmap est un utilitaire pour modifier les mappages de touches et les mappages de boutons de pointeur dans Xorg

https://wiki.archlinux.org/index.php/Xmodmap

Kamaraj
la source
2
Mais il ne connaît pas les délais :-)
Thomas Dickey