linux以C方式和内核交互监听键盘[香橙派搞机日记]

最近在深入研究我的香橙派,不可避免的遇到了怎么认识和使用Linux内核的问题。 我给自己留了一个简单的任务:使用原生C来监听内核,实现读取键盘的消息。

CSDN上也有其他文章来解决这个问题,不过要么是技术不达标(直接和终端交互显然不是我们想要的,这只是一个取巧的方案),要么是收费文章🥲 。楼主也是查阅了很多资料才解决了这个问题。如果这个方案解决了你的问题,还请你点赞,评论,收藏。您的支持是我的动力

如果你要学习知识,那可以从头读下去,如果你要快速使用,可以从 evtest 这个部分开始读,源代码贴在后面

接口在哪里?

在linux中,一切都是文件。哪怕你是一个物理设备,在内核中,你也得有个文件接口!

两个关键文件夹

/dev/input/

所有的设备,都是在dev 目录下统一管理的。让我们看看
/dev/input/

bash 复制代码
orangepi@orangepi3b:/dev/input$ ls
by-id	 event1   event12  event15  event18  event4  event7
by-path  event10  event13  event16  event2   event5  event8
event0	 event11  event14  event17  event3   event6  event9
orangepi@orangepi3b:/dev/input$ ls -l
total 0
drwxr-xr-x 2 root root     220 Apr 11 11:34 by-id
drwxr-xr-x 2 root root     280 Apr 11 11:34 by-path
crw-rw---- 1 root input 13, 64 Apr  6 12:37 event0
crw-rw---- 1 root input 13, 65 Apr  6 12:37 event1
crw-rw---- 1 root input 13, 74 Apr 11 11:34 event10
crw-rw---- 1 root input 13, 75 Apr 11 11:34 event11
crw-rw---- 1 root input 13, 76 Apr 11 11:34 event12
crw-rw---- 1 root input 13, 77 Apr 11 11:34 event13
crw-rw---- 1 root input 13, 78 Apr 11 11:34 event14
crw-rw---- 1 root input 13, 79 Apr 11 11:34 event15
crw-rw---- 1 root input 13, 80 Apr 11 11:34 event16
crw-rw---- 1 root input 13, 81 Apr 11 11:34 event17
crw-rw---- 1 root input 13, 82 Apr 11 11:34 event18
crw-rw---- 1 root input 13, 66 Apr  6 12:37 event2
crw-rw---- 1 root input 13, 67 Apr 11 11:34 event3
crw-rw---- 1 root input 13, 68 Apr 11 11:34 event4
crw-rw---- 1 root input 13, 69 Apr 11 11:34 event5
crw-rw---- 1 root input 13, 70 Apr 11 11:34 event6
crw-rw---- 1 root input 13, 71 Apr 11 11:34 event7
crw-rw---- 1 root input 13, 72 Apr 11 11:34 event8
crw-rw---- 1 root input 13, 73 Apr 11 11:34 event9

每个event代表一个事件,那么,怎么知道事件和设备的对应呢? 这要借助/proc/bus

(这个部分的知识点参考了蓝天居士的博客,他是一位水平非常高的专家,大家可以学习一下)

/proc/bus/input/devices

让我们转到这个目录

bash 复制代码
orangepi@orangepi3b:/proc$ cd bus
orangepi@orangepi3b:/proc/bus$ ls
input  pci
orangepi@orangepi3b:/proc/bus$ cd input
orangepi@orangepi3b:/proc/bus/input$ ls
devices  handlers
orangepi@orangepi3b:/proc/bus/input$ cd devices
bash: cd: devices: Not a directory
orangepi@orangepi3b:/proc/bus/input$ cd devices 
bash: cd: devices: Not a directory
orangepi@orangepi3b:/proc/bus/input$ tree
.
├── devices
└── handlers

0 directories, 2 files

看看设备信息,信息比较长。我们要拆开来看

event0是什么
bash 复制代码
orangepi@orangepi3b:/proc/bus/input$ cat devices 
I: Bus=0019 Vendor=0001 Product=0001 Version=0100
N: Name="hdmi_cec_key"
P: Phys=hdmi_cec_key/input0
S: Sysfs=/devices/platform/fe0a0000.hdmi/dw-hdmi-cec.1.auto/input/input0
U: Uniq=
H: Handlers=event0 
B: PROP=0
B: EV=3
B: KEY=10000000000000 0

