# 5. 输入设备适配

# 5.1 输入设备适配的基本步骤

在 AWTK 中,主循环(main_loop)主要负责维持事件分发和界面绘制这个不断循环的过程,而输入设备移植的本质就是捕获系统的触摸、鼠标、键盘等输入事件,再分发给 GUI 处理,各类输入设备移植的步骤基本相同,可总结为以下三步:

  1. 通过设备驱动获取相应的设备事件;
  2. 将设备事件转化为 AWTK 支持的事件;
  3. 向 GUI 主循环(main_loop)分发转化后的事件。

# 5.1.1 捕获系统输入事件

不同平台上捕获系统输入设备事件的方法都不一样,具体需要根据实际平台上的驱动实现,此处不做赘述。

# 5.1.2 AWTK 支持的输入事件

目前 AWTK 支持的输入设备事件详见下表,只要能将获取到的设备事件转化为下表中的事件,那么移植就没有太大问题,各个事件转化时需要设置的参数详见下文的示例教程:

设备事件类型 事件名称 常用设备
wheel_event_t 鼠标滚轮事件 鼠标
pointer_event_t 指针事件 鼠标、触摸屏
key_event_t 按键事件 键盘、特殊按键设备(比如3/5按键)
multi_gesture_event_t 多点触摸手势事件 支持多点触摸的触摸屏

以上设备事件包含的具体信息以及它们支持的事件类型详见:awtk/src/base/events.h。

# 5.1.3 分发输入设备事件

捕获到系统设备事件并将其转化为 AWTK 支持的事件后,可以调用 AWTK 提供的以下接口将事件分发到 GUI 的主循环中处理:

函数名称 说明 备注
main_loop_queue_event 分发事件请求 可分发 AWTK 中的任意事件,需提供事件请求结构体
main_loop_post_key_event 分发按键事件 需提供键值与按键状态
main_loop_post_pointer_event 分发指针事件 需提供指针坐标与指针状态
main_loop_post_multi_gesture_event 分发多点触摸事件 需提供多点触摸事件结构体

如果在非 GUI 线程中调用以上接口分发事件,那么必须参考本文第五章的内容适配互斥锁

# 5.1.4 分发输入设备事件的方式

常见的输入设备事件分发方式有两种,分别是在 GUI 线程中分发以及在非 GUI 线程中分发,AWTK 为这两种方式分别提供了简单的实现,以方便用户移植。

1、在 GUI 线程中分发事件

对于裸系统平台,AWTK 提供了 main_loop_raw.inc,该文件实现了裸系统 main_loop 的基本功能,在移植时用户只需实现负责输入设备事件的分发 platform_disaptch_input 函数即可,该函数在 GUI 主循环中被调用,会阻塞主线程,具体的流程详见本文 1.3 章节,示例代码如下:

/* 分发输入设备事件 */
ret_t platform_disaptch_input(main_loop_t *l) {
  /* 捕获系统的触摸、鼠标、键盘等输入事件,并转化为 AWTK 支持的事件 */
  event_queue_req_t req;           /* AWTK 的事件请求 */
  memset(&req, 0x00, sizeof(req));
  req.xxx = system_get_input_event();
  ...
  /* 调用本文上一小节中介绍的接口将事件分发到 GUI 主循环 */
  main_loop_queue_event(l, &req);
  return RET_OK; 
}

#include "main_loop/main_loop_raw.inc"

2、在非 GUI 线程中分发事件

在非 GUI 线程中获取并分发设备事件,不会阻塞主线程,可以在一定程度上提高 GUI 刷新效率。示例代码如下:

static void* input_run(void* ctx) {
  main_loop_t* loop = (main_loop_t*)ctx;
  while(1) {
    /* 捕获系统的触摸、鼠标、键盘等输入事件,并转化为 AWTK 支持的事件 */
    event_queue_req_t req;           /* AWTK 的事件请求 */
    memset(&req, 0x00, sizeof(req));
    req.xxx = system_get_input_event();
    ...
    /* 调用本文上一小节的接口将事件分发到 GUI 主循环 */
    main_loop_queue_event(loop, &req);
  }
  return NULL;
}

ret_t platform_disaptch_input(main_loop_t *l) {
  if (run_once) { /* 保证仅执行一次,创建事件分发线程 */
      system_create_thread(input_run);
  }
}

#include "main_loop/main_loop_raw.inc"

需要注意的是,本章节中的示例代码主要描述输入设备移植的步骤逻辑,具体的实现代码需要根据平台实际的设备驱动来编写,读者不必过于纠结示例代码中的实现细节。

# 5.2 鼠标移植教程

事件 event_type_t标识 事件类型及参数
鼠标左键按下 EVT_POINTER_DOWN pointer_event_t
鼠标左键弹起 EVT_POINTER_UP pointer_event_t
鼠标右键弹起 EVT_CONTEXT_MENU pointer_event_t
鼠标移动 EVT_POINTER_MOVE pointer_event_t
鼠标滚轮 EVT_WHEEL wheel_event_t
鼠标中键按下 EVT_KEY_DOWN key_event_t,TK_KEY_WHEEL
鼠标中键弹起 EVT_KEY_UP key_event_t,TK_KEY_WHEEL

