SDL_WINDOWID, wxWidgets, and win32/gtk differences

I’m trying to embed SDL in a wxWidgets panel. I had a perfect working
application in Windows XP.

However, it failed to work at all in Linux/GTK+ 2. It would always exit
with BadWindow error. I couldn’t find anything that definitively said
that the SDL_WINDOWID trick couldn’t be used with non top-level windows,
but there were some questions about whether this was possible or not.

I decided to abandon the panel for now and simply draw on the frame. I
got rid of the panel. The application once again works perfectly in
Windows, but is useless in Linux/GTK+ 2. Although the application
doesn’t crash and starts up, it never draws on the frame.

The gtk-demo works just fine. I had to change one line to get it to
compile under GTK+ 2.0, but it works fine after that:

// this no longer seems to exist in GTK+ 2.0
// gtk_accel_group_attach(accel_group, GTK_OBJECT(window));
gtk_window_add_accel_group(GTK_WINDOW(window), accel_group);

Here is my source. My application in Linux with wxWidgets is using GTK,
so I do not understand why the gtk-demo works, but my program does not.

If you comment out the putenv(s) line, the program will run, but the SDL
output will be in another window. I want it to co-opt my frame. The
wxFrame is a gtk top-level window.

/*******************************************************************************
// Headers
*******************************************************************************/

#include

#include <wx/wxprec.h>

#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif

#include “SDL.h”

#if defined(WXGTK)
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#endif

/*******************************************************************************
// Global Declarations
*******************************************************************************/

enum {
ID_FRAME = 10000,
IDM_FILE_EXIT,
IDM_HELP_ABOUT
};

/*******************************************************************************
// SDLFrame Class
*******************************************************************************/

DECLARE_EVENT_TYPE(EVT_PAINT_SDL_FRAME, 0)

class SDLFrame : public wxFrame {
DECLARE_CLASS(SDLFrame)
DECLARE_EVENT_TABLE()

private:
SDL_Surface *screen;

 /**
  * Called when exit from the file menu is selected.*
  * @param event The associated wxCommandEvent (unused).
  */
 void onFileExit(wxCommandEvent &event);

 /**
  * Called when about from the help menu is selected.
  *
  * @param event The associated wxCommandEvent (unused).
  */
 void onHelpAbout(wxCommandEvent &event);

 /**
  * Called to paint the frame.
  *
  * @param event The associated wxCommandEvent (unused).
  */
 void onPaint(wxCommandEvent &event);

public:
/**
* Creates a new SDLFrame.
*/
SDLFrame();

 /**
  * Gets the native window ID for this SDLFrame.
  *
  * @return The native window ID.
  */
 long getWindowID() const;

 /**
  * Sets the SDL_Surface to use with this SDLFrame.
  *
  * @param screen The new SDL_Surface.
  */
 void setScreen(SDL_Surface *screen);

};

inline void SDLFrame::onFileExit(wxCommandEvent &) { Close(); }

inline long SDLFrame::getWindowID() const {
#if defined(WXMSW)
return reinterpret_cast(GetHandle());
#elif defined(WXGTK)
return GDK_WINDOW_XWINDOW(
(static_cast<GtkWidget*>(GetHandle()))->window
);
#elif defined(WXOSX)
#error “We don’t yet work on OS-X. Sorry.”
#else
#error “SDLFrame only works on Windows, GTK, and OS-X.”
#endif
}

inline void SDLFrame::setScreen(SDL_Surface *screen) {
this->screen = screen;
}

IMPLEMENT_CLASS(SDLFrame, wxFrame)

BEGIN_EVENT_TABLE(SDLFrame, wxFrame)
EVT_MENU(IDM_FILE_EXIT, SDLFrame::onFileExit)
EVT_MENU(IDM_HELP_ABOUT, SDLFrame::onHelpAbout)

 EVT_COMMAND(ID_FRAME, EVT_PAINT_SDL_FRAME, SDLFrame::onPaint)

END_EVENT_TABLE()

DEFINE_EVENT_TYPE(EVT_PAINT_SDL_FRAME)

SDLFrame::SDLFrame() : screen(NULL) {
// Create the SDLFrame
Create(NULL, ID_FRAME, wxT(“Frame Title”), wxDefaultPosition,
wxSize(640, 480), wxCAPTION | wxSYSTEM_MENU |
wxMINIMIZE_BOX | wxCLOSE_BOX);

 // create the main menubar
 wxMenuBar *mb = new wxMenuBar;

 // create the file menu
 wxMenu *fileMenu = new wxMenu;
 fileMenu->Append(IDM_FILE_EXIT, wxT("E&xit"));

 // add the file menu to the menu bar
 mb->Append(fileMenu, wxT("&File"));

 // create the help menu
 wxMenu *helpMenu = new wxMenu;
 helpMenu->Append(IDM_HELP_ABOUT, wxT("About"));

 // add the help menu to the menu bar
 mb->Append(helpMenu, wxT("&Help"));

 // add the menu bar to the SDLFrame
 SetMenuBar(mb);

}

