UEFI中的界面设计(二)

可喜可贺,在昏天黑地的做了一周界面后,我终于休假了!
最近着重做了密码认证部分,这里主要用到的控件是PASSWORD,这个控件跟代码交互的地方非常多,不是简单的把VFR写上去就行。所以这一篇也会以此控件为例,讲一下如何根据需要写HII驱动代码!
在这里插入图片描述
首先还来看这张图啦,左边的Text Display Engine与CustomizedDisplayLib是制作控件的两个模块。
顾名思义,Text Display Engine更注重控件构成的流程,CustomizedDisplayLib是控件组成元素的具体实现,听上去很绕对吧?以话剧为例,前者就像剧本,记录了什么时候该干什么,而后者就像是演员和道具,构成了剧本中需要的部分——当然,演员是可以换的,所以它是以LIB的形式存在,不同架构可以给出不同实现,我们可以换一个更大的对话框、或者给Setup界面左右各画一条竖线,但Text Display Engine我不建议改动,影响可能会比较大。
所以,当你要使用一个不熟的控件时,建议先去Text Display Engine中看一下它是如何实现的,比如PASSWORD。在控件实现的ProcessOptions函数中,调用了PasswordProcess去实现PASSWORD这个控件。
这里有个小技巧,就是Text Display Engine中控件的定义类型格式为EFI_IFR_控件名_OP,如果想要找具体控件的实现信息,可以直接搜。(我就是这么搜的233)
那么PasswordProcess做了什么呢?

  //
  // Use a NULL password to test whether old password is required
  //
  *StringPtr = 0;
  Status     = Question->PasswordCheck (gFormData, Question, StringPtr);
  if ((Status == EFI_NOT_AVAILABLE_YET) || (Status == EFI_UNSUPPORTED)) {
    //
    // Password can't be set now.
    //
    if (Status == EFI_UNSUPPORTED) {
      do {
        CreateDialog (&Key, gEmptyString, gPasswordUnsupported, gPressEnter, gEmptyString, NULL);
      } while (Key.UnicodeChar != CHAR_CARRIAGE_RETURN);
    }

    FreePool (StringPtr);
    return EFI_SUCCESS;
  }

根据注释,这一段是它传了一个NULL进PasswordCheck这个函数,看看是否存有旧密码,如果你有见过这个控件的应用就应该知道,在修改密码时,需要先输入正确的旧密码才能走后面的流程,对应的就是这里。
PasswordCheck的核心代码如下

 if (PasswordString != NULL) {
    IfrTypeValue.string = NewString (PasswordString, gCurrentSelection->FormSet->HiiHandle);
  } else {
    IfrTypeValue.string = 0;
  }

  //
  // Send password to Configuration Driver for validation
  //
  Status = ConfigAccess->Callback (
                           ConfigAccess,
                           EFI_BROWSER_ACTION_CHANGING,
                           Question->QuestionId,
                           Question->HiiValue.Type,
                           &IfrTypeValue,
                           &ActionRequest
                           );

  //
  // Remove password string from HII database
  //
  if (PasswordString != NULL) {
    DeleteString (IfrTypeValue.string, gCurrentSelection->FormSet->HiiHandle);
  }

如果传进来的不是NULL(注意,这里传进来的是“”,不是NULL!),注册IfrTypeValue.string,然后调回界面的CallBack函数,这个CallBack函数做过界面的肯定不陌生,就是这个。

BOOT_MANAGER_CALLBACK_DATA  gBootManagerPrivate = {
  BOOT_MANAGER_CALLBACK_DATA_SIGNATURE,
  NULL,
  NULL,
  {
    BootManagerExtractConfig,
    BootManagerRouteConfig,
    BootManagerCallback
  }
};

在UI链接库的入口函数里注册的HII 驱动,也就是我一开始说的,控件和界面交互的地方。
那么将这个函数的声明与CheckPasswd传入的参数结合起来看

