Skip to content

UIA handler/notification event handling: fetch native window handle if present in runtime ID#16872

Closed
josephsl wants to merge 7 commits into
nvaccess:masterfrom
josephsl:UIANotificationNativeWindowFetch
Closed

UIA handler/notification event handling: fetch native window handle if present in runtime ID#16872
josephsl wants to merge 7 commits into
nvaccess:masterfrom
josephsl:UIANotificationNativeWindowFetch

Conversation

@josephsl

@josephsl josephsl commented Jul 17, 2024

Copy link
Copy Markdown
Contributor

Link to issue number:

Closes #16871

Summary of the issue:

NVDA does not handle UIA notification events for elements with native window handle despite shown in runtime ID.

Description of user facing changes

NVDA will announce UIA notifications in more apps such as Windows 11 Voice Access.

Description of development approach

Updated UIA handler/notification event handler:

  • Added type hints for event arguments.
  • If UIAHandler.handler.getNearestWindowHandle method returns None (for the sender), look at the sender's (element's) runtime ID tuple, returning early if the tuple is empty.
  • Pass in the just obtained native window handle to UIA NVDA object constructor.

Testing strategy:

Manual testing (see #16862 for details):

  1. Run Voice Access (Windows 11 2022 Update).
  2. After confugiring the microphone, press Space on "microphone active/sleep" button.
  3. Observe NVDA's UIA notificatoin event handler (should announce changed mic state).

Note that the test build must be installed or run with admin rights.

Known issues with pull request:

In some cases runtime ID is empty, and for that case, there's nothing NVDA can do. The native window handle check is done early for performance - if UIA NVDA object constructor does not see a window handle, the constructor will call UIAHandler.handler.getNearestWindowHandle method to obtain a window handle. Also, while more UIA notifications are announced, it may need some tweaks such as making sure the window handle is indeed a native window handle, and at least runtime ID tuple check is performed in this PR; these should be dealt with on a ase-by-case basis (as issues arise).

Code Review Checklist:

  • Documentation:
    • Change log entry
    • User Documentation
    • Developer / Technical Documentation
    • Context sensitive help for GUI changes
  • Testing:
    • Unit tests
    • System (end to end) tests
    • Manual testing
  • UX of all users considered:
    • Speech
    • Braille
    • Low Vision
    • Different web browsers
    • Localization in other languages / culture than English
  • API is compatible with existing add-ons.
  • Security precautions taken.

Summary by CodeRabbit

  • New Features

    • Added support for NVDA to announce additional UIA notifications in applications, such as Windows 11 Voice Access.
    • Expanded NVDA's utilization of events for caret movement across XAML, WPF text controls, and major browsers, ensuring more consistent and responsive feedback for text cursor positioning.
  • Documentation

    • Updated documentation to reflect the new enhancements and changes to caret movement handling and UIA notifications.

josephsl added 5 commits July 16, 2024 21:58
…w handle when the element reports nothing. Re nvaccess#16871.

Sometimes a UIA element may say there is no native window handle when in fact it is part of its runtime ID. Therefore fetch the runtime ID tuple if possible, logging the process along the way. If the runtime ID tuple (really an array) is empty, return early as there is nothing NVDA can do at this time.
…e window handle to UIA object constructor. Re nvaccess#16871.

Pass in the just located native window handle when instantiating UIA NVDA object to align with most event handlers 9such as focus handling).
@josephsl josephsl requested a review from a team as a code owner July 17, 2024 04:33
@josephsl josephsl requested a review from gerald-hartig July 17, 2024 04:33
@coderabbitai

coderabbitai Bot commented Jul 17, 2024

Copy link
Copy Markdown
Contributor

Walkthrough

The changes in this update focus on enhancing NVDA's handling of UIA notification events, particularly for Windows 11 Voice Access. This involves explicitly defining argument types for a function, updating a conditional block for handling native window handles, and ensuring NVDA does not give up on finding a native window handle when it's available through the UIA runtime ID.

Changes

