From d120c008e7b43a5c40bd8a3f639ce5ec26c1ebc5 Mon Sep 17 00:00:00 2001 From: cary-rowen Date: Sun, 31 Mar 2024 02:37:59 +0800 Subject: [PATCH] Added new features, compatible with NVDA2024.1. --- addon/appModules/qq/__init__.py | 93 ++++++++++++++++++++++++++++++--- addon/appModules/qq/chat.py | 5 +- addon/doc/zh_CN/readme.md | 6 ++- buildVars.py | 4 +- 4 files changed, 95 insertions(+), 13 deletions(-) diff --git a/addon/appModules/qq/__init__.py b/addon/appModules/qq/__init__.py index 3820e86..84f0bbd 100644 --- a/addon/appModules/qq/__init__.py +++ b/addon/appModules/qq/__init__.py @@ -1,15 +1,23 @@ #-*- coding:utf-8 -*- # QQEnhancement addon for NVDA -# Copyright 2023 SmileSky, NVDA Chinese Community and other contributors +# Copyright 2023-2024 SmileSky, Cary-rowen, NVDA Chinese Community and other contributors # released under GPL. # This code is borrowed from the original add-on NVBox: https://gitee.com/sscn.byethost3.com/nvbox -import appModuleHandler, tones, ui, winUser -from controlTypes.role import Role +import appModuleHandler +import tones +import ui +import winUser +import api from controlTypes.state import State +from controlTypes.role import Role +from displayModel import DisplayModelTextInfo +from NVDAObjects.IAccessible import IA2TextTextInfo from NVDAObjects.IAccessible import chromium from scriptHandler import script + from . import chat, faces + # QQ内嵌网页树拦截器类 class QQDocumentTreeInterceptor(chromium.ChromeVBuf): def _get_isAlive(self): @@ -17,19 +25,23 @@ def _get_isAlive(self): return super(QQDocumentTreeInterceptor,self).isAlive and self.rootNVDAObject.shouldCreateTreeInterceptor except AttributeError as e: return False + def __contains__(self, obj): if obj and 'TXGuiFoundation' == obj.windowClassName and isinstance(obj, QQDocument): # 本来想支持一下消息列表的光标浏览,结果多少有些小问题,所以尚未实现 return True return super().__contains__(obj) + # QQ 网页文档类 class QQDocument(chromium.Document): treeInterceptorClass=QQDocumentTreeInterceptor def _get_shouldCreateTreeInterceptor(self): return True + # QQ的一些特殊处理 class AppModule(appModuleHandler.AppModule): def event_NVDAObject_init(self, obj):pass + def chooseNVDAObjectOverlayClasses(self, obj, clsList): if obj.windowClassName=='WebAccessbilityHost': # 用于支持QQ内嵌网页的光标浏览 @@ -38,18 +50,71 @@ def chooseNVDAObjectOverlayClasses(self, obj, clsList): # 尚未实现 # clsList.insert(0, QQDocument) return clsList + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - def terminate(self): - super().terminate() + def script_speechToText(self, gesture): + focusObj = api.getFocusObject() + if focusObj.role == Role.PANE: + chat.clickButton('转为文字显示', obj=focusObj) + else: + gesture.send() + + # 执行语音转文字的手势 + __gestures={ + "kb:shift+enter": "speechToText" + } + + # 排除的对话框朗读内容 + alertFilter = ("已阅读并同意", "服务协议", "和", "QQ隐私保护指引", "登录中", "电脑管家正在进行安全检测", ".") + def shouldSkip(self, child): + return ( + not child.name or + child.name in self.alertFilter or + child.role in (Role.BUTTON, Role.CHECKBOX) or + State.INVISIBLE in child.states or + not child.states + ) + def event_gainFocus(self, obj, nextHandler): + # 处理消息列表内的文件上传窗格聚焦 + if obj.role == Role.PANE and obj.simpleFirstChild.role == Role.GRAPHIC: + try: + fileName = obj.firstChild.firstChild.next.name + fileSize = obj.firstChild.firstChild.next.next.name + obj.name = f"{fileName} {fileSize}" + except: pass + # 处理语音消息窗格聚焦 + if obj.role == Role.PANE and obj.value == "语音控件": + try: + duration = obj.simpleLastChild.name + speechToTextResult = obj.simpleFirstChild.name if obj.simpleFirstChild.role == Role.STATICTEXT else "" + speechToTextTip = "按 Shift+回车键可转文字" if obj.simpleFirstChild.simpleNext.description == "转为文字显示" else "" + obj.name = f"语音消息 {duration}秒 {speechToTextResult} 按空格键或回车键可播放 {speechToTextTip}" + obj.value = "" + except: pass + # 处理微软输入法上屏后不能朗读消息输入框内容 + poss=api.getReviewPosition().copy() + obj=api.getFocusObject() + if poss is not None and isinstance(poss, IA2TextTextInfo): + if obj.name is not None and obj.name.startswith(u"\u8f93\u5165"): + self.pos=poss + self.obj=obj + elif isinstance(poss, DisplayModelTextInfo): + try: + if self.pos and self.pos is not None: + api.setFocusObject(self.obj) + except: + pass + # 修正 QQ 按钮标题播报问题 if obj.role == Role.BUTTON and not obj.name: obj.name = obj.description try: nextHandler() except:pass + def event_selection(self, obj, nextHandler): # 针对 QQ 的 Ctrl+tab 切换聊天窗口做了支持 if obj.role == Role.TAB: @@ -65,16 +130,19 @@ def event_selection(self, obj, nextHandler): faces.onSelected(obj) return nextHandler() + def event_valueChange(self, obj, nextHandler): # 表情输入处理 - faces.onInput(obj) + faces.onInput(obj) nextHandler() + def event_nameChange(self, obj, nextHandler): # 处理 QQ 的 alert 对话框 if Role.PANE == obj.role: self.event_alert(obj, nextHandler) return nextHandler() + def event_alert(self, obj, nextHandler): # 获取 obj 的子孙 children = obj.recursiveDescendants @@ -84,9 +152,10 @@ def event_alert(self, obj, nextHandler): return # 朗读对话框的内容 for child in children: - if child.role in (Role.BUTTON, Role.CHECKBOX):continue + if self.shouldSkip(child): continue else: ui.message(child.name) + def event_foreground(self, obj, nextHandler): # 判断当前窗口是不是一个对话框 ws = obj.windowStyle @@ -94,6 +163,7 @@ def event_foreground(self, obj, nextHandler): self.event_alert(obj, nextHandler) return nextHandler() + def event_liveRegionChange(self, obj, nextHandler): # 禁止QQ下载群文件一直吵个不停 if obj and obj.name.startswith('更新时间:'):return @@ -107,16 +177,23 @@ def script_voiceChat(self, gesture): def script_videoChat(self, gesture): # 视频聊天 chat.clickButton('发起视频通话') + @script(gesture="kb:f9") def script_voiceMsgRecord(self, gesture): # 开始录音 chat.clickButton('更多', lambda x: x.next) chat.expectPopupMenu (lambda: chat.clickMenu ('语音消息')) + @script(gesture="kb:shift+f9") def script_voiceMsgSend(self, gesture): # 发送语音消息 chat.clickButton ('发送语音') + @script(gesture="kb:control+f9") def script_voiceMsgCancel(self, gesture): # 取消录音 - chat.clickButton ('取消发送语音') \ No newline at end of file + chat.clickButton ('取消发送语音') + + def terminate(self): + super().terminate() + diff --git a/addon/appModules/qq/chat.py b/addon/appModules/qq/chat.py index ecc31d9..699378a 100644 --- a/addon/appModules/qq/chat.py +++ b/addon/appModules/qq/chat.py @@ -8,9 +8,10 @@ import api, gui, IAccessibleHandler, mouseHandler, tones, ui, winUser, wx from controlTypes.role import Role -def clickButton(name, onChoice = lambda x: True): +def clickButton(name, onChoice = lambda x: True, obj=None): # 点击按钮 - obj = api.getForegroundObject() + if obj is None: + obj = api.getForegroundObject() if not obj: # 没有获取到窗口对象就直接返回 tones.beep(500, 50) diff --git a/addon/doc/zh_CN/readme.md b/addon/doc/zh_CN/readme.md index b857cb4..0c8d894 100644 --- a/addon/doc/zh_CN/readme.md +++ b/addon/doc/zh_CN/readme.md @@ -23,10 +23,14 @@ - 浏览到的表情回车即可填充到输入框,点击发送按钮即可发送。 5. 会话窗口的 Ctrl + Tab 支持。 6. 屏蔽了 QQ 下载群文件不停地朗读“更新时间”的问题。 +7. 修复因 QQ 无障碍缺陷导致消息列表内的文件传送记录会短暂卡焦点。 +8. 修复因 QQ 无障碍缺陷导致消息列表内的语音消息会短暂锁定焦点。 +9. 在语音消息支持转文字的情况下,支持用快捷键将语音转换为文字。 +10. 修复了微软拼音输入法在 QQ 输入框上屏后无法朗读输入框内容的错误。 + ## 版权 此代码源自 NV 宝盒插件: [https://gitee.com/sscn.byethost3.com/nvbox](https://gitee.com/sscn.byethost3.com/nvbox)。 SmileSky 是该插件的原作者,该插件在 GPL 2.0 许可下发布。 - diff --git a/buildVars.py b/buildVars.py index 069aa6d..a07a956 100644 --- a/buildVars.py +++ b/buildVars.py @@ -25,7 +25,7 @@ def _(arg): # Translators: Long description to be shown for this add-on on add-on information from add-ons manager "addon_description": _("""Enhance the experience of using PC QQ for NVDA users."""), # version - "addon_version": "1.0.2", + "addon_version": "1.2.0", # Author(s) "addon_author": "NVDA Chinese Community and other contributors, Original work by SmileSky", # URL for the add-on documentation support @@ -37,7 +37,7 @@ def _(arg): # Minimum NVDA version supported (e.g. "2018.3.0", minor version is optional) "addon_minimumNVDAVersion": "2021.1.0", # Last NVDA version supported/tested (e.g. "2018.4.0", ideally more recent than minimum version) - "addon_lastTestedNVDAVersion": "2023.2.0", + "addon_lastTestedNVDAVersion": "2024.1.0", # Add-on update channel (default is None, denoting stable releases, # and for development releases, use "dev".) # Do not change unless you know what you are doing!