void SDLFrame::onHelpAbout(wxCommandEvent &) {
wxMessageBox(wxT(“wx-sdl tutorial\nCopyright © 2005 John Ratliff”),
wxT(“about wx-sdl tutorial”),
wxOK | wxICON_INFORMATION);
}

void SDLFrame::onPaint(wxCommandEvent &) {
printf(“calling onPaint()…\n”);

 // Lock surface if needed
 if (SDL_MUSTLOCK(screen)) {
     if (SDL_LockSurface(screen) < 0) {
         return;
     }
 }

 printf("locked!\n");

 // Ask SDL for the time in milliseconds
 int tick = SDL_GetTicks();

 // Declare y offset variable
 int yofs = 0;

 for (int i = 0; i < 480; i++) {
     for (int j = 0, ofs = yofs; j < 640; j++, ofs++) {
         ((unsigned int *)screen->pixels)[ofs] =
		i * i + j * j + tick;
     }

     yofs += screen->pitch / 4;
 }

 printf("drawing...\n");

 // Unlock if needed
 if (SDL_MUSTLOCK(screen)) {
     SDL_UnlockSurface(screen);
 }

 printf("unlock!\n");

 // Tell SDL to update the whole screen
 SDL_UpdateRect(screen, 0, 0, 640, 480);

 printf("goodbye!\n");
 fflush(stdout);

}

/*******************************************************************************
// DisplayThread Class
*******************************************************************************/

class DisplayThread : public wxThread {
private:
SDLFrame &frame;
bool running;

 /**
  * The thread code. Called by wxThread::Run().
  */
 void *Entry();

public:
/**
* Creates a new DisplayThread.
*
* @param frame The SDLFrame to talk to.
*/
DisplayThread(SDLFrame &frame);

 /**
  * Stops this thread.
  */
 void stop();

};

inline DisplayThread::DisplayThread(SDLFrame &frame) : wxThread(),
frame(frame),
running(true) {}

inline void DisplayThread::stop() { running = false; }

void *DisplayThread::Entry() {
// event used to tell the frame to paint itself
wxCommandEvent event(EVT_PAINT_SDL_FRAME, frame.GetId());
event.SetEventObject(&frame);

 // while we're still going
 while (running) {
     printf("running...\n");

     // ask the frame to paint itself
     frame.AddPendingEvent(event);

     // throttle to avoid flooding the SDLFrame with paint events
     wxMilliSleep(33);
 }

 // unused thread return value
 return NULL;

}

/*******************************************************************************
// SDLApp Class
*******************************************************************************/

class SDLApp : public wxApp {
DECLARE_CLASS(SDLApp)

private:
SDLFrame *frame;
DisplayThread *thread;

public:
/**
* Called to initialize this SDLApp.
*
* @return true if initialization succeeded; false otherwise.
*/
bool OnInit();

 /**
  * Called to run this SDLApp.
  *
  * @return The status code (0 if good, non-0 if bad).
  */
 int OnRun();

 /**
  * Called when this SDLApp is ready to exit.
  *
  * @return The exit code.
  */
 int OnExit();

};

bool SDLApp::OnInit() {
// create the SDLFrame
frame = new SDLFrame;
frame->Show();

 // Our SDLFrame is the Top Window
 SetTopWindow(frame);

 // initialization should always succeed
 return true;

}

int SDLApp::OnRun() {
{
// hack to make SDL use our SDLFrame for drawing
char s[81];

     sprintf(s, "SDL_WINDOWID=%ld", frame->getWindowID());
     putenv(s);
 }

 // initialize SDL
 if (SDL_Init(SDL_INIT_VIDEO) < 0) {
     std::cerr << "unable to init SDL: " << SDL_GetError() << '\n';

     return -1;
 }

 // create the SDL_Surface for our SDLFrame
 SDL_Surface *screen;

if ((screen = SDL_SetVideoMode(640, 480, 32, SDL_SWSURFACE)) == NULL) {
std::cerr << "unable to set video mode (640x480): " <<
SDL_GetError() << ‘\n’;

     return -1;
 }

 frame->setScreen(screen);

 // start the DisplayThread to tell the SDLFrame to paint
 thread = new DisplayThread(*frame);
 thread->Create();
 thread->Run();

 // start the main loop
 return wxApp::OnRun();

}

int SDLApp::OnExit() {
// stop the DisplayThread
thread->stop();

 // cleanup SDL
 SDL_Quit();

 // return the standard exit code
 return wxApp::OnExit();

}

IMPLEMENT_CLASS(SDLApp, wxApp)
IMPLEMENT_APP(SDLApp)

Thanks,

–John Ratliff