NVDAと格闘する
ZennのScrapから移植
https://github.com/nvaccess/nvda
appModulesの中には、それぞれのアプリケーションごとの処理が入ってそう
explorer.pyのevent_UIA_elementSelectedだったら、エクスプローラーでタブが切り替わったときに読み上げる処理とか
braille.なんとかで点字用の処理も行っていそう
なんかaria-errormessageがChromeとFirefoxでサポートされてるっぽいので、読んでみる
https://github.com/nvaccess/nvda/pull/16411
a11ysupportにはまだ反映されていなさそう
https://a11ysupport.io/tech/aria/aria-errormessage_attribute
_get_errorMessage
IAccessibleHandler.RelationType.ERRORがaria-errormessageに対応する
ref: https://www.w3.org/TR/core-aam-1.2/#ariaErrorMessage
Relation: IA2_RELATION_ERROR points to accessible nodes matching IDREFs, if the referenced objects are in the accessibility tree
_getIA2RelationFirstTargetで、最初に見つかったそれを取ってきてる。どこからかは知らない
https://github.com/nvaccess/nvda/blob/13cb733684960127c58c33a013abbb2d1b88bb8c/source/NVDAObjects/IAccessible/__init__.py#L2001
NVDAHelper.localLib.nvdaInProcUtils_getTextFromIAccessibleでcppのコードを呼び出し、getRelationElementsOfTypeで取り出したerrorTargetからgetTextFromIAccessibleでIAから情報をもらっていそう
https://github.com/nvaccess/nvda/blob/13cb733684960127c58c33a013abbb2d1b88bb8c/source/NVDAObjects/IAccessible/__init__.py#L2231
https://github.com/nvaccess/nvda/blob/13cb733684960127c58c33a013abbb2d1b88bb8c/nvdaHelper/remote/IA2Support.cpp#L363
https://github.com/nvaccess/nvda/blob/13cb733684960127c58c33a013abbb2d1b88bb8c/nvdaHelper/remote/textFromIAccessible.cpp#L65
source/speech/speech.pyの方を見てみる
一番上の変更差分であるgetObjectPropertiesSpeechから
https://github.com/nvaccess/nvda/blob/13cb733684960127c58c33a013abbb2d1b88bb8c/source/speech/speech.py#L641
Objectはおそらく、引数にもらってるobjであるNVDAObjectを示している。こいつが持ってるプロパティを一通りスピーチする関数なのだろう
NVDAObjectはここらへんで定義されてるやつ。PRの説明文にもあるように、今回これにerrorMessageというプロパティが新しく追加されたので、これにさっきの_get_errorMessageしたやつを入れて、読み上げさせる的な感じなのだと考えた
https://github.com/nvaccess/nvda/blob/13cb733684960127c58c33a013abbb2d1b88bb8c/source/NVDAObjects/\_\_init\_\_.py#L205
が、_get_errorMessageが他のところで出てこないので、謎
getObjectPropertiesSpeechはすぐ上のspeakObjectPropertiesで使われて返り値がspeechSequenceに入り、speak(speakObjectPropertiesされる
https://github.com/nvaccess/nvda/blob/13cb733684960127c58c33a013abbb2d1b88bb8c/source/speech/speech.py#L1068
speak関数を追っていくと、SpeechManagerのgetSynth().speak(seq)という箇所にたどり着く。SynthDriverというものを使って音を流しているらしい
もう少しgetObjectPropertiesSpeechを見ていく
code:python
name == "errorMessage" and value and State.INVALID_ENTRY not in obj.states
で、後半はまあコメントに書いてあるように、NVDAObjectがaria-validというstateを持っていないときみたいな感じで、nameがなんなのかを見る。
nameはallowedProperties.items()がforで回されていて、allowedPropetiesは引数で渡されている
あとで出てくるので一応書いておくと、ここでnewPropertyValues["errorMessage"] = Noneを設定している
**allowedPropertiesの**ってなんだっけ?ダブルポインタ?と思いつつ、そういえばPythonのspread構文みたいな感じのやつだったことを思い出す
speakObjectPropertiesの引数がそのまま渡されてるっぽいので、speakObjectPropertiesで検索をかけると、例えばsource/browseMode.pyだと以下のような使われ方
code:python
speech.speakObjectProperties(
self.rootNVDAObject,
name=True,
states=True,
reason=OutputReason.FOCUS,
)
name=True?nameってstringじゃないの?と思いながらちょっと考えてみた結果、「objで渡されたものの中からkey=Trueであるkeyのみ取得する」という理解になったけど、理屈はよく分からなかった
name=True, states=Trueで渡したらallowedPropertiesは{name: True, states: True}になっちゃうのでは
次にgetPropertiesSpeechの↓を見ていく
code:python
errorMessage: str | None = propertyValues.get("errorMessage", None)
getObjectPropertiesSpeechでさっき作ったnewPropertyValuesをgetPropertiesSpeechに渡して、speechSequenceを作っている
https://github.com/nvaccess/nvda/blob/13cb733684960127c58c33a013abbb2d1b88bb8c/source/speech/speech.py#L765-L766
さっきNoneを渡してたんだからNoneのような気もするが、getPropertiesSpeechは色んなところで使われているので、今回関係あるところだとここ↓でも使われている
https://github.com/nvaccess/nvda/blob/13cb733684960127c58c33a013abbb2d1b88bb8c/source/speech/speech.py#L2309
ちょっと上でattrsからerrorMessageを取っているので、ここではちゃんとエラーメッセージが入る可能性がある
https://github.com/nvaccess/nvda/blob/13cb733684960127c58c33a013abbb2d1b88bb8c/source/speech/speech.py#L2244-L2246
attrsはgetControlFieldSpeechが複数使われているgetTextInfoSpeechで定義されていそう
info.getTextWithFieldsから取ったtextWithFieldsの中身であるfieldのfield.fieldをinitialFieldsに入れ、その中身のfieldをnewControlFieldStackに入れ、その中身のfieldをattrsとして使っていそう
https://github.com/nvaccess/nvda/blob/13cb733684960127c58c33a013abbb2d1b88bb8c/source/speech/speech.py#L1524
https://github.com/nvaccess/nvda/blob/13cb733684960127c58c33a013abbb2d1b88bb8c/source/speech/speech.py#L1613-L1614
そういえば、ファイル検索するときはincludeディレクトリとuser_docsディレクトリをexcludeするといい感じになった
_get_errorMessageがどこで実行されてどのタイミングでerrorMessageプロパティに値が入るのかが分からなかった
AutoPropertyObjectというのだと、_get_から始まるやつの返り値が勝手にプロパティに代入されそう
https://github.com/nvaccess/nvda/blob/13cb733684960127c58c33a013abbb2d1b88bb8c/source/baseObject.py#L123
なんか多分これ以上追ってもしょうがないけど、一応attrsについてもう少し調べたのでまとめ
attrsの型であるControlFieldは、ここ↓に書かれてる通り、table, button, formなど、テキストを包含するcontrolのaccessible nameやroleなどの情報を保有するclassらしい
https://github.com/nvaccess/nvda/blob/13cb733684960127c58c33a013abbb2d1b88bb8c/source/textInfos/\_\_init\_\_.py#L51-L55
Dictを継承しているので、先に出てきたようなerrorMessage = attrs.get("errorMessage", None)みたいな処理が可能になっている
newControlFieldStackはList[ControlField]型
textWithFieldsの中身であるfieldがFieldCommandで、そのfield.fieldがinitialFieldsになり、それとnewControlFieldStackの型が同じ
多分どこかでいい感じにNVDAObjectを取ってきて、attrsにしてる
L{Field}みたいな表記ってList[Field]か
https://github.com/nvaccess/nvda/blob/13cb733684960127c58c33a013abbb2d1b88bb8c/source/textInfos/\_\_init\_\_.py#L243-L244