Èñïîëüçîâàíèå pyatspi2 äëÿ èññëåäîâàíèÿ ãðàôè÷åñêèõ ïîëüçîâàòåëüñêèõ èíòåðôåéñîâ

Äàòà ïóáëèêàöèè:06.04.2023
Ïîäåëèòüñÿ â Twitter Ïîäåëèòüñÿ â F******k Ïîäåëèòüñÿ â VKontakte Ïîäåëèòüñÿ â Telegram Ïîäåëèòüñÿ â Mastodon

Îñíîâîé äëÿ ñòàòüè ïîñëóæèë ìàòåðèàë PyAtSpi2Example.

Ââåäåíèå

Ñïåöèôèêàöèÿ AT-SPI ïðåäñòàâëÿåò ñîáîé íàáîð ïðîòîêîëîâ è ñîãëàøåíèé î âçàèìîäåéñòâèè îïåðàöèîííîé ñèñòåìû, ïðèêëàäíûõ ïðîãðàìì è âñïîìîãàòåëüíûõ òåõíîëîãèé ñ öåëüþ îáåñïå÷èòü äîñòóï ê ãðàôè÷åñêîìó ïîëüçîâàòåëüñêîìó èíòåðôåéñó äëÿ ëèö ñ ðàçëè÷íûìè îãðàíè÷åíèÿìè çäîðîâüÿ, íàðóøàþùèìè ðàáîòó îðãàíîâ ÷óâñòâ, êîãíèòèâíûå ôóíêöèè è / èëè êîîðäèíàöèþ äâèæåíèé. AT-SPI ïðèìåíÿåòñÿ â ãðàôè÷åñêèõ ïîëüçîâàòåëüñêèõ îêðóæåíèÿõ äèñòðèáóòèâîâ GNU/Linux è ñòàíäàðòèçîâàíà â ãðóïïå ñòàíäàðòîâ ISO/IEC 13066.

Äëÿ òîãî ÷òîáû èçó÷èòü, êàê ýëåìåíòû ïîëüçîâàòåëüñêîãî èíòåðôåéñà ïðåäñòàâëåíû ñðåäñòâàìè AT-SPI äëÿ âñïîìîãàòåëüíûõ òåõíîëîãèé, ïîäîéä¸ò ìîäóëü pyatspi, ñîäåðæàùèé ïðèâÿçêè AT-SPI äëÿ python. Òàêæå pyatspi áóäåò ïîëåçåí äëÿ ñîçäàíèÿ àâòîìàòèçèðîâàííûõ òåñòîâ äîñòóïíîñòè ïîëüçîâàòåëüñêîãî èíòåðôåéñà ïðèëîæåíèÿ è äëÿ ïðîâåðêè ðàáîòîñïîñîáíîñòè ïîëüçîâàòåëüñêîãî èíòåðôåéñà ïðèëîæåíèÿ â öåëîì. Ñòîèò îòìåòèòü, ÷òî âî îïåðàöèîííûõ ñèñòåìàõ èíôðàñòðóêòóðà ïîääåðæêè âñïîìîãàòåëüíûõ òåõíîëîãèé ÷àñòî ñîâìåùåíà ñî ñðåäñòâàìè àâòîìàòè÷åñêîãî òåñòèðîâàíèÿ ïîëüçîâàòåëüñêîãî èíòåðôåéñà, ïîñêîëüêó îáå çàäà÷è áàçèðóþòñÿ íà ìàøèíî÷èòàåìîé èíôîðìàöèè, êîòîðóþ î ñâî¸ì èíòåðôåéñå ïðåäîñòàâëÿåò ïðèëîæåíèå è êîòîðóþ çàòåì ïîæíî èñïîëüçîâàòü ëèáî äëÿ ïðîâåðêè ðàáîòîñïîñîáíîñòè âèäæåòîâ (íàïðèìåð, èíñòðóìåíò Accerciser), ëèáà äëÿ ïðåäñòàâëåíèÿ åãî ïîëüçîâàòåëþ íåâèçóàëüíûì ñïîñîáîì (íàïðèìåð, ïðîãðàììà ýêðàííîãî äîñòóïà Orca, èñïîëüçóþùàÿ ñèíòåç ðå÷è è òàêòèëüíûé âûâîä).

 ïàêåòíûõ áàçàõ ìíîãèõ äèñòðèáóòèâîâ GNU/Linux óæå èìåþòñÿ ãîòîâûå ñáîðêè pyatspi. Íàïðèìåð, â Ubuntu pyatspi ìîæíî óñòàíîâèòü êîìàíäîé ñ ïðàâàìè root:

apt install python-pyatspi

