Skip to content
4 changes: 2 additions & 2 deletions .github/workflows/mac.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ jobs:
- name: wxMac macOS 26
runner: macos-26-intel
arch: x86_64
ruby: '3.4'
ruby: 'ruby'
swig: '4'
configure_flags: --disable-sys-libs --disable-webview
wxw_type: develop
Expand Down Expand Up @@ -101,7 +101,7 @@ jobs:
- name: wxMac macOS 15
runner: macos-15-intel
arch: x86_64
ruby: 'ruby'
ruby: '3.4'
swig: '4'
configure_flags: --disable-sys-libs --enable-webview
wxw_type: embed
Expand Down
137 changes: 135 additions & 2 deletions ext/wxruby3/include/wxRubyApp.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ WXRUBY_TRACE_GUARD(WxRubyTraceAppRun, "APP_RUN")

#include <memory>

#include <ruby/ractor.h>

#include <wx/scopedptr.h>
#include <wx/evtloop.h>

// this defines wxEventLoopPtr
wxDEFINE_TIED_SCOPED_PTR_TYPE(wxEventLoopBase)

/*
* WxRuby3 App class
*/
Expand All @@ -17,6 +25,121 @@ class wxRubyApp : public wxApp
private:
bool is_running_ = false;
VALUE self_ = Qnil;

void _store_ruby_exception(VALUE ex)
{
rb_iv_set(this->self_, "@exception", ex);
}

class EventLoop : public wxGUIEventLoop
{
public:
int GetExitCode() { return this->exit_code_; }

protected:
virtual int DoRun() override
{
static WxRuby_ID count_id("count");
static WxRuby_ID list_id("list");
static WxRuby_ID pass_id("pass");

// run our own event loop
bool needs_idle_evt = true;
for (;;)
{
while ( !m_shouldExit
&& !Pending()
&& !(wxTheApp && wxTheApp->HasPendingEvents()) )
{
// check Ruby Ractors and/or Threads
bool needs_pass = false;
VALUE result = rb_funcall(rb_cRactor, count_id(), 0);
needs_pass = !RB_NIL_P(result) && NUM2INT(result) > 1;
if (!needs_pass)
{
result = rb_funcall(rb_cThread, list_id(), 0);
needs_pass = !RB_NIL_P(result) && TYPE(result) == T_ARRAY && RARRAY_LEN(result) > 1;
}
if (needs_pass)
{
// call Thread.pass
bool ex_caught = false;
result = wxRuby_Funcall(ex_caught, rb_cThread, pass_id(), 0, 0);
if (ex_caught)
{
#ifdef __WXRB_DEBUG__
wxRuby_PrintException(result);
#endif
wxRubyApp::GetInstance()->_store_ruby_exception(result);
this->exit_code_ = 1;
m_shouldExit = true;
}
}

if (!m_shouldExit && needs_idle_evt)
{
needs_idle_evt = ProcessIdle();
if (needs_idle_evt)
break;
}
}

// if Exit() was called, don't dispatch any more events here
if (m_shouldExit)
break;

// process pending wx events first as they correspond to low-level events
// which happened before, i.e. typically pending events were queued by a
// previous call to Dispatch() and if we didn't process them now the next
// call to it might enqueue them again (as happens with e.g. socket events
// which would be generated as long as there is input available on socket
// and this input is only removed from it when pending event handlers are
// executed)
if ( wxTheApp && wxTheApp->HasPendingEvents() )
{
wxTheApp->ProcessPendingEvents();

// One of the pending event handlers could have decided to exit the
// loop so check for the flag before trying to dispatch more events
// (which could block indefinitely if no more are coming).
if ( m_shouldExit )
break;
}

// nothing doing, so just wait max 1 msec for an event
if (this->DispatchTimeout(1) == 0 && m_shouldExit)
break; // stop event loop

needs_idle_evt = true;
}

return this->exit_code_;
}

#if (wxMAJOR_VERSION > 3) || (wxMINOR_VERSION > 2)
virtual void DoStop(int rc) override
{
this->exit_code_ = rc;
#if !defined(__WXGTK__)
wxGUIEventLoop::DoStop(rc);
#endif
}
#else
virtual void ScheduleExit(int rc) override
{
this->exit_code_ = rc;
#if defined(__WXGTK__)
m_shouldExit = true;
#else
wxGUIEventLoop::ScheduleExit(rc);
#endif
}
#endif

private:
int exit_code_ {};
};

public:
static wxRubyApp* GetInstance () { return dynamic_cast<wxRubyApp*> (wxApp::GetInstance()); }

Expand Down Expand Up @@ -173,6 +296,16 @@ class wxRubyApp : public wxApp
return rc;
}

virtual int MainLoop() override
{
wxEventLoopBaseTiedPtr main_loop(&m_mainLoop, new wxRubyApp::EventLoop);

if (wxTheApp)
wxTheApp->OnLaunched();

return m_mainLoop ? m_mainLoop->Run() : -1;
}

// This method initializes the stock objects (Pens, Brushes, Fonts)
// before yielding to ruby by calling the App's on_init method.
// Note that as of wxWidget 2.8, the stock fonts in particular cannot
Expand Down Expand Up @@ -200,7 +333,7 @@ class wxRubyApp : public wxApp
#ifdef __WXRB_DEBUG__
wxRuby_PrintException(result);
#endif
rb_iv_set(this->self_, "@exception", result);
_store_ruby_exception(result);
result = Qfalse; // exit app
}

Expand Down Expand Up @@ -240,7 +373,7 @@ class wxRubyApp : public wxApp
#ifdef __WXRB_DEBUG__
wxRuby_PrintException(rc);
#endif
rb_iv_set(this->self_, "@exception", rc);
_store_ruby_exception(rc);
}
}

Expand Down
2 changes: 1 addition & 1 deletion ext/wxruby3/include/wxruby-SharedEventHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ static VALUE wxRuby_EvtHandler_make_shared(VALUE self)

static void wx_setup_WxRubySharedEvtHandler(VALUE mWxExt)
{
// mark thids extension Ractor safe
// mark this extension Ractor safe
rb_ext_ractor_safe(true);

cWxRubySharedEvtHandler = rb_define_class_under(mWxExt, "SharedEvtHandler", rb_cObject);
Expand Down
6 changes: 6 additions & 0 deletions lib/wx/core.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@
Wx::WXWIDGETS_MINOR_VERSION,
Wx::WXWIDGETS_RELEASE_NUMBER ]

# except when in debug mode or when a user defined diagnostics level is defined
# suppress all diagnostics from GTK
unless Wx::DEBUG || ENV['WXSUPPRESS_GTK_DIAGNOSTICS'] || Wx::PLATFORM != 'WXGTK'
Wx::App.gtk_suppress_diagnostics
end

# Helper functions
require 'wx/helpers'

Expand Down
5 changes: 0 additions & 5 deletions lib/wx/doc/core.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@
module Wx

class << self
# Returns trace level (always 0 if #wxrb_debug returns false)
# In case #wxrb_debug returns true #wxrb_trace_level= is also defined)
# @return [Integer]
attr_reader :wrb_trace_level

# Returns true if WXWIDGETS_VERSION >= ver
# @param [String,Array(Integer)] ver version string or integer array (1-3)
# @return [Boolean] true if WXWIDGETS_VERSION >= ver, false otherwise
Expand Down
Loading