前两天友人问我“NVDA 的日期朗读方式能不能自定义,让它在朗读日期的时候读出星期几”,我说是可以的,连按两次 NVDA + F12 的朗读方式其实是取决于你的系统设置,详细的可以看这里的新手问答
  另外友人困惑 NVDA + F12 朗读时间的时候为什么不读秒,这一点我在国际社区和一些国外的用户讨论过,它们似乎对此比较无感,它们告诉我,如果愿意,使用时钟插件可以高度自定义 NVDA +F12 的朗读方式。
  其实对我而言,是否读秒也没什么影响,但有人问了我就去探索一下呗,修改系统设置中的时间格式发现只影响日期朗读,对时间的朗读似乎是没有用的,那么我就从代码层面看看 NVDA 是如何实现这个功能的,以下偏技术一些:

  1. git clone 一下 NVDA 的代码到本地;
  2. 探索下目录结构,发现这个目录里包含了一些编译 NVDA 所需的文件;
  3. 看到了 source 这个目录(猜想,这就是存放源代码的地方喽);
  4. source 目录里的内容,从名字上看大致也就知道其作用了:
  • appModules 应用程序模块,打开看看,存放了一些对特定程序的特殊支持代码;
  • brailleDisplayDrivers 显然,这是盲文点显器驱动;
  • brailleViewer 这是盲文查看器;
  • synthDrivers 这是语音合成器驱动;
  • globalCommands.py 从名字上看,这是全局命令的意思。

  经过粗略的探索,我们猜想, NVDA+F12 这个功能最有可能在 globalCommands.py 里面,打开看看,文件的顶部是这个文件的版权声名:遵循的开源协议、贡献者等信息,不得不说,贡献者很多,大呼开源大法好。继续往下看,有一堆 import 学过 python 的同学已经不陌生了吧!什么,你问我各个模块是啥作用,除了从名字上推断以外,你还可以用 NVDA 的 python 控制台导入,查看各模块的文档呀。
  我们紧接着看到了一堆 SCRCAT 打头的常量定义,通过注释我们知道这是按键与手势类别的名称。继续往下
  出现了 @script 很明显,这是 python 的装饰器,包含 description, gesture 不难理解吧,这一部分是相应功能的按键定义,既然如此,让我利用查找功能,查找一下 "NVDA+f12" 这个关键词,不出所料找到了,就是下面的代码:

    @script(
        # Translators: Input help mode message for report date and time command.
        description=_("If pressed once, reports the current time. If pressed twice, reports the current date"),
        category=SCRCAT_SYSTEM,
        gesture="kb:NVDA+f12"
    )
    def script_dateTime(self,gesture):
        if scriptHandler.getLastScriptRepeatCount()==0:
            text=winKernel.GetTimeFormatEx(winKernel.LOCALE_NAME_USER_DEFAULT, winKernel.TIME_NOSECONDS, None, None)
        else:
            text=winKernel.GetDateFormatEx(winKernel.LOCALE_NAME_USER_DEFAULT, winKernel.DATE_LONGDATE, None, None)
        ui.message(text)

  确认过眼神儿,是我想要的,其中text=winKernel.GetTimeFormatEx(winKernel.LOCALE_NAME_USER_DEFAULT, winKernel.TIME_NOSECONDS, None, None)就是该功能的具体实现了, NVDA 调用了 winKernel 来获取的时间。
  聪明的你发现了吗,这个函数,第二个参数传入的是 winKernel.TIME_NOSECONDS 显然是人为的,不获取秒,对 win32api 熟悉的同学估计不用多说了,但我不熟悉,所以我打开 nvda 控制台输入以下代码来看看帮助:

import winKernel
help(winKernel.GetTimeFormatEx)

  其实没返回多少有用信息,但不妨碍,我们还有搜索引擎呢,把这个函数的声明粘贴到 google 搜索框(哦,不对,我打不开 google啊 QWQ),第一个搜索结果就是我们想要低:

https://docs.microsoft.com/en-us/windows/win32/api/datetimeapi/nf-datetimeapi-gettimeformatex

  微软老大已经把这个函数说的相当清楚了,那我把第二个参数传入 None 试试呗,先在 python 控制台里实验一下:

import winKernel
winKernel.GetTimeFormatEx(winKernel.LOCALE_NAME_USER_DEFAULT, None, None, None)

  要的要的,是我们预期的结果,有同学可能要问了:”我就是想获取秒, NVDA 默认的这个我不喜欢,能不能写成插件啊?”当然,最简单的,你如果只是自己使用都不用打包成插件,按照以下步骤操作就可以:

  1. 打开 NVDA 设置面板;
  2. 找到“高级”类别;
  3. 选中“我清楚更改这些设置可能导致 NVDA 无法正常运行”复选框;
  4. 选中“允许从开发者实验目录加载自定义代码”复选框;
  5. 点击“打开开发者实验目录”按钮,别忘了点击“确认”。
  6. 找到"globalPlugins"目录;
  7. 在其中新建一个文本文档,命名为 enhanceTime.py 粘贴以下内容并保存:
import globalPluginHandler
import ui
import winKernel
import scriptHandler

class GlobalPlugin(globalPluginHandler.GlobalPlugin):

    def script_dateTime(self,gesture):
        if scriptHandler.getLastScriptRepeatCount()==0:
            text=winKernel.GetTimeFormatEx(winKernel.LOCALE_NAME_USER_DEFAULT, None, None, None)
        else:
            text=winKernel.GetDateFormatEx(winKernel.LOCALE_NAME_USER_DEFAULT, winKernel.DATE_LONGDATE, None, None)
        ui.message(text)

    __gestures = {
        'kb:nvda+F12': 'dateTime',
    }

  最后重新启动 NVDA 就 OK 啦!本文结束,没什么技术含量,抛砖隐喻而已喽!