Skip to content

fix(gui): raise the context menu on Shift+F10 for VoiceOver users - #877

Merged
got3nks merged 3 commits into
amule-org:masterfrom
LSalami:a11y-context-menu-keyboard
Aug 10, 2026
Merged

fix(gui): raise the context menu on Shift+F10 for VoiceOver users#877
got3nks merged 3 commits into
amule-org:masterfrom
LSalami:a11y-context-menu-keyboard

Conversation

@LSalami

@LSalami LSalami commented Aug 9, 2026

Copy link
Copy Markdown

Summary

Second half of #180: with VoiceOver running, none of aMule's wxDataViewCtrl-backed lists (Downloads, Search Results, Shared Files, Friends, Clients, Servers) could open their context menu via the keyboard. VO+Shift+M (VoiceOver's context-menu gesture) performs AXShowMenu, but wx's Cocoa backend only ever raises wxEVT_DATAVIEW_ITEM_CONTEXT_MENU in response to a physical right-click -- there is no keyboard-triggered path and no AXShowMenu implementation for any control on this port at all, not specific to wxDataViewCtrl (tracked upstream since 2011: wxWidgets/wxWidgets#13010). So the gesture never reaches wx, let alone aMule.

As discussed on #180, this is fixable on aMule's side without waiting on wx: nothing stops us from handling a keystroke ourselves and raising the same context-menu event PopupMenu()-based handlers already listen for.

Change

