Cómo reasignar las teclas del teclado según cuánto tiempo mantenga presionada la tecla

9

Me gustaría reasignar las teclas en mi teclado numérico para que se comporten de manera diferente dependiendo de cuánto tiempo se mantenga presionada la tecla. Aquí hay un ejemplo:

Si mantengo presionada la tecla Numpad 9 durante menos de 300 ms, enviará el comando de teclado "pestaña anterior" Ctrl+Tab

Si mantengo presionada la tecla Numpad 9 durante 300-599 ms, enviará el comando de tecla "nueva pestaña" Ctrl+T

Si mantengo presionada la tecla Numpad 9 durante 600-899 ms, enviará el comando de tecla "cerrar pestaña / ventana" Ctrl+W

Si mantengo presionada la tecla Numpad 9 durante más de 899 ms, no hace nada en caso de que pierda la ventana de tiempo que quería.

En Windows podría hacer esto con AutoHotKey y en OS XI podría hacer esto con ControllerMate, pero no puedo encontrar una herramienta en UNIX / Linux que permita la reasignación de claves en función de cuánto tiempo se mantiene una clave.

Si conoce una herramienta que puede resolver mi problema, asegúrese de proporcionar una secuencia de comandos o una muestra de código que demuestre el comportamiento de duración de retención de clave condicional que describí anteriormente. No necesita ser el código completo para resolver mi ejemplo, pero debería ser suficiente para que lo reutilice para mi ejemplo.

kanoko
fuente
Esto es algo tan extravagante que hacer. ¿Cómo va a cronometrar su prensa de 600 milisegundos? : D +1 para idea loca.
Comodín el
Solo para agregar un poco de sabor a su vida, debe agregar una ventana de tiempo de 347 a 350 ms que forzará el apagado de su computadora. ;)
Comodín el
@Wildcard Realmente uso el teclado numérico en mi Razer Naga para esto y cuando implementé la idea con AutoHotKey en Windows usé ventanas de tiempo de 300-400 ms, pero ahora que he estado usando este sistema por un tiempo, uso ventanas de tiempo separadas unos 200 ms, y puedo obtener la ventana de tiempo deseada aproximadamente el 99% del tiempo. Es muy similar a la forma en que se comunicaría con el código Morse.
Kanoko

Respuestas:

7

Acabo de escribir esto 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; 

}

Use showkey -apara obtener el código clave de enlace:

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$ 

Ponga el código clave de enlace 5 y su comando (por ejemplo, ejecutar /tmp/.a.out) en ~ / .bashrc:

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

Tenga en cuenta que el código clave relevante también debe cambiar en el código fuente (el valor hexadecimal también puede obtenerse desde sudo showkey -aarriba):

int c = 0x35;

Compilar con (salida a /tmp/a.outen mi ejemplo):

cc filename.c -lcurses

Demostración:

Teclado numérico 5, presione brevemente para abrir una nueva pestaña, presione medianamente para abrir gedit y presione prolongadamente para abrir gnome-terminal.

ingrese la descripción de la imagen aquí

Esto no es directamente aplicable en ninguna ventana en el administrador de escritorio gnome, pero creo que debería darle una idea de cómo (difícil) implementarlo. También funciona en la Consola virtual (Ctrl + Alt + N), y funciona en algún emulador de terminal (por ejemplo, konsole, gnome-terminal, xterm).

p / s: no soy un programador de CA, así que perdóname si este código no está optimizado.

[ACTUALIZAR]

La respuesta anterior solo funciona en shell y requiere el enfoque, por lo que creo que analizar / dev / input / eventX es la solución para trabajar en toda la sesión X.

No quiero reinventar la rueda. Juego con la evtestutilidad y modifiqué la parte inferior de evtest.c con mi propio código:

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;
                    }
                }
            }
        }
    }
}

Tenga en cuenta que debe cambiar la parte del nombre de usuario ( xiaobai es mi nombre de usuario). Y también if ( (ev[i].code == 76) ) {es mi código clave Numpad 5, es posible que deba imprimir manualmente el código ev [i]. Para confirmar dos veces. Y, por supuesto, también debes cambiar la ruta del video :)

Compílelo y pruébelo directamente con (la parte `` es para obtener el correcto /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}')" ` &

