100ASK_IMX6ULL-PRO 数码相框扩展项目:支持鼠标输入

1. 鼠标输入事件

驱动已支持,测试 Hexdump /dev/input/eventX确定设备节点

项目中的输入模块

输入事件的获取

项目启动时初始化输入设备 InputDeviceInit,通过链表进行设备管理,对于每一个注册的输入设备创建一个线程阻塞式读取输入数据:

static void *InputEventThreadFunction(void *pVoid)
{
T_InputEvent tInputEvent;
/* 定义函数指针 */
int (*GetInputEvent)(PT_InputEvent ptInputEvent);
GetInputEvent = (int (*)(PT_InputEvent))pVoid;
while (1)
{
if(0 == GetInputEvent(&tInputEvent))
{
/* 唤醒主线程, 把tInputEvent的值赋给一个全局变量 */
/* 访问临界资源前,先获得互斥量 */
pthread_mutex_lock(&g_tMutex);
g_tInputEvent = tInputEvent;
/* 唤醒主线程 */
pthread_cond_signal(&g_tConVar);
/* 释放互斥量 */
pthread_mutex_unlock(&g_tMutex);
}
}
return NULL;
}

对外接口:

int GetInputEvent(PT_InputEvent ptInputEvent)
{
/* 休眠 */
pthread_mutex_lock(&g_tMutex);
pthread_cond_wait(&g_tConVar, &g_tMutex);
/* 被唤醒后,返回数据 */
*ptInputEvent = g_tInputEvent;
pthread_mutex_unlock(&g_tMutex);
return 0;
}

输入事件的应用

对于每一个界面,结合界面的UI布局对原始输入事件进行解析:

  • 确定响应的UI元素,主要是按钮,从而执行对应的业务逻辑
  • 将输入事件数据通过参数进行传递,以供进一步的解析应用(按下松开,移动的距离等)
/**********************************************************************
* 函数名称: GenericGetInputEvent
* 功能描述: 读取输入数据,并判断它位于哪一个图标上
* 输入参数: ptPageLayout - 内含多个图标的显示区域
* 输出参数: ptInputEvent - 内含得到的输入数据
* 返 回 值: -1 - 输入数据不位于任何一个图标之上
* 其他值 - 输入数据所落在的图标(PageLayout->atLayout数组的哪一项)
***********************************************************************/
int GenericGetInputEvent(PT_PageLayout ptPageLayout, PT_InputEvent ptInputEvent)
{
T_InputEvent tInputEvent;
int iRet;
int i = 0;
PT_Layout atLayout = ptPageLayout->atLayout;
/* 获得原始的触摸屏数据
* 它是调用input_manager.c的函数,此函数会让当前线否休眠
* 当触摸屏线程获得数据后,会把它唤醒
*/
iRet = GetInputEvent(&tInputEvent);
if (iRet)
{
return -1;
}
if (tInputEvent.iType == INPUT_TYPE_STDIN) // 仅响应触摸点击和鼠标点击
{
return -1;
}
*ptInputEvent = tInputEvent;
/* 处理鼠标事件 */
if ((ptInputEvent->iType == INPUT_TYPE_MOUSE) && (ptInputEvent->iPressure == MOUSE_MOVE)){
// 仅在鼠标移动时修改坐标,按下时要反转像素
SetMousePos(ptInputEvent->iX, ptInputEvent->iY);
SwitchRegionInDev(ptInputEvent->iX, ptInputEvent->iY, GetMouseIconPixelDatas(), GetMouseBox());
}
/* 处理数据 */
/* 确定触点位于哪一个按钮上 */
while (atLayout[i].strIconName)
{
if ((tInputEvent.iX >= atLayout[i].iTopLeftX) && (tInputEvent.iX <= atLayout[i].iBotRightX) && \
(tInputEvent.iY >= atLayout[i].iTopLeftY) && (tInputEvent.iY <= atLayout[i].iBotRightY))
{
/* 找到了被点中的按钮 */
return i;
}
else
{
i++;
}
}
/* 触点没有落在按钮上 */
return -1;
}

以主界面为例:

/**********************************************************************
* 函数名称: MainPageGetInputEvent
* 功能描述: 为"主页面"获得输入数据,判断输入事件位于哪一个图标上
* 输入参数: ptPageLayout - 内含多个图标的显示区域
* 输出参数: ptInputEvent - 内含得到的输入数据
* 返 回 值: -1 - 输入数据不位于任何一个图标之上
* 其他值 - 输入数据所落在的图标(PageLayout->atLayout数组的哪一项)
***********************************************************************/
static int MainPageGetInputEvent(PT_PageLayout ptPageLayout, PT_InputEvent ptInputEvent)
{
return GenericGetInputEvent(ptPageLayout, ptInputEvent);
}

鼠标事件的实现

