The Inside Story of NVDA: does NVDA sleep after pressing NVDA+Shift+S/Z? Not really #NVDA_Internals



The following Inside Story stems from the question posed in this thread: how does NVDA know to pass keyboard commands to the app while it is sleeping? The deeper question is, “is NVDA ‘sleeping’ when pressing NVDA+Shift+S/Z to toggle sleep mode?” The subject line gave the answer, and hopefully the below story should clarify as to why:

When you read the user guide, one of the commands you will come across is NVDA+Shift+S (laptop: NVDA+Shift+Z). This command toggles sleep mode where NVDA will not announce anything from the focused app, as well as passing keyboard commands to the application. But how? If you recall the Inside Story on NVDA components, NVDA consists of multiple parts, including input, output, event handling, and things in between, and parts of these components are turned off (not entered, technically) while NVDA is “sleeping” (notice the quotes, will explain shortly).

During normal business hours (when NVDA is not sleeping), NVDA will handle commands by itself or pass them to the application. First, after receiving a piece of keyboard input, NVDA converts what’s called “scan code” from the keyboard driver (as seen by Windows) to a form that NVDA can understand (typically to a string). Then, NVDA converts this into a gesture, which is then passed to input manager (inputCore.InputManager) which then runs the command associated with the gesture via “executeGesture” method (or function, if you will). The job of “execute gesture” routine is to figure out if there is a script for (bound to) the keyboard command you’ve entered, and if it does, it performs whatever you want NVDA to do; if not, it passes the keyboard input to the focused application unless NVDA is “taking a break” (or rather, you told NVDA to take a break a.k.a. sleep in the focused app).

In addition to keyboard commands, NVDA can react to events coming from the program. This is handled via “execute event” (eventHandler.executeEvent) routine where it checks various conditions to make sure it can react to whatever event it is handling at that moment. If the incoming event can be handled, NVDA then asks global plugins, app modules, tree interceptors, and NVDA objects (in that order) to handle current event in whatever shape or form, and this is where you will hear NVDA say something if any of the layers listed above tells the screen reader to do it.

But what happens when sleep mode is enabled, and if enabled, does it make NVDA “sleep”? The answer is no. Remember that whenever keyboard input is received by the screen reader, it wants to know if there is a command (script) associated with it. I did say that it will do something unless it is taking a break, and that happens to be checking a flag to see if NVDA should do nothing if the focused control and the app says, “go to sleep for now”. This attribute or a flag, called “sleepMode” (a true/false value) is defined in two places:

  1. NVDA object: a flag called NVDAObjects.NVDAObject.sleepMode tells NVDA to sleep while the control is focused. This flag is ultimately controlled by:
  2. App module: an app module can inform NVDA to sleep while it is being used (appModuleHandler.AppModule.sleepMode; does this flag seem familiar to some of you?).

Ultimately, sleep mode in NVDA depends on the sleep mode flag from the app module. The sleep mode toggle command goes something like this:

  1. NVDA+Shift+S/Z is pressed.
  2. Input manager realizes that there is a script associated with it, defined in global commands set as globalCommands.GlobalCommands.script_toggleCurrentAppSleepMode.
  3. At the script level, NVDA needs to know where you are at, so it fetches focused control and the app you are using (curFocus=api.getFocusObject(); curApp=curFocus.appModule).
  4. Checks sleep mode flag (if curApp.sleepMode), and if yes, it turns off sleep mode (curApp.sleepMode=False), says “sleep mode off” and performs a gain focus event (eventHandler.executeEvent("gainFocus",curFocus).
  5. If sleep mode is off to begin with (else), NVDA first performs a lose focus event (eventHandler.executeEvent("loseFocus",curFocus), enables sleep mode flag for the current app (curApp.sleepMode=True), then says “sleep mode on”.

What happens while NVDA is sleeping? From the input perspective:

  1. NVDA first checks if NVDA is frozen somehow (if watchdog.isAttemptingRecovery), and if yes, forgets the script (raise NoInputGestureAction), allowing NVDA to pass whatever keyboard command you have entered to the focused application.
  2. If a script is associated with the gesture (keyboard command, in this context; script = gesture.script), it checks where you are at (focus = api.getFocusObject()).
  3. It checks if sleep mode is on, either if the focused object says so (focus.sleepMode is focus.SLEEP_FULL) or the object and the apps says yes and the script is not supposed to be executed right now (focus.sleepMode and not getattr(script, 'allowInSleepMode', False))). The last part is a bit complicated as input gestures can inform NVDA that commands associated with it can be performed while in sleep mode (so far, sleep mode toggle command is the one people are most familiar with).
  4. If sleep mode is indeed on, NVDA will simply drop the gesture from itself and allow the focused app to handle it (raise NoInputGestureAction).

On the event handling side (somewhat tied to output):

  1. eventHandler.executeEvent is called, taking in the event (name) to be handled and where it is from (object).
  2. NVDA stays silent in the Windows lock screen (if objectBelowLockScreenAndWindowsIsLocked, taking a number of parameters).
  3. Otherwise, NVDA will find out if the event is coming from the focused control and a gain focus event is raised (isGainFocus = eventName == "gainFocus"), and if yes, it will check sleep mode flag (sleepMode=obj.sleepMode) and perform other preparations.
  4. If sleep mode is False, event executer (a Python generator) will be called, allowing various NVDA components to handle whatever event it has received, otherwise, NVDA will stay silent in the focused application.

I won’t go into output side of things (speech and braille) as it will touch NVDA Controller client (a DLL used by third-party applications to send speech and braille text to be announced by NVDA), but suffice to say that speech and braille announcements will be vetoed by NVDA if sleep mode is on for the focused application.

You may have noticed that sleep mode flag from app modules is what really makes NVDA do things or stay silent, more so when handling events. This is evident when you hear notifications from an app that is not the focused app (say, toast notifications) while sleep mode is active. This should answer a question I might come back to later: how can NVDA say things in the background? This is tied to event processing from foreground and background controls, more so when you think about background progress bar announcements (another time). For now, the key takeaway from this Inside Story is this: NVDA stays active even when you think it is sleeping as long as you don’t quit NVDA.

Hope this clarifies many things.



P.S. The code fragment comes from latest NVDA alpha snapshot (master branch in NVDA source code, and as of time of this post, destined for 2022.4).