Como remapear as teclas do teclado com base em quanto tempo você mantém a tecla

9

Gostaria de remapear as teclas no teclado numérico para que elas se comportem de maneira diferente, dependendo de quanto tempo a tecla for pressionada. Aqui está um exemplo:

Se eu segurar a tecla pressionada Numpad 9 por menos de 300ms ele enviará o "anterior guia" tecla de comando Ctrl+Tab

Se eu segurar a tecla pressionada Numpad 9 para 300-599ms ele irá enviar a "nova guia" tecla de comando Ctrl+T

Se eu segurar a tecla pressionada Numpad 9 para 600-899ms ele enviará o "próximo guia / janela" tecla de comando Ctrl+W

Se eu mantiver a tecla Numpad 9 pressionada por mais de 899ms, ela não fará nada caso eu perca a janela de tempo que queria.

No Windows, eu poderia fazer isso com o AutoHotKey e no OS XI, no ControllerMate, mas não consigo encontrar uma ferramenta no UNIX / Linux que permita o remapeamento da chave com base no tempo que uma chave é mantida.

Se você conhece uma ferramenta que pode resolver meu problema, forneça um exemplo de script ou código que demonstre o comportamento condicional da duração da retenção de chave que descrevi acima. Ele não precisa ser o código completo para resolver o meu exemplo, mas deve ser o suficiente para redirecioná-lo para o meu exemplo.

Kanoko
fonte
Isso é uma coisa tão estranha de se fazer. Como você vai programar sua impressora de 600 milissegundos? : D +1 para uma ideia maluca.
Curinga
Apenas para adicionar um pouco de tempero à sua vida, você deve adicionar uma janela de tempo de 347 a 350 ms que forçará o desligamento do seu computador. ;)
Curinga
@Wildcard Na verdade, uso o teclado numérico no meu Razer Naga para isso e, quando implementei a idéia com o AutoHotKey no Windows, usei janelas de tempo de 300 a 400ms, mas agora que uso esse sistema há algum tempo, uso janelas de tempo a cerca de 200 ms de distância e posso obter a janela de tempo desejada em 99% das vezes. É muito parecido com o modo como você se comunicaria com o código morse.
kanoko

Respostas:

7

Eu acabei de escrever isso em 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 obter o código-chave de ligação:

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$ 

Coloque o código-chave de ligação 5 e seu comando (por exemplo, execute /tmp/.a.out) em ~ / .bashrc:

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

Observe que o código-chave relevante também precisa ser alterado no código-fonte (o valor hexadecimal também pode ser visto sudo showkey -aacima):

int c = 0x35;

Compile com (saída para /tmp/a.outno meu exemplo):

cc filename.c -lcurses

Demonstração:

Numpad 5, pressão breve abre uma nova guia, pressão média abre gedit e pressão longa abre o terminal gnome.

insira a descrição da imagem aqui

Isso não é aplicável diretamente em nenhuma janela do gerenciador de desktop gnome, mas acho que deve lhe dar uma idéia de como (difícil) implementá-lo. Também funciona no console virtual (Ctrl + Alt + N) e funciona em algum emulador de terminal (por exemplo, konsole, gnome-terminal, xterm).

p / s: Eu não sou programador de CA, então me perdoe se esse código não for otimizado.

[ATUALIZAR]

A resposta anterior funciona apenas no shell e no foco necessário, então acho que analisar o / dev / input / eventX é a solução para trabalhar em toda a sessão do X.

Eu não quero reinventar a roda. Eu brinco com o evtestutilitário e modifiquei a parte inferior do evtest.c com meu próprio 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;
                    }
                }
            }
        }
    }
}

Observe que você deve alterar a parte do nome de usuário ( xiaobai é meu nome de usuário). E também if ( (ev[i].code == 76) ) {é o meu código-chave do Numpad 5, pode ser necessário imprimir manualmente o código ev [i]. Para confirmar duas vezes. E é claro que você também deve mudar o caminho do vídeo :)

Compile e teste-o diretamente com (a parte `` é para obter o correto /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}')" ` &

Note que /by-id/não funciona no Fedora 24, então eu mudo para / by-path /. Kali não existe esse problema.

Meu gerenciador de desktop é gdm3:

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