I: Bus=0019 Vendor=0001 Product=0001 Version=0100 这个信息:设备挂在总线19下,厂商ID(vendor=0001) 产品ID(Product=0001)

产品版本Version=0100

N: Name="hdmi_cec_key" 标识设备名称和用途

P: Phys=hdmi_cec_key/input0 设备的物理路径,标识设备在系统中的物理位置

S: Sysfs=/devices/platform/fe0a0000.hdmi/dw-hdmi-cec.1.auto/input/input0 设备早sysfs 文件系统中的路径,用于内核和用户空间交互

U: Uniq= 设备唯一标识符号,这里为空

H: Handlers=event0 备对应的事件处理程序是event0 标识该设备的输入事件通过events0暴露 这个信息是关键信息

B: PROP=0 设备属性标识,这里为0 标识无特殊属性

B: EV=3 标识设备支持的事件类型

B: KEY=10000000000000 0 标识设备支持的按键键值


鼠标

可见,一个设备可以有多个输入

bash 复制代码
I: Bus=0003 Vendor=046d Product=c08b Version=0111
N: Name="Logitech G502 HERO Gaming Mouse"
P: Phys=usb-fd800000.usb-1.1/input0
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.1/1-1.1:1.0/0003:046D:C08B.0001/input/input3
U: Uniq=0470323E3232
H: Handlers=event3 
B: PROP=0
B: EV=17
B: KEY=ffff0000 0 0 0 0
B: REL=1943
B: MSC=10

I: Bus=0003 Vendor=046d Product=c08b Version=0111
N: Name="Logitech G502 HERO Gaming Mouse Keyboard"
P: Phys=usb-fd800000.usb-1.1/input1
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.1/1-1.1:1.1/0003:046D:C08B.0002/input/input4
U: Uniq=0470323E3232
H: Handlers=sysrq kbd event4 
B: PROP=0
B: EV=100013
B: KEY=1000000000007 ff800000000007ff febeffdfffefffff fffffffffffffffe
B: MSC=10

I: Bus=0003 Vendor=046d Product=c08b Version=0111
N: Name="Logitech G502 HERO Gaming Mouse Consumer Control"
P: Phys=usb-fd800000.usb-1.1/input1
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.1/1-1.1:1.1/0003:046D:C08B.0002/input/input5
U: Uniq=0470323E3232
H: Handlers=kbd event5 
B: PROP=0
B: EV=1f
B: KEY=306ff 0 0 483ffff17aff32d bfd4444600000000 1 130ff38b17c000 677bfad9415fed 9ed68000004400 10000002
B: REL=1040
B: ABS=100000000
B: MSC=10

I: Bus=0003 Vendor=046d Product=c08b Version=0111
N: Name="Logitech G502 HERO Gaming Mouse System Control"
P: Phys=usb-fd800000.usb-1.1/input1
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.1/1-1.1:1.1/0003:046D:C08B.0002/input/input6
U: Uniq=0470323E3232
H: Handlers=kbd event6 
B: PROP=0
B: EV=13
B: KEY=c000 10000000000000 0
B: MSC=10
键盘
bash 复制代码
I: Bus=0003 Vendor=320f Product=5055 Version=0110
N: Name="VXE V87 VXE V87"
P: Phys=usb-fd800000.usb-1.3/input0
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.3/1-1.3:1.0/0003:320F:5055.0003/input/input9
U: Uniq=2020-12-15
H: Handlers=sysrq kbd leds event7 
B: PROP=0
B: EV=120013
B: KEY=1000000000007 ff9f207ac14057ff febeffdfffefffff fffffffffffffffe
B: MSC=10
B: LED=1f


I: Bus=0003 Vendor=320f Product=5055 Version=0110
N: Name="VXE V87 VXE V87 Keyboard"
P: Phys=usb-fd800000.usb-1.3/input1
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.3/1-1.3:1.1/0003:320F:5055.0004/input/input10
U: Uniq=2020-12-15
H: Handlers=kbd event8 
B: PROP=0
B: EV=100013
B: KEY=ff80000000000000 80b0ffcd01cfffff febffbffdffffffe
B: MSC=10

