Skip to content

Show the status of add-ons in the system config when copying user config to system config#19808

Merged
SaschaCowley merged 28 commits into
betafrom
i19550
Mar 23, 2026
Merged

Show the status of add-ons in the system config when copying user config to system config#19808
SaschaCowley merged 28 commits into
betafrom
i19550

Conversation

@SaschaCowley

@SaschaCowley SaschaCowley commented Mar 18, 2026

Copy link
Copy Markdown
Member

Link to issue number:

Closes #19550

Summary of the issue:

Since #19446, users can select which add-ons should be copied to the system-wide configuration when replacing it with their current user configuration. However, they cannot see whether an add-on is already present in the system configuration, and if so which version is present.

Description of user facing changes:

  • A new "System-wide version" column has been added to the copy add-ons dialog, which shows the version of the add-on present in the system config, if any.
  • The "Version" column in the same dialog has been renamed to "User version" to disambiguate it from the new "System-wide version" column.
  • The "Author" column in this dialog has been removed to cut down on verbosity. Author information is still available via the "About add-on..." button.
  • The list of add-ons now contains entries for add-ons which are only present in the system-wide configuration. In the case of these add-ons, the list item does not have an associated checkbox, and the name is prefixed with "[remove]".
  • The text in the dialogs has been updated to reflect that add-ons only present in the system configuration will be removed when replacing it with the user config.
  • Add-ons in the user configuration are checked by default if they also exist in the system-wide configuration.

Description of developer facing changes:

None in public API

Description of development approach:

  • Added a function to get add-on manifests from a directory without side-effects. This is similar to existing functionality, and this code should probably be refactored to reduce duplication. However, given how close we are to a release, this approach is safer.
  • Updated _CopyAddonsDialog to take two dictionaries - one mapping add-on names to Addon objects from the user config; and one mapping add-on names to AddonManifest objects from the system config (gathered with the new new _getAddonManifestsFromPath function).
  • Updated the prereqs for showing the copy add-ons dialog. It now requires that the user and system add-ons between them contain at least one add-on.
  • Created a tuple of (user AddonManifest, system AddonManifest) pairs, and used that to populate the add-ons list. This allows us to show details of the user and system add-ons.
  • Created a subclass of wx.ListCtrl which allows removing the checkbox from specific list items.
    • This works by sending a window message to the underlying win32 ListView which sets the item's state image to 0 (0 is no checkbox, 1 is unchecked and 2 is checked).
    • It also replaces the window proc for the containing dialog so that it can intercept and reject item state changing messages from the list control where those notifications represent attempting to check or uncheck an item which has had its checkbox removed. In all other cases, the original window proc is called.
    • Finally, it overrides the IsItemChecked implementation, since the win32 macro that wx uses internally returns evaluates to true if the state image is set to 0.
  • Used this new control to show the list of add-ons. In the case of add-ons that are only present in the system config, the checkbox is removed. Since the list of all add-ons is constructed in such a way that these add-ons are strictly after add-ons that are present in the user config, the logic regarding getting copyable add-on IDs elsewhere in the dialog doesn't need to change.
  • Throughout the dialog, where there is no clear way to decide whether to use the user or system copy of an add-on, the user version is preferred, and the system copy is used as a fallback.

Testing strategy:

Tested interacting with the dialog with various combinations of add-ons in the user and system configurations, and that in all cases the expected list of add-on IDs was returned. Testing was done by calling from gui.addonStoreGui.controls.messageDialogs import _getAddonsToCopy;_getAddonsToCopy(None) in the python console when running from source. _getAddonsToCopy was slightly modified to force it to use %ProgramFiles%\nvda\systemConfig\addons.

Known issues with pull request:

None known

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.

@SaschaCowley SaschaCowley added this to the 2026.1 milestone Mar 18, 2026
@SaschaCowley SaschaCowley self-assigned this Mar 18, 2026
@wmhn1872265132

Copy link
Copy Markdown
Contributor

Can you consider removing the author column from the dialog? Some Add ons have very long authors, which requires listening to a lot of useless information to obtain the checkbox status
Not chosen

@CyrilleB79

Copy link
Copy Markdown
Contributor

I have given a try to this for early feedback.

Checkbox list

Mixing items with and without checkbox in the same list is not the best UX, more because:

  1. The presence of the checkbox is reported after the whole item is being read but users manly want to hear the add-on's name and its state (copy / do not copy).
  2. having uncheckable items in the list will probably not be very frequent, actually only when you have already copied an add-on in system config and then you have removed it from user config; this makes more less opportunities for the user to get used to this strange UX.

Instead of the checkable items, could we add an action column containing "Copy" / "Do not copy" value for add-ons not already in the system-wide config and "Delete" / "Update" for add-ons already in the system-wide config. This could be the second column. And pressing space could allow to toggle the value in the column. At last, for add-ons only in system config but not in user one, you'd only have "Delete" value with no possibility to toggle it.