static T_InputOpr g_tMouseOpr = {
.name = "Mouse",
.DeviceInit = MouseDevInit,
.DeviceExit = MouseDevExit,
.GetInputEvent = MouseGetInputEvent,
};
  • 出入口函数实现:

调用open, close接口对鼠标输入文件进行打开和关闭操作

  • 输入事件获取函数实现:

调用read接口读取数据,无数据则休眠。

主要关注两种数据类型:鼠标左键操作EV_KEY(按下和松开事件的解析)和鼠标的相对位移操作EV_REL(移动事件的解析)

static int MouseGetInputEvent(PT_InputEvent ptInputEvent)
{
struct input_event tEvent;
int iLen;
while (1)
{
iLen = read(giFd, &tEvent, sizeof(tEvent)); /* 如果无数据则休眠 */
if (iLen != sizeof(tEvent))
DBG_PRINTF("read input_event form mouse failed.\n");
if (tEvent.type == EV_KEY)
{
if (tEvent.code == BTN_LEFT)
giPressure = tEvent.value;
else
continue;
}
else if (tEvent.type == EV_REL)
{
if (tEvent.code == REL_X ){
giPosX += tEvent.value;
giPosX = MAX(giPosX, 0);
giPosX = MIN(giPosX, giPosXres);
giPressure = MOUSE_MOVE;
}
else if ( tEvent.code == REL_Y){
giPosY += tEvent.value;
giPosY = MAX(giPosY, 0);
giPosY = MIN(giPosY, giPosYres);
giPressure = MOUSE_MOVE;
}
else
continue;
}
else
continue;
ptInputEvent->tTime = tEvent.time;
ptInputEvent->iType = INPUT_TYPE_MOUSE;
ptInputEvent->iX = giPosX;
ptInputEvent->iY = giPosY;
ptInputEvent->iPressure = giPressure;
return 0;
}
return 0;
}

2. 鼠标的显示

鼠标图标的数据来源

  • 直接生成像素数据
  • ** 使用图片:更加方便,且项目功能支持。采用该方案。**

刷新策略

刷新的时机

  • 鼠标移动操作,变更显示位置
  • 鼠标点击操作,发生页面的切换需要重新显示鼠标,包括主界面和浏览界面等之间的切换,还有浏览界面中不同层级的切换

刷新的策略

单独控制鼠标区域刷新,因为相比刷新整个页面效率更高

实现的思路:显示页面的局部交换

  • 交换区域:界面中以鼠标的位置作为左上角,与鼠标图标大小相同的区域

  • 交换流程:

    • step 1:还原

      • 将记录的内容还原到界面中,此处为实际显示界面 GetDevVideoMem
    • step 2:扣取

      • 重新记录界面中指定的区域,此处为设计显示界面 GetCurVideoMem
    • step 3:填充

      • 将鼠标图标填充到界面的指定区域进行显示。此处为实际显示界面 GetDevVideoMem

image

鼠标与界面的隔离保证了交换效果的实现,避免残留污染

  • GetDevVideoMem:实际的LCD显示效果页面,包含鼠标图标
  • GetCurVideoMem:当前的界面设计显示效果页面,不包含鼠标图标
