1. 找到键盘设备
linux的键盘设备在 /dev/input/eventX 中,通过 ls /dev/input 通常会看到很多个eventX
那么哪个才是键盘?继续使用指令 cat /proc/bus/input/devices 查看各个设备的描述,通过关键字 keyboard 可以定位到 event1
2. 读取按键
按照linux “一切皆文件” 的特点,读取按键只需三步,open、read和解析,特别注意的是读取的数据为 struct input_event 结构
#include <linux/input.h>
... ...
struct input_event key_info;
... ...
if (read(fd, &key_info, sizeof(struct input_event)) > 0)
{
//这是按键事件
if (key_info.type == EV_KEY)
//是哪个按键 及 按键状态
printf("按键: %d 状态: %d \r\n", key_info.code, key_info.value);
}
- key_info.code 是按键对应的标号,比如数字键1对应标号为2,具体对应可以在 linux/input.h 中查看(下面给出一段截图),或者把要用的按键都按一遍打印出来就知道了;
- key_info.value 是按键状态,只有三种数值,0/松开 1/按下 2/按住(按住不放会连续触发这个事件);特别注意的是,当已经有按键(假设按键a)处于 2/按住 状态时,再去按别的按键(假设按键b),会先读到按键b的 1/按下 事件,而按键a的 2/按住 事件将不再触发,如果继续按住b不放会连续触发按键b的 2/按住 事件,而按键a只会在松开时触发 0/松开 事件;如果有更多的按键同时操作,逻辑同上。
3. 代码示例
鉴于前面提到的多按键同时按下会掩盖掉前面按键的 2/按住 事件问题,以下代码对 2/按住 事件进行了自行管理,可自行定义最大同时按下按键数和连续触发间隔。
- key.h
#ifndef _KEY_H_
#define _KEY_H_
/*
* 如何确认键盘在"/dev/input/event"几号?
* 通过"cat /proc/bus/input/devices"可以看到"keyboard"所在的event号
*/
#define INPUT_DEV_PATH "/dev/input/event1"
/*
* 按键回调注册
* 参数:
* obj: 用户私有指针,会在互调的时候传回给用户
* callback: 回调函数原型 void callback(void *obj, int key, int type)
* 回调函数参数:
* obj: 前面传入的用户私有指针
* key: 键位,可以看头文件<linux/input.h>中的定义,或者先测试打印一遍就知道哪个按键对哪个值了
* type: 按键状态,0/松开时,1/按下时,2/一直按住(会反复触发回调,间隔多久我也忘了)
* 返回: 0/成功 -1/失败,找不到设备或者没有sudo运行
*/
int key_register(void *obj, void (*callback)(void *, int, int));
#endif
- key.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <linux/input.h>
#include "key.h"
//支持最大同按下的按键数量
#define KEY_COMBINATION 10
//长按按键时会反复触发事件,这里设置时间间隔ms,为0时不触发长按事件
#define KEY_HOLD_EVENT_INTERVALMS 50
//本地主结构体
typedef struct
{
int fd;
void *obj;
void (*callback)(void *, int, int);
} Key_Struct;
//回调线程传入参数结构体(就是把一堆参数打包成一个好传递)
typedef struct
{
void *obj;
int key;
int type;
void (*callback)(void *, int, int);
//数组指针
int *combin;
} Key_Param;
//延时工具
#include <sys/time.h>
void key_delayms(unsigned int ms)
{
struct timeval tv;
tv.tv_sec = ms / 1000;
tv.tv_usec = ms % 1000 * 1000;
select(0, NULL, NULL, NULL, &tv);
}
//抛线程工具
static void throwOut_thread(void *obj, void (*callback)(void *))
{
pthread_t th;
pthread_attr_t attr;
//attr init
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //禁用线程同步, 线程运行结束后自动释放
//抛出线程
pthread_create(&th, &attr, (void *)callback, (void *)obj);
//attr destroy
pthread_attr_destroy(&attr);
}
//回调线程,在这里回调用户传入的callback函数
static void key_callback(void *argv)
{
Key_Param *kp = (Key_Param *)argv;
do {
//按键事件回调
if (kp->callback)
kp->callback(kp->obj, kp->key, kp->type);
//该值为0时,不触发长按事件
if (KEY_HOLD_EVENT_INTERVALMS > 0) {
//按下事件,经过2倍延时后切换为长按事件
if (kp->type == 1) {
key_delayms(KEY_HOLD_EVENT_INTERVALMS * 2);
kp->type = 2;
}
//长按事件
else if (kp->type == 2)
key_delayms(KEY_HOLD_EVENT_INTERVALMS);
}
//周期触发长按事件
} while (kp->type == 2 && kp->combin[0] == kp->key);
free(kp);
}
//数组元素的设置和清除,返回位置
static int _arrayAdd(int *array, int len, int value)
{
int i;
for (i = 0; i < len; i++) {
if (array[i] == 0) {
array[i] = value;
return i;
}
}
return 0;
}
static int _arrayClear(int *array, int len, int value)
{
int i;
for (i = 0; i < len; i++) {
if (array[i] == value) {
array[i] = 0;
return i;
}
}
return 0;
}
static void key_thread(void *argv)
{
Key_Struct *ks = (Key_Struct *)argv;
Key_Param *kp;
struct input_event key_info;
int order;
int combin[KEY_COMBINATION] = {0};
while (1)
{
//阻塞读
if (read(ks->fd, &key_info, sizeof(struct input_event)) > 0)
{
//这是按键类事件(触屏类事件也是这样读的)
if (key_info.type == EV_KEY)
{
if (!ks->callback || key_info.value > 1)
continue;
//按键按下时注册到数组,释放时清除
order = 0;
if (key_info.value == 1)
order = _arrayAdd(combin, KEY_COMBINATION, key_info.code);
else
_arrayClear(combin, KEY_COMBINATION, key_info.code);
//参数准备
kp = (Key_Param *)calloc(1, sizeof(Key_Param));
kp->obj = ks->obj;
kp->key = key_info.code; //键位
kp->type = key_info.value; //键值
kp->callback = ks->callback;
kp->combin = &combin[order];
//抛线程,在异步线程中触发用户回调函数
throwOut_thread(kp, &key_callback);
}
}
}
}
/*
* 按键回调注册
* 参数:
* obj: 用户私有指针,会在互调的时候传回给用户
* callback: 回调函数原型 void callback(void *obj, int key, int type)
* 回调函数参数:
* obj: 前面传入的用户私有指针
* key: 键位值,可以看<linux/input.h>中的定义,或者先测试打印一遍就知道哪个按键对哪个值了
* type: 按键状态,0/松开时,1/按下时,2/一直按住(会反复触发回调)
* 返回: 0/成功 -1/失败,找不到设备或者没有sudo运行
*/
int key_register(void *obj, void (*callback)(void *, int, int))
{
Key_Struct *ks;
//关键参数检查
if (!callback)
return -1;
//只读打开键盘所在input设备
int fd = open(INPUT_DEV_PATH, O_RDONLY);
if (fd < 1)
{
printf("key_register: open %s failed\r\n", INPUT_DEV_PATH);
return -1;
}
//参数备份,抛线程检测按键
ks = (Key_Struct *)calloc(1, sizeof(Key_Struct));
ks->fd = fd;
ks->obj = obj;
ks->callback = callback;
throwOut_thread(ks, &key_thread);
return 0;
}
- main.c
#include <stdio.h>
#include <unistd.h>
#include "key.h"
void key_callback(void *obj, int key, int type)
{
printf("key/%d type/%d\r\n", key, type);
}
int main(void)
{
key_register(NULL, &key_callback);
while(1)
sleep(1);
return 0;
}
- 编译: gcc -o out main.c key.c -lpthread
- 运行: sudo ./out (由于打开 /dev/input/event1 需要管理员权限)
推荐文章
https://blog.csdn.net/lanmanck/article/details/8423669