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