I: Bus=0003 Vendor=320f Product=5055 Version=0110
N: Name="VXE V87 VXE V87 System Control"
P: Phys=usb-fd800000.usb-1.3/input1
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.3/1-1.3:1.1/0003:320F:5055.0004/input/input11
U: Uniq=2020-12-15
H: Handlers=kbd event9 
B: PROP=0
B: EV=13
B: KEY=c000 10000000000000 0
B: MSC=10

I: Bus=0003 Vendor=320f Product=5055 Version=0110
N: Name="VXE V87 VXE V87 Consumer Control"
P: Phys=usb-fd800000.usb-1.3/input1
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.3/1-1.3:1.1/0003:320F:5055.0004/input/input12
U: Uniq=2020-12-15
H: Handlers=kbd event10 
B: PROP=0
B: EV=1f
B: KEY=3f000307ff 0 0 483ffff17aff32d bfd4444600000000 1 130ff38b17d000 677bfad9415fed 19ed68000004400 10000002
B: REL=1040
B: ABS=100000000
B: MSC=10

I: Bus=0003 Vendor=320f Product=5055 Version=0110
N: Name="VXE V87 VXE V87"
P: Phys=usb-fd800000.usb-1.3/input1
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.3/1-1.3:1.1/0003:320F:5055.0004/input/input13
U: Uniq=2020-12-15
H: Handlers=event11 
B: PROP=0
B: EV=9
B: ABS=10000000000

I: Bus=0003 Vendor=320f Product=5055 Version=0110
N: Name="VXE V87 VXE V87 Mouse"
P: Phys=usb-fd800000.usb-1.3/input1
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.3/1-1.3:1.1/0003:320F:5055.0004/input/input14
U: Uniq=2020-12-15
H: Handlers=event12 
B: PROP=0
B: EV=17
B: KEY=1f0000 0 0 0 0
B: REL=1943
B: MSC=10

I: Bus=0003 Vendor=320f Product=5088 Version=0111
N: Name="Telink VXE V87 2.4G Dongle"
P: Phys=usb-fd800000.usb-1.4/input0
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.4/1-1.4:1.0/0003:320F:5088.0007/input/input21
U: Uniq=
H: Handlers=sysrq kbd leds event13 
B: PROP=0
B: EV=120013
B: KEY=1000000000007 ff9f207ac14057ff febeffdfffefffff fffffffffffffffe
B: MSC=10
B: LED=1f

I: Bus=0003 Vendor=320f Product=5088 Version=0111
N: Name="Telink VXE V87 2.4G Dongle Keyboard"
P: Phys=usb-fd800000.usb-1.4/input1
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.4/1-1.4:1.1/0003:320F:5088.0008/input/input22
U: Uniq=
H: Handlers=kbd event14 
B: PROP=0
B: EV=100013
B: KEY=ff80000000000000 80b0ffcd01cfffff febffbffdffffffe
B: MSC=10

I: Bus=0003 Vendor=320f Product=5088 Version=0111
N: Name="Telink VXE V87 2.4G Dongle System Control"
P: Phys=usb-fd800000.usb-1.4/input1
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.4/1-1.4:1.1/0003:320F:5088.0008/input/input23
U: Uniq=
H: Handlers=kbd event15 
B: PROP=0
B: EV=13
B: KEY=c000 10000000000000 0
B: MSC=10

I: Bus=0003 Vendor=320f Product=5088 Version=0111
N: Name="Telink VXE V87 2.4G Dongle Consumer Control"
P: Phys=usb-fd800000.usb-1.4/input1
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.4/1-1.4:1.1/0003:320F:5088.0008/input/input24
U: Uniq=
H: Handlers=kbd event16 
B: PROP=0
B: EV=1f
B: KEY=3f000307ff 0 0 483ffff17aff32d bfd4444600000000 1 130ff38b17d000 677bfad9415fed 19ed68000004400 10000002
B: REL=1040
B: ABS=100000000
B: MSC=10

I: Bus=0003 Vendor=320f Product=5088 Version=0111
N: Name="Telink VXE V87 2.4G Dongle"
P: Phys=usb-fd800000.usb-1.4/input1
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.4/1-1.4:1.1/0003:320F:5088.0008/input/input25
U: Uniq=
H: Handlers=event17 
B: PROP=0
B: EV=9
B: ABS=10000000000

