Skip to content

Commit

Permalink
Added new features, compatible with NVDA2024.1.
Browse files Browse the repository at this point in the history
  • Loading branch information
cary-rowen committed Mar 30, 2024
1 parent 32469af commit d120c00
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 13 deletions.
93 changes: 85 additions & 8 deletions addon/appModules/qq/__init__.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,47 @@
#-*- 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):
try:
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内嵌网页的光标浏览
Expand All @@ -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:
Expand All @@ -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
Expand All @@ -84,16 +152,18 @@ 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
if not (ws & winUser.WS_EX_APPWINDOW or ws & winUser.WS_GROUP):
self.event_alert(obj, nextHandler)
return
nextHandler()

def event_liveRegionChange(self, obj, nextHandler):
# 禁止QQ下载群文件一直吵个不停
if obj and obj.name.startswith('更新时间:'):return
Expand All @@ -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 ('取消发送语音')
chat.clickButton ('取消发送语音')

def terminate(self):
super().terminate()

5 changes: 3 additions & 2 deletions addon/appModules/qq/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 5 additions & 1 deletion addon/doc/zh_CN/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 许可下发布。

4 changes: 2 additions & 2 deletions buildVars.py
Original file line number Diff line number Diff line change
Expand Up @@ -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<[email protected]>",
# URL for the add-on documentation support
Expand All @@ -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!
Expand Down

0 comments on commit d120c00

Please sign in to comment.