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