Então, eu coloquei esta linha /etc/gdm3/PostLogin/Defaultpara executar este comando como root na inicialização do gdm ( /etc/X11/Xsession.d/*não 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 motivo desconhecido / etc/gdm/PostLogin/Defaultnão funciona no Fedora 24 'gdm, o que me dá " Permissão negada " ao verificar o /tmp/l_gdmElog. Manualmente não execute nenhum problema.

Demonstração:

O Numpad 5, pressionar instantaneamente (<= 0,2 segundo) será ignorado, pressionar brevemente (0,2 a 0,5 segundo) aberto nautilus, pressionar médio (0,5 a 1 segundo) aberto vlcpara reproduzir o vídeo, pressionar longo (1 a 2 segundos) aberto gnome-terminale pressione timeout (2 segundos) aberto gedit.

insira a descrição da imagem aqui

Carreguei o código completo (apenas um arquivo) aqui .

[ATUALIZAR novamente]

[1] Adicionado fluxo de múltiplas chaves e notify-sendfalha corrigida por define DBUS_SESSION_BUS_ADDRESS. [2] Adicionado XDG_CURRENT_DESKTOPe GNOME_DESKTOP_SESSION_IDpara garantir que o konsole use o gnome theme gui (altere-o se você não estiver usando o gnome).

Eu atualizei meu código aqui .

Observe que este código não trata do fluxo de teclas combinadas, por exemplo, Ctrl+ t.

ATUALIZAR:

Existem várias interfaces de dispositivo nas quais a sequência de entradas / dev / input / by-path / XXX-eventN é aleatória. Então, altero o comando da /etc/gdm3/PostLogin/Defaultseguinte forma ( Chesené o meu nome de teclado, no seu caso, você deve alterá- lo para 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 &

Você pode tentar a extração eventN em 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 
$ 

Neste exemplo acima, apenas sudo cat /dev/input/event7imprimirá uma saída bizarra quando clicar nos 12 dígitos do mouse Razer, que possui o padrão "sysrq kbd leds event7" para usar grep -P '^(?=.*sysrq)(?=.*leds)'acima (seu padrão pode variar). sudo cat /dev/input/event6imprimirá uma saída bizarra apenas quando clicar na tecla central para cima / baixo. Enquanto sudo cat /dev/input/event5imprimirá uma saída bizarra quando mover o mouse e rolar a roda.

[Atualização: Suporte ao cabo do teclado de recarga para recarregar o programa]

O seguinte deve ser uma auto-explicação:

$ 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 &
林果 皞
fonte
Presumo que esse método exija que uma janela de terminal esteja em foco ao pressionar as teclas? existe uma maneira de contornar isso?
Kanoko
@kanoko Atualizei a solução.
林果皞
thx, eu realmente aprecio o esforço que você coloca nisso. Eu vou tentar isso. você acha que esta solução terá um impacto notável no uso da CPU se eu a configurar com 12 teclas de atalho diferentes?
kanoko
@kanoko Atualizei o código novamente para brincar com várias teclas. IMHO eu não acho que é um impacto perceptível na CPU porque 10+ se-else é muito sutil, e só executa a verificação após a leitura (fd, ev, sizeof (struct input_event) * 64); , ou seja, ele apenas executa o if-elsepressionamento de todas as teclas, enquanto eu também adicionei if (currCode >= 59) && (currCode <= 81)para limitar o intervalo antes if-else.
林果皞
1
você é incrível!!! Muito obrigado por toda a sua ajuda. Se você tiver a chance de tentar fazer isso com um mouse MMO numpad como o Razer Naga, eu juro que isso mudará sua vida. Posso mostrar meus principais mapeamentos, se você estiver interessado.
kanoko
1

Você pode encontrar uma ferramenta que funcione com um conjunto específico de programas, mas não haverá ferramenta utilizável globalmente porque o comportamento relacionado ao tempo é feito nos aplicativos em X, e não no sistema de janelas.

Thomas Dickey
fonte
0

você checou o Xmodmap?

O xmodmap é um utilitário para modificar mapas de teclas e mapeamentos de botões de ponteiro no Xorg

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

Kamaraj
fonte
2
Mas ele não sabe sobre atrasos de tempo :-)
Thomas Dickey