src/video/x11/SDL_x11messagebox.c
author Ryan C. Gordon <icculus@icculus.org>
Sun, 31 May 2015 22:48:26 -0400
changeset 9695 363a7880b4f7
parent 9657 fbc01731d914
child 9698 bf0257e323d2
permissions -rw-r--r--
X11: Fixed message boxes not responding to click on titlebar close button.

The window needs to catch ClientMessage events for one specific window, but
XNextEvent() catches everything, and XWindowEvent doesn't catch ClientMessage,
so we need predicate procedure and XIfEvent() here.

Fixes Bugzilla #2980.
     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;
   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 
   415     /* Let the window manager know this is a dialog box */
   416     _NET_WM_WINDOW_TYPE = X11_XInternAtom(display, "_NET_WM_WINDOW_TYPE", False);
   417     _NET_WM_WINDOW_TYPE_DIALOG = X11_XInternAtom(display, "_NET_WM_WINDOW_TYPE_DIALOG", False);
   418     X11_XChangeProperty(display, data->window, _NET_WM_WINDOW_TYPE, XA_ATOM, 32,
   419                     PropModeReplace,
   420                     (unsigned char *)&_NET_WM_WINDOW_TYPE_DIALOG, 1);
   421 
   422     /* Allow the window to be deleted by the window manager */
   423     data->wm_protocols = X11_XInternAtom( display, "WM_PROTOCOLS", False );
   424     data->wm_delete_message = X11_XInternAtom( display, "WM_DELETE_WINDOW", False );
   425     X11_XSetWMProtocols( display, data->window, &data->wm_delete_message, 1 );
   426 
   427     if ( windowdata ) {
   428         XWindowAttributes attrib;
   429         Window dummy;
   430 
   431         X11_XGetWindowAttributes(display, windowdata->xwindow, &attrib);
   432         x = attrib.x + ( attrib.width - data->dialog_width ) / 2;
   433         y = attrib.y + ( attrib.height - data->dialog_height ) / 3 ;
   434         X11_XTranslateCoordinates(display, windowdata->xwindow, RootWindow(display, data->screen), x, y, &x, &y, &dummy);
   435     } else {
   436         x = ( DisplayWidth( display, data->screen ) - data->dialog_width ) / 2;
   437         y = ( DisplayHeight( display, data->screen ) - data->dialog_height ) / 3 ;
   438     }
   439     X11_XMoveWindow( display, data->window, x, y );
   440 
   441     sizehints = X11_XAllocSizeHints();
   442     if ( sizehints ) {
   443         sizehints->flags = USPosition | USSize | PMaxSize | PMinSize;
   444         sizehints->x = x;
   445         sizehints->y = y;
   446         sizehints->width = data->dialog_width;
   447         sizehints->height = data->dialog_height;
   448 
   449         sizehints->min_width = sizehints->max_width = data->dialog_width;
   450         sizehints->min_height = sizehints->max_height = data->dialog_height;
   451 
   452         X11_XSetWMNormalHints( display, data->window, sizehints );
   453 
   454         X11_XFree( sizehints );
   455     }
   456 
   457     X11_XMapRaised( display, data->window );
   458 
   459 #if SDL_VIDEO_DRIVER_X11_XDBE
   460     /* Initialise a back buffer for double buffering */
   461     if (SDL_X11_HAVE_XDBE) {
   462         int xdbe_major, xdbe_minor;
   463         if (X11_XdbeQueryExtension(display, &xdbe_major, &xdbe_minor) != 0) {
   464             data->xdbe = SDL_TRUE;
   465             data->buf = X11_XdbeAllocateBackBufferName(display, data->window, XdbeUndefined);
   466         } else {
   467             data->xdbe = SDL_FALSE;
   468         }
   469     }
   470 #endif
   471 
   472     return 0;
   473 }
   474 
   475 /* Draw our message box. */
   476 static void
   477 X11_MessageBoxDraw( SDL_MessageBoxDataX11 *data, GC ctx )
   478 {
   479     int i;
   480     Drawable window = data->window;
   481     Display *display = data->display;
   482 
   483 #if SDL_VIDEO_DRIVER_X11_XDBE
   484     if (SDL_X11_HAVE_XDBE && data->xdbe) {
   485         window = data->buf;
   486         X11_XdbeBeginIdiom(data->display);
   487     }
   488 #endif
   489 
   490     X11_XSetForeground( display, ctx, data->color[ SDL_MESSAGEBOX_COLOR_BACKGROUND ] );
   491     X11_XFillRectangle( display, window, ctx, 0, 0, data->dialog_width, data->dialog_height );
   492 
   493     X11_XSetForeground( display, ctx, data->color[ SDL_MESSAGEBOX_COLOR_TEXT ] );
   494     for ( i = 0; i < data->numlines; i++ ) {
   495         TextLineData *plinedata = &data->linedata[ i ];
   496 
   497         if (SDL_X11_HAVE_UTF8) {
   498             X11_Xutf8DrawString( display, window, data->font_set, ctx,
   499                              data->xtext, data->ytext + i * data->text_height,
   500                              plinedata->text, plinedata->length );
   501         } else {
   502             X11_XDrawString( display, window, ctx,
   503                          data->xtext, data->ytext + i * data->text_height,
   504                          plinedata->text, plinedata->length );
   505         }
   506     }
   507 
   508     for ( i = 0; i < data->numbuttons; i++ ) {
   509         SDL_MessageBoxButtonDataX11 *buttondatax11 = &data->buttonpos[ i ];
   510         const SDL_MessageBoxButtonData *buttondata = buttondatax11->buttondata;
   511         int border = ( buttondata->flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT ) ? 2 : 0;
   512         int offset = ( ( data->mouse_over_index == i ) && ( data->button_press_index == data->mouse_over_index ) ) ? 1 : 0;
   513 
   514         X11_XSetForeground( display, ctx, data->color[ SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND ] );
   515         X11_XFillRectangle( display, window, ctx,
   516                         buttondatax11->rect.x - border, buttondatax11->rect.y - border,
   517                         buttondatax11->rect.w + 2 * border, buttondatax11->rect.h + 2 * border );
   518 
   519         X11_XSetForeground( display, ctx, data->color[ SDL_MESSAGEBOX_COLOR_BUTTON_BORDER ] );
   520         X11_XDrawRectangle( display, window, ctx,
   521                         buttondatax11->rect.x, buttondatax11->rect.y,
   522                         buttondatax11->rect.w, buttondatax11->rect.h );
   523 
   524         X11_XSetForeground( display, ctx, ( data->mouse_over_index == i ) ?
   525                         data->color[ SDL_MESSAGEBOX_COLOR_BUTTON_SELECTED ] :
   526                         data->color[ SDL_MESSAGEBOX_COLOR_TEXT ] );
   527 
   528         if (SDL_X11_HAVE_UTF8) {
   529             X11_Xutf8DrawString( display, window, data->font_set, ctx,
   530                              buttondatax11->x + offset,
   531                              buttondatax11->y + offset,
   532                              buttondata->text, buttondatax11->length );
   533         } else {
   534             X11_XDrawString( display, window, ctx,
   535                          buttondatax11->x + offset, buttondatax11->y + offset,
   536                          buttondata->text, buttondatax11->length );
   537         }
   538     }
   539 
   540 #if SDL_VIDEO_DRIVER_X11_XDBE
   541     if (SDL_X11_HAVE_XDBE && data->xdbe) {
   542         XdbeSwapInfo swap_info;
   543         swap_info.swap_window = data->window;
   544         swap_info.swap_action = XdbeUndefined;
   545         X11_XdbeSwapBuffers(data->display, &swap_info, 1);
   546         X11_XdbeEndIdiom(data->display);
   547     }
   548 #endif
   549 }
   550 
   551 static Bool
   552 X11_MessageBoxEventTest(Display *display, XEvent *event, XPointer arg)
   553 {
   554     const SDL_MessageBoxDataX11 *data = (const SDL_MessageBoxDataX11 *) arg;
   555     return ((event->xany.display == data->display) && (event->xany.window == data->window)) ? True : False;
   556 }
   557 
   558 /* Loop and handle message box event messages until something kills it. */
   559 static int
   560 X11_MessageBoxLoop( SDL_MessageBoxDataX11 *data )
   561 {
   562     GC ctx;
   563     XGCValues ctx_vals;
   564     SDL_bool close_dialog = SDL_FALSE;
   565     SDL_bool has_focus = SDL_TRUE;
   566     KeySym last_key_pressed = XK_VoidSymbol;
   567     unsigned long gcflags = GCForeground | GCBackground;
   568 
   569     SDL_zero(ctx_vals);
   570     ctx_vals.foreground = data->color[ SDL_MESSAGEBOX_COLOR_BACKGROUND ];
   571     ctx_vals.background = data->color[ SDL_MESSAGEBOX_COLOR_BACKGROUND ];
   572 
   573     if (!SDL_X11_HAVE_UTF8) {
   574         gcflags |= GCFont;
   575         ctx_vals.font = data->font_struct->fid;
   576     }
   577 
   578     ctx = X11_XCreateGC( data->display, data->window, gcflags, &ctx_vals );
   579     if ( ctx == None ) {
   580         return SDL_SetError("Couldn't create graphics context");
   581     }
   582 
   583     data->button_press_index = -1;  /* Reset what button is currently depressed. */
   584     data->mouse_over_index = -1;    /* Reset what button the mouse is over. */
   585 
   586     while( !close_dialog ) {
   587         XEvent e;
   588         SDL_bool draw = SDL_TRUE;
   589 
   590         /* can't use XWindowEvent() because it can't handle ClientMessage events. */
   591         /* can't use XNextEvent() because we only want events for this window. */
   592         X11_XIfEvent( data->display, &e, X11_MessageBoxEventTest, (XPointer) data );
   593 
   594         /* If X11_XFilterEvent returns True, then some input method has filtered the
   595            event, and the client should discard the event. */
   596         if ( ( e.type != Expose ) && X11_XFilterEvent( &e, None ) )
   597             continue;
   598 
   599         switch( e.type ) {
   600         case Expose:
   601             if ( e.xexpose.count > 0 ) {
   602                 draw = SDL_FALSE;
   603             }
   604             break;
   605 
   606         case FocusIn:
   607             /* Got focus. */
   608             has_focus = SDL_TRUE;
   609             break;
   610 
   611         case FocusOut:
   612             /* lost focus. Reset button and mouse info. */
   613             has_focus = SDL_FALSE;
   614             data->button_press_index = -1;
   615             data->mouse_over_index = -1;
   616             break;
   617 
   618         case MotionNotify:
   619             if ( has_focus ) {
   620                 /* Mouse moved... */
   621                 const int previndex = data->mouse_over_index;
   622                 data->mouse_over_index = GetHitButtonIndex( data, e.xbutton.x, e.xbutton.y );
   623                 if (data->mouse_over_index == previndex) {
   624                     draw = SDL_FALSE;
   625                 }
   626             }
   627             break;
   628 
   629         case ClientMessage:
   630             if ( e.xclient.message_type == data->wm_protocols &&
   631                  e.xclient.format == 32 &&
   632                  e.xclient.data.l[ 0 ] == data->wm_delete_message ) {
   633                 close_dialog = SDL_TRUE;
   634             }
   635             break;
   636 
   637         case KeyPress:
   638             /* Store key press - we make sure in key release that we got both. */
   639             last_key_pressed = X11_XLookupKeysym( &e.xkey, 0 );
   640             break;
   641 
   642         case KeyRelease: {
   643             Uint32 mask = 0;
   644             KeySym key = X11_XLookupKeysym( &e.xkey, 0 );
   645 
   646             /* If this is a key release for something we didn't get the key down for, then bail. */
   647             if ( key != last_key_pressed )
   648                 break;
   649 
   650             if ( key == XK_Escape )
   651                 mask = SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT;
   652             else if ( ( key == XK_Return ) || ( key == XK_KP_Enter ) )
   653                 mask = SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT;
   654 
   655             if ( mask ) {
   656                 int i;
   657 
   658                 /* Look for first button with this mask set, and return it if found. */
   659                 for ( i = 0; i < data->numbuttons; i++ ) {
   660                     SDL_MessageBoxButtonDataX11 *buttondatax11 = &data->buttonpos[ i ];
   661 
   662                     if ( buttondatax11->buttondata->flags & mask ) {
   663                         *data->pbuttonid = buttondatax11->buttondata->buttonid;
   664                         close_dialog = SDL_TRUE;
   665                         break;
   666                     }
   667                 }
   668             }
   669             break;
   670         }
   671 
   672         case ButtonPress:
   673             data->button_press_index = -1;
   674             if ( e.xbutton.button == Button1 ) {
   675                 /* Find index of button they clicked on. */
   676                 data->button_press_index = GetHitButtonIndex( data, e.xbutton.x, e.xbutton.y );
   677             }
   678             break;
   679 
   680         case ButtonRelease:
   681             /* If button is released over the same button that was clicked down on, then return it. */
   682             if ( ( e.xbutton.button == Button1 ) && ( data->button_press_index >= 0 ) ) {
   683                 int button = GetHitButtonIndex( data, e.xbutton.x, e.xbutton.y );
   684 
   685                 if ( data->button_press_index == button ) {
   686                     SDL_MessageBoxButtonDataX11 *buttondatax11 = &data->buttonpos[ button ];
   687 
   688                     *data->pbuttonid = buttondatax11->buttondata->buttonid;
   689                     close_dialog = SDL_TRUE;
   690                 }
   691             }
   692             data->button_press_index = -1;
   693             break;
   694         }
   695 
   696         if ( draw ) {
   697             /* Draw our dialog box. */
   698             X11_MessageBoxDraw( data, ctx );
   699         }
   700     }
   701 
   702     X11_XFreeGC( data->display, ctx );
   703     return 0;
   704 }
   705 
   706 static int
   707 X11_ShowMessageBoxImpl(const SDL_MessageBoxData *messageboxdata, int *buttonid)
   708 {
   709     int ret;
   710     SDL_MessageBoxDataX11 data;
   711 #if SDL_SET_LOCALE
   712     char *origlocale;
   713 #endif
   714 
   715     SDL_zero(data);
   716 
   717     if ( !SDL_X11_LoadSymbols() )
   718         return -1;
   719 
   720 #if SDL_SET_LOCALE
   721     origlocale = setlocale(LC_ALL, NULL);
   722     if (origlocale != NULL) {
   723         origlocale = SDL_strdup(origlocale);
   724         if (origlocale == NULL) {
   725             return SDL_OutOfMemory();
   726         }
   727         setlocale(LC_ALL, "");
   728     }
   729 #endif
   730 
   731     /* This code could get called from multiple threads maybe? */
   732     X11_XInitThreads();
   733 
   734     /* Initialize the return buttonid value to -1 (for error or dialogbox closed). */
   735     *buttonid = -1;
   736 
   737     /* Init and display the message box. */
   738     ret = X11_MessageBoxInit( &data, messageboxdata, buttonid );
   739     if ( ret != -1 ) {
   740         ret = X11_MessageBoxInitPositions( &data );
   741         if ( ret != -1 ) {
   742             ret = X11_MessageBoxCreateWindow( &data );
   743             if ( ret != -1 ) {
   744                 ret = X11_MessageBoxLoop( &data );
   745             }
   746         }
   747     }
   748 
   749     X11_MessageBoxShutdown( &data );
   750 
   751 #if SDL_SET_LOCALE
   752     if (origlocale) {
   753         setlocale(LC_ALL, origlocale);
   754         SDL_free(origlocale);
   755     }
   756 #endif
   757 
   758     return ret;
   759 }
   760 
   761 /* Display an x11 message box. */
   762 int
   763 X11_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
   764 {
   765 #if SDL_FORK_MESSAGEBOX
   766     /* Use a child process to protect against setlocale(). Annoying. */
   767     pid_t pid;
   768     int fds[2];
   769     int status = 0;
   770 
   771     if (pipe(fds) == -1) {
   772         return X11_ShowMessageBoxImpl(messageboxdata, buttonid); /* oh well. */
   773     }
   774 
   775     pid = fork();
   776     if (pid == -1) {  /* failed */
   777         close(fds[0]);
   778         close(fds[1]);
   779         return X11_ShowMessageBoxImpl(messageboxdata, buttonid); /* oh well. */
   780     } else if (pid == 0) {  /* we're the child */
   781         int exitcode = 0;
   782         close(fds[0]);
   783         status = X11_ShowMessageBoxImpl(messageboxdata, buttonid);
   784         if (write(fds[1], &status, sizeof (int)) != sizeof (int))
   785             exitcode = 1;
   786         else if (write(fds[1], buttonid, sizeof (int)) != sizeof (int))
   787             exitcode = 1;
   788         close(fds[1]);
   789         _exit(exitcode);  /* don't run atexit() stuff, static destructors, etc. */
   790     } else {  /* we're the parent */
   791         pid_t rc;
   792         close(fds[1]);
   793         do {
   794             rc = waitpid(pid, &status, 0);
   795         } while ((rc == -1) && (errno == EINTR));
   796 
   797         SDL_assert(rc == pid);  /* not sure what to do if this fails. */
   798 
   799         if ((rc == -1) || (!WIFEXITED(status)) || (WEXITSTATUS(status) != 0)) {
   800             return SDL_SetError("msgbox child process failed");
   801         }
   802 
   803         if (read(fds[0], &status, sizeof (int)) != sizeof (int))
   804             status = -1;
   805         else if (read(fds[0], buttonid, sizeof (int)) != sizeof (int))
   806             status = -1;
   807         close(fds[0]);
   808 
   809         return status;
   810     }
   811 #else
   812     return X11_ShowMessageBoxImpl(messageboxdata, buttonid);
   813 #endif
   814 }
   815 #endif /* SDL_VIDEO_DRIVER_X11 */
   816 
   817 /* vi: set ts=4 sw=4 expandtab: */