如何根据您按住键的时间重新映射键盘键


9

我想重新映射数字键盘上的键,以便它们的行为取决于按下键的时间。这是一个例子:

如果我按住Numpad 9键的时间少于300ms,它将发送“ previous tab”键盘命令Ctrl+Tab

如果我按住Numpad 9键300-599ms,它将发送“新标签”键盘命令Ctrl+T

如果我按住Numpad 9键600-899毫秒,它将发送“关闭标签页/窗口”键盘命令Ctrl+W

如果按住Numpad 9键的时间超过899ms,万一我错过了想要的时间范围,它什么也不会做。

在Windows上,我可以使用AutoHotKey来执行此操作,而在OS XI上,可以通过ControllerMate来执行此操作,但是我在UNIX / Linux上找不到可以根据保持键的时间重新映射键的工具。

如果您知道可以解决我的问题的工具,请确保提供一个脚本或代码示例,以证明我上述的条件键保持持续时间行为。它不一定是完整的代码来解决我的示例,但是对于我来说,将其重新用作示例就足够了。


这是一件很奇怪的事情。您打算如何计时您的600毫秒压力机?:D +1表示疯狂的想法。
通配符

只是为了增加生活趣味,您应该添加一个从347到350 ms的时间窗口,这将强制关闭计算机。;)
通配符

@Wildcard我实际上是在Razer Naga上使用数字键盘的,当我第一次在Windows上通过AutoHotKey实现此想法时,我使用了300-400ms的时间窗口,但是现在我已经使用该系统一段时间了,所以我使用时间窗口相隔约200毫秒,我可以获得大约99%的时间所需的时间窗口。这与您与莫尔斯电码进行通信的方式非常相似。
kanoko '16

Answers:


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

示范:

数字键5,短按打开新选项卡,中按打开gedit,长按打开gnome-terminal。

在此处输入图片说明

这不是直接适用于gnome桌面管理器的任何窗口,但我认为它应该使您知道如何(很难)实现它。它也可以在虚拟控制台(Ctrl + Alt + N)中运行,并且可以在某些终端仿真器(例如konsole,gnome-terminal,xterm)中运行。

p / s:我不是交流程序员,所以如果这段代码没有经过优化,请原谅我。

[更新]

前面的答案仅在shell中有效并且需要关注,因此我认为解析/ 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在gdm启动时以root身份运行此命令以运行此命令(/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。手动运行没问题。

示范:

数字键5,即时按下(<= 0.2秒)将被忽略,短按(0.2到0.5秒)打开nautilus,中按(0.5到1秒)打开vlc播放视频,长按(1-2秒)打开gnome-terminal,然后按超时(2秒)打开gedit

在此处输入图片说明

我在这里上传了完整的代码(只有一个文件)

[再次更新]

[1]添加了多个键流,并notify-send通过define 修复了失败DBUS_SESSION_BUS_ADDRESS。[2]已添加XDG_CURRENT_DESKTOPGNOME_DESKTOP_SESSION_ID确保konsole使用gnome主题gui(如果不使用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 &

您可以尝试从中提取eventN摘录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单击Razer鼠标上的12位数字grep -P '^(?=.*sysrq)(?=.*leds)'(上面使用的模式为“ sysrq kbd leds event7”)时,仅会输出奇异的输出(您的模式可能会有所不同)。sudo cat /dev/input/event6仅当单击中间的上/下键时,才会打印出奇怪的输出。sudo cat /dev/input/event5当移动鼠标并滚动滚轮时,While 将输出奇异的输出。

[更新:支持重新插入键盘电缆以重新加载程序]

以下是不言自明的:

$ 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 '16

@kanoko我已经更新了解决方案。
水果

谢谢,我非常感谢您为此付出的努力。我会尝试的。如果我使用12个不同的热键进行设置,您认为该解决方案是否会对cpu的使用产生显着影响?
kanoko

@kanoko我再次更新了代码以使用多个键进行操作。恕我直言,我不认为这对cpu有明显影响,因为10+ if-else太微妙了,它只在read(fd,ev,sizeof(struct input_event)* 64)之后才运行检查。语句,即它只运行if-else每一次按键,而我之前也添加if (currCode >= 59) && (currCode <= 81)了限制范围if-else
水果

1
你真了不起!非常感谢您的所有帮助。如果您有机会尝试使用Razer Naga这样的MMO数字键盘鼠标,我发誓它将改变您的生活。如果您有兴趣,我可以向您显示我的密钥映射。
kanoko '16

1

您可能会找到一个可以与特定程序集配合使用的工具,但是将没有全局可用的工具,因为与时间相关的行为是在X的应用程序中完成的,而不是由窗口系统完成的。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.