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