UEFI开发学习4 – Shell下显示鼠标
前言
UEFI SPEC中,鼠标属于Pointer设备,它包括了触摸板,USB鼠标,PS/2鼠标等。BIOS跑完后,这些接口都已经初始化好了,所以进入shell也可以使用鼠标的,前提是要去捕捉这些数据。UEFI SPEC中定义了两个有关如何使用Pointer设备的Protocols,分别是Simple Pointer Protocol和Absolute Pointer Protocol。以后这一篇用的是前者,后者就不写了。
定义
typedef struct _EFI_SIMPLE_POINTER_PROTOCOL { EFI_SIMPLE_POINTER_RESET Reset; EFI_SIMPLE_POINTER_GET_STATE GetState; EFI_EVENT WaitForInput; EFI_SIMPLE_INPUT_MODE * Mode; } EFI_SIMPLE_POINTER_PROTOCOL;
Reset 用于重置设备
GetState 获取当前Pointer设备的状态,如有无触发Pointer的点击事件,是否有移动等
WaitForInput 等待输入事件,有关这个的以后再写
*Mode 这是一个Pointer device的属性,它包含了Pointer device是否支持左右键,x, y, z轴坐标的解析度等信息:
GetState函数的定义:
typedef EFI_STATUS (EFIAPI *EFI_SIMPLE_POINTER_GET_STATE) IN EFI_SIMPLE_POINTER_PROTOCOL *This, IN OUT EFI_SIMPLE_POINTER_STATE *State );
其中State定义如下:
//******************************************************* // EFI_SIMPLE_POINTER_STATE //******************************************************* typedef struct { INT32 RelativeMovementX; // x轴移动的距离 INT32 RelativeMovementY; // y轴移动的距离 INT32 RelativeMovementZ; // z轴移动的距离 BOOLEAN LeftButton; // 是否按下左键 BOOLEAN RightButton; // 是否按下右键 } EFI_SIMPLE_POINTER_STATE;
GetState函数返回值的定义也很重要:
调用GetState函数后可能会返回3个结果:
EFI_SUCCESS 鼠标设备有发生变化,包括移动、按下左右键等
EFI_NOT_READY 鼠标设备没有发生变化
EFI_DEVICE_ERROR 出错
Mode属性定义如下:
//******************************************************* // EFI_SIMPLE_POINTER_MODE //******************************************************* typedef struct { UINT64 ResolutionX; UINT64 ResolutionY; UINT64 ResolutionZ; BOOLEAN LeftButton; BOOLEAN RightButton; } EFI_SIMPLE_POINTER_MODE;
ResolutionX,ResolutionY,ResolutionZ定义的是鼠标的解析度,即CPI(count per inch),用来表示光电鼠标在移动一英寸距离时,传感器所能接收到的坐标数量,如CPI为800的鼠标,它表示鼠标的物理移动距离为1英寸(2.5厘米)时,它在屏幕可以移动800像素。此处的CPI在UEFI中使用的单位不是英寸,是毫米mm。
LeftButton,RightButton表示该Pointer Device是否有左右键
知道了这些信息,我们就可以动手写代码了
实例
实例1 - 打印鼠标信息
思路:
1.使用LocalProtocol()函数获得EFI_SIMPLE_POINTER_PROTOCOL实例
2.写一个while无限循环,不断调用GetState函数,并检查返回值,若返回EFI_SUCCESS ,则打印鼠标x,y轴的信息;若返回EFI_NOT_READY则不做处理;若出错则退出程序。
3.为了方便测试,再写一个退出程序的方法,如检测到按下鼠标左键便退出循环,不然得重启机器了
EFI_STATUS Status; EFI_SIMPLE_POINTER_PROTOCOL *Mouse; EFI_SIMPLE_POINTER_STATE State; Status = gBS->LocateProtocol(&gEfiSimplePointerProtocolGuid, NULL, (VOID **) &Mouse); if (EFI_ERROR (Status)) { return EFI_UNSUPPORTED; } while(1){ Status = Mouse->GetState(Mouse, &State); if (Status == EFI_DEVICE_ERROR){ Print(L"Get state Failed! \n\r"); return EFI_UNSUPPORTED; } else if (Status == EFI_SUCCESS){ Print(L"X: 0x%8X, Y: 0x%8X\n\r", State.RelativeMovementX , State.RelativeMovementY); } /* 按下左键退出循环 */ if (State.LeftButton) break; }
测试:必须要在实体机测试,因为模拟机捕捉不到实体机的鼠标设备。
实例2 - 在屏幕上显示鼠标
思路:
1.观察例1中的log知,鼠标最小的变化值为0x4000且呈其倍数变化,所以我们可以将变化0x4000的时候,鼠标便移动一个像素。
2.初始化鼠标的位置为屏幕正中间。
3.写一个while无限循环不断判断鼠标的值,当有变化时再用Blt()在相应坐标画一个“┃”作为“鼠标”,宽高为4*10。
EFI_STATUS Status; EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput; UINTN ScreenWidth, ScreenHeight; EFI_SIMPLE_POINTER_PROTOCOL *Mouse; EFI_SIMPLE_POINTER_STATE State; UINTN MoveX, MoveY, t; Status = gBS->LocateProtocol(&gEfiGraphicsOutputProtocolGuid, NULL, (VOID **) &GraphicsOutput); /* 取得画图实例 */ if (EFI_ERROR (Status)) { return EFI_UNSUPPORTED; } ScreenWidth = GraphicsOutput->Mode->Info->HorizontalResolution; ScreenHeight = GraphicsOutput->Mode->Info->VerticalResolution; MoveX = ScreenWidth / 2; /* 屏幕中间位置x */ MoveY = ScreenHeight / 2; /* 屏幕中间位置y */ Status = gBS->LocateProtocol(&gEfiSimplePointerProtocolGuid, NULL, (VOID **) &Mouse); /* Pointer 设备实例 */ if (EFI_ERROR (Status)) { return EFI_UNSUPPORTED; } while(1){ Status = Mouse->GetState(Mouse, &State); if (Status == EFI_DEVICE_ERROR){ Print(L"Get state Failed! %r\n\r", Status); return EFI_UNSUPPORTED; } else if (Status == EFI_SUCCESS){ DrawRectangle(GraphicsOutput, MoveX, MoveY, 4, 10, 1, gColorTable[BLACK], 1); /* 等同Blt()函数,鼠标移走后用黑色(背景色) “|” 填充原来的位置 */ /* X axis process */ if(State.RelativeMovementX & BIT63) MoveX -= ((t = (~(State.RelativeMovementX - 1) / 0x4000))>MoveX)? 0 : t ; else MoveX += State.RelativeMovementX / 0x4000; /* Limit x */ //if(MoveX < 1) MoveX = 1; if(MoveX > ScreenWidth-4) MoveX = ScreenWidth - 4; /* Y axis process */ if(State.RelativeMovementY & BIT63) MoveY -= ((t = (~(State.RelativeMovementY - 1) / 0x4000))>MoveY)? 0 : t ; else MoveY += State.RelativeMovementY / 0x4000; /* Limit y */ //if(MoveY < 1) MoveY = 1; if(MoveY > ScreenHeight-10) MoveY = ScreenHeight - 10; DrawRectangle(GraphicsOutput, MoveX, MoveY, 4, 10, 1, gColorTable[GREEN], 1); /* 绿色 “|” */ if (State.LeftButton) break; } }
结果如下:
例3 - 计算鼠标移动比例
例2中是通过观察打印信息得知鼠标的最小移动值是0x4000,这个值可能是随着不同设备而变化的,所以这不是一个通用的方法。为了研究这个通用的方法,琢磨了好几天,终于想出来了。前文中有提到个解析度,可以根据这个值来计算。
思路:
通过实验打印ResolutionX和ResolutionY的值可知,这两个值均为0x10000,即鼠标移动1mm时,屏幕可以移动0x10000个像素。而我们普通的屏幕根本没有这么大的像素,一般为1080*1920,所以需要对它进行按比例缩小。我们可以在自己使用的电脑用鼠标测试一下,从屏幕左上角移动到左下角,实际上鼠标的物理移动距离,我测试的结果大概是2cm左右。
把2cm换到要处理的机器上,即让移动2cm时,鼠标可以从屏幕左上角移动到左下角。按已知的CPI计算,移动2cm可移动的像素为2 * 10 * 0x10000;屏幕高为1080,最小值为1像素,得到:ResolutionX / (2 * 10 * 0x10000) = 1 / 1080,算得最小移动距离ResolutionX。
最后还有一个问题要处理,那就是当鼠标移动到屏幕边沿时,要限制x,y的最大值和最小值(例2中已经做了处理)。把例2中的0x4000替换下就可以了,如下:
UINTN GetMoveMin(UINT64 ResolutionX, UINTN ScreenWidth) { UINT64 MaxMove = 300; return ResolutionX * MaxMove / ScreenWidth; }
int main ( IN int Argc, IN char **Argv ) { EFI_STATUS Status; EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput; UINTN ScreenWidth, ScreenHeight; EFI_SIMPLE_POINTER_PROTOCOL *Mouse; EFI_SIMPLE_POINTER_STATE State; UINTN MoveX, MoveY, t, MinMove; Status = gBS->LocateProtocol(&gEfiGraphicsOutputProtocolGuid, NULL, (VOID **) &GraphicsOutput); /* 取得画图实例 */ if (EFI_ERROR (Status)) { return EFI_UNSUPPORTED; } ScreenWidth = GraphicsOutput->Mode->Info->HorizontalResolution; ScreenHeight = GraphicsOutput->Mode->Info->VerticalResolution; MoveX = ScreenWidth / 2; /* 屏幕中间位置x */ MoveY = ScreenHeight / 2; /* 屏幕中间位置y */ Status = gBS->LocateProtocol(&gEfiSimplePointerProtocolGuid, NULL, (VOID **) &Mouse); /* Pointer 设备实例 */ if (EFI_ERROR (Status)) { return EFI_UNSUPPORTED; } MinMove = GetMoveMin(Mouse->Mode->ResolutionX, ScreenWidth); while(1){ Status = Mouse->GetState(Mouse, &State); if (Status == EFI_DEVICE_ERROR){ Print(L"Get state Failed! %r\n\r", Status); return EFI_UNSUPPORTED; } else if (Status == EFI_SUCCESS){ DrawRectangle(GraphicsOutput, MoveX, MoveY, 4, 10, 1, gColorTable[BLACK], 1); /* 等同Blt()函数,鼠标移走后用黑色(背景色) “|” 填充原来的位置 */ /* X axis process */ if(State.RelativeMovementX & BIT63) MoveX -= ((t = (~(State.RelativeMovementX - 1) / MinMove))>MoveX)? 0 : t ; else MoveX += State.RelativeMovementX / MinMove; /* Limit x */ //if(MoveX < 1) MoveX = 1; if(MoveX > ScreenWidth-4) MoveX = ScreenWidth - 4; /* Y axis process */ if(State.RelativeMovementY & BIT63) MoveY -= ((t = (~(State.RelativeMovementY - 1) / MinMove))>MoveY)? 0 : t ; else MoveY += State.RelativeMovementY / MinMove; /* Limit y */ //if(MoveY < 1) MoveY = 1; if(MoveY > ScreenHeight-10) MoveY = ScreenHeight - 10; DrawRectangle(GraphicsOutput, MoveX, MoveY, 4, 10, 1, gColorTable[GREEN], 1); /* 绿色 “|” */ if (State.LeftButton) break; } } } /* End main */
版权声明:
作者:bin
链接:https://ay123.net/mystudy/uefi/766/
来源:爱影博客
文章版权归作者所有,未经允许请勿转载。
白菜
bin@白菜
白菜@bin
bin@白菜
白菜@bin
bin@白菜
白菜
白菜