Fix right-click context menus dismissing before they can be read (GTK) (#1437)

* Defer Voice Keyer right-click menu popup via CallAfter

On GTK, PopupMenu() called synchronously from the wxEVT_CONTEXT_MENU
handler opens the menu while the originating right-click's implicit
pointer grab is still active, so the matching button-up is delivered
straight to the menu, selecting whatever is under the cursor before it
can be read. Deferring via CallAfter lets the initiating click fully
complete first.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Fix Voice Keyer/PTT right-click menu using established RIGHT_UP workaround

The CallAfter approach in the previous commit didn't help: on a fast
click, GDK often has both the button-press and its paired release
already queued and dispatches both before any idle-priority callback
runs, so the menu still opened with the click already spent.

This file already has the correct fix for the identical issue on other
widgets (m_txLevelBox, m_txtTxLevelNum, m_btnTogTune, added earlier):
on wxGTK 3.3+, wxEVT_CONTEXT_MENU is generated from button-press, so
showing a menu from it opens while that click's release is still
pending and GTK delivers the release straight to the menu as a
selection. Bind wxEVT_RIGHT_UP instead for wx>=3.3 (keeping
wxEVT_CONTEXT_MENU on older wx, matching the existing widgets), which
only fires once the click has genuinely finished.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Fix FreeDV Reporter Send/Clear button right-click menus (same RIGHT_UP fix)

Same class of bug as the Voice Keyer/PTT buttons: wxEVT_CONTEXT_MENU is
generated from button-press on wxGTK 3.3+, so the menu opens while the
click's release is still pending and GTK delivers it straight to the
menu as a selection. Was only visible when the item under the cursor
happened to be enabled -- a disabled item just closes the menu
silently on release, masking the bug.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Scope the RIGHT_UP context-menu workaround to GTK only

The wxEVT_CONTEXT_MENU-on-button-press behaviour this works around is
GTK-specific: MSW already generates the event on button-up, and OSX
uses ctrl-click. The version-only guard (wxCHECK_VERSION(3,3,0)) would
otherwise apply the same override on Windows/Mac builds using wx 3.3+,
which could drop keyboard-invoked (Menu key/Shift+F10) context menus
on MSW since that only comes through wxEVT_CONTEXT_MENU. Add
defined(__WXGTK__) to all six guard sites, including the pre-existing
one for m_txLevelBox/m_txtTxLevelNum/m_btnTogTune this pattern was
copied from.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Drop version gate: dismiss-on-release bug confirmed present on wx 3.2 too

Direct testing on a wxGTK 3.2 build showed the same context-menu
dismiss-on-release bug the wxCHECK_VERSION(3,3,0) guard assumed was
3.3-specific (copied from the pre-existing txLevel/tune pattern this
was based on). Reading wx 3.2 vs 3.3 source confirmed the press-side
event synthesis is identical in both versions, so the fix now applies
on all wxGTK versions for these real windowed button widgets. The
original txLevel/tune block's version gate is left untouched since its
stated rationale (windowless widgets not generating RIGHT_UP pre-3.3)
is a separate, more specific concern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Add PR #1437 to changelog.

---------

Co-authored-by: Barry Jackson <barjac@mageia.org>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Mooneer Salem <mooneer@gmail.com>
pull/1438/head
Barry Jackson 2026-07-20 00:47:16 +01:00 committed by GitHub
parent 4a18367084
commit 9918843f4f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 38 additions and 8 deletions

View File

@ -965,7 +965,8 @@ LDPC | Low Density Parity Check Codes - a family of powerful FEC codes
* Only preserve previously selected tab on TX if it's in the same group as 'From Mic'. (PR #1428)
* Scalar plot label alignment fix for Y axis of Frm Decoder/Mic/Radio, SNR and Spectrum plots. (PR #1429) - thanks @barjac!
* Fix window position restore under KWin and labwc (main window + FreeDV Reporter window). (PR #1431, #1433) - thanks @barjac!
* Harden experimental tab layout persistence: fix positional index corruption across version upgrades, a crash on malformed saved layouts, incorrect tab restore with split tab groups, a saved layout getting silently overwritten by toggling Experimental Features off then back on before exiting, and a drag-and-drop assertion crash when an old saved layout leaves an empty tab group behind. (PR #1434) - thanks @barjac!
* Harden experimental tab layout persistence. (PR #1434) - thanks @barjac!
* Fix right-click context menus dismissing before they can be read (GTK) (PR #1437) - thanks @barjac!
2. Enhancements:
* Add UDP broadcast of received callsigns. (PR #1367)
* Add Time-Out Timer (TOT) capability to FreeDV. (PR #1366, #1398, #1405) - thanks @barjac!

View File

@ -628,9 +628,19 @@ FreeDVReporterDialog::FreeDVReporterDialog(wxWindow* parent, wxWindowID id, cons
m_statusMessage->Connect(wxEVT_TEXT, wxCommandEventHandler(FreeDVReporterDialog::OnStatusTextChange), NULL, this);
m_buttonSend->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FreeDVReporterDialog::OnStatusTextSend), NULL, this);
m_buttonSend->Connect(wxEVT_CONTEXT_MENU, wxContextMenuEventHandler(FreeDVReporterDialog::OnStatusTextSendContextMenu), NULL, this);
m_buttonClear->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FreeDVReporterDialog::OnStatusTextClear), NULL, this);
#if defined(__WXGTK__)
// wxGTK fires wxEVT_CONTEXT_MENU on button-press, causing GTK to
// dismiss PopupMenu on button release (see topFrame.cpp for the same
// workaround on other widgets); use RIGHT_UP instead. MSW/OSX are
// unaffected, so this is GTK-specific. Confirmed present on both
// wxGTK 3.2 and 3.3+, so no version gate here.
m_buttonSend->Bind(wxEVT_RIGHT_UP, [this](wxMouseEvent&) { wxContextMenuEvent ctx; OnStatusTextSendContextMenu(ctx); });
m_buttonClear->Bind(wxEVT_RIGHT_UP, [this](wxMouseEvent&) { wxContextMenuEvent ctx; OnStatusTextClearContextMenu(ctx); });
#else
m_buttonSend->Connect(wxEVT_CONTEXT_MENU, wxContextMenuEventHandler(FreeDVReporterDialog::OnStatusTextSendContextMenu), NULL, this);
m_buttonClear->Connect(wxEVT_CONTEXT_MENU, wxContextMenuEventHandler(FreeDVReporterDialog::OnStatusTextClearContextMenu), NULL, this);
#endif
m_buttonOK->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FreeDVReporterDialog::OnOK), NULL, this);
m_buttonSendQSY->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FreeDVReporterDialog::OnSendQSY), NULL, this);
@ -703,8 +713,10 @@ FreeDVReporterDialog::~FreeDVReporterDialog()
m_statusMessage->Disconnect(wxEVT_TEXT, wxCommandEventHandler(FreeDVReporterDialog::OnStatusTextChange), NULL, this);
m_buttonSend->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FreeDVReporterDialog::OnStatusTextSend), NULL, this);
m_buttonClear->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FreeDVReporterDialog::OnStatusTextClear), NULL, this);
#if !defined(__WXGTK__)
m_buttonSend->Disconnect(wxEVT_CONTEXT_MENU, wxContextMenuEventHandler(FreeDVReporterDialog::OnStatusTextSendContextMenu), NULL, this);
m_buttonClear->Disconnect(wxEVT_CONTEXT_MENU, wxContextMenuEventHandler(FreeDVReporterDialog::OnStatusTextClearContextMenu), NULL, this);
#endif
m_buttonOK->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FreeDVReporterDialog::OnOK), NULL, this);
m_buttonSendQSY->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FreeDVReporterDialog::OnSendQSY), NULL, this);

