Embedding an SDL Surface in a Tk Window

Tk is great, but sometimes it just isn't fast enough. SDL is fast, but has no support for input dialogs and other GUI conventions. By embedding an SDL surface in a Tk window you get the best of both worlds. Whether you want to use Tk to add a nice GUI to an SDL app or want to access SDL via Tcl/Tk, this article will show you how.

Embedding the SDL Surface

To embed an SDL surface in another window you have to alter the SDL_WINDOWID environmental variable so that it matches the ID of the window that you want the SDL surface embedded in. This must be done after the main window is displayed and before SDL_Init() is called.

To ensure that the Tk window is displayed you need to call something like:

Tcl_Eval(interp, "update");

From Tcl you must provide a window for the SDL surface to be embedded in. This should have the background set to "" otherwise you will get problems when other windows cover it:

frame .screen -width 400 -height 400 -background ""

Then to get the window ID and set SDL_WINDOWID:

int
setSDLWindowID(Tcl_Interp *interp)
{
  Tcl_Obj *result;
  char *windowID;
  char envBuf[50];

  /* .screen here is the name of the widget that you want to overwrite,
     this is normally a frame */
  if (Tcl_Eval(interp, "winfo id .screen") == TCL_ERROR)
    return 0;

  result = Tcl_GetObjResult(interp);
  Tcl_GetStringFromObj(windowID, NULL);

  snprintf(envBuf, 50, "SDL_WINDOWID=%s", windowID);
  SDL_putenv(buf);

  return 1;
}

The above uses SDL_putenv() rather than putenv() as this is recommended by an old SDL GUI FAQ

From this point you can call SDL_Init() and SDL_SetVideoMode(), but do remember to use the SDL_NOFRAME attribute:

SDL_Surface *
initScreenSurface(int width, int height, int depth)
{
  SDL_Surface *sfScreen;

  if (SDL_Init(SDL_INIT_EVERYTHING) < 0) {
    fprintf(stderr, "Couldn't initialize SDL: %s\n", SDL_GetError());
    return NULL;
  }

  sfScreen = SDL_SetVideoMode(width, height, depth,
                              SDL_NOFRAME|SDL_SWSURFACE|SDL_ANYFORMAT);
  if (sfScreen == NULL) {
    fprintf(stderr, "Couldn't initialize SDL: %s\n", SDL_GetError());
    return NULL;
  }

  return sfSreen;
}

The Event Loop

You must have an event loop that calls both SDL_PollEvent() and Tk_DoOneEvent(). The events will be handled mostly by Tk. However, you do need to detect SDL_QUIT from SDL_PollEvent() because SDL converts SIGTERM to this.

void
event_loop()
{
  SDL_Event event;

  while (!(SDL_PollEvent(&event) && event.type == SDL_QUIT)) {
    Tk_DoOneEvent(TK_ALL_EVENTS|TK_DONT_WAIT);
  }
}

Handling Key Release Events

From Tcl you can handle whichever events you need to detect. For example to bind the <KeyRelease> event to a key handler:

proc handleKey {key} {
  switch -regexp -- $key {
    .*Up$ {ball up}
    .*Down$ {ball down}
    .*Left$ {ball left}
    .*Right$ {ball right}
  }
}
bind all <KeyRelease> {handleKey %K}

Handling Expose Events

SDL also needs to know when the screen should be redrawn. From Tcl:

bind . <Expose> {screen_refresh}

And to provide the screen_refresh command:

static SDL_Surface *sfScreen = NULL;

void
screenRefresh(void)
{
  if (sfScreen != NULL)
    SDL_Flip(sfScreen);
}

static int
ScreenRefreshCmd(ClientData clientData, Tcl_Interp *interp,
                 int objc, Tcl_Obj *CONST objv[])
{
  if (objc != 1) {
    Tcl_WrongNumArgs(interp, 1, objv, "");
  }

  screenRefresh();
  return TCL_OK;
}

void createCommands(Tcl_Interp *interp)
{
  Tcl_CreateObjCommand(interp, "screen_refresh", ScreenRefreshCmd,
                       (ClientData) NULL,
                       (Tcl_CmdDeleteProc *) NULL);
}

A Small Demonstration

I have created the sdl_and_tk_demo on github to demonstrate how to put this altogether. The README contains information on how to compile and run the demo. This demo was inspired by Kent Mein's SDL and Tk MDI demo.

Creative Commons License
Embedding an SDL Surface in a Tk Window by Lawrence Woodman is licensed under a Creative Commons Attribution 4.0 International License.

Share This Post

Feedback/Discuss

Sign up to get new articles straight to your inbox.

Delivered by FeedBurner

Related Articles

Introducing Ornament a Tcl Template Module

Ornament is a Tcl template module that allows you to define, parse and compile a template to produce a script which can then be run using a safe interpreter. The idea came from the Templates and subst...   Read More

xdgbasedir: A Tcl Module to Access the XDG Base Directory Specification

Unix has traditionally lacked a consistent way of storing user specific and system wide configuration and support files. This has lead to a mess of dot files in a user's home directory and other assoc...   Read More

Compiling a Tcl Script into an Executable

Locating Tcl scripts to load from an executable can be awkward if you want to make your program cross-platform. An easier way is to compile a Tcl script directly into the executable and let that scrip...   Read More

Double-Width C64 Style Font on the Commodore VIC-20

The C64 used a fatter double-width font because the video quality of early VIC-IIs wasn't very good. Therefore they had to make the C64's font fatter than the VIC-20's. However, this fatter font look...   Read More

The Pilot Programming Language on CP/M

Pilot was created by John A. Starkweather in the early 1960s as a programming language for Computer Assisted Instruction. It has often been compared to Logo because of its use with children. However,...   Read More