src/video/x11/SDL_x11messagebox.c
author Sam Lantinga <slouken@libsdl.org>
Sat, 02 Jan 2016 10:10:34 -0800
changeset 9998 f67cf37e9cd4
parent 9801 752406828724
child 10609 d702ecbd8ba7
permissions -rw-r--r--
Updated copyright to 2016
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2016 Sam Lantinga <slouken@libsdl.org>
     4 
     5   This software is provided 'as-is', without any express or implied
     6   warranty.  In no event will the authors be held liable for any damages
     7   arising from the use of this software.
     8 
     9   Permission is granted to anyone to use this software for any purpose,
    10   including commercial applications, and to alter it and redistribute it
    11   freely, subject to the following restrictions:
    12 
    13   1. The origin of this software must not be misrepresented; you must not
    14      claim that you wrote the original software. If you use this software
    15      in a product, an acknowledgment in the product documentation would be
    16      appreciated but is not required.
    17   2. Altered source versions must be plainly marked as such, and must not be
    18      misrepresented as being the original software.
    19   3. This notice may not be removed or altered from any source distribution.
    20 */
    21 
    22 #include "../../SDL_internal.h"
    23 
    24 #if SDL_VIDEO_DRIVER_X11
    25 
    26 #include "SDL.h"
    27 #include "SDL_x11video.h"
    28 #include "SDL_x11dyn.h"
    29 #include "SDL_assert.h"
    30 
    31 #include <X11/keysym.h>
    32 #include <locale.h>
    33 
    34 
    35 #define SDL_FORK_MESSAGEBOX 1
    36 #define SDL_SET_LOCALE      1
    37 
    38 #if SDL_FORK_MESSAGEBOX
    39 #include <sys/types.h>
    40 #include <sys/wait.h>
    41 #include <unistd.h>
    42 #include <errno.h>
    43 #endif
    44 
    45 #define MAX_BUTTONS             8       /* Maximum number of buttons supported */
    46 #define MAX_TEXT_LINES          32      /* Maximum number of text lines supported */
    47 #define MIN_BUTTON_WIDTH        64      /* Minimum button width */
    48 #define MIN_DIALOG_WIDTH        200     /* Minimum dialog width */
    49 #define MIN_DIALOG_HEIGHT       100     /* Minimum dialog height */
    50 
    51 static const char g_MessageBoxFontLatin1[] = "-*-*-medium-r-normal--0-120-*-*-p-0-iso8859-1";
    52 static const char g_MessageBoxFont[] = "-*-*-*-*-*-*-*-120-*-*-*-*-*-*";
    53 
    54 static const SDL_MessageBoxColor g_default_colors[ SDL_MESSAGEBOX_COLOR_MAX ] = {
    55     { 56,  54,  53  }, /* SDL_MESSAGEBOX_COLOR_BACKGROUND, */
    56     { 209, 207, 205 }, /* SDL_MESSAGEBOX_COLOR_TEXT, */
    57     { 140, 135, 129 }, /* SDL_MESSAGEBOX_COLOR_BUTTON_BORDER, */
    58     { 105, 102, 99  }, /* SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND, */
    59     { 205, 202, 53  }, /* SDL_MESSAGEBOX_COLOR_BUTTON_SELECTED, */
    60 };
    61 
    62 #define SDL_MAKE_RGB( _r, _g, _b )  ( ( ( Uint32 )( _r ) << 16 ) | \
    63                                       ( ( Uint32 )( _g ) << 8 ) |  \
    64                                       ( ( Uint32 )( _b ) ) )
    65 
    66 typedef struct SDL_MessageBoxButtonDataX11 {
    67     int x, y;                           /* Text position */
    68     int length;                         /* Text length */
    69     int text_width;                     /* Text width */
    70 
    71     SDL_Rect rect;                      /* Rectangle for entire button */
    72 
    73     const SDL_MessageBoxButtonData *buttondata;   /* Button data from caller */
    74 } SDL_MessageBoxButtonDataX11;
    75 
    76 typedef struct TextLineData {
    77     int width;                          /* Width of this text line */
    78     int length;                         /* String length of this text line */
    79     const char *text;                   /* Text for this line */
    80 } TextLineData;
    81 
    82 typedef struct SDL_MessageBoxDataX11
    83 {
    84     Display *display;
    85     int screen;
    86     Window window;
    87 #if SDL_VIDEO_DRIVER_X11_XDBE
    88     XdbeBackBuffer buf;
    89     SDL_bool xdbe;                      /* Whether Xdbe is present or not */
    90 #endif
    91     long event_mask;
    92     Atom wm_protocols;
    93     Atom wm_delete_message;
    94 
    95     int dialog_width;                   /* Dialog box width. */
    96     int dialog_height;                  /* Dialog box height. */
    97 
    98     XFontSet font_set;                  /* for UTF-8 systems */
    99     XFontStruct *font_struct;           /* Latin1 (ASCII) fallback. */
   100     int xtext, ytext;                   /* Text position to start drawing at. */
   101     int numlines;                       /* Count of Text lines. */
   102     int text_height;                    /* Height for text lines. */
   103     TextLineData linedata[ MAX_TEXT_LINES ];
   104 
   105     int *pbuttonid;                     /* Pointer to user return buttonid value. */
   106 
   107     int button_press_index;             /* Index into buttondata/buttonpos for button which is pressed (or -1). */
   108     int mouse_over_index;               /* Index into buttondata/buttonpos for button mouse is over (or -1). */
   109 
   110     int numbuttons;                     /* Count of buttons. */
   111     const SDL_MessageBoxButtonData *buttondata;
   112     SDL_MessageBoxButtonDataX11 buttonpos[ MAX_BUTTONS ];
   113 
   114     Uint32 color[ SDL_MESSAGEBOX_COLOR_MAX ];
   115 
   116     const SDL_MessageBoxData *messageboxdata;
   117 } SDL_MessageBoxDataX11;
   118 
   119 /* Maximum helper for ints. */
   120 static SDL_INLINE int
   121 IntMax( int a, int b )
   122 {
   123     return ( a > b  ) ? a : b;
   124 }
   125 
   126 /* Return width and height for a string. */
   127 static void
   128 GetTextWidthHeight( SDL_MessageBoxDataX11 *data, const char *str, int nbytes, int *pwidth, int *pheight )
   129 {
   130     if (SDL_X11_HAVE_UTF8) {
   131         XRectangle overall_ink, overall_logical;
   132         X11_Xutf8TextExtents(data->font_set, str, nbytes, &overall_ink, &overall_logical);
   133         *pwidth = overall_logical.width;
   134         *pheight = overall_logical.height;
   135     } else {
   136         XCharStruct text_structure;
   137         int font_direction, font_ascent, font_descent;
   138         X11_XTextExtents( data->font_struct, str, nbytes,
   139                       &font_direction, &font_ascent, &font_descent,
   140                       &text_structure );
   141         *pwidth = text_structure.width;
   142         *pheight = text_structure.ascent + text_structure.descent;
   143     }
   144 }
   145 
   146 /* Return index of button if position x,y is contained therein. */
   147 static int
   148 GetHitButtonIndex( SDL_MessageBoxDataX11 *data, int x, int y )
   149 {
   150     int i;
   151     int numbuttons = data->numbuttons;
   152     SDL_MessageBoxButtonDataX11 *buttonpos = data->buttonpos;
   153 
   154     for ( i = 0; i < numbuttons; i++ ) {
   155         SDL_Rect *rect = &buttonpos[ i ].rect;
   156 
   157         if ( ( x >= rect->x ) &&
   158              ( x <= ( rect->x + rect->w ) ) &&
   159              ( y >= rect->y ) &&
   160              ( y <= ( rect->y + rect->h ) ) ) {
   161             return i;
   162         }
   163     }
   164 
   165     return -1;
   166 }
   167 
   168 /* Initialize SDL_MessageBoxData structure and Display, etc. */
   169 static int
   170 X11_MessageBoxInit( SDL_MessageBoxDataX11 *data, const SDL_MessageBoxData * messageboxdata, int * pbuttonid )
   171 {
   172     int i;
   173     int numbuttons = messageboxdata->numbuttons;
   174     const SDL_MessageBoxButtonData *buttondata = messageboxdata->buttons;
   175     const SDL_MessageBoxColor *colorhints;
   176 
   177     if ( numbuttons > MAX_BUTTONS ) {
   178         return SDL_SetError("Too many buttons (%d max allowed)", MAX_BUTTONS);
   179     }
   180 
   181     data->dialog_width = MIN_DIALOG_WIDTH;
   182     data->dialog_height = MIN_DIALOG_HEIGHT;
   183     data->messageboxdata = messageboxdata;
   184     data->buttondata = buttondata;
   185     data->numbuttons = numbuttons;
   186     data->pbuttonid = pbuttonid;
   187 
   188     data->display = X11_XOpenDisplay( NULL );
   189     if ( !data->display ) {
   190         return SDL_SetError("Couldn't open X11 display");
   191     }
   192 
   193     if (SDL_X11_HAVE_UTF8) {
   194         char **missing = NULL;
   195         int num_missing = 0;
   196         data->font_set = X11_XCreateFontSet(data->display, g_MessageBoxFont,
   197                                         &missing, &num_missing, NULL);
   198         if ( missing != NULL ) {
   199             X11_XFreeStringList(missing);
   200         }
   201         if ( data->font_set == NULL ) {
   202             return SDL_SetError("Couldn't load font %s", g_MessageBoxFont);
   203         }
   204     } else {
   205         data->font_struct = X11_XLoadQueryFont( data->display, g_MessageBoxFontLatin1 );
   206         if ( data->font_struct == NULL ) {
   207             return SDL_SetError("Couldn't load font %s", g_MessageBoxFontLatin1);
   208         }
   209     }
   210 
   211     if ( messageboxdata->colorScheme ) {
   212         colorhints = messageboxdata->colorScheme->colors;
   213     } else {
   214         colorhints = g_default_colors;
   215     }
   216 
   217     /* Convert our SDL_MessageBoxColor r,g,b values to packed RGB format. */
   218     for ( i = 0; i < SDL_MESSAGEBOX_COLOR_MAX; i++ ) {
   219         data->color[ i ] = SDL_MAKE_RGB( colorhints[ i ].r, colorhints[ i ].g, colorhints[ i ].b );
   220     }
   221 
   222     return 0;
   223 }
   224 
   225 /* Calculate and initialize text and button locations. */
   226 static int
   227 X11_MessageBoxInitPositions( SDL_MessageBoxDataX11 *data )
   228 {
   229     int i;
   230     int ybuttons;
   231     int text_width_max = 0;
   232     int button_text_height = 0;
   233     int button_width = MIN_BUTTON_WIDTH;
   234     const SDL_MessageBoxData *messageboxdata = data->messageboxdata;
   235 
   236     /* Go over text and break linefeeds into separate lines. */
   237     if ( messageboxdata->message && messageboxdata->message[ 0 ] ) {
   238         const char *text = messageboxdata->message;
   239         TextLineData *plinedata = data->linedata;
   240 
   241         for ( i = 0; i < MAX_TEXT_LINES; i++, plinedata++ ) {
   242             int height;
   243             char *lf = SDL_strchr( ( char * )text, '\n' );
   244 
   245             data->numlines++;
   246 
   247             /* Only grab length up to lf if it exists and isn't the last line. */
   248             plinedata->length = ( lf && ( i < MAX_TEXT_LINES - 1 ) ) ? ( lf - text ) : SDL_strlen( text );
   249             plinedata->text = text;
   250 
   251             GetTextWidthHeight( data, text, plinedata->length, &plinedata->width, &height );
   252 
   253             /* Text and widths are the largest we've ever seen. */
   254             data->text_height = IntMax( data->text_height, height );
   255             text_width_max = IntMax( text_width_max, plinedata->width );
   256 
   257             if (lf && (lf > text) && (lf[-1] == '\r')) {
   258                 plinedata->length--;
   259             }
   260 
   261             text += plinedata->length + 1;
   262 
   263             /* Break if there are no more linefeeds. */
   264             if ( !lf )
   265                 break;
   266         }
   267 
   268         /* Bump up the text height slightly. */
   269         data->text_height += 2;
   270     }
   271 
   272     /* Loop through all buttons and calculate the button widths and height. */
   273     for ( i = 0; i < data->numbuttons; i++ ) {
   274         int height;
   275 
   276         data->buttonpos[ i ].buttondata = &data->buttondata[ i ];
   277         data->buttonpos[ i ].length = SDL_strlen( data->buttondata[ i ].text );
   278 
   279         GetTextWidthHeight( data, data->buttondata[ i ].text, SDL_strlen( data->buttondata[ i ].text ),
   280                             &data->buttonpos[ i ].text_width, &height );
   281 
   282         button_width = IntMax( button_width, data->buttonpos[ i ].text_width );
   283         button_text_height = IntMax( button_text_height, height );
   284     }
   285 
   286     if ( data->numlines ) {
   287         /* x,y for this line of text. */
   288         data->xtext = data->text_height;
   289         data->ytext = data->text_height + data->text_height;
   290 
   291         /* Bump button y down to bottom of text. */
   292         ybuttons = 3 * data->ytext / 2 + ( data->numlines - 1 ) * data->text_height;
   293 
   294         /* Bump the dialog box width and height up if needed. */
   295         data->dialog_width = IntMax( data->dialog_width, 2 * data->xtext + text_width_max );
   296         data->dialog_height = IntMax( data->dialog_height, ybuttons );
   297     } else {
   298         /* Button y starts at height of button text. */
   299         ybuttons = button_text_height;
   300     }
   301 
   302     if ( data->numbuttons ) {
   303         int x, y;
   304         int width_of_buttons;
   305         int button_spacing = button_text_height;
   306         int button_height = 2 * button_text_height;
   307 
   308         /* Bump button width up a bit. */
   309         button_width += button_text_height;
   310 
   311         /* Get width of all buttons lined up. */
   312         width_of_buttons = data->numbuttons * button_width + ( data->numbuttons - 1 ) * button_spacing;
   313 
   314         /* Bump up dialog width and height if buttons are wider than text. */
   315         data->dialog_width = IntMax( data->dialog_width, width_of_buttons + 2 * button_spacing );
   316         data->dialog_height = IntMax( data->dialog_height, ybuttons + 2 * button_height );
   317 
   318         /* Location for first button. */
   319         x = ( data->dialog_width - width_of_buttons ) / 2;
   320         y = ybuttons + ( data->dialog_height - ybuttons - button_height ) / 2;
   321 
   322         for ( i = 0; i < data->numbuttons; i++ ) {
   323             /* Button coordinates. */
   324             data->buttonpos[ i ].rect.x = x;
   325             data->buttonpos[ i ].rect.y = y;
   326             data->buttonpos[ i ].rect.w = button_width;
   327             data->buttonpos[ i ].rect.h = button_height;
   328 
   329             /* Button text coordinates. */
   330             data->buttonpos[ i ].x = x + ( button_width - data->buttonpos[ i ].text_width ) / 2;
   331             data->buttonpos[ i ].y = y + ( button_height - button_text_height - 1 ) / 2 + button_text_height;
   332 
   333             /* Scoot over for next button. */
   334             x += button_width + button_spacing;
   335         }
   336     }
   337 
   338     return 0;
   339 }
   340 
   341 /* Free SDL_MessageBoxData data. */
   342 static void
   343 X11_MessageBoxShutdown( SDL_MessageBoxDataX11 *data )
   344 {
   345     if ( data->font_set != NULL ) {
   346         X11_XFreeFontSet( data->display, data->font_set );
   347         data->font_set = NULL;
   348     }
   349 
   350     if ( data->font_struct != NULL ) {
   351         X11_XFreeFont( data->display, data->font_struct );
   352         data->font_struct = NULL;
   353     }
   354 
   355 #if SDL_VIDEO_DRIVER_X11_XDBE
   356     if ( SDL_X11_HAVE_XDBE && data->xdbe ) {
   357         X11_XdbeDeallocateBackBufferName(data->display, data->buf);
   358     }
   359 #endif
   360 
   361     if ( data->display ) {
   362         if ( data->window != None ) {
   363             X11_XWithdrawWindow( data->display, data->window, data->screen );
   364             X11_XDestroyWindow( data->display, data->window );
   365             data->window = None;
   366         }
   367 
   368         X11_XCloseDisplay( data->display );
   369         data->display = NULL;
   370     }
   371 }
   372 
   373 /* Create and set up our X11 dialog box indow. */
   374 static int
   375 X11_MessageBoxCreateWindow( SDL_MessageBoxDataX11 *data )
   376 {
   377     int x, y;
   378     XSizeHints *sizehints;
   379     XSetWindowAttributes wnd_attr;
   380     Atom _NET_WM_WINDOW_TYPE, _NET_WM_WINDOW_TYPE_DIALOG, _NET_WM_NAME, UTF8_STRING;
   381     Display *display = data->display;
   382     SDL_WindowData *windowdata = NULL;
   383     const SDL_MessageBoxData *messageboxdata = data->messageboxdata;
   384 
   385     if ( messageboxdata->window ) {
   386         SDL_DisplayData *displaydata =
   387             (SDL_DisplayData *) SDL_GetDisplayForWindow(messageboxdata->window)->driverdata;
   388         windowdata = (SDL_WindowData *)messageboxdata->window->driverdata;
   389         data->screen = displaydata->screen;
   390     } else {
   391         data->screen = DefaultScreen( display );
   392     }
   393 
   394     data->event_mask = ExposureMask |
   395                        ButtonPressMask | ButtonReleaseMask | KeyPressMask | KeyReleaseMask |
   396                        StructureNotifyMask | FocusChangeMask | PointerMotionMask;
   397     wnd_attr.event_mask = data->event_mask;
   398 
   399     data->window = X11_XCreateWindow(
   400                        display, RootWindow(display, data->screen),
   401                        0, 0,
   402                        data->dialog_width, data->dialog_height,
   403                        0, CopyFromParent, InputOutput, CopyFromParent,
   404                        CWEventMask, &wnd_attr );
   405     if ( data->window == None ) {
   406         return SDL_SetError("Couldn't create X window");
   407     }
   408 
   409     if ( windowdata ) {
   410         /* http://tronche.com/gui/x/icccm/sec-4.html#WM_TRANSIENT_FOR */
   411         X11_XSetTransientForHint( display, data->window, windowdata->xwindow );
   412     }
   413 
   414     X11_XStoreName( display, data->window, messageboxdata->title );
   415     _NET_WM_NAME = X11_XInternAtom(display, "_NET_WM_NAME", False);
   416     UTF8_STRING = X11_XInternAtom(display, "UTF8_STRING", False);
   417     X11_XChangeProperty(display, data->window, _NET_WM_NAME, UTF8_STRING, 8,
   418                     PropModeReplace, (unsigned char *) messageboxdata->title,
   419                     strlen(messageboxdata->title) + 1 );
   420 
   421     /* Let the window manager know this is a dialog box */
   422     _NET_WM_WINDOW_TYPE = X11_XInternAtom(display, "_NET_WM_WINDOW_TYPE", False);
   423     _NET_WM_WINDOW_TYPE_DIALOG = X11_XInternAtom(display, "_NET_WM_WINDOW_TYPE_DIALOG", False);
   424     X11_XChangeProperty(display, data->window, _NET_WM_WINDOW_TYPE, XA_ATOM, 32,
   425                     PropModeReplace,
   426                     (unsigned char *)&_NET_WM_WINDOW_TYPE_DIALOG, 1);
   427 
   428     /* Allow the window to be deleted by the window manager */
   429     data->wm_protocols = X11_XInternAtom( display, "WM_PROTOCOLS", False );
   430     data->wm_delete_message = X11_XInternAtom( display, "WM_DELETE_WINDOW", False );
   431     X11_XSetWMProtocols( display, data->window, &data->wm_delete_message, 1 );
   432 
   433     if ( windowdata ) {
   434         XWindowAttributes attrib;
   435         Window dummy;
   436 
   437         X11_XGetWindowAttributes(display, windowdata->xwindow, &attrib);
   438         x = attrib.x + ( attrib.width - data->dialog_width ) / 2;
   439         y = attrib.y + ( attrib.height - data->dialog_height ) / 3 ;
   440         X11_XTranslateCoordinates(display, windowdata->xwindow, RootWindow(display, data->screen), x, y, &x, &y, &dummy);
   441     } else {
   442         const SDL_VideoDevice *dev = SDL_GetVideoDevice();
   443         if ((dev) && (dev->displays) && (dev->num_displays > 0)) {
   444             const SDL_VideoDisplay *dpy = &dev->displays[0];
   445             const SDL_DisplayData *dpydata = (SDL_DisplayData *) dpy->driverdata;
   446             x = dpydata->x + (( dpy->current_mode.w - data->dialog_width ) / 2);
   447             y = dpydata->y + (( dpy->current_mode.h - data->dialog_height ) / 3);
   448         } else {   /* oh well. This will misposition on a multi-head setup. Init first next time. */
   449             x = ( DisplayWidth( display, data->screen ) - data->dialog_width ) / 2;
   450             y = ( DisplayHeight( display, data->screen ) - data->dialog_height ) / 3 ;
   451         }
   452     }
   453     X11_XMoveWindow( display, data->window, x, y );
   454 
   455     sizehints = X11_XAllocSizeHints();
   456     if ( sizehints ) {
   457         sizehints->flags = USPosition | USSize | PMaxSize | PMinSize;
   458         sizehints->x = x;
   459         sizehints->y = y;
   460         sizehints->width = data->dialog_width;
   461         sizehints->height = data->dialog_height;
   462 
   463         sizehints->min_width = sizehints->max_width = data->dialog_width;
   464         sizehints->min_height = sizehints->max_height = data->dialog_height;
   465 
   466         X11_XSetWMNormalHints( display, data->window, sizehints );
   467 
   468         X11_XFree( sizehints );
   469     }
   470 
   471     X11_XMapRaised( display, data->window );
   472 
   473 #if SDL_VIDEO_DRIVER_X11_XDBE
   474     /* Initialise a back buffer for double buffering */
   475     if (SDL_X11_HAVE_XDBE) {
   476         int xdbe_major, xdbe_minor;
   477         if (X11_XdbeQueryExtension(display, &xdbe_major, &xdbe_minor) != 0) {
   478             data->xdbe = SDL_TRUE;
   479             data->buf = X11_XdbeAllocateBackBufferName(display, data->window, XdbeUndefined);
   480         } else {
   481             data->xdbe = SDL_FALSE;
   482         }
   483     }
   484 #endif
   485 
   486     return 0;
   487 }
   488 
   489 /* Draw our message box. */
   490 static void
   491 X11_MessageBoxDraw( SDL_MessageBoxDataX11 *data, GC ctx )
   492 {
   493     int i;
   494     Drawable window = data->window;
   495     Display *display = data->display;
   496 
   497 #if SDL_VIDEO_DRIVER_X11_XDBE
   498     if (SDL_X11_HAVE_XDBE && data->xdbe) {
   499         window = data->buf;
   500         X11_XdbeBeginIdiom(data->display);
   501     }
   502 #endif
   503 
   504     X11_XSetForeground( display, ctx, data->color[ SDL_MESSAGEBOX_COLOR_BACKGROUND ] );
   505     X11_XFillRectangle( display, window, ctx, 0, 0, data->dialog_width, data->dialog_height );
   506 
   507     X11_XSetForeground( display, ctx, data->color[ SDL_MESSAGEBOX_COLOR_TEXT ] );
   508     for ( i = 0; i < data->numlines; i++ ) {
   509         TextLineData *plinedata = &data->linedata[ i ];
   510 
   511         if (SDL_X11_HAVE_UTF8) {
   512             X11_Xutf8DrawString( display, window, data->font_set, ctx,
   513                              data->xtext, data->ytext + i * data->text_height,
   514                              plinedata->text, plinedata->length );
   515         } else {
   516             X11_XDrawString( display, window, ctx,
   517                          data->xtext, data->ytext + i * data->text_height,
   518                          plinedata->text, plinedata->length );
   519         }
   520     }
   521 
   522     for ( i = 0; i < data->numbuttons; i++ ) {
   523         SDL_MessageBoxButtonDataX11 *buttondatax11 = &data->buttonpos[ i ];
   524         const SDL_MessageBoxButtonData *buttondata = buttondatax11->buttondata;
   525         int border = ( buttondata->flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT ) ? 2 : 0;
   526         int offset = ( ( data->mouse_over_index == i ) && ( data->button_press_index == data->mouse_over_index ) ) ? 1 : 0;
   527 
   528         X11_XSetForeground( display, ctx, data->color[ SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND ] );
   529         X11_XFillRectangle( display, window, ctx,
   530                         buttondatax11->rect.x - border, buttondatax11->rect.y - border,
   531                         buttondatax11->rect.w + 2 * border, buttondatax11->rect.h + 2 * border );
   532 
   533         X11_XSetForeground( display, ctx, data->color[ SDL_MESSAGEBOX_COLOR_BUTTON_BORDER ] );
   534         X11_XDrawRectangle( display, window, ctx,
   535                         buttondatax11->rect.x, buttondatax11->rect.y,
   536                         buttondatax11->rect.w, buttondatax11->rect.h );
   537 
   538         X11_XSetForeground( display, ctx, ( data->mouse_over_index == i ) ?
   539                         data->color[ SDL_MESSAGEBOX_COLOR_BUTTON_SELECTED ] :
   540                         data->color[ SDL_MESSAGEBOX_COLOR_TEXT ] );
   541 
   542         if (SDL_X11_HAVE_UTF8) {
   543             X11_Xutf8DrawString( display, window, data->font_set, ctx,
   544                              buttondatax11->x + offset,
   545                              buttondatax11->y + offset,
   546                              buttondata->text, buttondatax11->length );
   547         } else {
   548             X11_XDrawString( display, window, ctx,
   549                          buttondatax11->x + offset, buttondatax11->y + offset,
   550                          buttondata->text, buttondatax11->length );
   551         }
   552     }
   553 
   554 #if SDL_VIDEO_DRIVER_X11_XDBE
   555     if (SDL_X11_HAVE_XDBE && data->xdbe) {
   556         XdbeSwapInfo swap_info;
   557         swap_info.swap_window = data->window;
   558         swap_info.swap_action = XdbeUndefined;
   559         X11_XdbeSwapBuffers(data->display, &swap_info, 1);
   560         X11_XdbeEndIdiom(data->display);
   561     }
   562 #endif
   563 }
   564 
   565 static Bool
   566 X11_MessageBoxEventTest(Display *display, XEvent *event, XPointer arg)
   567 {
   568     const SDL_MessageBoxDataX11 *data = (const SDL_MessageBoxDataX11 *) arg;
   569     return ((event->xany.display == data->display) && (event->xany.window == data->window)) ? True : False;
   570 }
   571 
   572 /* Loop and handle message box event messages until something kills it. */
   573 static int
   574 X11_MessageBoxLoop( SDL_MessageBoxDataX11 *data )
   575 {
   576     GC ctx;
   577     XGCValues ctx_vals;
   578     SDL_bool close_dialog = SDL_FALSE;
   579     SDL_bool has_focus = SDL_TRUE;
   580     KeySym last_key_pressed = XK_VoidSymbol;
   581     unsigned long gcflags = GCForeground | GCBackground;
   582 
   583     SDL_zero(ctx_vals);
   584     ctx_vals.foreground = data->color[ SDL_MESSAGEBOX_COLOR_BACKGROUND ];
   585     ctx_vals.background = data->color[ SDL_MESSAGEBOX_COLOR_BACKGROUND ];
   586 
   587     if (!SDL_X11_HAVE_UTF8) {
   588         gcflags |= GCFont;
   589         ctx_vals.font = data->font_struct->fid;
   590     }
   591 
   592     ctx = X11_XCreateGC( data->display, data->window, gcflags, &ctx_vals );
   593     if ( ctx == None ) {
   594         return SDL_SetError("Couldn't create graphics context");
   595     }
   596 
   597     data->button_press_index = -1;  /* Reset what button is currently depressed. */
   598     data->mouse_over_index = -1;    /* Reset what button the mouse is over. */
   599 
   600     while( !close_dialog ) {
   601         XEvent e;
   602         SDL_bool draw = SDL_TRUE;
   603 
   604         /* can't use XWindowEvent() because it can't handle ClientMessage events. */
   605         /* can't use XNextEvent() because we only want events for this window. */
   606         X11_XIfEvent( data->display, &e, X11_MessageBoxEventTest, (XPointer) data );
   607 
   608         /* If X11_XFilterEvent returns True, then some input method has filtered the
   609            event, and the client should discard the event. */
   610         if ( ( e.type != Expose ) && X11_XFilterEvent( &e, None ) )
   611             continue;
   612 
   613         switch( e.type ) {
   614         case Expose:
   615             if ( e.xexpose.count > 0 ) {
   616                 draw = SDL_FALSE;
   617             }
   618             break;
   619 
   620         case FocusIn:
   621             /* Got focus. */
   622             has_focus = SDL_TRUE;
   623             break;
   624 
   625         case FocusOut:
   626             /* lost focus. Reset button and mouse info. */
   627             has_focus = SDL_FALSE;
   628             data->button_press_index = -1;
   629             data->mouse_over_index = -1;
   630             break;
   631 
   632         case MotionNotify:
   633             if ( has_focus ) {
   634                 /* Mouse moved... */
   635                 const int previndex = data->mouse_over_index;
   636                 data->mouse_over_index = GetHitButtonIndex( data, e.xbutton.x, e.xbutton.y );
   637                 if (data->mouse_over_index == previndex) {
   638                     draw = SDL_FALSE;
   639                 }
   640             }
   641             break;
   642 
   643         case ClientMessage:
   644             if ( e.xclient.message_type == data->wm_protocols &&
   645                  e.xclient.format == 32 &&
   646                  e.xclient.data.l[ 0 ] == data->wm_delete_message ) {
   647                 close_dialog = SDL_TRUE;
   648             }
   649             break;
   650 
   651         case KeyPress:
   652             /* Store key press - we make sure in key release that we got both. */
   653             last_key_pressed = X11_XLookupKeysym( &e.xkey, 0 );
   654             break;
   655 
   656         case KeyRelease: {
   657             Uint32 mask = 0;
   658             KeySym key = X11_XLookupKeysym( &e.xkey, 0 );
   659 
   660             /* If this is a key release for something we didn't get the key down for, then bail. */
   661             if ( key != last_key_pressed )
   662                 break;
   663 
   664             if ( key == XK_Escape )
   665                 mask = SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT;
   666             else if ( ( key == XK_Return ) || ( key == XK_KP_Enter ) )
   667                 mask = SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT;
   668 
   669             if ( mask ) {
   670                 int i;
   671 
   672                 /* Look for first button with this mask set, and return it if found. */
   673                 for ( i = 0; i < data->numbuttons; i++ ) {
   674                     SDL_MessageBoxButtonDataX11 *buttondatax11 = &data->buttonpos[ i ];
   675 
   676                     if ( buttondatax11->buttondata->flags & mask ) {
   677                         *data->pbuttonid = buttondatax11->buttondata->buttonid;
   678                         close_dialog = SDL_TRUE;
   679                         break;
   680                     }
   681                 }
   682             }
   683             break;
   684         }
   685 
   686         case ButtonPress:
   687             data->button_press_index = -1;
   688             if ( e.xbutton.button == Button1 ) {
   689                 /* Find index of button they clicked on. */
   690                 data->button_press_index = GetHitButtonIndex( data, e.xbutton.x, e.xbutton.y );
   691             }
   692             break;
   693 
   694         case ButtonRelease:
   695             /* If button is released over the same button that was clicked down on, then return it. */
   696             if ( ( e.xbutton.button == Button1 ) && ( data->button_press_index >= 0 ) ) {
   697                 int button = GetHitButtonIndex( data, e.xbutton.x, e.xbutton.y );
   698 
   699                 if ( data->button_press_index == button ) {
   700                     SDL_MessageBoxButtonDataX11 *buttondatax11 = &data->buttonpos[ button ];
   701 
   702                     *data->pbuttonid = buttondatax11->buttondata->buttonid;
   703                     close_dialog = SDL_TRUE;
   704                 }
   705             }
   706             data->button_press_index = -1;
   707             break;
   708         }
   709 
   710         if ( draw ) {
   711             /* Draw our dialog box. */
   712             X11_MessageBoxDraw( data, ctx );
   713         }
   714     }
   715 
   716     X11_XFreeGC( data->display, ctx );
   717     return 0;
   718 }
   719 
   720 static int
   721 X11_ShowMessageBoxImpl(const SDL_MessageBoxData *messageboxdata, int *buttonid)
   722 {
   723     int ret;
   724     SDL_MessageBoxDataX11 data;
   725 #if SDL_SET_LOCALE
   726     char *origlocale;
   727 #endif
   728 
   729     SDL_zero(data);
   730 
   731     if ( !SDL_X11_LoadSymbols() )
   732         return -1;
   733 
   734 #if SDL_SET_LOCALE
   735     origlocale = setlocale(LC_ALL, NULL);
   736     if (origlocale != NULL) {
   737         origlocale = SDL_strdup(origlocale);
   738         if (origlocale == NULL) {
   739             return SDL_OutOfMemory();
   740         }
   741         setlocale(LC_ALL, "");
   742     }
   743 #endif
   744 
   745     /* This code could get called from multiple threads maybe? */
   746     X11_XInitThreads();
   747 
   748     /* Initialize the return buttonid value to -1 (for error or dialogbox closed). */
   749     *buttonid = -1;
   750 
   751     /* Init and display the message box. */
   752     ret = X11_MessageBoxInit( &data, messageboxdata, buttonid );
   753     if ( ret != -1 ) {
   754         ret = X11_MessageBoxInitPositions( &data );
   755         if ( ret != -1 ) {
   756             ret = X11_MessageBoxCreateWindow( &data );
   757             if ( ret != -1 ) {
   758                 ret = X11_MessageBoxLoop( &data );
   759             }
   760         }
   761     }
   762 
   763     X11_MessageBoxShutdown( &data );
   764 
   765 #if SDL_SET_LOCALE
   766     if (origlocale) {
   767         setlocale(LC_ALL, origlocale);
   768         SDL_free(origlocale);
   769     }
   770 #endif
   771 
   772     return ret;
   773 }
   774 
   775 /* Display an x11 message box. */
   776 int
   777 X11_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
   778 {
   779 #if SDL_FORK_MESSAGEBOX
   780     /* Use a child process to protect against setlocale(). Annoying. */
   781     pid_t pid;
   782     int fds[2];
   783     int status = 0;
   784 
   785     if (pipe(fds) == -1) {
   786         return X11_ShowMessageBoxImpl(messageboxdata, buttonid); /* oh well. */
   787     }
   788 
   789     pid = fork();
   790     if (pid == -1) {  /* failed */
   791         close(fds[0]);
   792         close(fds[1]);
   793         return X11_ShowMessageBoxImpl(messageboxdata, buttonid); /* oh well. */
   794     } else if (pid == 0) {  /* we're the child */
   795         int exitcode = 0;
   796         close(fds[0]);
   797         status = X11_ShowMessageBoxImpl(messageboxdata, buttonid);
   798         if (write(fds[1], &status, sizeof (int)) != sizeof (int))
   799             exitcode = 1;
   800         else if (write(fds[1], buttonid, sizeof (int)) != sizeof (int))
   801             exitcode = 1;
   802         close(fds[1]);
   803         _exit(exitcode);  /* don't run atexit() stuff, static destructors, etc. */
   804     } else {  /* we're the parent */
   805         pid_t rc;
   806         close(fds[1]);
   807         do {
   808             rc = waitpid(pid, &status, 0);
   809         } while ((rc == -1) && (errno == EINTR));
   810 
   811         SDL_assert(rc == pid);  /* not sure what to do if this fails. */
   812 
   813         if ((rc == -1) || (!WIFEXITED(status)) || (WEXITSTATUS(status) != 0)) {
   814             return SDL_SetError("msgbox child process failed");
   815         }
   816 
   817         if (read(fds[0], &status, sizeof (int)) != sizeof (int))
   818             status = -1;
   819         else if (read(fds[0], buttonid, sizeof (int)) != sizeof (int))
   820             status = -1;
   821         close(fds[0]);
   822 
   823         return status;
   824     }
   825 #else
   826     return X11_ShowMessageBoxImpl(messageboxdata, buttonid);
   827 #endif
   828 }
   829 #endif /* SDL_VIDEO_DRIVER_X11 */
   830 
   831 /* vi: set ts=4 sw=4 expandtab: */