Введение в разработку плагинов для NVDA

(По материалам NVDA Developer Guide)
Дата публикации:05.12.2013
Twitter Facebook Vkontakte

Предисловие

Это руководство содержит основополагающую информацию для новых разработчиков, желающих развивать компоненты NVDA. Отметим, что это только введение. Для получения полной информации следует обращаться к документации по исходному коду.

Программа NVDA и её компоненты в основной своей массе написаны на языке программирования Python. Обучения читателя этому языку программирования не входит в задачу данного руководства и нет никаких иллюзий по поводу того, что приводимые в этом руководстве примеры могут хоть сколько-нибудь помочь в изучении синтаксиса Python. Задача примеров заключается в том, чтобы продемонстрировать реализации методов и классов, необходимых для понимания принципов разработки плагинов NVDA. Изучать язык следует по соответствующим учебникам или документации на официальном сайте http://www.python.org.

Плагины

Плагины (plugins) позволяют программировать поведение NVDA как в целом, так и в определённых приложениях. Они способны:

Существует два типа плагинов:

  1. Модуль приложения (App Module) - код специфичный для конкретного приложения. Данный плагин получает все события для определённой программы, даже если она в данный момент не активна (не находится на переднем плане). Когда же приложение активно, любые команды, связанные с модулем приложения, могут быть вызваны по нажатию клавиш или через другие средства ввода.
  2. Глобальный плагин (Global Plugin) - код, дополняющий общий функционал NVDA, который используется во всех приложениях. Глобальные плагины получают все события для всех элементов управления операционной системы, а также все команды, связанные с таким плагином, будут выполняться не зависимо от активного в данный момент приложения.

Если требуется улучшить доступность конкретной программы, то следует использовать модуль приложения, если же требуется добавить какой-то общий функционал NVDA (например, скрипт, сообщающий о состоянии беспроводного соединения), то правильным выбором будет глобальный плагин.

Модули приложений и глобальные плагины имеют общий внешний вид. Они представляют собой исходные файлы Python (с расширением ".py"), оба определяют специальный класс, содержащий все обработчики событий, скрипты и привязки, и оба могут включать пользовательские классы для доступа к текстовой информации и сложным документам. Однако в отношении некоторых аспектов они имеют ряд отличий.

Ниже будут рассмотрены специфические черты модулей приложения и глобальных плагинов по отдельности, после чего изложение вернётся к общему их рассмотрению в контексте структурных элементов плагина.

Модули приложений

Модуль приложений имеет расширение ".py" и называется так же, как и основной исполняемый файл приложения, для которого создаётся плагин. Например, модуль для приложения Блокнот будет называться "notepad.py", так как основной исполняемый файл Блокнота носит название "notepad.exe".

Файлы модулей приложений следует помещать в каталог "appModules" внутри каталога пользовательских настроек NVDA.

Модули приложений должны определить класс с именем "AppModule", который наследуется от "appModuleHandler.AppModule". Этот класс может определять методы обработки событий, методы скриптов, жесты и прочий код. Подробнее это будет рассмотрено позже.

NVDA загружает модуль сразу, как только приложение, для которого он написан, активизируется. Выгружается модуль когда приложение закрывается или когда выгружается сама программа экранного доступа.

Следующий пример демонстрирует модуль приложения, который воспроизводит звук каждый раз, когда системный фокус переходит по элементам управления.

Исходный код можно скопировать в текстовый файл, который затем сохранить в каталоге "appModules" под именем "notepad.py". После этого следует перегрузить NVDA или её плагины.

В коде важно сохранить все отступы, так как их наличие является критичным для Python.

Для тестирования плагина следует запустить приложение Блокнот и перемещаться по пунктам меню. При каждом изменении фокуса, NVDA будет издавать звуковой сигнал, однако при перемещении вне окна Блокнота, звук воспроизводиться не будет.

# -*- coding: windows-1251 -*-
# Модуль приложения Блокнот для NVDA
# Пример 1 из Developer guide

import appModuleHandler

class AppModule(appModuleHandler.AppModule):

	def event_gainFocus(self, obj, nextHandler):
		import tones
		tones.beep(550, 50)
		nextHandler()

Код модуля приложения начинается с трёх строк комментариев, которые задают кодировку файла и кратко описывают его назначение.

Затем идёт импорт модуля "appModuleHandler", так что app-модуль получает доступ к основному классу "AppModule".

Далее определяется класс "AppModule", который унаследовал от "appModuleHandler.AppModule".