EFI_STATUS
EFIAPI
BootManagerCallback (
  IN  CONST EFI_HII_CONFIG_ACCESS_PROTOCOL  *This,
  IN  EFI_BROWSER_ACTION                    Action,
  IN  EFI_QUESTION_ID                       QuestionId,
  IN  UINT8                                 Type,
  IN  EFI_IFR_TYPE_VALUE                    *Value,
  OUT EFI_BROWSER_ACTION_REQUEST            *ActionRequest
  )
{
  Status = ConfigAccess->Callback (
                           ConfigAccess,
                           EFI_BROWSER_ACTION_CHANGING,
                           Question->QuestionId,
                           Question->HiiValue.Type,
                           &IfrTypeValue,
                           &ActionRequest
                           );

可以看到,我们传入了EFI_BROWSER_ACTION_CHANGING作为行为类型,有人问我为什么用的CHANGING而不是CHANGED,我第一反应是这人家官方代码写死的我能怎么办(,严肃的说,可能是因为此控件处于并没有完成功能(设置密码)状态,当然,是我的猜测。
同时,""作为IfrTypeValue.string被传回去了,同时还有控件的KEY值QuestionId,通过这个值可以在HII驱动里确定是哪个控件调回来的,这是上一章的内容。
那么在代码里,这一段的写法就很明确了。

if (action == CHANGING)
  if (QuestionId == 0x0525)  //0525是我在vfr中设置的此控件key值
    if (Value->string = "")
      return 是否有旧密码?

此处结束,我们回到PasswordProcess这个函数

Status     = Question->PasswordCheck (gFormData, Question, StringPtr);
  if ((Status == EFI_NOT_AVAILABLE_YET) || (Status == EFI_UNSUPPORTED)) {
    //
    // Password can't be set now.
    //
    if (Status == EFI_UNSUPPORTED) {
      do {
        CreateDialog (&Key, gEmptyString, gPasswordUnsupported, gPressEnter, gEmptyString, NULL);
      } while (Key.UnicodeChar != CHAR_CARRIAGE_RETURN);
    }

    FreePool (StringPtr);
    return EFI_SUCCESS;
  }

如果是这两个错,认为此控件不支持,直接退出(呵呵,不知道是哪个小傻子把返回值设成了EFI_UNSUPPORTED,查了一天),其中,CreateDialog就是由CustomizedDisplayLib实现的。
如果不是这两个错,但不是成功,就说明有旧密码,进入以下流程

 if (EFI_ERROR (Status)) {
    //
    // Old password exist, ask user for the old password
    // 这个ReadString是封装好的一个弹窗,可以获取用户的输入,具体实现可以看代码,
    //
    Status = ReadString (MenuOption, gPromptForPassword, StringPtr);
    if (EFI_ERROR (Status)) {
      ZeroMem (StringPtr, (Maximum + 1) * sizeof (CHAR16));
      FreePool (StringPtr);
      return Status;
    }

    //
    // Check user input old password
    //
    Status = Question->PasswordCheck (gFormData, Question, StringPtr);
    if (EFI_ERROR (Status)) {
      if (Status == EFI_NOT_READY) {
        //
        // Typed in old password incorrect
        //
        PasswordInvalid ();
      } else {
        Status = EFI_SUCCESS;
      }

      ZeroMem (StringPtr, (Maximum + 1) * sizeof (CHAR16));
      FreePool (StringPtr);
    }

将用户输入的旧密码再次传入PasswordCheck,与刚才类似,不过IfrTypeValue.string的内容不是“”,而是用户输入的字符串。
对应的,在回调函数中,我们就需要以下部分

if (action == CHANGING)
  if (QuestionId == 0x0525)  //0525是我在vfr中设置的此控件key值
    if (Value->string != "")
      return 旧密码是否正确?

接着往下看,如果验证正确,这个时候该进入正常的设置密码流程了

 //
  // Ask for new password
  //
  ZeroMem (StringPtr, (Maximum + 1) * sizeof (CHAR16));
  Status = ReadString (MenuOption, gPromptForNewPassword, StringPtr);
  if (EFI_ERROR (Status)) {
    //
    // Reset state machine for password
    //
    Question->PasswordCheck (gFormData, Question, NULL);
    ZeroMem (StringPtr, (Maximum + 1) * sizeof (CHAR16));
    FreePool (StringPtr);
    return Status;
  }

这里可以说一下,ReadString里封装了对于此控件的一些输入检测,比如输入过长、过短、两次输入不一致,如果没有通过检测,很不幸,它又调回PasswordCheck了,内容是NULL,对应的,传入回调函数的参数为NULL,所以回调函数里需要这一段。

if (action == CHANGING)
  if (QuestionId == 0x0525)  //0525是我在vfr中设置的此控件key值
    if (Value->string == NULL)
      做点输入错误内容的事情
      return SUCCESS;

经过错误检测后,它认可了用户传入的新密码
然后这个函数就结束了
当时我一脸懵逼,啊?没了?那新密码是怎么传回去的?
于是我使用了搜索EFI_IFR_PASSWORD_OP大法,找到了这段

case EFI_IFR_PASSWORD_OP:
  if (UserInput->InputValue.Buffer == NULL) {
    //
    // User not input new password, just return.
    //
    break;
  }

  DeleteString (Statement->HiiValue.Value.string, gCurrentSelection->FormSet->HiiHandle);
  Statement->HiiValue.Value.string = UserInput->InputValue.Value.string;
  CopyMem (Statement->BufferValue, UserInput->InputValue.Buffer, (UINTN)UserInput->InputValue.BufferLen);
  ZeroMem (UserInput->InputValue.Buffer, (UINTN)UserInput->InputValue.BufferLen);
  FreePool (UserInput->InputValue.Buffer);
  //
  // Two password match, send it to Configuration Driver
  //
  if ((Statement->QuestionFlags & EFI_IFR_FLAG_CALLBACK) != 0) {
    PasswordCheck (NULL, UserInput->SelectedStatement, (CHAR16 *)Statement->BufferValue);

嗯,看着就像
浅提一下,这段出现在ProcessUserInput,用于处理用户输入,可以看到,它又调回了PasswordCheck,走的是与验证密码类似的流程
所以,回调函数里需要这一段

if (action == CHANGING)
  if (QuestionId == 0x0525)  //0525是我在vfr中设置的此控件key值
    if (Value->string != "")
      设置密码
      return SUCCESS;

是的,你没看错,这判断方式和验证密码一样——也就是说,你要自己判断现在处于哪个阶段,是验证旧密码还是设置新密码?
类似的,取消密码用的是输入两次“”,这和控件传入“”判断是否有旧密码是同样的判断条件,也要判断。也意味着你需要测试各种情况,真正的写代码一天,改代码三天(手动微笑)。
不过这也算是相当复杂的控件了,其他控件会好一些。
界面还是很有趣的,我曾经抱怨EDK2的帮助文档说的语焉不详,一点不像QT的保姆教程,恨不得详细到小学生都能上手做,现在想想这也是这个框架有魅力的地方,根据底层构造去写代码、实现功能,也算是弥补了我一些没有选择QT为职业的遗憾吧。
哦对了,HII驱动其实除了CallBack函数,还有两个大哥,负责给控件写初始值和刷新值的,PASSWORD用不到,以后有机会细说吧(挖坑!)。

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值