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/
来源:爱影博客
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
海报
UEFI开发学习4 – Shell下显示鼠标
前言 UEFI SPEC中,鼠标属于Pointer设备,它包括了触摸板,USB鼠标,PS/2鼠标等。BIOS跑完后,这些接口都已经初始化好了,所以进入shell也可以使用鼠标的,前……
<<上一篇
下一篇>>