Tenga en cuenta que /by-id/no funciona en Fedora 24, así que lo cambio a / by-path /. Kali no tiene tal problema.

Mi administrador de escritorio es gdm3:

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

Entonces, puse esta línea /etc/gdm3/PostLogin/Defaultpara ejecutar este comando como root en el inicio de gdm ( /etc/X11/Xsession.d/*no funciona):

/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 &

Por una razón desconocida / etc/gdm/PostLogin/Defaultno funciona en Fedora 24 'gdm que me da " Permiso denegado " cuando verifica el /tmp/l_gdmEregistro. Sin embargo, no se ejecutan manualmente.

Demostración:

Teclado numérico 5, se ignorará la presión instantánea (<= 0.2 segundos), presión corta (0.2 a 0.5 segundos) abierta nautilus, presión media (0.5 a 1 segundo) abierta vlcpara reproducir video, presión larga (1 a 2 segundos) abierto gnome-terminal, y tiempo de espera-presione (2 segundos) abierto gedit.

ingrese la descripción de la imagen aquí

Subí el código completo (solo un archivo) aquí .

[ACTUALIZAR de nuevo]

[1] Se agregó flujo de claves múltiples y se corrigió el notify-senderror por definición DBUS_SESSION_BUS_ADDRESS. [2] Agregado XDG_CURRENT_DESKTOPy GNOME_DESKTOP_SESSION_IDpara garantizar que konsole use la interfaz de usuario de tema gnome (cámbielo si no está usando gnome).

Actualicé mi código aquí .

Tenga en cuenta que este código no maneja el flujo de teclas de combinación, por ejemplo, Ctrl+ t.

ACTUALIZAR:

Hay múltiples interfaces de dispositivo cuya secuencia de entradas / dev / input / by-path / XXX-eventN es aleatoria. Así que cambio el comando de la /etc/gdm3/PostLogin/Defaultsiguiente manera ( Chesenes el nombre de mi teclado, para su caso, debería cambiarlo a grep Razer):

/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 &

Puedes probar el extracto de 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 
$ 

En este ejemplo anterior, solo sudo cat /dev/input/event7imprimirá resultados extraños cuando haga clic en los 12 dígitos del mouse Razer, que tiene el patrón "sysrq kbd leds event7" para usar grep -P '^(?=.*sysrq)(?=.*leds)'arriba (su patrón puede variar). sudo cat /dev/input/event6imprimirá resultados extraños solo cuando haga clic en la tecla central arriba / abajo. Mientras sudo cat /dev/input/event5imprime resultados extraños cuando mueve el mouse y desplaza la rueda.

[Actualización: admite el cable del teclado Replug para volver a cargar el programa]

Lo siguiente debe explicarse por sí mismo:

$ 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 &
林果 皞
fuente
Supongo que este método requiere que una ventana de terminal esté enfocada mientras presiona las teclas. ¿Hay alguna forma de evitar esto?
Kanoko
@kanoko He actualizado la solución.
林果 皞
Gracias, realmente aprecio el esfuerzo que pones en esto. Probaré esto. ¿Crees que esta solución tendrá un impacto notable en el uso de la CPU si la configuro con 12 teclas de acceso rápido diferentes?
kanoko
@kanoko He actualizado el código nuevamente para jugar con varias teclas. En mi humilde opinión, no creo que sea un impacto notable en la CPU porque 10+ if-else es demasiado sutil, y solo ejecuta la comprobación después de leer (fd, ev, sizeof (struct input_event) * 64); declaración, es decir, solo ejecuta if-elsecada pulsación de tecla, mientras que también agregué if (currCode >= 59) && (currCode <= 81)para limitar el rango antes if-else.
林果 皞
1
¡¡¡Eres fabuloso!!! Muchas gracias por toda su ayuda. Si alguna vez tienes la oportunidad de probar esto con un mouse numérico MMO como Razer Naga, juro que cambiará tu vida. Puedo mostrarte mis asignaciones clave si estás interesado.
Kanoko
1

Es posible que encuentre una herramienta que funcione con un conjunto particular de programas, pero no habrá una herramienta utilizable globalmente porque el comportamiento relacionado con el tiempo se realiza en aplicaciones en X, en lugar de hacerlo mediante el sistema de ventanas.

Thomas Dickey
fuente