Внутри этого класса определяется один или несколько методов обработки событий, скрипты или назначаются жесты. В данном случае определён один метод для события "gainFocus" (event_gainFocus), который при каждом выполнении воспроизводит короткий звуковой сигнал.

Наиболее важной частью является сам класс, события подробно будут рассмотрены позже.

По завершению тестирования следует удалить файлы "notepad.py" и "notepad.pyo" и перегрузить NVDA или её плагины. После чего исходный функционал будет восстановлен. Аналогичную операцию необходимо выполнять для всех последующих примеров.

Глобальные плагины

Глобальные плагины обладают расширением ".py" и должны иметь уникальное имя, желательно отражающее их назначение.

Данные файлы должны быть помещены в каталог "globalPlugins" внутри каталога пользовательских настроек NVDA.

Глобальные плагены должны определить класс с именем "GlobalPlugin", который наследуется от "globalPluginHandler.GlobalPlugin". Этот класс может определять методы обработки событий , методы скриптов, привязки жестов и прочий код. Подробнее это будет рассмотрено позже.

NVDA загружает все глобальные плагены сразу после запуска и выгружает их только при завершении работы.

Следующий пример демонстрирует исходный код глобального плагина, который при нажатии комбинации NVDA+Shift+V объявит текущую версию программы экранного доступа.

После сохранения файла в директорию "globalPlugins" следует перегрузить NVDA или её плагины.

В коде важно сохранить все отступы, так как их наличие является критичным для Python.

Для тестирования плагина следует нажать комбинацию клавиш NVDA+Shift+V после чего будет произнесена информация о версии программы, причём это не зависит от активного приложения.

# -*- coding: windows-1251 -*-
# Глобальный плагин, объявляющий версию NVDA
# Пример 2 из Developer guide

import globalPluginHandler
import ui
import versionInfo

class GlobalPlugin(globalPluginHandler.GlobalPlugin):

	def script_announceNVDAVersion(self, gesture):
		ui.message(versionInfo.version)

	__gestures={
		"kb:NVDA+shift+v": "announceNVDAVersion",
	}

Код глобального плагина начинается с трёх строк комментариев, которые задают кодировку файла и кратко описывают его назначение.

Затем идёт импорт модуля "globalPluginHandler", так что глобальный плагин получает доступ к основному классу "GlobalPlugin".

Здесь же импортируется ещё несколько модулей, а именно "ui" и "versionInfo", которые требуются для выполнения поставленных задач.

Далее определяется класс с именем "GlobalPlugin", унаследованный от "globalPluginHandler.GlobalPlugin".

Внутри этого класса определяются один или несколько методов обработки событий, методы скриптов или назначаются жесты . В рассматриваемом примере определён метод скрипта, который получает данные о версии программы, а также осуществляется привязка клавиатурной команды NVDA+Shift+V для вызова скрипта.

Наиболее важной частью является сам класс, детали скрипта и назначения жеста не столь существенны.

По завершению тестирования следует удалить py- и pyo-файлы примера и перегрузить NVDA или её плагины. После чего исходный функционал будет восстановлен.

Объекты NVDA

NVDA представляет элементы управления и другие объекты графического интерфейса (GUI) как "объекты NVDA" (NVDA Objects). Объекты NVDA обладают стандартизованными свойствами, такими как метка, тип, значение, состояние или описание, которые позволяют обращаться к ним из кода программы. Например, кнопка "ОК" в диалоговом окне будет представлена как объект NVDA с меткой "ОК" и типом "button", а флажок с названием "Я согласен" будет иметь метку "Я согласен", тип "checkbox", и, если отмечен, состояние - "checked".

Поскольку существует много различных GUI инструментариев и платформ, а также API доступности, объекты NVDA сводят их в обобщённую форму, которую NVDA может использовать, независимо от инструментария или API, посредством которого реализован определённый элемент управления. Например, кнопка "ОК", упомянутая выше, могла быть виджетом в Java-приложении, объектом MSAA, объектом IAccessible2 или элементом UI Automation.

Объекты NVDA обладают многими свойствами. Некоторые из свойств, встречающихся чаще всего, являются:

Существуют также несколько упрощённых свойств навигации, такие как simpleParent, simpleNext, simpleFirstChild и simpleLastChild (упрощённый родительский, упрощённый следующий, первый упрощённый дочерний и последний упрощённый дочерний - соответственно). В целом они соответствуют аналогичным свойствам, описанным выше, но при их использовании NVDA автоматически отфильтровывает излишние объекты. Эти свойства используются при облегчённом режиме просмотра, который включён по умолчанию. Они могут быть проще в использовании, но полноценные свойства навигации более точно отражают основную структуру операционной системы.