View File

@ -969,9 +969,21 @@ TopFrame::TopFrame(wxWindow* parent, wxWindowID id, const wxString& title, const
m_togBtnOnOff->Connect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnTogBtnOnOff), NULL, this);
m_togBtnAnalog->Connect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnTogBtnAnalogClick), NULL, this);
m_togBtnVoiceKeyer->Connect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnTogBtnVoiceKeyerClick), NULL, this);
m_togBtnVoiceKeyer->Connect(wxEVT_CONTEXT_MENU, wxContextMenuEventHandler(TopFrame::OnTogBtnVoiceKeyerRightClick), NULL, this);
m_btnTogPTT->Connect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnTogBtnPTT), NULL, this);
#if defined(__WXGTK__)
// wxGTK fires wxEVT_CONTEXT_MENU on button-press for these widgets,
// causing GTK to dismiss PopupMenu on button release; use RIGHT_UP
// instead. MSW/OSX are unaffected (MSW generates the event on
// button-up already; OSX uses ctrl-click), so this is GTK-specific.
// Confirmed present on both wxGTK 3.2 and 3.3+, unlike the windowless
// widget case below, so no version gate here.
m_togBtnVoiceKeyer->Bind(wxEVT_RIGHT_UP, [this](wxMouseEvent&) { wxContextMenuEvent ctx; OnTogBtnVoiceKeyerRightClick(ctx); });
m_btnTogPTT->Bind(wxEVT_RIGHT_UP, [this](wxMouseEvent&) { wxContextMenuEvent ctx; OnTogBtnPTTRightClick(ctx); });
#else
m_togBtnVoiceKeyer->Connect(wxEVT_CONTEXT_MENU, wxContextMenuEventHandler(TopFrame::OnTogBtnVoiceKeyerRightClick), NULL, this);
m_btnTogPTT->Connect(wxEVT_CONTEXT_MENU, wxContextMenuEventHandler(TopFrame::OnTogBtnPTTRightClick), NULL, this);
#endif
m_BtnCallSignReset->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnCallSignReset), NULL, this);
m_BtnBerReset->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnBerReset), NULL, this);
@ -996,15 +1008,18 @@ TopFrame::TopFrame(wxWindow* parent, wxWindowID id, const wxString& title, const
m_btnTxLevelPP->Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler(TopFrame::OnTxLevelMouseWheel), NULL, this);
m_btnTogTune->Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler(TopFrame::OnTxLevelMouseWheel), NULL, this);
#if wxCHECK_VERSION(3, 3, 0)
#if wxCHECK_VERSION(3, 3, 0) && defined(__WXGTK__)
// wxGTK 3.3+ fires wxEVT_CONTEXT_MENU on button-press for these widget types,
// causing GTK to dismiss PopupMenu on button release; use RIGHT_UP instead.
// MSW/OSX are unaffected (MSW generates the event on button-up already;
// OSX uses ctrl-click), so this is GTK-specific.
m_txLevelBox->Bind(wxEVT_RIGHT_UP, [this](wxMouseEvent&) { wxContextMenuEvent ctx; OnTxLevelContextMenu(ctx); });
m_txtTxLevelNum->Bind(wxEVT_RIGHT_UP, [this](wxMouseEvent&) { wxContextMenuEvent ctx; OnTxLevelContextMenu(ctx); });
m_btnTogTune->Bind(wxEVT_RIGHT_UP, [this](wxMouseEvent&) { wxContextMenuEvent ctx; OnTuneAttenContextMenu(ctx); });
#else
// wxGTK < 3.3 does not generate RIGHT_UP for windowless widget types
// (wxStaticBox, wxStaticText); CONTEXT_MENU works without the dismiss issue.
// (Also used as-is on MSW/OSX regardless of wx version -- see above.)
m_txLevelBox->Connect(wxEVT_CONTEXT_MENU, wxContextMenuEventHandler(TopFrame::OnTxLevelContextMenu), NULL, this);
m_txtTxLevelNum->Connect(wxEVT_CONTEXT_MENU, wxContextMenuEventHandler(TopFrame::OnTxLevelContextMenu), NULL, this);
m_btnTogTune->Connect(wxEVT_CONTEXT_MENU, wxContextMenuEventHandler(TopFrame::OnTuneAttenContextMenu), NULL, this);
@ -1089,9 +1104,11 @@ TopFrame::~TopFrame()
m_togBtnOnOff->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnTogBtnOnOff), NULL, this);
m_togBtnAnalog->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnTogBtnAnalogClick), NULL, this);
m_togBtnVoiceKeyer->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnTogBtnVoiceKeyerClick), NULL, this);
m_togBtnVoiceKeyer->Disconnect(wxEVT_CONTEXT_MENU, wxContextMenuEventHandler(TopFrame::OnTogBtnVoiceKeyerRightClick), NULL, this);
m_btnTogPTT->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnTogBtnPTT), NULL, this);
#if !defined(__WXGTK__)
m_togBtnVoiceKeyer->Disconnect(wxEVT_CONTEXT_MENU, wxContextMenuEventHandler(TopFrame::OnTogBtnVoiceKeyerRightClick), NULL, this);
m_btnTogPTT->Disconnect(wxEVT_CONTEXT_MENU, wxContextMenuEventHandler(TopFrame::OnTogBtnPTTRightClick), NULL, this);
#endif
m_btnCenterRx->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnCenterRx), NULL, this);
@ -1108,7 +1125,7 @@ TopFrame::~TopFrame()
m_btnTxLevelM->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnTxLevelDecr), NULL, this);
m_btnTxLevelP->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnTxLevelIncr), NULL, this);
m_btnTxLevelPP->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(TopFrame::OnTxLevelIncrBig), NULL, this);
#if !wxCHECK_VERSION(3, 3, 0)
#if !(wxCHECK_VERSION(3, 3, 0) && defined(__WXGTK__))
m_txLevelBox->Disconnect(wxEVT_CONTEXT_MENU, wxContextMenuEventHandler(TopFrame::OnTxLevelContextMenu), NULL, this);
m_txtTxLevelNum->Disconnect(wxEVT_CONTEXT_MENU, wxContextMenuEventHandler(TopFrame::OnTxLevelContextMenu), NULL, this);
m_btnTogTune->Disconnect(wxEVT_CONTEXT_MENU, wxContextMenuEventHandler(TopFrame::OnTuneAttenContextMenu), NULL, this);

View File

@ -199,7 +199,7 @@ class TopFrame : public wxFrame
virtual void OnTogBtnAnalogClick( wxCommandEvent& event ) { event.Skip(); }
virtual void OnTogBtnVoiceKeyerClick( wxCommandEvent& event ) { event.Skip(); }
virtual void OnTogBtnVoiceKeyerRightClick( wxContextMenuEvent& event ) { event.Skip(); }
virtual void OnTogBtnPTT( wxCommandEvent& event ) { event.Skip(); }
virtual void OnTogBtnPTTRightClick( wxContextMenuEvent& event ) { event.Skip(); }

View File

@ -208,7 +208,7 @@ void MainFrame::OnTogBtnVoiceKeyerRightClick( wxContextMenuEvent& )
bool enabled = vk_state == VK_IDLE && !m_btnTogPTT->GetValue();
chooseVKFileMenuItem_->Enable(vk_state == VK_IDLE);
recordNewVoiceKeyerFileMenuItem_->Enable(enabled);
// Trigger right-click menu popup in a location that will prevent it from
// ending up off the screen.
auto sz = m_togBtnVoiceKeyer->GetSize();