src/video/x11/SDL_x11messagebox.c
author Ryan C. Gordon <icculus@icculus.org>
Mon, 15 Oct 2018 00:46:43 -0400
changeset 12330 cf64a70ad02a
parent 11811 5d94cb6b24d3
child 12503 806492103856
permissions -rw-r--r--
x11: Don't hardcode limit on lines of text in message boxes.

Plus other text parsing fixes.

Fixes Bugzilla #4306.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2018 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 #include "SDL_x11messagebox.h"
    31 
    32 #include <X11/keysym.h>
    33 #include <locale.h>
    34 
    35 
    36 #define SDL_FORK_MESSAGEBOX 1
    37 #define SDL_SET_LOCALE      1
    38 
    39 #if SDL_FORK_MESSAGEBOX
    40 #include <sys/types.h>
    41 #include <sys/wait.h>
    42 #include <unistd.h>
    43 #include <errno.h>
    44 #endif
    45 
    46 #define MAX_BUTTONS             8       /* Maximum number of buttons 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[] = "-*-*-medium-r-normal--*-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;
   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 static int
   226 CountLinesOfText(const char *text)
   227 {
   228     int retval = 0;
   229     while (text && *text) {
   230         const char *lf = SDL_strchr(text, '\n');
   231         retval++;  /* even without an endline, this counts as a line. */
   232         text = lf ? lf + 1 : NULL;
   233     }
   234     return retval;
   235 }
   236 
   237 /* Calculate and initialize text and button locations. */
   238 static int
   239 X11_MessageBoxInitPositions( SDL_MessageBoxDataX11 *data )
   240 {
   241     int i;
   242     int ybuttons;
   243     int text_width_max = 0;
   244     int button_text_height = 0;
   245     int button_width = MIN_BUTTON_WIDTH;
   246     const SDL_MessageBoxData *messageboxdata = data->messageboxdata;
   247 
   248     /* Go over text and break linefeeds into separate lines. */
   249     if ( messageboxdata->message && messageboxdata->message[ 0 ] ) {
   250         const char *text = messageboxdata->message;
   251         const int linecount = CountLinesOfText(text);
   252         TextLineData *plinedata = (TextLineData *) SDL_malloc(sizeof (TextLineData) * linecount);
   253 
   254         if (!plinedata) {
   255             return SDL_OutOfMemory();
   256         }
   257 
   258         data->linedata = plinedata;
   259         data->numlines = linecount;
   260 
   261         for ( i = 0; i < linecount; i++, plinedata++ ) {
   262             const char *lf = SDL_strchr( text, '\n' );
   263             const int length = lf ? ( lf - text ) : SDL_strlen( text );
   264             int height;
   265 
   266             plinedata->text = text;
   267 
   268             GetTextWidthHeight( data, text, length, &plinedata->width, &height );
   269 
   270             /* Text and widths are the largest we've ever seen. */
   271             data->text_height = IntMax( data->text_height, height );
   272             text_width_max = IntMax( text_width_max, plinedata->width );
   273 
   274             plinedata->length = length;
   275             if (lf && (lf > text) && (lf[-1] == '\r')) {
   276                 plinedata->length--;
   277             }
   278 
   279             text += length + 1;
   280 
   281             /* Break if there are no more linefeeds. */
   282             if ( !lf )
   283                 break;
   284         }
   285 
   286         /* Bump up the text height slightly. */
   287         data->text_height += 2;
   288     }
   289 
   290     /* Loop through all buttons and calculate the button widths and height. */
   291     for ( i = 0; i < data->numbuttons; i++ ) {
   292         int height;
   293 
   294         data->buttonpos[ i ].buttondata = &data->buttondata[ i ];
   295         data->buttonpos[ i ].length = SDL_strlen( data->buttondata[ i ].text );
   296 
   297         GetTextWidthHeight( data, data->buttondata[ i ].text, SDL_strlen( data->buttondata[ i ].text ),
   298                             &data->buttonpos[ i ].text_width, &height );
   299 
   300         button_width = IntMax( button_width, data->buttonpos[ i ].text_width );
   301         button_text_height = IntMax( button_text_height, height );
   302     }
   303 
   304     if ( data->numlines ) {
   305         /* x,y for this line of text. */
   306         data->xtext = data->text_height;
   307         data->ytext = data->text_height + data->text_height;
   308 
   309         /* Bump button y down to bottom of text. */
   310         ybuttons = 3 * data->ytext / 2 + ( data->numlines - 1 ) * data->text_height;
   311 
   312         /* Bump the dialog box width and height up if needed. */
   313         data->dialog_width = IntMax( data->dialog_width, 2 * data->xtext + text_width_max );
   314         data->dialog_height = IntMax( data->dialog_height, ybuttons );
   315     } else {
   316         /* Button y starts at height of button text. */
   317         ybuttons = button_text_height;
   318     }
   319 
   320     if ( data->numbuttons ) {
   321         int x, y;
   322         int width_of_buttons;
   323         int button_spacing = button_text_height;
   324         int button_height = 2 * button_text_height;
   325 
   326         /* Bump button width up a bit. */
   327         button_width += button_text_height;
   328 
   329         /* Get width of all buttons lined up. */
   330         width_of_buttons = data->numbuttons * button_width + ( data->numbuttons - 1 ) * button_spacing;
   331 
   332         /* Bump up dialog width and height if buttons are wider than text. */
   333         data->dialog_width = IntMax( data->dialog_width, width_of_buttons + 2 * button_spacing );
   334         data->dialog_height = IntMax( data->dialog_height, ybuttons + 2 * button_height );
   335 
   336         /* Location for first button. */
   337         x = ( data->dialog_width - width_of_buttons ) / 2;
   338         y = ybuttons + ( data->dialog_height - ybuttons - button_height ) / 2;
   339 
   340         for ( i = 0; i < data->numbuttons; i++ ) {
   341             /* Button coordinates. */
   342             data->buttonpos[ i ].rect.x = x;
   343             data->buttonpos[ i ].rect.y = y;
   344             data->buttonpos[ i ].rect.w = button_width;
   345             data->buttonpos[ i ].rect.h = button_height;
   346 
   347             /* Button text coordinates. */
   348             data->buttonpos[ i ].x = x + ( button_width - data->buttonpos[ i ].text_width ) / 2;
   349             data->buttonpos[ i ].y = y + ( button_height - button_text_height - 1 ) / 2 + button_text_height;
   350 
   351             /* Scoot over for next button. */
   352             x += button_width + button_spacing;
   353         }
   354     }
   355 
   356     return 0;
   357 }
   358 
   359 /* Free SDL_MessageBoxData data. */
   360 static void
   361 X11_MessageBoxShutdown( SDL_MessageBoxDataX11 *data )
   362 {
   363     if ( data->font_set != NULL ) {
   364         X11_XFreeFontSet( data->display, data->font_set );
   365         data->font_set = NULL;
   366     }
   367 
   368     if ( data->font_struct != NULL ) {
   369         X11_XFreeFont( data->display, data->font_struct );
   370         data->font_struct = NULL;
   371     }
   372 
   373 #if SDL_VIDEO_DRIVER_X11_XDBE
   374     if ( SDL_X11_HAVE_XDBE && data->xdbe ) {
   375         X11_XdbeDeallocateBackBufferName(data->display, data->buf);
   376     }
   377 #endif
   378 
   379     if ( data->display ) {
   380         if ( data->window != None ) {
   381             X11_XWithdrawWindow( data->display, data->window, data->screen );
   382             X11_XDestroyWindow( data->display, data->window );
   383             data->window = None;
   384         }
   385 
   386         X11_XCloseDisplay( data->display );
   387         data->display = NULL;
   388     }
   389 
   390     SDL_free(data->linedata);
   391 }
   392 
   393 /* Create and set up our X11 dialog box indow. */
   394 static int
   395 X11_MessageBoxCreateWindow( SDL_MessageBoxDataX11 *data )
   396 {
   397     int x, y;
   398     XSizeHints *sizehints;
   399     XSetWindowAttributes wnd_attr;
   400     Atom _NET_WM_WINDOW_TYPE, _NET_WM_WINDOW_TYPE_DIALOG, _NET_WM_NAME;
   401     Display *display = data->display;
   402     SDL_WindowData *windowdata = NULL;
   403     const SDL_MessageBoxData *messageboxdata = data->messageboxdata;
   404     char *title_locale = NULL;
   405 
   406     if ( messageboxdata->window ) {
   407         SDL_DisplayData *displaydata =
   408             (SDL_DisplayData *) SDL_GetDisplayForWindow(messageboxdata->window)->driverdata;
   409         windowdata = (SDL_WindowData *)messageboxdata->window->driverdata;
   410         data->screen = displaydata->screen;
   411     } else {
   412         data->screen = DefaultScreen( display );
   413     }
   414 
   415     data->event_mask = ExposureMask |
   416                        ButtonPressMask | ButtonReleaseMask | KeyPressMask | KeyReleaseMask |
   417                        StructureNotifyMask | FocusChangeMask | PointerMotionMask;
   418     wnd_attr.event_mask = data->event_mask;
   419 
   420     data->window = X11_XCreateWindow(
   421                        display, RootWindow(display, data->screen),
   422                        0, 0,
   423                        data->dialog_width, data->dialog_height,
   424                        0, CopyFromParent, InputOutput, CopyFromParent,
   425                        CWEventMask, &wnd_attr );
   426     if ( data->window == None ) {
   427         return SDL_SetError("Couldn't create X window");
   428     }
   429 
   430     if ( windowdata ) {
   431         /* http://tronche.com/gui/x/icccm/sec-4.html#WM_TRANSIENT_FOR */
   432         X11_XSetTransientForHint( display, data->window, windowdata->xwindow );
   433     }
   434 
   435     X11_XStoreName( display, data->window, messageboxdata->title );
   436     _NET_WM_NAME = X11_XInternAtom(display, "_NET_WM_NAME", False);
   437 
   438     title_locale = SDL_iconv_utf8_locale(messageboxdata->title);
   439     if (title_locale) {
   440         XTextProperty titleprop;
   441         Status status = X11_XStringListToTextProperty(&title_locale, 1, &titleprop);
   442         SDL_free(title_locale);
   443         if (status) {
   444             X11_XSetTextProperty(display, data->window, &titleprop, XA_WM_NAME);
   445             X11_XFree(titleprop.value);
   446         }
   447     }
   448 
   449 #ifdef X_HAVE_UTF8_STRING
   450     if (SDL_X11_HAVE_UTF8) {
   451         XTextProperty titleprop;
   452         Status status = X11_Xutf8TextListToTextProperty(display, (char **) &messageboxdata->title, 1,
   453                                             XUTF8StringStyle, &titleprop);
   454         if (status == Success) {
   455             X11_XSetTextProperty(display, data->window, &titleprop,
   456                                  _NET_WM_NAME);
   457             X11_XFree(titleprop.value);
   458         }
   459     }
   460 #endif
   461 
   462     /* Let the window manager know this is a dialog box */
   463     _NET_WM_WINDOW_TYPE = X11_XInternAtom(display, "_NET_WM_WINDOW_TYPE", False);
   464     _NET_WM_WINDOW_TYPE_DIALOG = X11_XInternAtom(display, "_NET_WM_WINDOW_TYPE_DIALOG", False);
   465     X11_XChangeProperty(display, data->window, _NET_WM_WINDOW_TYPE, XA_ATOM, 32,
   466                     PropModeReplace,
   467                     (unsigned char *)&_NET_WM_WINDOW_TYPE_DIALOG, 1);
   468 
   469     /* Allow the window to be deleted by the window manager */
   470     data->wm_protocols = X11_XInternAtom( display, "WM_PROTOCOLS", False );
   471     data->wm_delete_message = X11_XInternAtom( display, "WM_DELETE_WINDOW", False );
   472     X11_XSetWMProtocols( display, data->window, &data->wm_delete_message, 1 );
   473 
   474     if ( windowdata ) {
   475         XWindowAttributes attrib;
   476         Window dummy;
   477 
   478         X11_XGetWindowAttributes(display, windowdata->xwindow, &attrib);
   479         x = attrib.x + ( attrib.width - data->dialog_width ) / 2;
   480         y = attrib.y + ( attrib.height - data->dialog_height ) / 3 ;
   481         X11_XTranslateCoordinates(display, windowdata->xwindow, RootWindow(display, data->screen), x, y, &x, &y, &dummy);
   482     } else {
   483         const SDL_VideoDevice *dev = SDL_GetVideoDevice();
   484         if ((dev) && (dev->displays) && (dev->num_displays > 0)) {
   485             const SDL_VideoDisplay *dpy = &dev->displays[0];
   486             const SDL_DisplayData *dpydata = (SDL_DisplayData *) dpy->driverdata;
   487             x = dpydata->x + (( dpy->current_mode.w - data->dialog_width ) / 2);
   488             y = dpydata->y + (( dpy->current_mode.h - data->dialog_height ) / 3);
   489         } else {   /* oh well. This will misposition on a multi-head setup. Init first next time. */
   490             x = ( DisplayWidth( display, data->screen ) - data->dialog_width ) / 2;
   491             y = ( DisplayHeight( display, data->screen ) - data->dialog_height ) / 3 ;
   492         }
   493     }
   494     X11_XMoveWindow( display, data->window, x, y );
   495 
   496     sizehints = X11_XAllocSizeHints();
   497     if ( sizehints ) {
   498         sizehints->flags = USPosition | USSize | PMaxSize | PMinSize;
   499         sizehints->x = x;
   500         sizehints->y = y;
   501         sizehints->width = data->dialog_width;
   502         sizehints->height = data->dialog_height;
   503 
   504         sizehints->min_width = sizehints->max_width = data->dialog_width;
   505         sizehints->min_height = sizehints->max_height = data->dialog_height;
   506 
   507         X11_XSetWMNormalHints( display, data->window, sizehints );
   508 
   509         X11_XFree( sizehints );
   510     }
   511 
   512     X11_XMapRaised( display, data->window );
   513 
   514 #if SDL_VIDEO_DRIVER_X11_XDBE
   515     /* Initialise a back buffer for double buffering */
   516     if (SDL_X11_HAVE_XDBE) {
   517         int xdbe_major, xdbe_minor;
   518         if (X11_XdbeQueryExtension(display, &xdbe_major, &xdbe_minor) != 0) {
   519             data->xdbe = SDL_TRUE;
   520             data->buf = X11_XdbeAllocateBackBufferName(display, data->window, XdbeUndefined);
   521         } else {
   522             data->xdbe = SDL_FALSE;
   523         }
   524     }
   525 #endif
   526 
   527     return 0;
   528 }
   529 
   530 /* Draw our message box. */
   531 static void
   532 X11_MessageBoxDraw( SDL_MessageBoxDataX11 *data, GC ctx )
   533 {
   534     int i;
   535     Drawable window = data->window;
   536     Display *display = data->display;
   537 
   538 #if SDL_VIDEO_DRIVER_X11_XDBE
   539     if (SDL_X11_HAVE_XDBE && data->xdbe) {
   540         window = data->buf;
   541         X11_XdbeBeginIdiom(data->display);
   542     }
   543 #endif
   544 
   545     X11_XSetForeground( display, ctx, data->color[ SDL_MESSAGEBOX_COLOR_BACKGROUND ] );
   546     X11_XFillRectangle( display, window, ctx, 0, 0, data->dialog_width, data->dialog_height );
   547 
   548     X11_XSetForeground( display, ctx, data->color[ SDL_MESSAGEBOX_COLOR_TEXT ] );
   549     for ( i = 0; i < data->numlines; i++ ) {
   550         TextLineData *plinedata = &data->linedata[ i ];
   551 
   552         if (SDL_X11_HAVE_UTF8) {
   553             X11_Xutf8DrawString( display, window, data->font_set, ctx,
   554                              data->xtext, data->ytext + i * data->text_height,
   555                              plinedata->text, plinedata->length );
   556         } else {
   557             X11_XDrawString( display, window, ctx,
   558                          data->xtext, data->ytext + i * data->text_height,
   559                          plinedata->text, plinedata->length );
   560         }
   561     }
   562 
   563     for ( i = 0; i < data->numbuttons; i++ ) {
   564         SDL_MessageBoxButtonDataX11 *buttondatax11 = &data->buttonpos[ i ];
   565         const SDL_MessageBoxButtonData *buttondata = buttondatax11->buttondata;
   566         int border = ( buttondata->flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT ) ? 2 : 0;
   567         int offset = ( ( data->mouse_over_index == i ) && ( data->button_press_index == data->mouse_over_index ) ) ? 1 : 0;
   568 
   569         X11_XSetForeground( display, ctx, data->color[ SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND ] );
   570         X11_XFillRectangle( display, window, ctx,
   571                         buttondatax11->rect.x - border, buttondatax11->rect.y - border,
   572                         buttondatax11->rect.w + 2 * border, buttondatax11->rect.h + 2 * border );
   573 
   574         X11_XSetForeground( display, ctx, data->color[ SDL_MESSAGEBOX_COLOR_BUTTON_BORDER ] );
   575         X11_XDrawRectangle( display, window, ctx,
   576                         buttondatax11->rect.x, buttondatax11->rect.y,
   577                         buttondatax11->rect.w, buttondatax11->rect.h );
   578 
   579         X11_XSetForeground( display, ctx, ( data->mouse_over_index == i ) ?
   580                         data->color[ SDL_MESSAGEBOX_COLOR_BUTTON_SELECTED ] :
   581                         data->color[ SDL_MESSAGEBOX_COLOR_TEXT ] );
   582 
   583         if (SDL_X11_HAVE_UTF8) {
   584             X11_Xutf8DrawString( display, window, data->font_set, ctx,
   585                              buttondatax11->x + offset,
   586                              buttondatax11->y + offset,
   587                              buttondata->text, buttondatax11->length );
   588         } else {
   589             X11_XDrawString( display, window, ctx,
   590                          buttondatax11->x + offset, buttondatax11->y + offset,
   591                          buttondata->text, buttondatax11->length );
   592         }
   593     }
   594 
   595 #if SDL_VIDEO_DRIVER_X11_XDBE
   596     if (SDL_X11_HAVE_XDBE && data->xdbe) {
   597         XdbeSwapInfo swap_info;
   598         swap_info.swap_window = data->window;
   599         swap_info.swap_action = XdbeUndefined;
   600         X11_XdbeSwapBuffers(data->display, &swap_info, 1);
   601         X11_XdbeEndIdiom(data->display);
   602     }
   603 #endif
   604 }
   605 
   606 static Bool
   607 X11_MessageBoxEventTest(Display *display, XEvent *event, XPointer arg)
   608 {
   609     const SDL_MessageBoxDataX11 *data = (const SDL_MessageBoxDataX11 *) arg;
   610     return ((event->xany.display == data->display) && (event->xany.window == data->window)) ? True : False;
   611 }
   612 
   613 /* Loop and handle message box event messages until something kills it. */
   614 static int
   615 X11_MessageBoxLoop( SDL_MessageBoxDataX11 *data )
   616 {
   617     GC ctx;
   618     XGCValues ctx_vals;
   619     SDL_bool close_dialog = SDL_FALSE;
   620     SDL_bool has_focus = SDL_TRUE;
   621     KeySym last_key_pressed = XK_VoidSymbol;
   622     unsigned long gcflags = GCForeground | GCBackground;
   623 
   624     SDL_zero(ctx_vals);
   625     ctx_vals.foreground = data->color[ SDL_MESSAGEBOX_COLOR_BACKGROUND ];
   626     ctx_vals.background = data->color[ SDL_MESSAGEBOX_COLOR_BACKGROUND ];
   627 
   628     if (!SDL_X11_HAVE_UTF8) {
   629         gcflags |= GCFont;
   630         ctx_vals.font = data->font_struct->fid;
   631     }
   632 
   633     ctx = X11_XCreateGC( data->display, data->window, gcflags, &ctx_vals );
   634     if ( ctx == None ) {
   635         return SDL_SetError("Couldn't create graphics context");
   636     }
   637 
   638     data->button_press_index = -1;  /* Reset what button is currently depressed. */
   639     data->mouse_over_index = -1;    /* Reset what button the mouse is over. */
   640 
   641     while( !close_dialog ) {
   642         XEvent e;
   643         SDL_bool draw = SDL_TRUE;
   644 
   645         /* can't use XWindowEvent() because it can't handle ClientMessage events. */
   646         /* can't use XNextEvent() because we only want events for this window. */
   647         X11_XIfEvent( data->display, &e, X11_MessageBoxEventTest, (XPointer) data );
   648 
   649         /* If X11_XFilterEvent returns True, then some input method has filtered the
   650            event, and the client should discard the event. */
   651         if ( ( e.type != Expose ) && X11_XFilterEvent( &e, None ) )
   652             continue;
   653 
   654         switch( e.type ) {
   655         case Expose:
   656             if ( e.xexpose.count > 0 ) {
   657                 draw = SDL_FALSE;
   658             }
   659             break;
   660 
   661         case FocusIn:
   662             /* Got focus. */
   663             has_focus = SDL_TRUE;
   664             break;
   665 
   666         case FocusOut:
   667             /* lost focus. Reset button and mouse info. */
   668             has_focus = SDL_FALSE;
   669             data->button_press_index = -1;
   670             data->mouse_over_index = -1;
   671             break;
   672 
   673         case MotionNotify:
   674             if ( has_focus ) {
   675                 /* Mouse moved... */
   676                 const int previndex = data->mouse_over_index;
   677                 data->mouse_over_index = GetHitButtonIndex( data, e.xbutton.x, e.xbutton.y );
   678                 if (data->mouse_over_index == previndex) {
   679                     draw = SDL_FALSE;
   680                 }
   681             }
   682             break;
   683 
   684         case ClientMessage:
   685             if ( e.xclient.message_type == data->wm_protocols &&
   686                  e.xclient.format == 32 &&
   687                  e.xclient.data.l[ 0 ] == data->wm_delete_message ) {
   688                 close_dialog = SDL_TRUE;
   689             }
   690             break;
   691 
   692         case KeyPress:
   693             /* Store key press - we make sure in key release that we got both. */
   694             last_key_pressed = X11_XLookupKeysym( &e.xkey, 0 );
   695             break;
   696 
   697         case KeyRelease: {
   698             Uint32 mask = 0;
   699             KeySym key = X11_XLookupKeysym( &e.xkey, 0 );
   700 
   701             /* If this is a key release for something we didn't get the key down for, then bail. */
   702             if ( key != last_key_pressed )
   703                 break;
   704 
   705             if ( key == XK_Escape )
   706                 mask = SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT;
   707             else if ( ( key == XK_Return ) || ( key == XK_KP_Enter ) )
   708                 mask = SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT;
   709 
   710             if ( mask ) {
   711                 int i;
   712 
   713                 /* Look for first button with this mask set, and return it if found. */
   714                 for ( i = 0; i < data->numbuttons; i++ ) {
   715                     SDL_MessageBoxButtonDataX11 *buttondatax11 = &data->buttonpos[ i ];
   716 
   717                     if ( buttondatax11->buttondata->flags & mask ) {
   718                         *data->pbuttonid = buttondatax11->buttondata->buttonid;
   719                         close_dialog = SDL_TRUE;
   720                         break;
   721                     }
   722                 }
   723             }
   724             break;
   725         }
   726 
   727         case ButtonPress:
   728             data->button_press_index = -1;
   729             if ( e.xbutton.button == Button1 ) {
   730                 /* Find index of button they clicked on. */
   731                 data->button_press_index = GetHitButtonIndex( data, e.xbutton.x, e.xbutton.y );
   732             }
   733             break;
   734 
   735         case ButtonRelease:
   736             /* If button is released over the same button that was clicked down on, then return it. */
   737             if ( ( e.xbutton.button == Button1 ) && ( data->button_press_index >= 0 ) ) {
   738                 int button = GetHitButtonIndex( data, e.xbutton.x, e.xbutton.y );
   739 
   740                 if ( data->button_press_index == button ) {
   741                     SDL_MessageBoxButtonDataX11 *buttondatax11 = &data->buttonpos[ button ];
   742 
   743                     *data->pbuttonid = buttondatax11->buttondata->buttonid;
   744                     close_dialog = SDL_TRUE;
   745                 }
   746             }
   747             data->button_press_index = -1;
   748             break;
   749         }
   750 
   751         if ( draw ) {
   752             /* Draw our dialog box. */
   753             X11_MessageBoxDraw( data, ctx );
   754         }
   755     }
   756 
   757     X11_XFreeGC( data->display, ctx );
   758     return 0;
   759 }
   760 
   761 static int
   762 X11_ShowMessageBoxImpl(const SDL_MessageBoxData *messageboxdata, int *buttonid)
   763 {
   764     int ret;
   765     SDL_MessageBoxDataX11 data;
   766 #if SDL_SET_LOCALE
   767     char *origlocale;
   768 #endif
   769 
   770     SDL_zero(data);
   771 
   772     if ( !SDL_X11_LoadSymbols() )
   773         return -1;
   774 
   775 #if SDL_SET_LOCALE
   776     origlocale = setlocale(LC_ALL, NULL);
   777     if (origlocale != NULL) {
   778         origlocale = SDL_strdup(origlocale);
   779         if (origlocale == NULL) {
   780             return SDL_OutOfMemory();
   781         }
   782         setlocale(LC_ALL, "");
   783     }
   784 #endif
   785 
   786     /* This code could get called from multiple threads maybe? */
   787     X11_XInitThreads();
   788 
   789     /* Initialize the return buttonid value to -1 (for error or dialogbox closed). */
   790     *buttonid = -1;
   791 
   792     /* Init and display the message box. */
   793     ret = X11_MessageBoxInit( &data, messageboxdata, buttonid );
   794     if ( ret != -1 ) {
   795         ret = X11_MessageBoxInitPositions( &data );
   796         if ( ret != -1 ) {
   797             ret = X11_MessageBoxCreateWindow( &data );
   798             if ( ret != -1 ) {
   799                 ret = X11_MessageBoxLoop( &data );
   800             }
   801         }
   802     }
   803 
   804     X11_MessageBoxShutdown( &data );
   805 
   806 #if SDL_SET_LOCALE
   807     if (origlocale) {
   808         setlocale(LC_ALL, origlocale);
   809         SDL_free(origlocale);
   810     }
   811 #endif
   812 
   813     return ret;
   814 }
   815 
   816 /* Display an x11 message box. */
   817 int
   818 X11_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
   819 {
   820 #if SDL_FORK_MESSAGEBOX
   821     /* Use a child process to protect against setlocale(). Annoying. */
   822     pid_t pid;
   823     int fds[2];
   824     int status = 0;
   825 
   826     if (pipe(fds) == -1) {
   827         return X11_ShowMessageBoxImpl(messageboxdata, buttonid); /* oh well. */
   828     }
   829 
   830     pid = fork();
   831     if (pid == -1) {  /* failed */
   832         close(fds[0]);
   833         close(fds[1]);
   834         return X11_ShowMessageBoxImpl(messageboxdata, buttonid); /* oh well. */
   835     } else if (pid == 0) {  /* we're the child */
   836         int exitcode = 0;
   837         close(fds[0]);
   838         status = X11_ShowMessageBoxImpl(messageboxdata, buttonid);
   839         if (write(fds[1], &status, sizeof (int)) != sizeof (int))
   840             exitcode = 1;
   841         else if (write(fds[1], buttonid, sizeof (int)) != sizeof (int))
   842             exitcode = 1;
   843         close(fds[1]);
   844         _exit(exitcode);  /* don't run atexit() stuff, static destructors, etc. */
   845     } else {  /* we're the parent */
   846         pid_t rc;
   847         close(fds[1]);
   848         do {
   849             rc = waitpid(pid, &status, 0);
   850         } while ((rc == -1) && (errno == EINTR));
   851 
   852         SDL_assert(rc == pid);  /* not sure what to do if this fails. */
   853 
   854         if ((rc == -1) || (!WIFEXITED(status)) || (WEXITSTATUS(status) != 0)) {
   855             return SDL_SetError("msgbox child process failed");
   856         }
   857 
   858         if (read(fds[0], &status, sizeof (int)) != sizeof (int))
   859             status = -1;
   860         else if (read(fds[0], buttonid, sizeof (int)) != sizeof (int))
   861             status = -1;
   862         close(fds[0]);
   863 
   864         return status;
   865     }
   866 #else
   867     return X11_ShowMessageBoxImpl(messageboxdata, buttonid);
   868 #endif
   869 }
   870 #endif /* SDL_VIDEO_DRIVER_X11 */
   871 
   872 /* vi: set ts=4 sw=4 expandtab: */