About add-on button

Regarding the "About add-on..." button: Does it open the information of the user version or the system-wide version?

Dialog introduction text

I was already of the opinion that the intro message of the "Copy add-ons" dialog was poorly worded and had to be reworded. This is more true with this new experience / feature.
I may provide a suggestion once the UX of the dialog is validated.

Extra note

By the way, you should not use:
systemAddonsDir = os.path.join("c:", "Program Files", "nvda", "systemConfig", "addons")
but (note the backslash after "c:":
systemAddonsDir = os.path.join("c:\\", "Program Files", "nvda", "systemConfig", "addons")
Strange, isn't it? Thanks to old MS-DOS syntax subtilities...

Mixing items with and without checkbox in the same list is not the best UX, more because:

  1. The presence of the checkbox is reported after the whole item is being read but users manly want to hear the add-on's name and its state (copy / do not copy).
  2. having uncheckable items in the list will probably not be very frequent, actually only when you have already copied an add-on in system config and then you have removed it from user config; this makes more less opportunities for the user to get used to this strange UX.

Instead of the checkable items, could we add an action column containing "Copy" / "Do not copy" value for add-ons not already in the system-wide config and "Delete" / "Update" for add-ons already in the system-wide config. This could be the second column. And pressing space could allow to toggle the value in the column. At last, for add-ons only in system config but not in user one, you'd only have "Delete" value with no possibility to toggle it.

Regarding the "About add-on..." button: Does it open the information of the user version or the system-wide version?

@SaschaCowley SaschaCowley marked this pull request as ready for review March 19, 2026 04:25
@SaschaCowley SaschaCowley requested review from a team as code owners March 19, 2026 04:25
@SaschaCowley

Copy link
Copy Markdown
Member Author

@wmhn1872265132

Can you consider removing the author column from the dialog?

Done. I was already thinking of doing this, so I'm glad I wasn't the only one. The author information is still available through the "About add-on..." button.

@SaschaCowley

Copy link
Copy Markdown
Member Author

@CyrilleB79

Mixing items with and without checkbox in the same list is not the best UX, more because:

  1. The presence of the checkbox is reported after the whole item is being read but users manly want to hear the add-on's name and its state (copy / do not copy).

This goes for all checkboxes in NVDA.

  1. having uncheckable items in the list will probably not be very frequent, actually only when you have already copied an add-on in system config and then you have removed it from user config; this makes more less opportunities for the user to get used to this strange UX.

Note that I have removed the author column, so there is less verbosity before you hear (or rather don't hear) that there is no checkbox. I've also added a prefix to add-ons without checkboxes.

Instead of the checkable items, could we add an action column containing "Copy" / "Do not copy" value for add-ons not already in the system-wide config and "Delete" / "Update" for add-ons already in the system-wide config. This could be the second column. And pressing space could allow to toggle the value in the column. At last, for add-ons only in system config but not in user one, you'd only have "Delete" value with no possibility to toggle it.

Possibly in a future enhancement of this dialog. For now, what we have uses features already in wx or the underlying win32 controls. Your proposal would be more difficult (though by no means impossible) to implement.

Regarding the "About add-on..." button: Does it open the information of the user version or the system-wide version?

User version, falling back to system version. In a future enhancement perhaps we could use a tabbed dialog to present both.

By the way, you should not use: systemAddonsDir = os.path.join("c:", "Program Files", "nvda", "systemConfig", "addons") but (note the backslash after "c:": systemAddonsDir = os.path.join("c:\\", "Program Files", "nvda", "systemConfig", "addons") Strange, isn't it? Thanks to old MS-DOS syntax subtilities...

TIL! It happened to work here, but looking at the output it was indeed wrong. Thanks for the tip :)

@wmhn1872265132

Copy link
Copy Markdown
Contributor

Perhaps disabling the checkbox is better than removing it? At least this ensures consistency in behavior, that is, all items have checkboxes.

@SaschaCowley

Copy link
Copy Markdown
Member Author

@wmhn1872265132

Perhaps disabling the checkbox is better than removing it? At least this ensures consistency in behavior, that is, all items have checkboxes.

That is what I would prefer too, but to the best of my knowledge it isn't possible with a win32 list-view control.

@CyrilleB79

Copy link
Copy Markdown
Contributor

Note that I have removed the author column, so there is less verbosity before you hear (or rather don't hear) that there is no checkbox.

Yes, I agree with removing this column too; I was about to suggest it myself too.

I've also added a prefix to add-ons without checkboxes.

Nice addition. This strongly mitigates the difficulty that user could encounter when discovering this list with unusual mixing of checkbox items with no-checkbox items.

Instead of the checkable items, could we add an action column containing "Copy" / "Do not copy" value for add-ons not already in the system-wide config and "Delete" / "Update" for add-ons already in the system-wide config. This could be the second column. And pressing space could allow to toggle the value in the column. At last, for add-ons only in system config but not in user one, you'd only have "Delete" value with no possibility to toggle it.

Possibly in a future enhancement of this dialog. For now, what we have uses features already in wx or the underlying win32 controls. Your proposal would be more difficult (though by no means impossible) to implement.

I do not understand why it would be more difficult from a GUI point of view; it's just about removing the use of checkbox items and add a column containing the corresponding information in text.
Anyway, given your previous modifications (prepending with [remove]" and removing the Author column), this is already addressed.

In the case of add-ons not present in user config, I've just thought another design.
Since the initial description is becoming a bit long in this case, we may split it and the add-ons list in two:

  • 1st list: Add-ons of the user config, that you can copy or not in system config, with checkable items
  • 2nd list: Add-ons of system config which are not in user config and which will be removed in any case. Would just be an information list with standard non-checkable items.

It's just an improvement idea, but the current design that you have is already acceptable. You may want to ship this PR as is so that all beta PRs be quickly fixed.

@seanbudd seanbudd requested a review from Copilot March 19, 2026 07:55

Copilot AI 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.

Pull request overview

This PR enhances the “Copy settings to system-wide configuration” flow by showing which add-ons already exist in the system-wide config (and their versions), and by surfacing add-ons that will be removed when replacing the system config with the user config.

Changes:

  • Updates the copy add-ons dialog UI/UX: adds “System-wide version”, renames “Version” to “User version”, removes “Author”, and lists system-only add-ons as “[remove]” without checkboxes.
  • Adds lower-level Win32 list-view plumbing (new CommCtrl bindings + a list control that can suppress per-item checkboxes) to support the UI behavior.
  • Introduces a side-effect-free helper to enumerate add-on manifests from a directory for system-config inspection.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
user_docs/en/userGuide.md Updates user guide text to describe new dialog behavior and columns.
user_docs/en/changes.md Updates changelog entry regarding deprecated WinUser structures.
source/winUser.py Deprecates NMHdrStruct in favor of winBindings.user32.NMHDR.
source/winBindings/user32.py Adds Win32 APIs/types needed for wndproc subclassing + defines NMHDR.
source/winBindings/CommCtrl.py Adds CommCtrl list-view constants/structs for checkbox state manipulation.
source/gui/nvdaControls.py Adds _CheckListCtrl that can remove item checkboxes by intercepting notifications.
source/gui/addonStoreGui/controls/messageDialogs.py Updates _CopyAddonsDialog to show user/system versions and system-only removal rows.
source/addonHandler/init.py Adds _getAddonManifestsFromPath and _loadManifest for manifest-only directory scans.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread source/winBindings/commCtrl.py
Comment thread source/winUser.py Outdated
Comment thread source/winBindings/CommCtrl.py Outdated
Comment thread source/gui/nvdaControls.py Outdated
Comment thread source/addonHandler/__init__.py Outdated
Comment thread source/addonHandler/__init__.py Outdated
Comment thread source/gui/nvdaControls.py
Comment thread source/gui/addonStoreGui/controls/messageDialogs.py Outdated
Comment thread user_docs/en/userGuide.md Outdated
Comment thread user_docs/en/changes.md Outdated
@CyrilleB79

Copy link
Copy Markdown
Contributor

Another small issue:
If the add-on is in the user config, you display the visible name, as expected, e.g. "Character Information".
If the add-on is only in the system config, but not in the user config, the add-on ID (e.g. "charInfo") is displayed instead of the visible name.

@SaschaCowley SaschaCowley marked this pull request as draft March 19, 2026 23:25
@SaschaCowley

Copy link
Copy Markdown
Member Author

@CyrilleB79

If the add-on is in the user config, you display the visible name, as expected, e.g. "Character Information". If the add-on is only in the system config, but not in the user config, the add-on ID (e.g. "charInfo") is displayed instead of the visible name.

Fixed. Good catch, thank you.

@SaschaCowley SaschaCowley marked this pull request as ready for review March 20, 2026 02:49
Comment thread user_docs/en/userGuide.md Outdated
Comment thread source/addonHandler/__init__.py Outdated
Comment thread source/gui/addonStoreGui/controls/messageDialogs.py Outdated

@Qchristensen Qchristensen left a comment

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.

Docs look good

@SaschaCowley SaschaCowley requested a review from seanbudd March 20, 2026 04:16
@SaschaCowley SaschaCowley merged commit e260538 into beta Mar 23, 2026
72 of 75 checks passed
@SaschaCowley SaschaCowley deleted the i19550 branch March 23, 2026 05:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants