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