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