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时不一定在按钮中,仅当鼠标停留在按钮中才认定为点击有效,再刷新鼠标显示