При разработке плагинов, в большинстве случаев, не важно, каким инструментарием или API порождён рассматриваемый объект NVDA, потому что он, как правило, обладает только обобщёнными свойствами, такими как name, role и value. Тем не менее, в случае продвинутых плагинов, может возникнуть необходимость получения более углублённой информации.

Существует три основных способа использования объектов NVDA в плагинах:

  1. Большинство событийных плагинов строится как раз на том, что они принимают аргумент, являющийся объектом NVDA, в котором и произошло событие. Например, event_gainFocus принимает объект, представляющий собой попавший в фокус элемент управления.
  2. Методы скриптов, методы обработки событий или другой код могут получить объекты, представляющие интерес, такие как объект NVDA в фокусе, текущий объект навигатора или, возможно, объект на рабочем столе. Код затем может возвратить информацию от объекта или даже возвратить другой связанный с ним объект, например, родительский или дочерний, и так далее.
  3. Плагин может определить свои собственные (пользовательские) классы объектов NVDA, которые будут использоваться для обёртывания элемента управления, чтобы предать ему дополнительную функциональность, изменить его свойства и так далее.

Как и модули приложений или глобальные плагины, объекты NVDA также могут определить методы обработки событий, методы скриптов и назначить жесты.

Скрипты и назначение жестов

Модули приложений, глобальные плагины и объекты NVDA могут определить специальные методы, которые будут связаны с так называемыми жестами. Жесты -- это конкретный вариант пользовательского ввода, например, нажатие клавиш клавиатуры. NVDA ссылается на эти методы как на скрипты, поэтому далее будет использоваться именно этот термин.

Скрипт -- это всего лишь метод, имя которого начинается с префикса "script_", например, команда NVDA+F12 связана с методом script_sayDateTime.

Скрипт принимает два аргумента:

  1. self: ссылка на экземпляр модуля приложения, глобального плагина или объекта NVDA;
  2. gesture: объект, который соответствует жесту, вызывающему запуск скрипта.

Кроме определения самого скрипта, необходимо, в той или иной форме, определить сам жест и связать его со скриптом, чтобы программа NVDA знала, что должна выполнить скрипт.

Чтобы назначить жест на выполнение скрипта, специальный словарь Python "__gestures" может быть определён как переменная класса модуля приложения, глобального плагина или объекта NVDA. Эти словари должны содержать строковой идентификатор жеста, указывающий на имя запрашиваемого скрипта, причём без префикса "script_".

Существуют и продвинутые способы назначения жестов на основе более динамичных функций, но словарь жестов является наиболее простым.

Идентификатор жеста -- это простая строка, содержащая название команды. Она состоит из двухбуквенного кода, обозначающего источник ввода, необязательной конкретизации устройства в квадратных скобках, знака двоеточия (":") и одного или нескольких имён, разделённых знаком плюс ("+") и обозначающих клавиши клавиатуры или другие средства ввода.

Идентификаторы могут выглядеть следующим образом:

На момент написания этого материала, в NVDA доступны следующие источники ввода:

Когда программа NVDA получает данные о вводе, она ищет соответствующую привязку жеста в определённом порядке. После того, как жест найден, связанный с ним скрипт выполняется и никаких дополнительных действий не осуществляется, в противном случае жест передаётся напрямую операционной системе.

Порядок поиска жеста таков:

  1. Загруженные глобальные плагины;
  2. модуль активного приложения.
  3. Tree Interceptor объекта NVDA в фокусе (если существует), например, какой-либо virtualBuffer;
  4. объект NVDA в фокусе;
  5. глобальные команды (встроенные команды, например, команда завершения работы NVDA, команды объектной навигации и так далее).

