Migrate GUI from tkinter to wxPython for VoiceOver accessibility#1
Open
payown wants to merge 9 commits intoacbnational:mainfrom
Open
Migrate GUI from tkinter to wxPython for VoiceOver accessibility#1payown wants to merge 9 commits intoacbnational:mainfrom
payown wants to merge 9 commits intoacbnational:mainfrom
Conversation
1. Remove [project] table from pyproject.toml The [project] table conflicted with setup.py metadata. Newer setuptools tried to merge both and crashed with an AttributeError because long_description, install_requires, and extras_require were defined in setup.py but not declared as dynamic in pyproject.toml. Removing [project] lets setup.py be the single source of truth; [tool.ruff] and [tool.pyright] sections are unaffected. 2. Gracefully handle macOS root requirement in hotkeys The keyboard library's listen() unconditionally checks os.geteuid() == 0 on macOS. Without root, it raises OSError in a background thread and triggers a SIGTRAP that kills the process. Now we mirror that same check in register() so hotkey registration is skipped gracefully, and install a threading.excepthook safety net. The app starts normally without hotkeys and logs a clear warning instead of crashing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
tkinter's Tcl/Tk backend does not implement Apple's NSAccessibility protocol, making the Settings and Status windows completely invisible to VoiceOver on macOS. wxPython uses native Cocoa widgets (and native Win32 on Windows), so controls automatically participate in the OS accessibility hierarchy — fixing VoiceOver and improving JAWS/NVDA. - app.py: replace tk.Tk/mainloop with wx.App/MainLoop, wx.CallAfter, wx.CallLater; hidden wx.Frame anchors the event loop - ui.py: full rewrite — ScrolledWindow, StaticBoxSizer, FlexGridSizer, ListCtrl, SpinCtrl, DirDialog, wx.Timer, EVT_CHAR_HOOK for hotkey capture; all controls get .SetName() for screen-reader labels - requirements.txt / setup.py: add wxPython>=4.2.0 dependency - tray.py: docstring-only update (no functional change) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- tray.py: sort imports (Any before Protocol) to satisfy ruff I001 - watcher.py: remove unused Dict/Tuple imports, sort remaining imports, wrap long watchdog import line - ci.yml: install libgtk-3-dev before pip install so wxPython can build Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
wxPython requires native GTK/Cocoa libs and takes 20+ minutes to build from source on headless Linux CI runners. Instead: - Add minimal typestubs/wx/__init__.pyi (treats all wx attrs as Any) - Point pyright at it via stubPath in pyproject.toml - Skip wxPython in CI pip install (grep -v wxPython) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The grep -v | $() approach broke on lines with semicolons and == in platform markers. Write to a temp file and use pip install -r instead. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- pyproject.toml: use extraPaths (not stubPath) so pyright resolves import wx from typestubs/ even when wxPython is not installed - ui.py: use local variables in _build() methods to avoid reportOptionalMemberAccess on self._win, self._list_ctrl, etc. - watcher.py: cast event.src_path to str (watchdog types it bytes|str) - notify.py: add type: ignore[import-not-found] for Windows-only accessible_output2 - platform_utils.py: add type: ignore[attr-defined] for winreg/winsound attributes unavailable on Linux Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Rename typestubs/wx/__init__.pyi to .py (extraPaths resolves .py not .pyi for module discovery) - Add type: ignore[attr-defined] on each line referencing winreg constants (HKEY_CURRENT_USER, KEY_SET_VALUE, REG_SZ) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
pyrightconfig.json overrides pyproject.toml settings. It was missing the typestubs directory in extraPaths, so pyright couldn't resolve import wx from the stub. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
.SetName()accessible labels so screen readers announce them correctlyWhat changed
acb_sync/ui.pywx.Dialog,wx.Frame,wx.ScrolledWindow,wx.ListCtrl,wx.StaticBoxSizer,wx.FlexGridSizer,wx.Timer,wx.EVT_CHAR_HOOKfor hotkey capture)acb_sync/app.pytk.Tk/mainloopwithwx.App/MainLoop,wx.CallAfterfor thread-safe dispatch,wx.CallLaterfor delayed callssetup.pywxPython>=4.2.0toinstall_requiresrequirements.txtwxPython>=4.2.0acb_sync/tray.pyNo changes to:
config.py,notify.py,hotkeys.py,platform_utils.py,watcher.py,copier.py— none of these import tkinter.Key wxPython patterns used
wx.ScrolledWindowreplaces the Canvas+Scrollbar hackwx.StaticBox+wx.StaticBoxSizerreplacesttk.LabelFramewx.SpinCtrlreplacesttk.Spinbox(returns int directly, no string parsing needed)wx.Choicereplacesttk.Combobox(readonly)wx.ListCtrl(LC_REPORT)replacesttk.Treeviewwx.Timerreplaces recursiveroot.after()for periodic refreshwx.EVT_CHAR_HOOKreplaces<KeyPress>binding for hotkey recordingTest plan
pip install -e .— verify wxPython installs and app launches🤖 Generated with Claude Code