I: Bus=0003 Vendor=320f Product=5088 Version=0111
N: Name="Telink VXE V87 2.4G Dongle Mouse"
P: Phys=usb-fd800000.usb-1.4/input1
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.4/1-1.4:1.1/0003:320F:5088.0008/input/input26
U: Uniq=
H: Handlers=event18 
B: PROP=0
B: EV=17
B: KEY=1f0000 0 0 0 0
B: REL=1943
B: MSC=10

看起来有些复杂。不过我们很容易看出event* 和设备的对应关系。你觉得有些麻烦? 已经有人为我们制作了工具!

使用evtest来测试你的输入设备

bash 复制代码
orangepi@orangepi3b:~/bop_code$ sudo apt install evtest
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
evtest is already the newest version (1:1.34-1).
0 upgraded, 0 newly installed, 0 to remove and 1 not upgraded.
扫描设备
bash 复制代码
orangepi@orangepi3b:~/bop_code$ sudo evtest
No device specified, trying to scan all of /dev/input/event*
Available devices:
/dev/input/event0:      hdmi_cec_key
/dev/input/event1:      rk805 pwrkey
/dev/input/event2:      rockchip-rk809 Headset
/dev/input/event3:      Logitech G502 HERO Gaming Mouse
/dev/input/event4:      Logitech G502 HERO Gaming Mouse Keyboard
/dev/input/event5:      Logitech G502 HERO Gaming Mouse Consumer Control
/dev/input/event6:      Logitech G502 HERO Gaming Mouse System Control
/dev/input/event7:      VXE V87 VXE V87
/dev/input/event8:      VXE V87 VXE V87 Keyboard
/dev/input/event9:      VXE V87 VXE V87 System Control
/dev/input/event10:     VXE V87 VXE V87 Consumer Control
/dev/input/event11:     VXE V87 VXE V87
/dev/input/event12:     VXE V87 VXE V87 Mouse
/dev/input/event13:     Telink VXE V87 2.4G Dongle
/dev/input/event14:     Telink VXE V87 2.4G Dongle Keyboard
/dev/input/event15:     Telink VXE V87 2.4G Dongle System Control
/dev/input/event16:     Telink VXE V87 2.4G Dongle Consumer Control
/dev/input/event17:     Telink VXE V87 2.4G Dongle
/dev/input/event18:     Telink VXE V87 2.4G Dongle Mouse
选取并监听

这里以键盘为例子

bash 复制代码
Select the device event number [0-18]: 7
Input driver version is 1.0.1
Input device ID: bus 0x3 vendor 0x320f product 0x5055 version 0x110
Input device name: "VXE V87 VXE V87"
Supported events:
  Event type 0 (EV_SYN)
  Event type 1 (EV_KEY)
    Event code 1 (KEY_ESC)
    Event code 2 (KEY_1)
    Event code 3 (KEY_2)
    Event code 4 (KEY_3)
    ....
    Event code 190 (KEY_F20)
    Event code 191 (KEY_F21)
    Event code 192 (KEY_F22)
    Event code 193 (KEY_F23)
    Event code 194 (KEY_F24)
    Event code 240 (KEY_UNKNOWN)
  Event type 4 (EV_MSC)
    Event code 4 (MSC_SCAN)
  Event type 17 (EV_LED)
    Event code 0 (LED_NUML) state 0
    Event code 1 (LED_CAPSL) state 0
    Event code 2 (LED_SCROLLL) state 0
    Event code 3 (LED_COMPOSE) state 0
    Event code 4 (LED_KANA) state 0
Key repeat handling:
  Repeat type 20 (EV_REP)
    Repeat code 0 (REP_DELAY)
      Value    250
    Repeat code 1 (REP_PERIOD)
      Value     33
Properties:
Testing ... (interrupt to exit)
Event: time 1744374523.592185, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70028
Event: time 1744374523.592185, type 1 (EV_KEY), code 28 (KEY_ENTER), value 0
Event: time 1744374523.592185, -------------- SYN_REPORT ------------
Event: time 1744374526.435125, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70013
Event: time 1744374526.435125, type 1 (EV_KEY), code 25 (KEY_P), value 1
Event: time 1744374536.039071, -------------- SYN_REPORT ------------
Event: time 1744374536.207057, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70006
Event: time 1744374536.207057, type 1 (EV_KEY), code 46 (KEY_C), value 1
Event: time 1744374536.207057, -------------- SYN_REPORT ------------
Event: time 1744374536.295063, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70006
Event: time 1744374536.295063, type 1 (EV_KEY), code 46 (KEY_C), value 0
Event: time 1744374536.295063, -------------- SYN_REPORT ------------

