Как переназначить клавиши клавиатуры в зависимости от того, как долго вы держите клавишу


9

Я хотел бы переназначить клавиши на моей цифровой клавиатуре, чтобы они вели себя по-разному в зависимости от того, как долго нажата клавиша. Вот пример:

Если удерживать клавишу Numpad 9 нажатой менее 300 мс, она отправит команду «предыдущая вкладка» Ctrl+Tab

Если я удержу клавишу Numpad 9 в течение 300-599 мс, она отправит команду «новая вкладка» Ctrl+T

Если я удержу клавишу Numpad 9 в течение 600-899 мс, она отправит команду «закрыть вкладку / окно» Ctrl+W

Если я удерживаю клавишу Numpad 9 нажатой более 899 мс, это ничего не даст, если я пропустил желаемое временное окно.

В Windows я мог сделать это с AutoHotKey, а в OS XI мог сделать это с ControllerMate, но я не могу найти инструмент в UNIX / Linux, который позволяет переназначать ключи в зависимости от того, как долго удерживается ключ.

Если вам известно об инструменте, который может решить мою проблему, обязательно предоставьте сценарий или пример кода, который демонстрирует поведение времени удержания условной клавиши, которое я описал выше. Это не должен быть полный код, чтобы решить мой пример, но этого должно быть достаточно для меня, чтобы повторно использовать его для моего примера.


Это такая странная вещь. Как ты собираешься рассчитывать свою 600-миллисекундную печать? : D +1 за безумную идею.
Wildcard

Просто для того, чтобы добавить немного остроты в вашу жизнь, вы должны добавить временное окно от 347 до 350 мс, которое принудительно выключит ваш компьютер. ;)
Wildcard

@Wildcard Я фактически использую цифровую клавиатуру на своем Razer Naga для этого, и когда я впервые реализовал идею с AutoHotKey в Windows, я использовал временные окна 300-400 мс, но теперь, когда я некоторое время использую эту систему, я использую временные окна примерно на расстоянии 200 мс, и я могу получить желаемое временное окно примерно в 99% случаев. Это очень похоже на то, как вы общаетесь с азбукой Морзе.
Каноко

Ответы:


7

Я просто написал это в 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; 

}

Используйте, showkey -aчтобы получить код привязки:

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$ 

Поместите код привязки 5 и его команду (например, run /tmp/.a.out) в ~ / .bashrc:

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

Обратите внимание, что соответствующий код ключа также должен быть изменен в исходном коде (шестнадцатеричное значение также может быть получено sudo showkey -aсверху):

int c = 0x35;

Скомпилируйте с (выходные данные /tmp/a.outв моем примере):

cc filename.c -lcurses

Демонстрация:

Numpad 5, короткое нажатие, открытие новой вкладки, среднее нажатие, открытие, gedit, и долгое нажатие, открытие gnome-терминала.

введите описание изображения здесь

Это не относится непосредственно к любому окну в gnome desktop manager, но я думаю, что оно должно дать вам некоторое представление о том, как (сложно) реализовать его. Он также работает в виртуальной консоли (Ctrl + Alt + N) и работает в некотором эмуляторе терминала (например, konsole, gnome-terminal, xterm).

p / s: я не программист, так что простите, если этот код не оптимизирован.

[ОБНОВИТЬ]

Предыдущий ответ работает только в оболочке и требует фокуса, поэтому я думаю, что разбор / dev / input / eventX - это решение для работы во всей X сессии.

Я не хочу изобретать велосипед. Я играю с evtestутилитой и модифицирую нижнюю часть evtest.c своим собственным кодом:

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

Обратите внимание, что вы должны изменить имя пользователя ( xiaobai - мое имя пользователя). Кроме того, if ( (ev[i].code == 76) ) {это мой код ключа Numpad 5, вам может потребоваться вручную распечатать ev [i] .code для двойного подтверждения. И, конечно, вы должны изменить путь к видео тоже :)

Скомпилируйте и протестируйте его непосредственно с помощью (`` часть для того, чтобы получить правильное значение /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}')" ` &