移植鼠标设备时,用户只要获取鼠标设备信息将其转化为 AWTK 的事件,并调用 main_loop_queue_event 接口将事件分发到 main_loop 中即可,代码如下:

/* 获取鼠标设备信息,将其转化为 AWTK 指针事件,并分发到 main_loop 中 */
void input_dispatch_mouse(/*...*/) {
  event_queue_req_t req;   /* AWTK 的事件请求 */
  memset(&req, 0x00, sizeof(req));

  /* 获取鼠标设备信息(请根据实际驱动实现) */
  evt = system_get_mouse_event();

  /* 将鼠标信息转化为 AWTK 的指针事件,此处以指针抬起为例 */
  req.event.type = EVT_POINTER_UP;    /* 设置事件类型 */
  req.pointer_event.pressed = evt.pressed; /* 设置是否按压 */
  req.pointer_event.x = evt.x;        /* 鼠标指针的 x 坐标 */
  req.pointer_event.y = evt.y;        /* 鼠标指针的 y 坐标 */
  ...

  /* 设置事件结构大小为 pointer_event_t,其他事件类似 */
  req.event.size = sizeof(req.pointer_event);

  /* 向主循环分发一个事件 */
  main_loop_queue_event(main_loop(), &req); 
}

# 5.3 触摸移植教程

事件 event_type_t标识 事件类型及参数
触摸按下 EVT_POINTER_DOWN pointer_event_t
触摸弹起 EVT_POINTER_UP pointer_event_t
触摸拖动 EVT_POINTER_MOVE pointer_event_t

触摸设备的移植与鼠标设备类似,代码如下:

/* 获取触摸屏设备信息,将其转化为 AWTK 指针事件,并分发到 main_loop 中 */
void input_dispatch_touch(/*...*/) {
  event_queue_req_t req   /* AWTK 的事件请求 */
  memset(&req, 0x00, sizeof(req));

  /* 获取触摸屏设备信息(请根据实际驱动实现) */
  evt = system_get_touch_event();

  /* 将触摸屏信息转化为 AWTK 的指针事件,此处以手指抬起为例 */
  req.event.type = EVT_POINTER_UP;    /* 设置事件类型 */
  req.pointer_event.pressed = evt.pressed; /* 设置是否按压 */
  req.pointer_event.x = evt.x;        /* 触摸的 x 坐标 */
  req.pointer_event.y = evt.y;        /* 触摸的 y 坐标 */
  ...

  /* 设置事件结构大小为 pointer_event_t,其他事件类似 */
  req.event.size = sizeof(req.pointer_event);

  /* 向主循环分发一个事件 */
  main_loop_queue_event(main_loop(), &req); 
}

# 5.4 键盘移植教程

事件 event_type_t标识 事件类型及参数
按键按下 EVT_KEY_DOWN key_event_t
按键弹起 EVT_KEY_UP key_event_t

触摸设备的移植同样与鼠标设备类似,区别在于获取键盘按键扫描码后,要将其转化成 AWTK 的 key_code_t 代码,代码如下:

/* 获取键盘设备信息,将其转化为 AWTK 按键事件,并分发到 main_loop 中 */
void input_dispatch_keyboard(/*...*/) {
  event_queue_req_t req   /* AWTK 的事件请求 */
  memset(&req, 0x00, sizeof(req));

  /* 获取键盘设备信息(请根据实际驱动实现) */
  evt = system_get_key_event();
  key_code = system_key_to_key_code(evt.key);

  /* 将键盘信息转化为 AWTK 的按键事件,此处以某键按下为例 */
  req.event.type = EVT_KEY_DOWN;    /* 设置事件类型 */
  req.key_event.key = key_code;     /* 设置键值TK_KEY_xxx,AWTK的键值请参考 awtk/src/base/keys.h */
  ...

  /* 设置事件结构大小为 key_event_t,其他事件类似 */
  req.event.size = sizeof(req.key_event);

  /* 向主循环分发一个事件 */
  main_loop_queue_event(main_loop(), &req); 
}

# 5.5 其他设备移植教程

根据以上章节中鼠标、触摸屏和键盘设备的移植教程,可以发现各类输入设备的移植流程大同小异,其核心步骤都是获取设备事件将其转化为 AWTK 支持的事件并调用 main_loop_queue_event 接口分发到 main_loop 中,区别只在于设备事件的获取与转化。

某些嵌入式平台会提供一些特殊的输入设备,比如 STM32f103ze 平台提供四个特殊按键,分别表示上、下、左、右,常用于 GUI 界面上切换控件焦点,与移植键盘设备类似,实现代码如下:

/* 获取特殊按键设备信息,将其转化为 AWTK 按键事件,并分发到 main_loop 中 */
void input_dispatch_4key(/*...*/) {
  bool is_press = FLASE; /* 默认按键为抬起状态 */

  /* 获取键盘设备信息(请根据实际驱动实现) */
  int key = KEY_Scan(0);  

  /* 获取键值和按键状态,AWTK 的键值请参考 awtk/src/base/keys.h */
  input_dispatch_get_key_value_and_state(&key, &is_press);

  /* 向主循环分发一个按键事件 */
  main_loop_post_key_event(main_loop(), is_press, key);
}