Èññëåäîâàíèå äåðåâà AT-SPI

 ñèñòåìàõ ãðàôè÷åñêîãî ïîëüçîâàòåëüñêîãî èíòåðôåéñà ýëåìåíòû, êàê ïðàâèëî, îðãàíèçîâàíû â âèäå èåðàðõè÷åñêîãî äåðåâà ñî ñâÿçÿìè ðîäèòåëü (êîíòåéíåð) — ïîòîìêè, êîòîðûå ñàìè ìîãóò áûòü êîíòåéíåðíûìè ýëåìåíòàìè äëÿ ïîòîìêîâ ñëåäóþùåãî óðîâíÿ, à ìîãóò è íå ñîäåðæàòü äî÷åðíèõ ýëåìåíòîâ. Ïî ýòîé ïðè÷èíå ïðåäñòàâëåíèå ïîëüçîâàòåëüñêîãî èíòåðôåéñà äëÿ öåëåé âñïîìîãàòåëüíûõ òåõíîëîãèé (assistive technology) âûïîëíÿåòñÿ â âèäå èåðàðõè÷åñêîãî äåðåâà, êîòîðîå ÷àñòî íàçûâàþò äåðåâîì äîñòóïíîñòè (accessibility three). Ñïåöèôèêàöèÿ AT-SPI íå ÿâëÿåòñÿ èñêëþ÷åíèåì è ýêñïîíèðóåò ýëåìåíòû óïðàâëåíèÿ (âèäæåòû) ïîëüçîâàòåëüñêîãî èíòåðôåéñà â âèäå èåðàðõè÷åñêîãî äåðåâà AT-SPI, íàâèãàöèþ ïî êîòîðîìó ìîæíî îñóùåñòâëÿòü, èñïîëüçóÿ API ìîäóëÿ pyatspi.

Êîíå÷íî, ñíà÷àëà íóæíî èìïîðòèðîâàòü ïðîñòðàíñòâî èì¸í ìîäóëÿ pyatspi:

import pyatspi

Çàòåì ìîæíî ïåðå÷èñëèòü àêòèâíûå ïðèëîæåíèÿ:

desktop = pyatspi.Registry.getDesktop(0)
for application in desktop:
print(application.name)

Òåïåðü ïîñìîòðèì, ÷ò÷òî ïðåäñòàâëÿåò ñîáîé ïðèëîæåíèå â ñìûñëå ïîëüçîâàòåëüñêîãî èíòåðôåéñà:

a = desktop[0]
print(a.name)
for o in a:
print(o)

Ïðèìåð âûâîäà (çàâèñèò îò òîãî, êàêèå ïðèëîæåíèÿ îòêðûòû âî âðåìÿ âûïîëíåíèÿ êîäà):

gucharmap
[frame | Character Map]

Ñëåäîâàòåëüíî, ýòî ïðèëîæåíèå gucharmap, êîòîðîå èìååò îäíî îêíî (ôðåéì), ñ çàãîëîâêîì «Character Map». Ýòîò îáúåêò, êàê è îñòàëüíûå ýëåìåíòû äåðåâà AT-SPI, ïîçâîëÿåò âçàèìîäåéñòâîâàòü ñ îáúåêòàìè äîñòóïíîñòè (accessibility object).

Èòàê, ÷òî ìîæíî ïîëó÷èòü:

for o in a:
print(o.role, o.name)

Âûâîä:


 Character Map

Ýòî çíà÷åíèÿ ñâîéñòâ îáúåêòà äîñòóïíîñòè, ñâÿçàííîãî ñ îêíîì ïðèëîæåíèÿ (ôðåéìîì) — ðîëü è íàèìåíîâàíèå.

Òåïåðü Ïåðåéä¸ì íà îäèí óðîâåíü ãëóáæå:

frame = a[0]
for o in frame:
print(o)

Âûâîä:

[panel | ]
[menu bar | ]

Ñëåäîâàòåëüíî, ôðåéì ñîäåðæèò ïàíåëü è ñòðîêó ìåíþ.

Òåïåðü ïîêàæåì ñîäåðæèìîå ïàíåëè:

panel = frame[0]
for o in panel:
print(o)

Âûâîä:

[status bar | U+10000 LINEAR B SYLLABLE B008 A]
[text | test]
[push button | Copy]
[label | Text to copy:]
[split pane | ]
[filler | Font]

Ïàíåëü ñîäåðæèò, ïîìèìî ïðî÷åãî, ïîëå äëÿ ââîäà òåêñòà è ìåòêó. Ó íèõ ðàçíûå ðîëè, íî ýòè îáúåêòû ðåàëèçóþò èíòåðôåéñ äëÿ ïîëó÷åíèÿ òåêñòîâîãî ñîäåðæèìîãî, ÷òî ïîçâîëÿåò ìàíèïóëèðîâàòü ýòèì ñîäåðæèìûì:

t = panel[1]
l = panel[3]
print(t.role)
print(l.role)
t_text = t.queryText()
l_text = l.queryText()

Ïîäðîáíåå î âçàèìîäåéñòâèè ñ òåêñòîâûì ñîäåðæèìûì îáúåêòîâ äîñòóïíîñòè ñì.: pydoc pyatspi.Text