При добавлении очередного скрипта необходимо помещать его описание в строке документации (docstring. Это описание должно пояснять пользователю действие скрипта. Например, описание будет озвучено в режиме подсказки по клавиатуре и показано в диалоге жестов. Чтобы добавить описание скрипта, необходимо атрибуту "__doc__" соответствующего скрипта присвоить строку с описанием. Скрипт не появится в диалоге жестов, если отсутствует описание.

Существует возможность указать категорию скрипта, чтобы в диалоге жестов его можно было группировать со схожими скриптами. Например, скрипт в глобальном плагине, который добавляет навигацию при помощи быстрых клавиш, может быть отнесён к категории "Browse mode" ("Режим просмотра"). Для отдельных скриптов это делается путём присваивания строки с именем категории атрибуту "category" соответствующего скрипта. Также можно установить атрибут "scriptCategory" класса плагина, который будет

использован для скриптов, для которых категория не указана. Существуют константы для общих категорий с префиксом CRCAT_ в модулях inputCore и globalCommands. Сценарий будут перечислены в указанной категории в диалоге жестов. Если категория не указана, то сценарии будут помещены в категорию "Miscellaneous" ("Разное").

Следующий глобальный плагин позволяет по нажатию NVDA+Стрелка_влево узнать класс текущего окна, а по NVDA+Стрелка_вправо его идентификатор. Данный пример показывает, как определить один или несколько скриптов и жестов в классе модуля приложения, глобального плагина или объекта NVDA.

# -*- coding: windows-1251 -*-
# Скрипты оконной утилиты для NVDA
# Пример 3 из Developer guide

import globalPluginHandler
import ui
import api

class GlobalPlugin(globalPluginHandler.GlobalPlugin):

	def script_announceWindowClassName(self, gesture):
		focusObj = api.getFocusObject()
		name = focusObj.name
		windowClassName = focusObj.windowClassName
		ui.message("class for %s window: %s" % (name, windowClassName))

	def script_announceWindowControlID(self, gesture):
		focusObj = api.getFocusObject()
		name = focusObj.name
		windowControlID = focusObj.windowControlID
		ui.message("Control ID for %s window: %d" % (name, windowControlID))

	__gestures = {
		"kb:NVDA+leftArrow": "announceWindowClassName",
		"kb:NVDA+rightArrow": "announceWindowControlID",
	}

События

Когда NVDA обнаруживает события, приходящие от операционной системы или различных API вспомогательных технологий, то взамен порождается обобщённое событие для плагинов и объектов NVDA.

Несмотря на то, что большинство событий связаны с конкретным объектом NVDA (например, изменение его имени, получение фокуса, изменение состояния, и так далее), эти события могут быть обработаны на различных уровнях.

Когда событие обработано, его движение по цепочке обработчиков прекращается. Однако код внутри обработчика события может изменить такое поведение и распространять событие дальше, если это необходимо.

Событие переходит с уровня на уровень, пока не будет найден соответствующий метод обработки события. Иерархия уровней такова:

Событие -- это всего лишь реализация метода, имя которого начинается с префикса "event_", за которым следует фактическое наименование события, которое этот метод обрабатывает (например, gainFocus). Такой метод может принимать различные аргументы в зависимости от того, на каком из уровней обработки событий он определён. Если метод определён в классе самого объекта NVDA, то он принимает только один обязательный аргумент self (ссылка на экземпляр объекта NVDA). Обработчики некоторых событий могут принимать дополнительные аргументы, но это случается редко.

Если метод обработки события определён в глобальном плагине, модуле приложения или Tree Interceptor, то он принимает следующие аргументы:

Некоторые распространённые события объектов NVDA:

Есть много других событий, но перечисленные выше -- самые востребованные при разработке плагинов.

Пример исходного кода модуля приложения, содержащего обработчик события, можно посмотреть в примере 1 (звуковой сигнал при перемещении фокуса в приложении Блокнот).

Переменная SleepMode в модуле приложения

Модуль приложения имеет полезное свойство под именем "sleepMode", которое если его установить в true, почти полностью отключает NVDA в этом приложении. "Режим сна" (sleep mode) полезен в тех случаях, когда приложение способно озвучивать само себя и имеет собственные средства чтения экрана, а также в некоторых играх, когда требуется полный контроль над клавиатурой.

Несмотря на то, что пользователь может включать или отключать "режим сна" при помощи комбинации NVDA+shift+s, разработчики могут установить "режим сна" по умолчанию для определённого приложения.

Нижеследующий код можно скопировать в текстовый файл, а затем сохранить в каталоге appModules под именем, соответствующим тому приложению, в котором необходимо перевести NVDA в "режим сна". Как всегда, файл модуля должен иметь расширение .py.

# -*- coding: windows-1251 -*-
# Пример 4 из Developer guide

import appModuleHandler

class AppModule(appModuleHandler.AppModule):

	sleepMode = True

Пользовательские классы объектов NVDA

Предоставление пользовательских классов объектов NVDA, пожалуй, является самым действенным способом улучшить доступность приложения в плагине NVDA. Этот метод позволяет размещать всю необходимую логику для конкретного элемента управления целиком в одном классе объекта NVDA, а не рассеивать код для многих элементов управления по всем плагинам, обрабатывающим события.

Достаточно сделать два шага, чтобы предоставить пользовательский класс объекта NVDA:

Существует множество базовых классов NVDAObject, которые можно использовать для определения пользовательского класса NVDAObject. Эти базовые классы содержат основную поддержку вспомогательных технологий или ОС API, например, win32, MSAA или Java Access Bridge. При разработке плагина необходимо, как правило, наследовать пользовательский

класс NVDAObject от наивысшего базового класса в иерархии наследования. Например, если пользовательский класс

NVDAObject будет использоваться, когда

Имя класса окна " edit" и идентификатор элемента управления 15, то имеет смысл породить пользовательский класс от NVDAObjects.window.Window , так как очевидно, что данный объект является окном. Если предполагается использовать свойство accRole объекта MSAA, то разумно будет в качестве базового класса выбрать NVDAObjects.IAccessible.IAccessible.

Кроме того, выбор базового класса зависит от того, какие свойства предполагается переопределить в пользовательском классе NVDAObject. Например, если необходимо переопределить специфические свойства IAccessible, такие как shouldAllowIAccessibleFocusEvent, то в качестве базового класса необходимо поставить NVDAObjects.IAccessible.IAccessible .

Метод chooseNVDAObjectOverlayClasses должен быть реализован в классе модуля приложения или глобального плагина. Этот метод принимает три аргумента:

Внутри этого метода необходимо проверить свойства объекта NVDA и, если они удовлетворяют определённому условию, добавить в список классов пользовательский класс NVDAObject, который, как правило, помещается в самом начале списка. Кроме того, можно удалять из списка классы, добавленные NVDA, хотя это требуется крайне редко.

Ниже приведён исходный код модуля приложения для Блокнота, который предоставляет команду для озвучивания количества символов в поле редактирования. Для активации этой команды необходимо использовать комбинацию NVDA+l. Обратите внимание, что команда специфична только для полей редактирования; то есть это работает только если фокус находится в поле редактирования, а не где-нибудь ещё.

Исходный код можно скопировать и сохранить в каталоге appModules в текстовом файле под именем notepad.py.

# -*- coding: windows-1251 -*-
# Пример 5 из Developer Guide

import appModuleHandler
from NVDAObjects.IAccessible import IAccessible
import controlTypes
import ui

class AppModule(appModuleHandler.AppModule):

	def chooseNVDAObjectOverlayClasses(self, obj, clsList):
		if obj.windowClassName == "Edit" and obj.role == controlTypes.ROLE_EDITABLETEXT:
			clsList.insert(0, EnhancedEditField)

class EnhancedEditField(IAccessible):

	def script_reportLength(self, gesture):
		ui.message("%d" % len(self.value))

	__gestures = {
		"kb:NVDA+l": "reportLength",
	}

Небольшие изменения объектов NVDA в модулях приложения

Иногда достаточно сделать совсем немного, чтобы изменить мир к лучшему... Например, переопределить имя или роль объекта NVDA в конкретном приложении. В таких случаях нет необходимости создавать пользовательский класс объекта NVDA. Достаточно использовать событие NVDAObject_init, доступное только в модуле приложения.

Метод event_NVDAObject_init принимает два аргумента:

Внутри этого метода необходимо проверить релевантность объекта NVDA и переопределить его свойства соответствующим образом.

Ниже приведён пример исходного кода модуля приложения для Блокнота, в котором для поля редактирования имя переопределено на "content". Когда поле редактирования получит фокус, NVDA скажет "content edit".

Приведённый исходный код можно скопировать и поместить в текстовый файл в каталоге appModules, а затем сохранить этот файл с именем notepad.py.

# -*- coding: windows-1251 -*-
# Пример 6 из Developer Guide

import appModuleHandler
from NVDAObjects.window import Window

class AppModule(appModuleHandler.AppModule):

	def event_NVDAObject_init(self, obj):
		if isinstance(obj, Window) and obj.windowClassName == "Edit" and obj.windowControlID == 15:
			obj.name = "Content"


Распространение материалов сайта означает, что распространитель принимает условия лицензионного соглашения.
Идея и реализация: © Владимир Довыденков и Анатолий Камынин,  2004-2017
Rambler's Top100