We have arrived at our last station stop for Studio app module internals: configuration management. This facility allows a broadcaster to configure various options such as alarms, column announcement order and so on, as well as package settings for a show as a broadcast profile to be invoked during the show. Along the way you'll learn how NVDA screen reader stores various settings, what happens if something goes wrong and get into internals of how broadcast profiles work.
ConfigObj: NVDA's configuration manager assistant
NVDA uses ConfigObj library to manage settings. This Python module, inspired by Python's own Config Parser, allows developers to store settings in a text file, read and interpret settings and validate options against default configuration options.
NVDA comes with a collection of default options. They live in source/config/__init__ and are used for various things, including presenting preferences, validating user configuration and so on. The config management module also includes facilities to handle profiles (a package of user settings to be used in an app, during say all or reserved for manual activation).
NVDA configuration profiles: multiple configuration files, one online database
A number of users asked NV Access if it would be possible to have profiles where certain settings can take effect while one is using apps or during say all. NV Access listened and introduced configuration profiles in late 2013. As of August 2015, one can create a manual or an automated (triggered) profile, with the latter further divided into say all profile and app-specific one.
Configuration profiles involve a few support routines and a careful coordination between configuration files. In essence, each configuration profile (stored in profiles folder in user configuration folder) is a snapshot of differences between the profile and the main user configuration file (named nvda.ini). When a profile becomes active, NVDA will load the profile file associated with the given profile and modify user settings according to values stored in the newly activated profile, and wwill record the name of the profile file to remind itself as to which profile is active (the default user configuration profile is named "normal configuration" with the file name of nvda.ini).
What if settings had errors? As part of the startup routine (portions of main function (source/core.py) prior to entering the main loop), NVDA will display a configuration error dialog if it detects serious issues with configuration values (in reality, ConfigObj notifies NVDA of this problem). You'll see this is also implemented in the Studio app module to deal with add-on configuration issues.
All about Studio add-on Configuration Manager
Until a few months ago, Studio app module handled all add-on configuration routines. With the advent of add-on 5.0 which introduced add-on settings dialog, configuration management routines were split into a dedicated Configuration Manager (splstudio.splconfig). The new module takes care of configuration routines, including validating the user configuration, presenting add-on settings dialog and other dialogs inside it, handling broadcast profiles and more. We'll cover each routine in this article.
How settings are loaded, used and saved
As mentioned in an article on life of the Studio app module, one of the things the app module does is load the add-on configuration database by calling splconfig.initConfig function. The job of this function is to load the add-on configuration map from various places (for add-on 5.x, it will be the main configuration map only, while 6.0 also searches appModules/profiles folder to load broadcast profiles). The format of the configuration file is that of a typical ini file, and as far as NVDA is concerned, it is a dictionary.
When the configuration database is ready, Studio app module will then use values stored in this settings dictionary to perform various tasks, including microphone alarm, announcing listener count and so on. If multiple profiles are defined, NVDA will start with the first configuration map (normal profile), and the active profile is denoted by splconfig.SPLConfig map (more on profiles in a moment).
After you are done using Studio, close Studio so settings can be saved to disk. This involves saving individual profiles, copying global settings to the normal profile and saving the normal profile to disk.
The Studio Add-on Settings Dialog
Studio app module allows you to configure various settings in two ways: via a shortcut key (discussed in an article on configuring basic settings) or via the settings dialog. When you use a shortcut key to change settings, NVDA will look up the value for the setting, change it, announce the new setting and store the newly changed value in the settings map.
Alternatively, you can configure settings via the add-on settings dialog (Control+NVDA+0). As it is a settings dialog (powered by gui.SettingsDialog), it will look just like any NVDA preferences dialog. For some advanced options, this dialog is the only gateway to access them (covered below).
The add-on settings dialog (splconfig.SPLConfigDialog) contains following options:
* Broadcast profile controls (add-on 6.0 and later): inspired by NVDA screen reader's configuration profiles dialog, this group of controls shows a list of profiles loaded and buttons to create a brand new profile or a copy of an existing profile, rename and delete profiles. It also contains a button (really a toggle button) that tells NVDA to switch to the selected profile upon request (more on this in a second).
* Global settings: these are settings not affected by profiles. These include status announcements, column announcement order, announcing listener count, toggling Track Dial, library scan options and so on.
* Profile-specific settings: Currently alarm settings are profile-specific. These are end of track alarm and the option to use this alarm, song ramp (intro) time and the setting to use this alarm, and microphone alarm. In case of microphone alarm, Studio stores this value as an integer in the map while it is shown as a string in the add-on settings dialog. For other alarm settings, it is a spin control (wx.SpinCtrl; use up or down arrow keys to change them).
* Reset settings: NVDA will ask if you wish to reset settings in the currently active profile back to factory defaults. This is done by using a function in splconfig module (splconfig.resetConfig) that will set current profile values to defaults (a default configuration map is included for this purpose; this map uses a configuration specification (confspec, part of defining validation routine via validator module (a close friend of ConfigObj), and this confspec is defined in the splconfig module).
When you press Control+NVDA+0 from Studio to open this dialog, the following will happen:
1. Just like alarm dialogs (see previous articles), NVDA will make sure no other dialogs are opened.
2. It'll then call a function in splconfig module, which in turn will prepare the dialog to be shown.
3. The preparation routine (SettingsDialog.makeSettings) will populate the dialog with controls and options associated with each control, with current options coming from configuration values stored in the active profile.
4. Once the dialog is ready, it'll pop up and you'll land on the status message checkbox or list of active profiles depending on the add-on version (former is 5.x, latter is 6.0). You can then use typical dialog navigation commands to navigate through various options.
After configuring some settings, click OK. NVDA will then locate the selected profile and tell SPLConfig to use this profile, then store options from the settings dialog into the configuration map. There is one showstopper, however: if you enter a wrong value into the microphone alarm, NVDA will tell you to enter something else and will return you to microphone alarm field.
In case you discard new settings (clicking Cancel), NVDA will check to see if an instant switch profile is defined, and if so, it'll perform various operations depending on whether the instant profile has been renamed or deleted.
All about broadcast profiles
In Studio app module world, a broadcast profile (usually shortened to profile) is a group of settings to be used in a show. This is achieved by using a configuration profile list (splconfig.SPLConfigPool) for storing these profiles, and one of them is used at any given time (by default, the first profile).
There are two ways of creating a profile: brand new or as a copy. Both uses the same dialog (splconfig.NewProfileDialog), with the difference being the base profile in use. For a brand new profile, settings from the normal profile will be used (minus profile-specific settings, which are set to default values), and for a copy, the new profile will contain all settings from the base profile. In both cases, a memory resident profile will be created and initialized just like other profiles (splconfig.unlockConfig, taking the name of the new profile as a parameter); this was done to reduce unnecessary disk writes. Also, new/copy profile dialog (and other dialogs invoked from the main configuration dialog) will disable the main settings dialog.
In case the selected profile is deleted, the profile will be removed from the profiles list, the configuration file associated with the profile will be deleted (if it exists) and normal profile will be set as the active profile. In case of a rename operation, it'll look for a profile with the old name and change some properties to reflect name change. There is an important step the app module will performed if an instant switch profile is renamed or deleted (if renamed, the instant profile variable will hold the new name, and if deleted, instant profile value will be None).
How does instant profile switching work
An instant switch profile is a profile to be switched to if told by the user. This is used before you connect to a streaming server to load settings appropriate for a show (as of time of this writing, only one can be selected as an instant switch profile; to define this profile, select a profile to be used as a show, then go to profile switching button and select it).
Once a profile is defined as an instant switch profile (via add-on settings dialog), invoke SPL Assistant layer and press F12. NVDA will call splconfig.instantProfileSwitch, which will perform the following:
1. Performs some checks, including:
* Checks if an instant switch profile is defined.
* If a profile is defined, it'll make sure you are not using the instant switch profile yet.
2. Saves the index of the active profile.
3. Locates the name of the instant switch profile and the profile associated with it and switches to the switch profile (reassigns SPLConfig to use the switch profile).
4. If told to return to the previously active profile, it'll tell SPLConfig to use the previously active profile (the index for the previously active profile is located and is used to pull the profile with the given index from the config pool).
Configuration management, if used right, could open up new possibilities and allow you to tailor the program to your liking. Studio add-on for NVDA is no exception to this idea: the configuration management routines discussed above allows you to configure how NVDA acts while using Studio, create profiles to be used in a show and so forth. In essence, all the features we talked about so far (library scan, Track Dial and so on) cannot become personal unless ability to configure settings is in place, and with broadcast profiles, the fun is just beginning.
This article concludes a detailed tour of Studio app module internals. The rest of this series will focus on SPL Studio Utilities global plugin, encoder support and a few thoughts on how the add-on is developed, starting with a tour of SPL Controller layer commands.
1. Configparser (Python documentation, Python Software Foundation): https://docs.python.org/2/library/configparser.html
2. ConfigObj documentation: http://www.voidspace.org.uk/python/configobj.html
3. Validate module documentation: http://www.voidspace.org.uk/python/validate.html
4. Spin control (wx.SpinCtrl) documentation (WXPython): http://wxpython.org/Phoenix/docs/html/SpinCtrl.html