print(t_text.caretOffset)
print(l_text.caretOffset)

Îáðàòèòå âíèìàíèå, ÷òî .name âèäæåòà ìîæåò íå îòëè÷àòüñÿ, à ìîæåò îòëè÷àòüñÿ îò åãî ñîäåðæèìîãî: íàïðèìåð, ïîëÿ äëÿ ââîäà òåêñòà îáû÷íî èìåþò ìåòêó, êîòîðàÿ ñîîáùàåò, äëÿ ÷åãî ïðåäíàçíà÷åíà òåêñòîâîå ïîëå, òàê ÷òî .name áóäåò ñîäåðæàòü òåêñò ìåòêè, à òåêñòîâîå ïîëå áóäåò ñîäåðæàòü ââåä¸ííûé òåêñò.

Îò äî÷åðíåãî óçëà ìîæíî âåðíóòüñÿ ê ðîäèòåëüñêîìó:

p = t.parent
print(p == panel)

Äëÿ òîãî ÷òîáû îòñëåæèâàòü èçìåíåíèÿ, ïðîèñõîäÿùèå ñ ýëåìåíòàìè äåðåâà AT-SPI, ðàññìîòðèì, êàê ðåàãèðîâàòü íà ñîáûòèÿ...

Ðåàêöèÿ íà ñîáûòèÿ

Ñîáûòèÿ AT-SPI — ýòî ñïîñîá óâåäîìèòü âñïîìîãàòåëüíûå òåõíîëîãèè îá èçìåíåíèÿõ, êîòîðûå ïðîèçîøëè â ïîëüçîâàòåëüñêîì èíòåðôåéñå ïðèëîæåíèÿ. Òàêèå èçìåíåíèÿ ìîãóò áûòü âûçâàíû äåéñòâèÿìè ïîëüçîâàòåëÿ (àêòèâàöèÿ ýëåìåíòîâ, ïåðåìåùåíèå ôîêóñà, ââîä òåêñòà è ò.ä.) èëè ñàìèì ïðèëîæåíèåì (âñïëûâàþùèå óâåäîìëåíèÿ, ñîîáùåíèÿ â ñòðîêå ñòàòóñà è ò.ä.).

Íàïðèìåð, ÷òîáû ñëåäèòü çà ïåðåìåùåíèåì òåêñòîâîãî êóðñîðà (êàðåòêè), íåîáõîäèìî çàðåãèñòðèðîâàòü îáðàáîò÷èê ñîîòâåòñòâóþùåãî ñîáûòèÿ:

def f(e):
print(e.source, " got caret ", e.detail1)
pyatspi.Registry.registerEventListener(f, "object:text-caret-moved")
pyatspi.Registry.start()

Âûâîä ïîñëå íåêîòîðûõ äåéñòâèé ïîëüçîâàòåëÿ:

[text | ] got caret 2
[text | ] got caret 1
[text | ] got caret 0
[entry | ] got caret 23
[entry | ] got caret 24
[entry | ] got caret 25

Ýòîò âûâîä ïîêàçûâàåò, ÷òî ïîëüçîâàòåëü ïåðåìåñòèë êóðñîð âíóòðè òåêñòîâîãî âèäæåòà, à çàòåì â âèäæåò çàïèñè.

Ñõîæèì îáðàçîì ìîæíî îòñëåæèâàòü ïåðåìåùåíèå ôîêóñà:

def f(e):
print(e.source, " got focus " if e.detail1 else " lost focus")
pyatspi.Registry.registerEventListener(f, "object:state-changed:focused")
pyatspi.Registry.start()

è ýòî ïîçâîëèò ïîëó÷èòü èíòåðåñíûé âûâîä äëÿ ïîñëåäóþùåé îáðàáîòêè.

Ìîæíî îòñëåæèâàòü âñå âàðèàíòû èçìåíåíèÿ ñîñòîÿíèÿ, çàðåãèñòðèðîâàâ îáðàáîò÷èêè äëÿ object:state-changed è ò.ä. Ñïèñîê ñîáûòèé ìîæíî óçíàòü èç XML-ñïåöèôèêàöèè áàçîâîãî ïðîòîêîëà dbus: https://github.com/GNOME/at-spi2-core/blob/master/xml/Event.xml. Íàïðèìåð, ñîáûòèå xml Object StateChanged dbus ñîîòâåòñòâóåò ñòðîêå "object:state-changed" ïðè ðåãèñòðàöèè îáðàáîò÷èêà â pyatspi2.

Ññûëêè



Ðàñïðîñòðàíåíèå ìàòåðèàëîâ ñàéòà îçíà÷àåò, ÷òî ðàñïðîñòðàíèòåëü ïðèíÿë óñëîâèÿ ëèöåíçèîííîãî ñîãëàøåíèÿ.
Èäåÿ è ðåàëèçàöèÿ: © Âëàäèìèð Äîâûäåíêîâ è Àíàòîëèé Êàìûíèí,  2004-2026