/**********************************************************************
* 函数名称: SwitchRegionInDev
* 功能描述: 交换在显示设备上的指定区域像素数据
* 输入参数: iX - 起始点X坐标
* iY - 起始点Y坐标
* ptNewRegion - 更新显示的象素数据
* 输出参数: ptOldRegion - 更新前显示的像素数据
* 返 回 值: 0 - 成功, 其他值 - 失败
***********************************************************************/
int SwitchRegionInDev(int iX, int iY, PT_PixelDatas ptNewRegion, PT_PixelDatas ptOldRegion){
int i;
int iBoxX, iBoxY;
int iMaxLineBytes;
unsigned char *pucOld;
unsigned char *pucPage;
PT_PixelDatas ptDevPixelDatas = &(GetDevVideoMem()->tPixelDatas);
PT_PixelDatas ptCurPixelDatas;
PT_VideoMem ptCurVideoMem = GetCurVideoMem();
// 交换基于页面而不是显示设备当前的像素数据, 隔离页面和鼠标的显示
if (ptCurVideoMem)
ptCurPixelDatas = &(ptCurVideoMem->tPixelDatas);
else
// ptCurPixelDatas = ptDevPixelDatas;
return -1;
ptOldRegion->iWidth = ptNewRegion->iWidth;
ptOldRegion->iHeight = ptNewRegion->iHeight;
ptOldRegion->iBpp = ptNewRegion->iBpp;
ptOldRegion->iLineBytes = ptOldRegion->iWidth * ptOldRegion->iBpp / 8;
ptOldRegion->iTotalBytes = ptOldRegion->iLineBytes * ptOldRegion->iHeight;
// step 1 还原:将 ptOldRegion 刷新到显示设备
if (!ptOldRegion->aucPixelDatas){ // 第一次交换之前没有旧数据
ptOldRegion->aucPixelDatas = (unsigned char *)malloc(ptNewRegion->iTotalBytes);
if (!ptOldRegion->aucPixelDatas){
DBG_PRINTF("%s malloc error!\n", __FUNCTION__);
return -1;
}
}
else {
GetMouseBoxPos(&iBoxX, &iBoxY);
if ((iBoxX != iX) || (iBoxY != iY)){ // 原地交换时无需刷新
// FlushRegionToDev(iBoxX, iBoxY, ptOldRegion);
PicMerge(iBoxX, iBoxY, ptOldRegion, ptDevPixelDatas);
}
}
// step 2 扣取:从页面上复制对应区域的像素数据保存到 ptOldRegion
pucOld = ptOldRegion->aucPixelDatas;
pucPage = ptCurPixelDatas->aucPixelDatas + iY * ptCurPixelDatas->iLineBytes + iX * ptCurPixelDatas->iBpp / 8;
iMaxLineBytes = MIN(ptOldRegion->iLineBytes, (ptCurPixelDatas->iWidth - iX) * ptCurPixelDatas->iBpp / 8); // 保证不越出右边界的最大长度
for (i = 0; i < ptOldRegion->iHeight; i++)
{
if (iY + i >= ptCurPixelDatas->iHeight) // 底部越界,结束
break;
memcpy(pucOld, pucPage, iMaxLineBytes);
pucPage += ptCurPixelDatas->iLineBytes;
pucOld += ptOldRegion->iLineBytes;
}
SetMouseBoxPos(iX, iY);
// step 3 填充:将 ptNewRegion 像素数据刷新至显示设备上
FlushRegionToDev(iX, iY, ptNewRegion);
return 0;
}

一些细节问题的处理

透明背景的实现

PNG_parser只读取RGB数据,并且ConvertOneLine仅支持24bpp