使用代码实现内核监听

我们在刚刚,已经获得了键盘输入在event7的信息

!!!注意! 你必须使用evtest来观察你的键盘的正确位置!!! 否则代码会失败!

下面按照这个来写,这是源代码

cpp 复制代码
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <linux/input.h>


int main(int argc, char **argv) {
    const char* devloc = "/dev/input/event7";
    int fd = open(devloc, O_RDONLY) ;

    if (fd == -1) {
        perror("无法打开输入设备");
        return 1;
    }
    struct input_event ev;


   while(true)
   {
        ssize_t n = read(fd , &ev ,sizeof(ev));

        if(n ==-1)
        {
            perror("this error  \n");
            break;
        }

        if(n!= sizeof(ev))
        {
            continue;
        }

        if(ev.type ==EV_KEY && ev.value ==1)
        {
            printf(" key!!!\n");
            switch ((ev.code))
            {
            case  KEY_P:
                printf("hello");
                break;
            case  KEY_K:
                printf(" keyboard");
                break;            
            default:
                break;
            }

        }


   }


    close(fd);
 
    return 0;
}

让我们把代码封装成类吧

cpp 复制代码
#include <iostream>
#include <thread>
#include <unordered_map>
#include <functional>


#include <linux/input.h>
#include <fcntl.h>  //open函数在这个库
#include <unistd.h> //read在这个库

class bop_keylistener
{
private:
    int fd;
    std::unordered_map<int, std::function<void()>> keyCallbacks;
    std::thread listenerThread;
    struct input_event ev;


    bool running = true;
    int millsec_wait ;
    void listenkey()
    {
        
        while(running)
        {
            ssize_t n = read(fd , &ev ,sizeof(ev));

            if(n ==-1)
            {
                std::cerr << "this event message  error  "<<std::endl;
            }
    
            if(n!= sizeof(ev))
            {
                continue;
            }
    
            if(ev.type ==EV_KEY && ev.value ==1)
            {
                for(const auto& pair :keyCallbacks)
                {
                    auto keyi=pair.first;
                    if(keyi == ev.code)
                    {
                        pair.second();
                    }
                }

                std::this_thread::sleep_for(std::chrono::milliseconds(millsec_wait));
    
            }

        }
        
    }
public:
    bop_keylistener(const char* devloc , int millsecwait = 50)
    {
        millsec_wait =millsecwait;

        fd = open(devloc, O_RDONLY) ;
        if (fd == -1) {
            perror("无法打开输入设备");
        }

        listenerThread = std::thread(&bop_keylistener::listenkey, this);
    }

    ~bop_keylistener()
    {
        running = false;
        if (listenerThread.joinable()) {
            listenerThread.join();
        }
    }

      // 注册按键和对应的回调函数
      void registerKeyCallback(int key, const std::function<void()>& callback) {
        keyCallbacks[key] = callback;
    }

};


// 示例回调函数
void exampleCallback() {
    std::cout << "Example callback executed!" << std::endl;
}

int main() {

    bop_keylistener mkeyl("/dev/input/event7" );
   

    // 注册按键 'A' 和对应的回调函数
    mkeyl.registerKeyCallback(KEY_1, exampleCallback);

    // 保持主线程运行
    std::this_thread::sleep_for(std::chrono::hours(1));

    return 0;
}

创作不易,还请多多支持

相关推荐
yangang18523 分钟前
linuxbash原理
linux·运维·服务器
小小毛桃26 分钟前
在Ubuntu系统中运行Windows程序
linux·windows·ubuntu
码农新猿类1 小时前
服务器本地搭建
linux·网络·c++
虔城散人1 小时前
C语言 |位域结构体
c语言
Susea&1 小时前
数据结构初阶:队列
c语言·开发语言·数据结构
小度爱学习1 小时前
linux中的执行命令格式及命令帮助
linux·运维·chrome
良许Linux2 小时前
嵌入式算吃青春饭么?
linux
良许Linux2 小时前
马上要毕业去工作了,做嵌入式软件开发工程师,但是完全不会编程怎么办?
linux
良许Linux2 小时前
学stm32,有什么学习方法?
linux
良许Linux2 小时前
为啥有好多人说 Arduino 是玩具?
linux