File Change Summary
source/UIAHandler/__init__.py Updated the copyright year to 2024. Explicitly defined argument types for the IUIAutomationNotificationEventHandler_HandleNotificationEvent function and added a conditional block to handle native window handles for notifications.
user_docs/en/changes.md Documented enhancements regarding NVDA's use of events for caret movement in various controls and the announcement of additional UIA notifications in applications like Windows 11 Voice Access.

Sequence Diagram(s)

sequenceDiagram
    participant NVDA
    participant UIAHandler
    participant NVDAObjects

    NVDA->>UIAHandler: IUIAutomationNotificationEventHandler_HandleNotificationEvent
    UIAHandler->>NVDAObjects: Create UIA object with windowHandle and UIAElement
    Note right of NVDAObjects: If windowHandle is not found, use UIA runtime ID.
    NVDAObjects->>NVDA: Return UIA object
    NVDA->>User: Announce notification (e.g., "microphone active/sleep")
Loading

Assessment against linked issues

Objective Addressed Explanation
Ensure NVDA handles native window handles fetched via UIA runtime ID if not cached (#16871)
Update function argument types for clarity and correctness (#16871)
Document NVDA's new behavior regarding caret movement and UIA notifications (#16871)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share
Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Outside diff range, codebase verification and nitpick comments (2)
user_docs/en/changes.md (2)

Line range hint 1-3: Ensure consistency in change log entries.

The change log entry accurately reflects the PR's focus on enhancing event handling for caret movement. However, the style guideline of one sentence per line is not followed.

- * NVDA once again relies on events for caret movement in several cases, rather than only on manual querying of the caret position.
+ * NVDA once again relies on events for caret movement in several cases.
+ * This is rather than only on manual querying of the caret position.

21-21: Ensure clarity and completeness in documentation.

The entry mentions the enhancement related to UIA notifications but could be more descriptive about the specific functionality added.

- * NVDA can announce more UIA notifications in apps such as Windows 11 Voice Access.
+ * NVDA now supports enhanced announcement of UIA notifications in applications like Windows 11 Voice Access, improving accessibility.

@AppVeyorBot

Copy link
Copy Markdown

See test results for failed build of commit 6d0b097280

Comment thread source/UIAHandler/__init__.py Outdated
f"activityId={activityId}"
)
if any(runtimeID := sender.getRuntimeID()):
# Second item in runtime ID array is native window handle.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you say with certainty that this always applies?

@josephsl

josephsl commented Jul 17, 2024 via email

Copy link
Copy Markdown
Contributor Author

@josephsl

josephsl commented Jul 17, 2024 via email

Copy link
Copy Markdown
Contributor Author

…cess#16871.

Comment from Leonard de Ruijter: make sure we're dealing with the correct UIA runtime ID representatio (the one with native window handle has four elements; sometimes tuple item count can vary, including an empty tuple).
@LeonarddeR

Copy link
Copy Markdown
Collaborator

@coderabbitai is there anything you can say about the assumption that the window handle is part of an UIA runtime id?

@coderabbitai

coderabbitai Bot commented Jul 17, 2024

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@LeonarddeR, UI Automation (UIA) runtime IDs are unique identifiers assigned to automation elements, and their structure can vary depending on the element and the application providing the UIA implementation. Typically, a runtime ID is an array of integers, where the presence of a window handle as part of this array is not guaranteed but can occur, especially for elements associated directly with a window.

In the context of the NVDA changes you're reviewing, the assumption that a window handle could be part of the runtime ID is based on practical observations and specific use cases, such as with Windows 11 Voice Access. However, it's important to note that this is not a universal rule for all UIA elements. The modification made in the PR to check the runtime ID for a window handle when getNearestWindowHandle returns None is a way to ensure more robust handling of UIA elements that do include the window handle in their runtime IDs, particularly when other methods fail to retrieve this handle.

This approach helps in cases where the window handle is crucial for properly associating the UIA element with its corresponding UI context, enhancing NVDA's ability to provide accurate notifications and interactions.

@LeonarddeR

Copy link
Copy Markdown
Collaborator

I know that at least accesskit creates a runtime identifier based on its internal node ID. Therefore I don't think the current approach is feasible.
Why is NVDA unable to report notifications for objects without a window handle?

@josephsl

Copy link
Copy Markdown
Contributor Author

Hi,

Okay, let's see what Code Rabbit says.

@coderabbitai analyze other event handlers in UIA handler module and tell us why a window handle is a must.

Postscript: I'm doing this to train the bot and point out possible inaccuracies from AI summaries. Thanks.

@coderabbitai

coderabbitai Bot commented Jul 17, 2024

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@josephsl, after analyzing the event handlers in the source/UIAHandler module, it is evident that window handles play a crucial role in the accurate handling of UIA events. Here are the key points:

  1. Associating UIA Elements with UI Context:

    • Event handlers like IUIAutomationEventHandler_HandleAutomationEvent and IUIAutomationFocusChangedEventHandler_HandleFocusChangedEvent use window handles to associate UIA elements with their corresponding UI context. This association is essential for accurately processing events related to the UI elements.
  2. Ensuring Accurate Event Handling:

    • Window handles are used to verify whether an event should be accepted or ignored. For instance, in IUIAutomationEventHandler_HandleAutomationEvent, the window handle is checked to determine if the event should be processed (shouldAcceptEvent). This helps in filtering out irrelevant or duplicate events, improving the reliability of event handling.
  3. Performance Considerations:

    • Caching window handles can enhance performance by avoiding redundant computations. For example, getNearestWindowHandle caches the window handle to speed up subsequent queries for the same UIA element.
  4. Handling Special Cases:

    • In some cases, such as with Windows Defender Application Guard (WDAG), window handles are used to identify and handle remote UIA elements appropriately.

In summary, window handles are essential for ensuring that UIA events are accurately associated with their UI context, processed correctly, and handled efficiently. This is why the presence of a window handle is often checked and utilized in the event handlers within the UIAHandler module.


Analysis chain


Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for the UIA handler module and extract the implementation of event handlers to analyze the use of window handles.
fd "UIAHandler" --exec cat {} \; | grep -A 5 -B 5 "event handler"

Length of output: 109


Script:

#!/bin/bash
# Search for function definitions related to event handlers within the UIA handler module files to understand the use of window handles.
ast-grep --lang python --pattern $'def $_($_, $_, $_, $_, $_) -> $_:\n    $$$\n    # Event handler' --source-path source/UIAHandler

Length of output: 345


Script:

#!/bin/bash
# Correct the syntax to search for function definitions related to event handlers within the UIA handler module files.
ast-grep --lang python --pattern $'def $_($_, $_, $_, $_, $_) -> $_:\n    $$$\n    # Event handler' source/UIAHandler

Length of output: 116


Script:

#!/bin/bash
# Search for all function definitions within the UIA handler module files to identify event handlers and their use of window handles.
ast-grep --lang python --pattern $'def $_($_, $$$):\n    $$$' source/UIAHandler

Length of output: 176299


Learnings added
Learnt from: josephsl
URL: https://github.com/nvaccess/nvda/pull/16872

Timestamp: 2024-07-17T14:24:55.234Z
Learning: Window handles are crucial in the `UIAHandler` module for associating UIA elements with their UI context, ensuring accurate event handling, and improving performance.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@josephsl

josephsl commented Jul 17, 2024

Copy link
Copy Markdown
Contributor Author

Hi,

At least Code Rabbit gave some answers, but that's only part of the picture. It is correct that window handles are needed to identify UIA elements. More importantly, window handles are needed to construct a more accurate view of a UIA element, as without this, UIAHandler.handler.getNearestWindowHandle can fail to notice elements due to COM error while executing walkers. Also, creation of UIA NVDA object ensures consistency with IAccessible/IA2 objects and allows other components such as app modules and default object implementation to handle events, although creation of objects and a chain of events can take some time to complete (performance).

The heart of the issue is inaccurate UIA element (sender) information in apps such as Voice Access when they raise notification event. The fact that another assistive technology (Narrator) must be active to see correct sender information is concerning. While I agree that retrieving window handle through runtime ID is a fragile approach, it is one sure way to identify elements (window handle check was also performed via dev info (NVDA+F1) command).

Thanks.

@LeonarddeR

Copy link
Copy Markdown
Collaborator

The heart of the issue is inaccurate UIA element (sender) information in apps such as Voice Access when they raise notification event.

This makes me think about how we handle invalid window handles with WDAG. May be that approach should be generalized instead?

The fact that another assistive technology (Narrator) must be active to see correct sender information is concerning.
Could you elaborate?

@josephsl

Copy link
Copy Markdown
Contributor Author

Hi,

Generalizing WDAG approach: perhaps we could recycle that approach but to cover background events as well (WDAG is deprecated). For Voice Access, the most effective use case is dictation while not focused on Voice Access interface itself.

Comment on sender info with Narrator active: when you run Voice Access whiel using NVDA with debug logging on (with UIA included), you'll notice that NVDA will log that there is no window handle. However, when Narrator runs, notification is announced and the UIA object is created successfully; when Narrator exits, NVDA reverts to the inablity to obtain window handle.

Thanks.

Comment on lines +1063 to +1072
else:
# No runtime ID (tuple is empty).
if _isDebug():
log.debugWarning(
"HandleNotificationEvent: native window handle not found in runtime ID: "
f"NotificationProcessing={NotificationProcessing} "
f"displayString={displayString} "
f"activityId={activityId}"
)
return

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this be?

Suggested change
else:
# No runtime ID (tuple is empty).
if _isDebug():
log.debugWarning(
"HandleNotificationEvent: native window handle not found in runtime ID: "
f"NotificationProcessing={NotificationProcessing} "
f"displayString={displayString} "
f"activityId={activityId}"
)
return
else:
# No runtime ID (tuple is empty).
if _isDebug():
log.debugWarning(
"HandleNotificationEvent: native window handle not found in runtime ID: "
f"NotificationProcessing={NotificationProcessing} "
f"displayString={displayString} "
f"activityId={activityId}"
)
return

@josephsl

Copy link
Copy Markdown
Contributor Author

Hi,

Upon further investigation, while handling notification events, it turns out elements raising this event become "native" while Narrator is running, "non-native" otherwise. Therefore, this issue may require editing native UIA element fetcher (UIA handler) to tell NVDA to treat some elements as native even though it is not seen as such at first. While editing notification event handler could be a useful change, I think the issue lies deeper (this is why I'm not taking Sean's suggested edits yet).

Thanks.

@LeonarddeR

Copy link
Copy Markdown
Collaborator

May be it makes sense to contact Microsoft about this?

@josephsl

Copy link
Copy Markdown
Contributor Author

Hi,

Sure.

I have narrowed the issue to UIAHandler.handler.getNearestWindowHandle - the method that says yes or no about native UIA element depends on nearest window handle getter. In short, tree walker and window handle caching succeeds or fails in Voice Access while Narrator is enabled and disabled, respectively.

There is a related issue that also deals with UIA notification: input language switcher. For that one, runtiem ID is undefined (empty). Given that language switch can happen everywhere, I think we should revisit the decision to limit notification handling to focused app (I think our justification back then (annoyance) does not hold well); that's for another issue.

Thanks.

@michaelDCurran

Copy link
Copy Markdown
Member

I would be uncomfortable accepting this pr as there is no clear evidence that the second value is always a native window handle. Clearly there is something special going on between narrator and VoiceAccess. This really needs to be talked about with Microsoft, specifically the VoiceAccess team. I am happy to reach out to them through my other Microsoft contacts and get them connected with you @josephsl if that would help?

@josephsl

josephsl commented Jul 23, 2024

Copy link
Copy Markdown
Contributor Author

Hi,

I came to the same conclusion. At this point, there is no direction, and turns out the PR won't work with portable NVDA anyway. It could be a good use case for add-ons but not up to par with NVDA Core code quality. I think we could also raise a point about File Explorer/shell/input switch notification event as this is perhaps the weirdest element we came across (no runtime ID at all).

Thanks.

@michaelDCurran

Copy link
Copy Markdown
Member

Closing based on above comments. Hopefully it can be addressed in VoiceAccess itself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

UIA notification event handling: NVDA gives up if cached native window handle is not found despite its presence via runtime ID

5 participants