SDL_x11messagebox.c (32395B)
1 /* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2020 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: */