One handler in CMuleDataViewCtrl::OnKeyDown -- the base class every affected list derives from, so this fixes all six in one place -- that raises wxEVT_DATAVIEW_ITEM_CONTEXT_MENU on Shift+F10, targeting the current item. Every list's existing OnItemRightClicked/OnRightClick handler picks it up unchanged: they already tolerate the event's item not matching the current selection (falling back to whatever's already selected), which is exactly what a keyboard-triggered menu needs.

Ctrl+Return was tried first (the alternative suggested on #180, for users who avoid the function row) and rejected: NSOutlineView's own keyDown: treats bare Return as "activate the row" before this handler ever sees it, Control held or not. Confirmed live -- it opened a shared file in its associated app instead of showing a menu. Shift+F10 has no such native meaning for an outline view and fires correctly.

Testing

Verified against a live build with a real system key event (System Events key code 109 using shift down, not just an accessibility action) sent to a selected row in Shared Files: the correct context menu opened with every expected item (screenshot taken during development). Ctrl+Return's rejection is from an equally live repro, not a guess.

Local clang-tidy CI replica (Tier-1 whole-tree + Tier-2 changed-lines vs. upstream/master): zero hits on this file; the Tier-1 hits that exist elsewhere are pre-existing, in unittests/ files this change never touches.

Fixes the context-menu half of #180. The other half (custom-renderer cells reading as <wxCustomRendererObject: ...>) is a separate, already-filed upstream wx issue/PR (wxWidgets/wxWidgets#26808, wxWidgets/wxWidgets#26809).

Test plan

  • Built locally against Homebrew wxwidgets, confirmed clean compile.
  • Live keyboard test: Shift+F10 on a selected Shared Files row opens the correct context menu.
  • Live keyboard test: Ctrl+Return does not (and cannot, on this port) -- confirmed and documented rather than left silently unhandled.
  • Local clang-tidy CI replica, Tier-1 + Tier-2, clean on the changed file.

wx's Cocoa backend only ever fires wxEVT_DATAVIEW_ITEM_CONTEXT_MENU in
response to a physical right-click. There is no keyboard-triggered path
and no AXShowMenu implementation for any control on this port
(wxWidgets/wxWidgets#13010, open since 2011, not specific to
wxDataViewCtrl). VoiceOver's own context-menu gesture (VO+Shift+M)
performs AXShowMenu, so it never reaches wx at all -- reported by a
blind user for every wxDataViewCtrl-backed list in aMule
(amule-org#180).

Add a Shift+F10 handler to CMuleDataViewCtrl::OnKeyDown (the base class
every list -- Downloads, Search, Shared Files, Friends, Clients,
Servers -- derives from) that raises the same context-menu event
ourselves, reusing every list's existing OnItemRightClicked/OnRightClick
handler unchanged. Ctrl+Return was tried first, since Shift+F10 sits on
the function row some users avoid, but NSOutlineView's own keyDown:
treats bare Return as "activate the row" before this handler ever sees
it, Control held or not -- confirmed by it opening a shared file in its
associated app instead of showing a menu, so it can never fire here on
this port.

Verified against a live build: sent Shift+F10 via a real system key
event (not just an accessibility action) to a selected row in Shared
Files, screenshotted the result, and confirmed the correct list-specific
context menu opened with all its items. Ctrl+Return's rejection is
likewise from a live repro, not a guess. Local clang-tidy CI replica
(Tier-1 whole-tree + Tier-2 changed-lines) reports nothing on this file;
the Tier-1 hits that do exist are pre-existing, in unittests/ files this
change never touches.

@got3nks got3nks left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Confirmed the macOS diagnosis: wxEVT_DATAVIEW_ITEM_CONTEXT_MENU has no keyboard path on the Cocoa port, and GetCurrentItem() / GetItemRect() are both implemented there, so the approach works.

1. The gap isn't macOS-only — Windows and Linux are equally affected.

wxEVT_DATAVIEW_ITEM_CONTEXT_MENU is mouse-only on every backend: generic/datavgen.cpp:5077 raises it under if (event.RightUp()) (that is the backend MSW uses), and gtk/dataview.cpp:4754 inside the button-press handler. Since all six lists bind only that event, keyboard users have no route to these context menus on any platform.

The difference is that Windows and GTK already deliver a portable event that we ignore. Measured with a minimal wxDataViewCtrl probe on both:

Windows (wx 3.3.2, MSW):   Shift+F10   -> wxEVT_CONTEXT_MENU pos=(-1,-1)
Ubuntu  (wx 3.2.9, GTK3):  Shift+F10   -> wxEVT_CONTEXT_MENU pos=(-1,-1)
both:                      right-click -> wxEVT_DATAVIEW_ITEM_CONTEXT_MENU pos=(64,72)

pos=(-1,-1) is wx's keyboard-origin marker. On MSW it comes from WM_CONTEXTMENU (msw/window.cpp:3645), which the OS also sends for the Applications key.

Fix: bind wxEVT_CONTEXT_MENU in CMuleDataViewCtrl and raise wxEVT_DATAVIEW_ITEM_CONTEXT_MENU at the current item from there. That covers Windows and Linux with less code than the macOS path needs, and the #ifdef __WXOSX__ Shift+F10 handler then becomes the shim for the one port where nothing fires — feeding the same handler, so all three platforms end up on the same key.

2. The menu opens at the mouse pointer, not at the focused row.

All six handlers call PopupMenu(menu) with no position, which uses the cursor — as the comment at MuleNotebook.cpp:186 already notes. That is correct for a right-click, and wrong for a keyboard user, whose pointer may be sitting on another window or another display.

Fix: wxDataViewEvent carries a position, so set it when synthesising:

const wxDataViewItem item = GetCurrentItem();
wxDataViewEvent menuEvent(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, this, item);
const wxRect rect = item.IsOk() ? GetItemRect(item) : wxRect(GetClientSize());
menuEvent.SetPosition(rect.GetLeft(), rect.GetBottom());

and pass event.GetPosition() through to PopupMenu in the handlers, as ChatWnd.cpp:114 and TransferWnd.cpp:393 already do. ServerWnd.cpp:497 has the same keyboard-origin fallback for its info list.

3. Minor: the 14-line comment is longer than the code it explains and largely restates the PR description. The Ctrl+Return rejection is the part worth keeping in-tree, since it stops the next person retrying it; the rest reads better condensed.

…w position

got3nks' review on amule-org#877 found the Shift+F10 fix was scoped too narrowly to
macOS. wxEVT_DATAVIEW_ITEM_CONTEXT_MENU is mouse-only on every backend, not
just Cocoa: MSW and GTK already deliver a portable wxEVT_CONTEXT_MENU for
Shift+F10/the Applications key (measured directly: pos=(-1,-1), wx's own
keyboard-origin marker), which none of the six lists were listening for.

CMuleDataViewCtrl now binds EVT_CONTEXT_MENU and, for the keyboard-origin
case only (evt.GetPosition() == wxDefaultPosition; a real right-click's own
wxEVT_DATAVIEW_ITEM_CONTEXT_MENU already fires and must not be duplicated),
synthesises the item context-menu event itself. The macOS Shift+F10 handler
in OnKeyDown now shares that same synthesis helper (RaiseItemContextMenu),
since Cocoa has no equivalent portable event at all.

Also fixes the menu popping up at the mouse cursor instead of the focused
row for a keyboard-triggered open (wrong when the pointer is on another
window/display): RaiseItemContextMenu positions the synthesized event at the
current item's rect, and all six list handlers now pass their event's
position through to PopupMenu() instead of leaving it at the default (which
uses the current cursor position), matching the pattern already used in
ChatWnd.cpp/TransferWnd.cpp/ServerWnd.cpp.

Verified: `cmake --build . --target amule` clean before and after
clang-format; a live GTK/MSW probe isn't available locally, so the portable
event's existence and keyboard-origin marker are taken from got3nks'
measurements quoted in the PR review, not independently re-verified here.
@LSalami

LSalami commented Aug 10, 2026

Copy link
Copy Markdown
Author

Thanks for the thorough review -- all three addressed:

  1. CMuleDataViewCtrl now binds EVT_CONTEXT_MENU and synthesises wxEVT_DATAVIEW_ITEM_CONTEXT_MENU for the keyboard-origin case (evt.GetPosition() == wxDefaultPosition), covering MSW/GTK's Shift+F10/Applications key as you found. The #ifdef __WXOSX__ Shift+F10 handler now shares the same synthesis helper (RaiseItemContextMenu), since Cocoa has no portable wxEVT_CONTEXT_MENU equivalent at all.
  2. RaiseItemContextMenu positions the synthesized event at the current item's rect instead of leaving it at the mouse position, and all six list handlers now pass event.GetPosition() through to PopupMenu(), matching the ChatWnd.cpp/TransferWnd.cpp/ServerWnd.cpp pattern.
  3. Condensed the comment to just the Ctrl+Return rejection.

Verified: cmake --build . --target amule clean, and the local clang-tidy CI replica (Tier-1 + Tier-2 against upstream/master) clean on the changed lines -- Tier-1's 13 hits are all pre-existing, in unittests/tests/ files this PR doesn't touch.

I don't have a Windows/Linux box to re-measure the wxEVT_CONTEXT_MENU behavior myself, so that part rests on your measurements quoted above rather than independent verification here -- flagging that in case it's worth a second look before merge.

…ed away

Two things from the second review round, both in the keyboard path.

GetItemRect() answers an empty rect for a row that is not currently on
screen -- the same limitation MoveByPage() documents a few lines above,
which is why it uses GetCountPerPage() instead. RaiseItemContextMenu()
took that rect at face value, so a cursor scrolled out of view put the
menu in the control's top-left corner rather than at the row it acts on.
Reachable by clicking a row and scrolling away before pressing the key.
The row is brought into view first, which also shows the user what the
menu is about to apply to, and does nothing when it is already visible.

And the comment on the Cocoa branch said that port has no
wxEVT_CONTEXT_MENU handling at all. It does: wxDataViewCtrl::
OnContextMenu() in osx/dataview_osx.cpp is exactly how a right-click
becomes a dataview event there. What macOS lacks is any keystroke that
raises that event. The distinction matters, because it makes the Skip()
in OnContextMenuKey load-bearing on that port rather than tidy -- our
handler runs first, being the derived class, so swallowing a
mouse-origin event would leave right-click with no menu at all. Both
comments now say so.
@got3nks

got3nks commented Aug 10, 2026

Copy link
Copy Markdown

Re-reviewed the update — all three points are addressed, and I verified the parts that could bite rather than reading the diff alone, since you flagged you couldn't re-measure Windows and Linux yourself.

The PopupMenu(event.GetPosition()) change is safe on every backend. This was the one I wanted certainty on, because it now affects the ordinary right-click path too and a wrong coordinate space would misplace the menu for everyone. Against wx 3.3.1's source:

  • generic (what MSW uses) converts explicitly — ClientToScreen() then m_owner->ScreenToClient() before SetPosition(), so the header offset is already handled
  • GTK calls gtk_tree_view_convert_bin_window_to_widget_coords() before SetPosition()
  • Cocoa never calls SetPosition() at all, and wxDataViewEvent::Init() leaves m_pos = wxDefaultPosition, so PopupMenu() falls back to the pointer exactly as before

No double-menu risk either: neither gtk/dataview.cpp nor generic/datavgen.cpp binds wxEVT_CONTEXT_MENU, so OnContextMenuKey is the only consumer on those ports.

I've pushed a follow-up commit (445d586) with two small things rather than sending you round again:

  1. GetItemRect() answers an empty rect for a row that is off screen — the same limitation MoveByPage() documents a few lines above, which is why it uses GetCountPerPage() instead. So a cursor scrolled out of view put the menu in the control's top-left corner instead of at its row; reachable by clicking a row, scrolling away, then pressing the key. It now brings the row into view first, which also shows what the menu is about to act on.

  2. The Cocoa comment was inverted in a load-bearing way. That port does handle wxEVT_CONTEXT_MENUwxDataViewCtrl::OnContextMenu() in osx/dataview_osx.cpp is exactly how a right-click becomes a dataview event there; what it lacks is any keystroke that raises it. That makes the evt.Skip() in OnContextMenuKey essential rather than tidy on macOS: our handler runs first as the derived class, so swallowing a mouse-origin event would leave right-click with no menu at all. Someone trusting the old comment could have removed it.

Building on all three platforms now and testing before merge — macOS is already clean with the follow-up; Linux and Windows are rebuilding onto it. @nickinesio, I'll post a build once that's done, if you'd like to confirm Shift+F10 under VoiceOver on the real thing.

@got3nks
got3nks merged commit 4b1faa5 into amule-org:master Aug 10, 2026
14 checks passed
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.

2 participants