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
- 将鼠标图标填充到界面的指定区域进行显示。此处为实际显示界面
-
鼠标与界面的隔离保证了交换效果的实现,避免残留污染
- 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); } } } }
-
鼠标显示控制问题
- 按钮点击特效:显示设备按钮区域的像素反转会影响鼠标,另外按钮之间的图标也会影响
页面和鼠标显示逻辑分离,按钮特效不再使用显示设备像素数据 GetDevVideoMem
而是使用页面缓冲 GetCurVideoMem
- 页面切换时鼠标残留和偏移
- 清除交换区域缓存,不同页面之间数据不通用
- 将鼠标移动事件作为鼠标位置更新的唯一条件,以免发生位置偏移
- 按钮点击的有效性:点击事件按照按下和松开时是否在按钮区域
- press时一定在按钮中,刷新鼠标显示
- release时不一定在按钮中,仅当鼠标停留在按钮中才认定为点击有效,再刷新鼠标显示
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库