From c676ac76129e6a15e41ec87583a788cff017598b Mon Sep 17 00:00:00 2001 From: Marcel Date: Fri, 6 Nov 2026 00:50:05 +0100 Subject: [PATCH 1/5] fix(mfcwx): serve main-thread requests from Sleep() on the main thread A worker holding a lock while waiting in RunOnMainThread deadlocked with the main thread spinning in a Sleep loop (CDlgProgressFactorisation::OnInitDialog), freezing the factorisation dialog. Sleep() now processes pending events on the main thread. --- port/mfcwx/src/internal.h | 1 + port/mfcwx/src/kernel.cpp | 3 +++ port/mfcwx/src/wnd.cpp | 8 ++++++++ 3 files changed, 12 insertions(+) diff --git a/port/mfcwx/src/internal.h b/port/mfcwx/src/internal.h index f71b2900..89639dac 100644 --- a/port/mfcwx/src/internal.h +++ b/port/mfcwx/src/internal.h @@ -146,6 +146,7 @@ bool IsMainThread(); // Runs fn on the main thread and waits for it (Windows SendMessage semantics across threads). void RunOnMainThread(const std::function& fn); void PostToMainThread(std::function fn); +void PumpMainThreadTasks(); // Converts a window rectangle in dialog units to pixels for the given dialog window. wxRect DialogUnitsToPixels(wxWindow* dialog, int x, int y, int cx, int cy); diff --git a/port/mfcwx/src/kernel.cpp b/port/mfcwx/src/kernel.cpp index 8ffda7c0..09eafdf4 100644 --- a/port/mfcwx/src/kernel.cpp +++ b/port/mfcwx/src/kernel.cpp @@ -584,8 +584,11 @@ HANDLE GetCurrentProcess(void) { return kCurrentProcess; } HANDLE GetCurrentThread(void) { return kCurrentThread; } +namespace mfcwx { void PumpMainThreadTasks(); } + void Sleep(DWORD ms) { CheckSuspend(); + mfcwx::PumpMainThreadTasks(); if (ms == 0) { sched_yield(); } else if (ms == INFINITE) { diff --git a/port/mfcwx/src/wnd.cpp b/port/mfcwx/src/wnd.cpp index 026d4b46..7d9ffe94 100644 --- a/port/mfcwx/src/wnd.cpp +++ b/port/mfcwx/src/wnd.cpp @@ -50,6 +50,14 @@ void RunOnMainThread(const std::function& fn) { std::rethrow_exception(error); } +// Sleep() on the main thread must keep serving RunOnMainThread requests from worker threads, +// otherwise a worker holding a lock while waiting for the main thread deadlocks with the main +// thread spinning in a Sleep loop (e.g. the factorisation progress dialog). +void PumpMainThreadTasks() { + if (IsMainThread() && wxTheApp) + wxTheApp->ProcessPendingEvents(); +} + void PostToMainThread(std::function fn) { if (wxTheApp) wxTheApp->CallAfter(std::move(fn)); From a018bcd4b54266d96ef37f68178d64d3ba0a9466 Mon Sep 17 00:00:00 2001 From: Marcel Date: Fri, 6 Nov 2026 00:50:05 +0100 Subject: [PATCH 2/5] fix(mfcwx): do not re-notify edit controls when the text is unchanged SetControlText called SetValue in both branches, so setting an edit to its current text still fired a change event. Handlers that update sibling edits (e.g. the stereotyped-message dialog) then recursed until the stack overflowed when a long modulus was pasted. --- port/mfcwx/src/controls.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/port/mfcwx/src/controls.cpp b/port/mfcwx/src/controls.cpp index c112bde4..a0209600 100644 --- a/port/mfcwx/src/controls.cpp +++ b/port/mfcwx/src/controls.cpp @@ -191,10 +191,11 @@ void SetControlText(wxWindow* w, const wxString& text) { case ControlKind::RichEdit: if (auto* t = AsText(w)) { wxString v = ToEditText(*st, text); + // Only notify when the text really changes: dialogs such as the stereotyped-message + // attack set sibling edits from their change handlers, which otherwise recurse + // until the stack overflows. if (t->GetValue() != v) t->SetValue(v); - else - t->SetValue(v); Extra(*st).modified = false; } return; From da9f2249231f11ec2eb7b96ec245abc7a5a601d0 Mon Sep 17 00:00:00 2001 From: Marcel Date: Fri, 6 Nov 2026 00:50:05 +0100 Subject: [PATCH 3/5] fix(mfcwx): keep group boxes at the bottom of the z-order Win32 group boxes are transparent to the mouse, but rc files often declare them after the controls they enclose. A wxStaticBox created later sat on top and swallowed clicks, making edits such as the ciphertext field of the stereotyped-message dialog unclickable. --- port/mfcwx/src/controls.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/port/mfcwx/src/controls.cpp b/port/mfcwx/src/controls.cpp index a0209600..420c68d8 100644 --- a/port/mfcwx/src/controls.cpp +++ b/port/mfcwx/src/controls.cpp @@ -1136,6 +1136,12 @@ wxWindow* CreateControl(wxWindow* parent, const char* cls, const char* text, int // Statics are never in the tab order. st.style &= ~WS_TABSTOP; } + if (kind == ControlKind::GroupBox) { + // Win32 group boxes are transparent to the mouse, but rc files often declare them after the + // controls they enclose. A wxStaticBox created later would sit on top and swallow clicks + // (e.g. the ciphertext edit of the stereotyped-message dialog), so keep it at the bottom. + w->Lower(); + } if (kind == ControlKind::Slider) { Extra(st).maxValue = 100; } From d883b47140dfe256ed80f052b3a96036d6f0cc4a Mon Sep 17 00:00:00 2001 From: Marcel Date: Fri, 6 Nov 2026 01:39:59 +0100 Subject: [PATCH 4/5] fix(mfcwx): never auto-disable the MDI tab control as an unhandled command The AUI tab control and notebook get wx ids that fall into the command id range, so UpdateCommandUI treated them as menu commands without a handler and disabled them (MFC auto-menu-enable), leaving document tabs unclickable and not closable. --- port/mfcwx/src/docview.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/port/mfcwx/src/docview.cpp b/port/mfcwx/src/docview.cpp index a463f6e1..4e4f2c53 100644 --- a/port/mfcwx/src/docview.cpp +++ b/port/mfcwx/src/docview.cpp @@ -60,6 +60,12 @@ class MfcFrame : public wxFrame { } static void UpdateCommandUI(wxWindow* frame, wxUpdateUIEvent& e) { + // The tab control / notebook of the MDI client get wx-generated ids that may fall into the + // command id range; auto-disabling them as "unhandled commands" made tabs unclickable. + if (wxWindow* w = wxDynamicCast(e.GetEventObject(), wxWindow)) { + if (wxDynamicCast(w, wxAuiTabCtrl) || wxDynamicCast(w, wxAuiNotebook)) + return; + } CWnd* p = PermanentWnd(frame); if (!p || !IsCommandWxId(e.GetId()) || !p->IsKindOf(RUNTIME_CLASS(CFrameWnd))) { if (e.GetId() == wxID_ABOUT || e.GetId() == wxID_EXIT) From 2ccea2945eabc16bcba74d8cc0b2502be15ea1c8 Mon Sep 17 00:00:00 2001 From: Marcel Date: Fri, 6 Nov 2026 01:39:59 +0100 Subject: [PATCH 5/5] fix(rsa-bloemer-may): ignore delta/bit-length change handlers during programmatic updates checkStartButton updates the bit length and delta edits, whose change handlers clear N, e and d (meant for user edits). With change notifications on programmatic updates this erased the modulus as soon as a digit was typed. Skip those handlers while the dialog updates the fields itself. --- CrypTool/RSABloemerMayDlg.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CrypTool/RSABloemerMayDlg.cpp b/CrypTool/RSABloemerMayDlg.cpp index f9df4181..d7394957 100644 --- a/CrypTool/RSABloemerMayDlg.cpp +++ b/CrypTool/RSABloemerMayDlg.cpp @@ -446,6 +446,10 @@ BOOL CRSABloemerMayDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) } +// Set while the dialog itself updates the delta / bit length fields, so their change +// handlers (which clear N, e and d after a user edit) do not fire on those updates. +static bool g_programmaticUpdate = false; + void CRSABloemerMayDlg::checkStartButton() { NTLExpPars myPars; @@ -457,7 +461,9 @@ void CRSABloemerMayDlg::checkStartButton() m_delta= doubleToCString(log(d)/log(N)); m_BitsOfN.Format("%d",NumBits(N)); GetDlgItem(IDC_BUTTONSTART)->EnableWindow(N>0 && e>0 && m_m>3); + g_programmaticUpdate = true; UpdateData(false); + g_programmaticUpdate = false; } void CRSABloemerMayDlg::OnButtondefault() @@ -536,6 +542,7 @@ void CRSABloemerMayDlg::OnChangeEditd() void CRSABloemerMayDlg::OnChangeEditbitsofn() { + if (g_programmaticUpdate) return; UpdateData(); m_N=""; m_e=""; @@ -545,6 +552,7 @@ void CRSABloemerMayDlg::OnChangeEditbitsofn() void CRSABloemerMayDlg::OnChangeEditdelta() { + if (g_programmaticUpdate) return; UpdateData(); m_d=""; if(m_choice==1){