LCD 仅支持 RGB,利用alpha 控制显示与隐藏 ShowPixel: FlushRegionToDev

  • png图片解析与转换

    • 调用libpng库转换时保留alpha信息

      // 透明度处理
      if (iRGBMode == 3)
      png_set_strip_alpha(ptPNG); // 丢弃透明度和背景
      else {
      if (iColorType!= PNG_COLOR_TYPE_RGBA) { // 保留透明度
      png_set_expand(ptPNG);
      }
      if (iBitDepth!= 8) {
      png_set_strip_16(ptPNG);
      }
      png_set_packing(ptPNG);
      if (iColorType == PNG_COLOR_TYPE_GRAY || iColorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
      png_set_gray_to_rgb(ptPNG);
      }
      } // iRGBMode = 4;
    • 支持32位的 bpp 格式转换:注意ARGB的格式

      /**********************************************************************
      * 函数名称: CovertOneLine
      * 功能描述: 根据BPP进行像素数据转换
      * 输入参数: iWidth - 像素个数
      * iSrcBpp - 源数据BPP
      * iDstBpp - 目的数据BPP
      * pudSrcDatas - 源像素数据,注意 png, bmp, jpg 传入像素数据格式不同,暂时无法通用
      * 输出参数: pudDstDatas - 目的像素数据
      * 返 回 值: 0 - 成功, 其他值 - 失败
      ***********************************************************************/
      static int CovertOneLine(int iWidth, int iSrcBpp, int iDstBpp, unsigned char *pudSrcDatas, unsigned char *pudDstDatas)
      {
      unsigned int dwRed;
      unsigned int dwGreen;
      unsigned int dwBlue;
      unsigned int dwColor;
      unsigned short *pwDstDatas16bpp = (unsigned short *)pudDstDatas;
      unsigned int *pwDstDatas32bpp = (unsigned int *)pudDstDatas;
      int i;
      /*
      BPP格式转换说明: from 32/24 to 32/24/16
      */
      if (iSrcBpp != 24 && iSrcBpp != 32)
      {
      return -1;
      }
      if (iDstBpp == iSrcBpp)
      {
      if (iSrcBpp == 32){ // from 32 to 32: RGBA --> ARGB
      unsigned int dwAlpha;
      for (i = 0; i < iWidth; i++){
      dwRed = pudSrcDatas[i * iSrcBpp/8 + 0];
      dwGreen = pudSrcDatas[i * iSrcBpp/8 + 1];
      dwBlue = pudSrcDatas[i * iSrcBpp/8 + 2];
      dwAlpha = pudSrcDatas[i * iSrcBpp/8 + 3];
      dwColor = (dwAlpha << 24) | (dwRed << 16) | (dwGreen << 8) | dwBlue;
      *pwDstDatas32bpp = dwColor;
      pwDstDatas32bpp++;
      }
      }
      else // from 24 to 24: RGb --> RGB
      memcpy(pudDstDatas, pudSrcDatas, iWidth * iDstBpp / 8);
      }
      else // iDstBpp == iSrcBpp,不考虑alpha
      {
      for (i = 0; i < iWidth; i++)
      {
      dwRed = pudSrcDatas[i * iSrcBpp/8 + 0];
      dwGreen = pudSrcDatas[i * iSrcBpp/8 + 1];
      dwBlue = pudSrcDatas[i * iSrcBpp/8 + 2];
      if (iDstBpp == 32)
      {
      dwColor = (dwRed << 16) | (dwGreen << 8) | dwBlue;
      *pwDstDatas32bpp = dwColor;
      pwDstDatas32bpp++;
      }
      else if (iDstBpp == 24){
      *pudDstDatas = dwRed;
      pudDstDatas++;
      *pudDstDatas = dwGreen;
      pudDstDatas++;
      *pudDstDatas = dwBlue;
      pudDstDatas++;
      }
      else if (iDstBpp == 16)
      {
      /* 565 */
      dwRed = dwRed >> 3;
      dwGreen = dwGreen >> 2;
      dwBlue = dwBlue >> 3;
      dwColor = (dwRed << 11) | (dwGreen << 5) | (dwBlue);
      *pwDstDatas16bpp = dwColor;
      pwDstDatas16bpp++;
      }
      }
      }
      return 0;
      }
    • 显示刷新接口针对alpha数据处理:注意字节序问题

      /**********************************************************************
      * 函数名称: FlushRegionToDev
      * 功能描述: 在显示设备上显示指定像素数据中的图像, 支持透明度
      * 输入参数: iX - 起始点X坐标
      * iY - 起始点Y坐标
      * ptPixelDatas - 象素数据
      * 输出参数: 无
      * 返 回 值: 0 - 成功, 其他值 - 失败
      ***********************************************************************/
      void FlushRegionToDev(int iX, int iY, PT_PixelDatas ptPixelDatas){
      // PicMerge(iX, iY, ptPixelDatas, &(GetDevVideoMem()->tPixelDatas)); // 考虑边界问题,透明度问题
      int iBytes;
      unsigned char *pucSrc;
      unsigned char *pucDst;
      PT_PixelDatas ptDevPixelDatas = &(GetDevVideoMem()->tPixelDatas);
      if ((ptPixelDatas->iWidth > ptDevPixelDatas->iWidth) ||
      (ptPixelDatas->iHeight > ptDevPixelDatas->iHeight) ||
      (ptPixelDatas->iBpp != ptDevPixelDatas->iBpp))
      {
      return ;
      }
      iBytes = ptDevPixelDatas->iBpp / 8;
      pucSrc = ptPixelDatas->aucPixelDatas;
      pucDst = ptDevPixelDatas->aucPixelDatas + iY * ptDevPixelDatas->iLineBytes + iX * iBytes;
      int i, j, k;
      for (i = 0; i < ptPixelDatas->iHeight; i++)
      {
      // 底部越界,结束当前区域显示
      if (iY + i >= ptDevPixelDatas->iHeight)
      break;
      for(j=0; j< ptPixelDatas->iWidth; j++){
      // 右边越界,结束当前行显示
      if (j + iX >= ptDevPixelDatas->iWidth)
      break;
      //LCD格式0x00RRGGBB, 小字节序保存 BBGGRRAA, alpha为最后一个字节
      if (*(pucSrc + (i * ptPixelDatas->iWidth + j) * iBytes + iBytes - 1) == 0){ // 像素点alpha = 0, 则不显示,
      continue;
      }
      else
      for (k = 0; k < iBytes; k++){ // 像素点alpha != 0, 刷新显示
      *(pucDst + (i * ptDevPixelDatas->iWidth + j) * iBytes + k) = *(pucSrc + (i * ptPixelDatas->iWidth + j) * iBytes + k);
      }
      }
      }
      }

鼠标显示控制问题

  1. 按钮点击特效:显示设备按钮区域的像素反转会影响鼠标,另外按钮之间的图标也会影响

页面和鼠标显示逻辑分离,按钮特效不再使用显示设备像素数据 GetDevVideoMem 而是使用页面缓冲 GetCurVideoMem

  1. 页面切换时鼠标残留和偏移
  • 清除交换区域缓存,不同页面之间数据不通用
  • 将鼠标移动事件作为鼠标位置更新的唯一条件,以免发生位置偏移
  1. 按钮点击的有效性:点击事件按照按下和松开时是否在按钮区域
  • press时一定在按钮中,刷新鼠标显示
  • release时不一定在按钮中,仅当鼠标停留在按钮中才认定为点击有效,再刷新鼠标显示
posted @   libq8  阅读(22)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示