Обратите внимание, что /by-id/это не работает в Fedora 24, поэтому я изменил его на / by-path /. Кали нет такой проблемы.

Мой менеджер рабочего стола gdm3:

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

Итак, я вставил эту строку /etc/gdm3/PostLogin/Defaultдля запуска этой команды от имени root при запуске gdm ( /etc/X11/Xsession.d/*не работает):

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

По неизвестной причине / etc/gdm/PostLogin/Defaultне работает на Fedora 24 'gdm, который выдает " Отказано в доступе " при проверке /tmp/l_gdmEжурнала. Вручную запустить без проблем, хотя.

Демонстрация:

Numpad 5, мгновенное нажатие (<= 0,2 секунды) будет проигнорировано, короткое нажатие (от 0,2 до 0,5 секунды) открыто nautilus, среднее нажатие (от 0,5 до 1 секунды) открыто vlcдля воспроизведения видео, длительное нажатие (от 1 до 2 секунд) открыть gnome-terminal, и тайм-аут (2 секунды) открыть gedit.

введите описание изображения здесь

Я загрузил полный код (только один файл) здесь .

[ОБНОВЛЕНИЕ снова]

[1] Добавлен поток нескольких ключей и исправлена notify-sendошибка с определением DBUS_SESSION_BUS_ADDRESS. [2] Добавлено XDG_CURRENT_DESKTOPи GNOME_DESKTOP_SESSION_IDдля обеспечения того, чтобы konsole использовала графический интерфейс темы gnome (измените его, если вы не используете gnome).

Я обновил свой код здесь .

Обратите внимание, что этот код не обрабатывает поток комбинационных клавиш, например, Ctrl+ t.

ОБНОВИТЬ:

Существует несколько интерфейсов устройств, в которых последовательность записей / dev / input / by-path / XXX-eventN является случайной. Поэтому я изменил команду, /etc/gdm3/PostLogin/Defaultкак показано ниже ( Chesenэто имя моей клавиатуры, для вашего случая, вы должны изменить его 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 &

Вы можете попробовать извлечь событие из 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 
$ 

В приведенном выше примере sudo cat /dev/input/event7выводятся причудливые выходные данные только при щелчке 12 цифр на мыши Razer, в которой используется шаблон «sysrq kbd leds event7», который можно использовать grep -P '^(?=.*sysrq)(?=.*leds)'выше (ваш шаблон может отличаться). sudo cat /dev/input/event6будет выводить причудливый вывод только при нажатии средней клавиши вверх / вниз. Хотя sudo cat /dev/input/event5будет выводить причудливый вывод при перемещении мыши и прокрутке колеса.

[Обновление: Поддержка Replug клавиатуры кабеля для перезагрузки программы]

Следующее должно быть самоочевидным:

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

я предполагаю, что этот метод требует, чтобы окно терминала было в фокусе во время нажатия клавиш? Есть ли способ обойти это?
Каноко

@kanoko Я обновил решение.
Фрукт

спасибо, я очень ценю ваши усилия. Я попробую это. Как вы думаете, это решение окажет заметное влияние на использование процессора, если я настрою его с помощью 12 различных горячих клавиш?
Каноко

@kanoko Я снова обновил код, чтобы поиграть с несколькими клавишами. ИМХО, я не думаю, что это заметно влияет на процессор, потому что 10+ if-else слишком тонкие, и он запускает проверку только после чтения (fd, ev, sizeof (struct input_event) * 64); оператор, то есть он запускает только if-elseкаждое нажатие клавиши, в то время как я также добавил, if (currCode >= 59) && (currCode <= 81)чтобы ограничить диапазон раньше if-else.
Фрукт

1
ты великолепен!!! Большое спасибо за вашу помощь. если у вас когда-нибудь будет возможность попробовать это с помощью MMO-мыши numpad, такой как Razer Naga, я клянусь, это изменит вашу жизнь. Я могу показать вам свои раскладки клавиш, если вам интересно.
Каноко

1

Вы можете найти инструмент, который работает с определенным набором программ, но не будет никакого глобального инструмента, потому что поведение, связанное со временем, выполняется в приложениях в X, а не в оконной системе.


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.