Saturday, August 15, 2015

StationPlaylist Add-on Internals: Life of the Studio app module from birth to death and activities in between

Now you have a bit of an overview of the add-on, let's dive into the core components: SPL app module and support modules. In this article, we'll go through life of the SPL Studio app module: from start to finish and everything in between. We'll cover more specific features of the app module in the next few Internals articles.
Note: Due to indirect requests, I'll try including some portions of the source code to let you better understand how something works (mostly pseudo code will be provided). Also, certain things will require explaining how NVDA Core (the screen reader itself) works (so you'll learn several things at once).
SPL Studio app module: design and code overview
As noted previously, the SPL Studio app module (splstudio/ consists of several sections. These include (from top to bottom):
* Imports: Many modules from Python packages and from NVDA screen reader are imported here, including IAccessible controls support, configuration manager and so on.
* Layer command wrapper: I talked about how layer commands work in a previous article, and the "finally" function at the top is the one that makes this possible.
* Few helper functions and checks: This includes a flag specifying minimum version of Studio needed, the cached value for Studio window handle (SPLWin) and place holders for threads such as microphone alarm timer (more on this in threads article). This section also includes helper functions such as "messageSound" (displays a message on a braille display and plays a wave file) and other helper functions.
* Track item overlay classes: two classes are provided to support Playlist Viewer items in Studio 5.0x and 5.10, respectivley. We'll come back to these objects later.
* App module class: This is the core of not only the app module, but the entire add-on package. The app module class (appModules.splstudio.AppModule) is further divided into sections as described in add-on design article.
Let's now tour the lifecycle of the app module object in question.
Before birth: NVDA's app module import routines
Before we go any further, it is important for you to understand how NVDA loads various app modules. This routine, available from source/ (NVDA Core), can be summarized as follows:
1. If a new process (program) runs, NvDA will try to obtain the process ID (PID) for the newly loaded process.
2. Next, NvDA will look for an app module matching the name of the executable for the newly created process. It looks in various places, including source/appModules, userConfigDirectory/appModules and addonname/appModules, then resorting to the default app module if no app module with the given name is found.
3. Next, NvDA will attempt to use Python's built-in __import__ function to load the app module, raising errors if necessary. No errors means the app module is ready for use.
4. Once the newly loaded module is ready, NVDA will instantiate appModule.AppModule class (make it available). If a constructor (__init__ method) is defined, Python (not NVDA) will call the app module constructor (more on this below).
In case the app module's AppModule class has a constructor defined, Python will follow directions specified in the constructor. Just prior to performing app module specific constructor routines, it is important to call the constructor for the default app module first as in the following code:
def __init__(self, *args, **kwargs):
 super(AppModule, self).__init__(*args, **kwargs)
This is a must because the default app module constructor performs important activities, including:
1. The default app module constructor will call another base constructor (this time, it is baseObject.ScriptableObject, containing gestures support among other important properties).
2. Initializes various properties, such as PID (process ID), app module name (if defined), application name and the handle to the app in question via kernel32.dll's OpenProcess function (XP/Server 2003 and Vista/Server 2008 and later requires different arguments).
3. Lastly, the constructor initializes process injection handle and helper binding handle in case such routines are required.
Birth: SPL Studio app module construction
Certain app module add-ons ship with an app module with a constructor define, and SPL Studio is one of them. After calling the base constructor as described above, SPL app module's constructor (__init__ method that runs when the app module starts) does the following:
1. Checks whether a supported version of Studio is running, and if not, raises RuntimeError exception, preventing you from using the app module while an unsupported version of Studio is in use (as of add-on 5.3/6.0, you need to use Studio 5.00 and later).
2. NvDA announces, "Using SPL Studio version 5.01" if Studio 5.01 is in use (of course, NVDA will say 5.10 when Studio 5.10 is in use). This is done via ui.message function (part of NVDA Core) which lets you hear spoken messages or read the message on a braille display. In reality, ui.message function calls two functions serially (one after the other): speech.speakMessage (speaking something via a synthesizer) and braille.handler.message (brailling messages on a braille display if connected).
3. Next, add-on settings are initialized by calling splconfig.initConfig(). This is done as follows:
A. Loads a predefined configuration file named userConfigPath/splstudio.ini. In add-on 6.0 and later, this is known as "normal profile). This is done by calling splconfig.unlockConfig() function that handles configuration validation via ConfigObj and Validator.
B. For add-on 6.0 and later, loads broadcast profiles from addonDir/profiles folder. These are .ini files and are processed just like the normal profile.
C. Each profile is then appended to splconfig.SPLConfigPool, a list of profiles in use. Then the active profile is set and splconfig.SPLConfig (user configuration map) is set to the first profile in the configuration pool (normal profile; for add-on 5.x and earlier, there is just one profile so append step is skipped).
D. If errors were found, NvDA either displays an error dialog (5.x and earlier) or a status dialog (6.0 and later) detailing the error in question and what NvDA has done to faulty profiles. This can range from applying default values to some settings to resetting everything to defaults.
4. Starting with NVDA 2015.3, it became possible for an app module to request NvDA to monitor certain events for certain controls even if the app is not being used. This is done by calling eventHandler.requestEvents function with three arguments: process ID, window class for the control in question and the event to be monitored. For earlier versions of NvDA (checked via built-in hasattr function), this step is skipped, and background status monitor flag is then set accordingly. We'll come back to event handling in a future installment.
5. Next, GUI subsystem is initialized (NVDA uses wxPython). This routine adds an entry in NVDA's preferences menu entitled "SPL Studio Settings", the add-on configuration dialog.
6. Lastly, as described in the previous article on SPL Studio handle, the app module will look for the window handle for the Studio app.
Life of the app module: events, commands and output
Once the Studio app module is ready, you can then move to Studio window and perform activities such as:
* Press commands, and NvDA will respond by either opening a dialog or speaking what it did.
* Announce status changes such as microphone status.
* Find tracks.
* Examine information in columns via Track Dial.
* Listen to progress of a library scan in the background.
* Perform SPL Assistant gestures.
* For 6.0 and later, manage broadcast profiles (we'll talk about broadcast profiles in configuration management article).
Death: termination routines
While using Studio add-on, you can stop using the add-on in various ways, including exiting or restarting NVDA, turning off your computer or logging off or closing Studio. Just like initialization routines, the Studio app module has specific directions to follow when add-on is closed.
Here is a list of steps Studio app module performs when it is about to leave this world:
1. The "terminate" method is called. Just like the startup (constructor) routine, this method first calls the terminate method defined in the default app module, which closes handles and performs other closing routines.
2. Calls splconfig.saveConfig() function to save add-on settings. This function goes through following steps in add-on 6.0:
A. Any global settings used by the active profile is first copied to normal profile.
B. Profile-specific settings are then saved to disk.
C. Finally, normal profile is saved and various flags, active profile and config pool is cleared.
D. For add-on 5.x and earlier, there is only one broadcast profile to worry about, and this profile is saved at this point.
3. NVDA then attempts to remove SPL Studio Settings entry from NVDA's preferences menu, then various maps used by Studio add-on (such as Cart Explorer map) are cleared.
4. As the app module is laid to rest, the window handle value for Studio window is cleared. This is a must, as the handle will be different next time Studio runs. At this point, NVDA removes splstudio (Studio app module) from list of app modules in use.
By this point, you should have a better understanding of life of an app module such as SPL Studio. Many add-ons, especially those that ships with app modules go through similar lifecycle as described above.
Now that we know how Studio app module is born and dies, it is time for us to look at what happens while the Studio add-on is alive, and we'll start with how Studio add-on announces time, work with alarms and uses basic settings.
1. OpenProcess (kernel32.dll) reference (Windows API):
2. wxPython online docs:

No comments:

Post a Comment