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 | |
50 | static const char g_MessageBoxFontLatin1[] = "-*-*-medium-r-normal--0-120-*-*-p-0-iso8859-1" ; |
51 | static const char g_MessageBoxFont[] = "-*-*-medium-r-normal--*-120-*-*-*-*-*-*" ; |
52 | |
53 | static const SDL_MessageBoxColor g_default_colors[ SDL_MESSAGEBOX_COLOR_MAX ] = { |
54 | { 56, 54, 53 }, /* SDL_MESSAGEBOX_COLOR_BACKGROUND, */ |
55 | { 209, 207, 205 }, /* SDL_MESSAGEBOX_COLOR_TEXT, */ |
56 | { 140, 135, 129 }, /* SDL_MESSAGEBOX_COLOR_BUTTON_BORDER, */ |
57 | { 105, 102, 99 }, /* SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND, */ |
58 | { 205, 202, 53 }, /* SDL_MESSAGEBOX_COLOR_BUTTON_SELECTED, */ |
59 | }; |
60 | |
61 | #define SDL_MAKE_RGB( _r, _g, _b ) ( ( ( Uint32 )( _r ) << 16 ) | \ |
62 | ( ( Uint32 )( _g ) << 8 ) | \ |
63 | ( ( Uint32 )( _b ) ) ) |
64 | |
65 | typedef struct SDL_MessageBoxButtonDataX11 { |
66 | int x, y; /* Text position */ |
67 | int length; /* Text length */ |
68 | int text_width; /* Text width */ |
69 | |
70 | SDL_Rect rect; /* Rectangle for entire button */ |
71 | |
72 | const SDL_MessageBoxButtonData *buttondata; /* Button data from caller */ |
73 | } SDL_MessageBoxButtonDataX11; |
74 | |
75 | typedef struct TextLineData { |
76 | int width; /* Width of this text line */ |
77 | int length; /* String length of this text line */ |
78 | const char *text; /* Text for this line */ |
79 | } TextLineData; |
80 | |
81 | typedef struct SDL_MessageBoxDataX11 |
82 | { |
83 | Display *display; |
84 | int screen; |
85 | Window window; |
86 | #if SDL_VIDEO_DRIVER_X11_XDBE |
87 | XdbeBackBuffer buf; |
88 | SDL_bool xdbe; /* Whether Xdbe is present or not */ |
89 | #endif |
90 | long event_mask; |
91 | Atom wm_protocols; |
92 | Atom wm_delete_message; |
93 | |
94 | int dialog_width; /* Dialog box width. */ |
95 | int dialog_height; /* Dialog box height. */ |
96 | |
97 | XFontSet font_set; /* for UTF-8 systems */ |
98 | XFontStruct *font_struct; /* Latin1 (ASCII) fallback. */ |
99 | int xtext, ytext; /* Text position to start drawing at. */ |
100 | int numlines; /* Count of Text lines. */ |
101 | int text_height; /* Height for text lines. */ |
102 | TextLineData *linedata; |
103 | |
104 | int *pbuttonid; /* Pointer to user return buttonid value. */ |
105 | |
106 | int button_press_index; /* Index into buttondata/buttonpos for button which is pressed (or -1). */ |
107 | int mouse_over_index; /* Index into buttondata/buttonpos for button mouse is over (or -1). */ |
108 | |
109 | int numbuttons; /* Count of buttons. */ |
110 | const SDL_MessageBoxButtonData *buttondata; |
111 | SDL_MessageBoxButtonDataX11 buttonpos[ MAX_BUTTONS ]; |
112 | |
113 | Uint32 color[ SDL_MESSAGEBOX_COLOR_MAX ]; |
114 | |
115 | const SDL_MessageBoxData *messageboxdata; |
116 | } SDL_MessageBoxDataX11; |
117 | |
118 | /* Maximum helper for ints. */ |
119 | static SDL_INLINE int |
120 | IntMax( int a, int b ) |
121 | { |
122 | return ( a > b ) ? a : b; |
123 | } |
124 | |
125 | /* Return width and height for a string. */ |
126 | static void |
127 | GetTextWidthHeight( SDL_MessageBoxDataX11 *data, const char *str, int nbytes, int *pwidth, int *pheight ) |
128 | { |
129 | if (SDL_X11_HAVE_UTF8) { |
130 | XRectangle overall_ink, overall_logical; |
131 | X11_Xutf8TextExtents(data->font_set, str, nbytes, &overall_ink, &overall_logical); |
132 | *pwidth = overall_logical.width; |
133 | *pheight = overall_logical.height; |
134 | } else { |
135 | XCharStruct text_structure; |
136 | int font_direction, font_ascent, font_descent; |
137 | X11_XTextExtents( data->font_struct, str, nbytes, |
138 | &font_direction, &font_ascent, &font_descent, |
139 | &text_structure ); |
140 | *pwidth = text_structure.width; |
141 | *pheight = text_structure.ascent + text_structure.descent; |
142 | } |
143 | } |
144 | |
145 | /* Return index of button if position x,y is contained therein. */ |
146 | static int |
147 | GetHitButtonIndex( SDL_MessageBoxDataX11 *data, int x, int y ) |
148 | { |
149 | int i; |
150 | int numbuttons = data->numbuttons; |
151 | SDL_MessageBoxButtonDataX11 *buttonpos = data->buttonpos; |
152 | |
153 | for ( i = 0; i < numbuttons; i++ ) { |
154 | SDL_Rect *rect = &buttonpos[ i ].rect; |
155 | |
156 | if ( ( x >= rect->x ) && |
157 | ( x <= ( rect->x + rect->w ) ) && |
158 | ( y >= rect->y ) && |
159 | ( y <= ( rect->y + rect->h ) ) ) { |
160 | return i; |
161 | } |
162 | } |
163 | |
164 | return -1; |
165 | } |
166 | |
167 | /* Initialize SDL_MessageBoxData structure and Display, etc. */ |
168 | static int |
169 | X11_MessageBoxInit( SDL_MessageBoxDataX11 *data, const SDL_MessageBoxData * messageboxdata, int * pbuttonid ) |
170 | { |
171 | int i; |
172 | int numbuttons = messageboxdata->numbuttons; |
173 | const SDL_MessageBoxButtonData *buttondata = messageboxdata->buttons; |
174 | const SDL_MessageBoxColor *colorhints; |
175 | |
176 | if ( numbuttons > MAX_BUTTONS ) { |
177 | return SDL_SetError("Too many buttons (%d max allowed)" , MAX_BUTTONS); |
178 | } |
179 | |
180 | data->dialog_width = MIN_DIALOG_WIDTH; |
181 | data->dialog_height = MIN_DIALOG_HEIGHT; |
182 | data->messageboxdata = messageboxdata; |
183 | data->buttondata = buttondata; |
184 | data->numbuttons = numbuttons; |
185 | data->pbuttonid = pbuttonid; |
186 | |
187 | data->display = X11_XOpenDisplay( NULL ); |
188 | if ( !data->display ) { |
189 | return SDL_SetError("Couldn't open X11 display" ); |
190 | } |
191 | |
192 | if (SDL_X11_HAVE_UTF8) { |
193 | char **missing = NULL; |
194 | int num_missing = 0; |
195 | data->font_set = X11_XCreateFontSet(data->display, g_MessageBoxFont, |
196 | &missing, &num_missing, NULL); |
197 | if ( missing != NULL ) { |
198 | X11_XFreeStringList(missing); |
199 | } |
200 | if ( data->font_set == NULL ) { |
201 | return SDL_SetError("Couldn't load font %s" , g_MessageBoxFont); |
202 | } |
203 | } else { |
204 | data->font_struct = X11_XLoadQueryFont( data->display, g_MessageBoxFontLatin1 ); |
205 | if ( data->font_struct == NULL ) { |
206 | return SDL_SetError("Couldn't load font %s" , g_MessageBoxFontLatin1); |
207 | } |
208 | } |
209 | |
210 | if ( messageboxdata->colorScheme ) { |
211 | colorhints = messageboxdata->colorScheme->colors; |
212 | } else { |
213 | colorhints = g_default_colors; |
214 | } |
215 | |
216 | /* Convert our SDL_MessageBoxColor r,g,b values to packed RGB format. */ |
217 | for ( i = 0; i < SDL_MESSAGEBOX_COLOR_MAX; i++ ) { |
218 | data->color[ i ] = SDL_MAKE_RGB( colorhints[ i ].r, colorhints[ i ].g, colorhints[ i ].b ); |
219 | } |
220 | |
221 | return 0; |
222 | } |
223 | |
224 | static int |
225 | CountLinesOfText(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. */ |
237 | static int |
238 | X11_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. */ |
367 | static void |
368 | X11_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. */ |
401 | static int |
402 | X11_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. */ |
551 | static void |
552 | X11_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 | |
626 | static Bool |
627 | X11_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. */ |
634 | static int |
635 | X11_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 | |
781 | static int |
782 | X11_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. */ |
837 | int |
838 | X11_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 | |