FreeBASIC  0.91.0
gfx_x11.c
Go to the documentation of this file.
1 /* x11 window management code shared by x11 and opengl drivers */
2 
3 #ifndef DISABLE_X11
4 
5 #include "../fb_gfx.h"
6 #include "fb_gfx_x11.h"
7 #include "../../rtlib/unix/fb_private_scancodes_x11.h"
8 #include <pthread.h>
9 
10 /* Horizontal scroll wheel (6 == left, 7 == right)
11  X headers do not define these, as X was not designed for this,
12  but they are still generated, on modern systems anyways. There probably
13  is no guarantee that 6 and 7 will always be hwheel, but as far as gfxlib2
14  is concerned it should be ok to assume 6 and 7 to be hwheel instead of
15  treating them as regular buttondown. */
16 #ifndef Button6
17 #define Button6 6
18 #endif
19 #ifndef Button7
20 #define Button7 7
21 #endif
22 
24 
25 static pthread_t thread;
26 static pthread_mutex_t mutex;
27 static pthread_cond_t cond;
28 
29 static Drawable root_window;
31 static Colormap color_map = None;
32 static Time last_click_time = 0;
34 static int orig_rate, target_rate;
35 static Rotation orig_rotation;
36 static Cursor blank_cursor, arrow_cursor = None;
40 
41 static int calc_comp_height( int h )
42 {
43  if( h < 240 )
44  return 240;
45  else if( h < 480 )
46  return 480;
47  else if( h < 600 )
48  return 600;
49  else if( h < 768 )
50  return 768;
51  else if( h < 1024 )
52  return 1024;
53  else
54  return h;
55 }
56 
57 static int key_repeated(XEvent *event)
58 {
59  /* this function is shamelessly copied from SDL, which
60  * shamelessly copied it from yet another place :P
61  */
62  XEvent peek_event;
63 
64  if (XPending(fb_x11.display)) {
65  XPeekEvent(fb_x11.display, &peek_event);
66  if ((peek_event.type == KeyPress) && (peek_event.xkey.keycode == event->xkey.keycode) &&
67  ((peek_event.xkey.time - event->xkey.time) < 2)) {
68  XNextEvent(fb_x11.display, &peek_event);
69  return TRUE;
70  }
71  }
72  return FALSE;
73 }
74 
75 static int translate_key(XEvent *event, int scancode)
76 {
77  unsigned char key[8];
78  int k;
79 
80  if( XLookupString( &event->xkey, (char *)key, 8, NULL, NULL ) == 1 ) {
81  k = key[0];
82 
83  /* Remap ASCII DEL to FB's extended keycode for DELETE,
84  to match behaviour of console mode and other platforms */
85  if( k == 0x7F ) {
86  k = KEY_DEL;
87  }
88  } else {
89  k = fb_hScancodeToExtendedKey( scancode );
90  }
91 
92  return k;
93 }
94 
95 static void hOnAltEnter( )
96 {
97  fb_x11.exit();
98  fb_x11.flags ^= DRIVER_FULLSCREEN;
99  if (fb_x11.init()) {
100  fb_x11.exit();
101  fb_x11.flags ^= DRIVER_FULLSCREEN;
102  fb_x11.init();
103  }
105  fb_hMemSet(__fb_gfx->key, FALSE, 128);
106 }
107 
108 static void *window_thread(void *arg)
109 {
110  XEvent event;
111  EVENT e;
112  int key;
113 
114  (void)arg;
115 
116  is_running = TRUE;
117  if (fb_x11.init())
118  is_running = FALSE;
119  cursor_shown = TRUE;
120  mouse_x_root = -1;
121 
122  pthread_mutex_lock(&mutex);
123  pthread_cond_signal(&cond);
124  pthread_mutex_unlock(&mutex);
125 
126  while (is_running) {
127  fb_hX11Lock();
128 
129  fb_x11.update();
130 
131  /* This line is causing fbgfx OpenGL code to freeze. - GOK
132  * XSync(fb_x11.display, False);
133  */
134  while (XPending(fb_x11.display)) {
135  e.type = 0;
136  XNextEvent(fb_x11.display, &event);
137  switch (event.type) {
138  case FocusIn:
139  case MapNotify:
140  if (!has_focus) {
141  has_focus = TRUE;
143  }
144  /* fallthrough */
145 
146  case Expose:
147  fb_hMemSet(__fb_gfx->dirty, TRUE, fb_x11.h);
148  break;
149 
150  case FocusOut:
151  fb_hMemSet(__fb_gfx->key, FALSE, 128);
154  break;
155 
156  case EnterNotify:
157  if (has_focus) {
158  mouse_on = TRUE;
160  }
161  break;
162 
163  case LeaveNotify:
164  if (has_focus) {
165  mouse_on = FALSE;
167  }
168  break;
169 
170  case MotionNotify:
171  if (mouse_x_root < 0) {
172  e.dx = e.dy = 0;
173  }
174  else {
175  e.dx = event.xmotion.x_root - mouse_x_root;
176  e.dy = event.xmotion.y_root - mouse_y_root;
177  }
178  mouse_x_root = event.xmotion.x_root;
179  mouse_y_root = event.xmotion.y_root;
180  mouse_x = event.xmotion.x;
181  mouse_y = event.xmotion.y - fb_x11.display_offset;
182  mouse_on = ((mouse_x >= 0) && (mouse_x < fb_x11.w) && (mouse_y >= 0) && (mouse_y < fb_x11.h));
183  if (has_focus) {
185  e.x = mouse_x;
186  e.y = mouse_y;
187  }
188  break;
189 
190  case ButtonPress:
191  switch (event.xbutton.button) {
192  case Button1: mouse_buttons |= BUTTON_LEFT ; e.button = BUTTON_LEFT ; break;
193  case Button3: mouse_buttons |= BUTTON_RIGHT ; e.button = BUTTON_RIGHT ; break;
194  case Button2: mouse_buttons |= BUTTON_MIDDLE; e.button = BUTTON_MIDDLE; break;
195  case Button4: e.z = mouse_wheel++; break;
196  case Button5: e.z = mouse_wheel--; break;
197  case Button6: e.w = mouse_hwheel--; break;
198  case Button7: e.w = mouse_hwheel++; break;
199  }
200 
201  switch (event.xbutton.button) {
202  case Button4:
203  case Button5:
205  break;
206  case Button6:
207  case Button7:
209  break;
210  default:
212  /* Double click check -- done for everything except [h]wheel scrolling */
213  if (event.xbutton.time - last_click_time < DOUBLE_CLICK_TIME)
215  last_click_time = event.xbutton.time;
216  break;
217  }
218 
219  break;
220 
221  case ButtonRelease:
223  switch (event.xbutton.button) {
224  case Button1: mouse_buttons &= ~BUTTON_LEFT ; e.button = BUTTON_LEFT ; break;
225  case Button3: mouse_buttons &= ~BUTTON_RIGHT ; e.button = BUTTON_RIGHT ; break;
226  case Button2: mouse_buttons &= ~BUTTON_MIDDLE; e.button = BUTTON_MIDDLE; break;
227  default: e.type = 0; break;
228  }
229  break;
230 
231  case ConfigureNotify:
232  if( (event.xconfigure.width != fb_x11.w) ||
233  ((event.xconfigure.height != fb_x11.h) &&
234  (event.xconfigure.height != real_h)) ) {
235  /* Window has been maximized: simulate ALT-Enter */
237  hOnAltEnter( );
238  }
239  break;
240 
241  case KeyPress:
242  if (has_focus) {
243  e.scancode = fb_x11keycode_to_scancode[event.xkey.keycode];
244  e.ascii = 0;
245  __fb_gfx->key[e.scancode] = TRUE;
246 
247  if( __fb_gfx->key[SC_ENTER] && __fb_gfx->key[SC_ALT] && !(fb_x11.flags & DRIVER_NO_SWITCH) ) {
248  hOnAltEnter( );
249  } else {
250  key = translate_key( &event, e.scancode );
251  if( key ) {
252  fb_hPostKey( key );
253  /* Don't return extended keycodes in the ascii field */
254  e.ascii = ((key < 0) || (key > 0xFF)) ? 0 : key;
255  }
256  }
257 
258  e.type = EVENT_KEY_PRESS;
259  }
260  break;
261 
262  case KeyRelease:
263  if (has_focus) {
264  e.scancode = fb_x11keycode_to_scancode[event.xkey.keycode];
265  key = translate_key( &event, e.scancode );
266  /* Don't return extended keycodes in the ascii field */
267  e.ascii = ((key < 0) || (key > 0xFF)) ? 0 : key;
268  if (key_repeated(&event)) {
269  if( key )
270  fb_hPostKey( key );
272  } else {
273  __fb_gfx->key[e.scancode] = FALSE;
275  }
276  }
277  break;
278 
279  case ClientMessage:
280  if ((Atom)event.xclient.data.l[0] == wm_delete_window) {
283  }
284  break;
285  }
286  if (e.type)
287  fb_hPostEvent(&e);
288  }
289 
290  pthread_cond_signal(&cond);
291 
292  fb_hX11Unlock();
293 
294  usleep(30000);
295  }
296 
297  fb_x11.exit();
298 
299  return NULL;
300 }
301 
303 {
304  if ((!fb_x11.config) || (target_size < 0))
305  return -1;
306 
307  if (target_rate < 0) {
308  if (XRRSetScreenConfig(fb_x11.display, fb_x11.config, root_window, target_size, orig_rotation, CurrentTime) == BadValue)
309  return -1;
310  } else {
311  if (XRRSetScreenConfigAndRate(fb_x11.display, fb_x11.config, root_window, target_size, orig_rotation, target_rate, CurrentTime) == BadValue)
312  return -1;
314  }
315 
316  XWarpPointer(fb_x11.display, None, fb_x11.window, 0, 0, 0, 0, fb_x11.w >> 1, real_h >> 1);
317  XSync(fb_x11.display, False);
318  while (XGrabPointer(fb_x11.display, fb_x11.window, True, 0,
319  GrabModeAsync, GrabModeAsync, fb_x11.window, None, CurrentTime) != GrabSuccess)
320  usleep(10000);
321  if (XGrabKeyboard(fb_x11.display, root_window, False,
322  GrabModeAsync, GrabModeAsync, CurrentTime) != GrabSuccess)
323  return -1;
324 
326  *h = real_h;
327 
328  return 0;
329 }
330 
332 {
333  if ((!fb_x11.config) || (target_size < 0))
334  return;
335 
336  if (current_size != orig_size) {
337  if ((target_rate <= 0) || (XRRSetScreenConfigAndRate(fb_x11.display, fb_x11.config, root_window, orig_size, orig_rotation, orig_rate, CurrentTime) == BadValue))
338  XRRSetScreenConfig(fb_x11.display, fb_x11.config, root_window, orig_size, orig_rotation, CurrentTime);
339  XUngrabPointer(fb_x11.display, CurrentTime);
340  XUngrabKeyboard(fb_x11.display, CurrentTime);
343  }
344 }
345 
346 void WaitMapped(Window w)
347 {
348  XEvent e;
349  do {
350  XMaskEvent(fb_x11.display, StructureNotifyMask, &e);
351  } while ((e.type != MapNotify) || (e.xmap.event != w));
352 }
353 
354 void fb_hX11InitWindow(int x, int y)
355 {
356  XEvent event;
357 
358  if (!(fb_x11.flags & DRIVER_FULLSCREEN)){
359  /* windowed */
360  XResizeWindow(fb_x11.display, fb_x11.wmwindow, fb_x11.w, fb_x11.h);
361  XResizeWindow(fb_x11.display, fb_x11.window, fb_x11.w, fb_x11.h);
362 
363  if (!(fb_x11.flags & DRIVER_NO_FRAME)) {
364  XReparentWindow(fb_x11.display, fb_x11.window, fb_x11.wmwindow, 0, 0);
365  XMapRaised(fb_x11.display,fb_x11.wmwindow);
366  WaitMapped(fb_x11.wmwindow);
367  }
368  XMapRaised(fb_x11.display, fb_x11.window);
369  if (fb_x11.flags & DRIVER_NO_FRAME)
370  XMoveWindow(fb_x11.display, fb_x11.window, x, y);
371  WaitMapped(fb_x11.window);
372  XRaiseWindow(fb_x11.display, fb_x11.window);
373  } else {
374  /* fullscreen */
375  XMoveResizeWindow(fb_x11.display, fb_x11.fswindow, 0, 0, fb_x11.w, fb_x11.h);
376  XMoveResizeWindow(fb_x11.display, fb_x11.window, 0, 0, fb_x11.w, fb_x11.h);
377  XReparentWindow(fb_x11.display, fb_x11.window, fb_x11.fswindow, 0, 0);
378  XMapRaised(fb_x11.display, fb_x11.fswindow);
379  /* use XSync instead of WaitMapped for unmanaged windows */
380  XSync(fb_x11.display, False);
381  XMapRaised(fb_x11.display, fb_x11.window);
382  XSync(fb_x11.display, False);
383  XRaiseWindow(fb_x11.display, fb_x11.window);
384  }
385 
386  if (fb_x11.flags & DRIVER_ALWAYS_ON_TOP) {
387  fb_hMemSet(&event, 0, sizeof(event));
388  event.xclient.type = ClientMessage;
389  event.xclient.send_event = True;
390  event.xclient.message_type = XInternAtom(fb_x11.display, "_NET_WM_STATE", False);
391  event.xclient.window = fb_x11.wmwindow;
392  event.xclient.format = 32;
393  event.xclient.data.l[0] = 1;
394  event.xclient.data.l[1] = XInternAtom(fb_x11.display, "_NET_WM_STATE_ABOVE", False);
395  XSendEvent(fb_x11.display, root_window, False, SubstructureRedirectMask | SubstructureNotifyMask, &event);
396  }
397 }
398 
399 void fb_hXlibInit(void)
400 {
401  if (!xlib_inited) {
402  XInitThreads();
403  xlib_inited = TRUE;
404  }
405 }
406 
407 int fb_hX11Init(char *title, int w, int h, int depth, int refresh_rate, int flags)
408 {
409  XPixmapFormatValues *format;
410  XSetWindowAttributes attribs;
411  XWMHints hints;
412  XpmAttributes xpm_attribs;
413  XSizeHints *size;
414  Pixmap pixmap;
415  XColor color;
416  XGCValues gc_values;
417  XRRScreenSize *sizes;
418  short *rates;
419  int version, dummy;
420  int i, num_formats, num_sizes, num_rates, supersized_h;
421  int gc_mask;
422  const char *intern_atoms[] = { "_MOTIF_WM_HINTS", "KWM_WIN_DECORATION", "_WIN_HINTS" };
423  int intern_hints[] = { 0x2, 0, 0, 0, 0 };
424 
425  is_running = FALSE;
426  fb_hXlibInit();
427 
428  fb_x11.w = w;
429  fb_x11.h = h;
430  fb_x11.flags = flags;
431  fb_x11.refresh_rate = refresh_rate;
432 
433  target_size = -1;
434  target_rate = -1;
435  current_size = -1;
436  supersized_h = calc_comp_height(fb_x11.h);
437 
438  color_map = None;
439  arrow_cursor = None;
440  wm_intern_hints = None;
441 
442  if (fb_x11.visual) {
443  fb_x11.depth = depth;
444  } else {
445  fb_x11.display = XOpenDisplay(NULL);
446  if (!fb_x11.display)
447  return -1;
448  fb_x11.screen = XDefaultScreen(fb_x11.display);
449  fb_x11.visual = XDefaultVisual(fb_x11.display, fb_x11.screen);
450  fb_x11.depth = XDefaultDepth(fb_x11.display, fb_x11.screen);
451  format = XListPixmapFormats(fb_x11.display, &num_formats);
452  for (i = 0; i < num_formats; i++) {
453  if (format[i].depth == fb_x11.depth) {
454  if (format[i].bits_per_pixel == 16)
455  fb_x11.visual_depth = format[i].depth;
456  else
457  fb_x11.visual_depth = format[i].bits_per_pixel;
458  break;
459  }
460  }
461  XFree(format);
462  }
463  root_window = XDefaultRootWindow(fb_x11.display);
464 
465  attribs.border_pixel = attribs.background_pixel = XBlackPixel(fb_x11.display, fb_x11.screen);
466  attribs.event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask |
467  PointerMotionMask | FocusChangeMask | EnterWindowMask | LeaveWindowMask | ExposureMask | StructureNotifyMask;
468  attribs.backing_store = NotUseful;
469  attribs.colormap = XCreateColormap( fb_x11.display, root_window, fb_x11.visual, AllocNone);
470  fb_x11.window = XCreateWindow(fb_x11.display, root_window, 0, 0, fb_x11.w, fb_x11.h,
471  0, fb_x11.depth, InputOutput, fb_x11.visual,
472  CWBackPixel | CWBorderPixel | CWEventMask | CWBackingStore | CWColormap, &attribs);
473  fb_x11.wmwindow = XCreateWindow(fb_x11.display, root_window, 0, 0, fb_x11.w, fb_x11.h,
474  0, fb_x11.depth, InputOutput, fb_x11.visual,
475  CWBackPixel | CWBorderPixel | CWEventMask | CWBackingStore | CWColormap, &attribs);
476  attribs.override_redirect = True;
477  fb_x11.fswindow = XCreateWindow(fb_x11.display, root_window, 0, 0, fb_x11.w, fb_x11.h,
478  0, fb_x11.depth, InputOutput, fb_x11.visual,
479  CWOverrideRedirect | CWBackPixel | CWBorderPixel | CWEventMask | CWBackingStore | CWColormap, &attribs);
480 
481  if (!fb_x11.window)
482  return -1;
483  XStoreName(fb_x11.display, fb_x11.window, title);
484  if (fb_program_icon) {
485  hints.flags = IconPixmapHint | IconMaskHint;
486  xpm_attribs.valuemask = XpmReturnAllocPixels | XpmReturnExtensions;
487  XpmCreatePixmapFromData(fb_x11.display, fb_x11.window, fb_program_icon, &hints.icon_pixmap, &hints.icon_mask, &xpm_attribs);
488  XSetWMHints(fb_x11.display, fb_x11.wmwindow, &hints);
489  }
490 
491  size = XAllocSizeHints();
492  size->flags = PBaseSize | PMinSize | PMaxSize | PResizeInc;
493  size->min_width = size->base_width = fb_x11.w;
494  size->min_height = size->base_height = fb_x11.h;
495  if (flags & DRIVER_NO_SWITCH) {
496  size->max_width = size->min_width;
497  size->max_height = size->min_height;
498  } else {
499  size->max_width = XDisplayWidth(fb_x11.display, fb_x11.screen);
500  size->max_height = XDisplayHeight(fb_x11.display, fb_x11.screen);
501  }
502  size->width_inc = 0x10000;
503  size->height_inc = 0x10000;
504  XSetWMNormalHints(fb_x11.display, fb_x11.window, size);
505  XSetWMNormalHints(fb_x11.display, fb_x11.fswindow, size);
506  size->max_width = size->min_width;
507  size->max_height = size->min_height;
508  XSetWMNormalHints(fb_x11.display, fb_x11.wmwindow, size);
509  XFree(size);
510 
511  if (flags & DRIVER_NO_FRAME) {
512  for (i = 0; i < 3; i++) {
513  wm_intern_hints = XInternAtom(fb_x11.display, intern_atoms[i], True);
514  if (wm_intern_hints != None) {
515  XChangeProperty(fb_x11.display, fb_x11.window, wm_intern_hints, wm_intern_hints,
516  32, PropModeReplace, (unsigned char *)&intern_hints[i], (i == 0) ? 5 : 1);
517  break;
518  }
519  }
520  if (wm_intern_hints == None)
521  return -1;
522  }
523 
524  wm_delete_window = XInternAtom(fb_x11.display, "WM_DELETE_WINDOW", False);
525  XSetWMProtocols(fb_x11.display, fb_x11.wmwindow, &wm_delete_window, 1);
526 
527  if (fb_x11.visual->class == PseudoColor) {
528  color_map = XCreateColormap(fb_x11.display, root_window, fb_x11.visual, AllocAll);
529  XSetWindowColormap(fb_x11.display, fb_x11.window, color_map);
530  }
531  XClearWindow(fb_x11.display, fb_x11.window);
532 
533  pixmap = XCreatePixmap(fb_x11.display, fb_x11.window, 1, 1, 1);
534  gc_mask = GCFunction | GCForeground | GCBackground;
535  gc_values.function = GXcopy;
536  gc_values.foreground = gc_values.background = 0;
537  fb_x11.gc = XCreateGC(fb_x11.display, pixmap, gc_mask, &gc_values);
538  XDrawPoint(fb_x11.display, pixmap, fb_x11.gc, 0, 0);
539  XFreeGC(fb_x11.display, fb_x11.gc);
540  color.pixel = color.red = color.green = color.blue = 0;
541  color.flags = DoRed | DoGreen | DoBlue;
542  blank_cursor = XCreatePixmapCursor(fb_x11.display, pixmap, pixmap, &color, &color, 0, 0);
543  arrow_cursor = XCreateFontCursor(fb_x11.display, XC_left_ptr);
544  XFreePixmap(fb_x11.display, pixmap);
545  fb_x11.gc = DefaultGC(fb_x11.display, fb_x11.screen);
546  XSync(fb_x11.display, False);
547 
548  if (XRRQueryExtension(fb_x11.display, &dummy, &dummy) &&
549  XRRQueryVersion(fb_x11.display, &version, &dummy) && (version >= 1)) {
550  fb_x11.config = XRRGetScreenInfo(fb_x11.display, root_window);
551  orig_size = current_size = XRRConfigCurrentConfiguration(fb_x11.config, &orig_rotation);
552  orig_rate = XRRConfigCurrentRate(fb_x11.config);
553  sizes = XRRConfigSizes(fb_x11.config, &num_sizes);
554  for (i = 0; i < num_sizes; i++) {
555  if (sizes[i].width == fb_x11.w) {
556  if (sizes[i].height == fb_x11.h) {
557  target_size = i;
558  real_h = fb_x11.h;
559  break;
560  }
561  else if (sizes[i].height == supersized_h) {
562  target_size = i;
563  real_h = supersized_h;
564  break;
565  }
566  }
567  }
568  if ((fb_x11.refresh_rate > 0) && (target_size >= 0)) {
569  rates = XRRConfigRates(fb_x11.config, target_size, &num_rates);
570  for (i = 0; i < num_rates; i++) {
571  if (rates[i] == fb_x11.refresh_rate) {
572  target_rate = rates[i];
573  break;
574  }
575  }
576  } else {
577  fb_x11.refresh_rate = orig_rate;
578  }
580  }
581 
582  fb_hInitX11KeycodeToScancodeTb( fb_x11.display, XDisplayKeycodes, XGetKeyboardMapping );
583 
584  if (flags & DRIVER_FULLSCREEN) {
585  mouse_on = TRUE;
586  mouse_x = fb_x11.w >> 1;
587  mouse_y = fb_x11.h >> 1;
588  } else {
589  mouse_on = FALSE;
590  }
592 
593  pthread_mutex_init(&mutex, NULL);
594  pthread_cond_init(&cond, NULL);
595  pthread_mutex_lock(&mutex);
596  if (!pthread_create(&thread, NULL, window_thread, NULL)) {
597  pthread_cond_wait(&cond, &mutex);
598  pthread_mutex_unlock(&mutex);
599  if (is_running)
600  return 0;
601  pthread_join(thread, NULL);
602  }
603  pthread_cond_destroy(&cond);
604  pthread_mutex_destroy(&mutex);
605 
606  return -1;
607 }
608 
609 void fb_hX11Exit(void)
610 {
611  if (is_running) {
612  is_running = FALSE;
613  pthread_join(thread, NULL);
614  pthread_mutex_destroy(&mutex);
615  pthread_cond_destroy(&cond);
616  }
617  if (fb_x11.display) {
619  XSync(fb_x11.display, False);
620  if (arrow_cursor != None) {
621  XUndefineCursor(fb_x11.display, fb_x11.window);
622  XFreeCursor(fb_x11.display, arrow_cursor);
623  arrow_cursor = None;
624  XFreeCursor(fb_x11.display, blank_cursor);
625  blank_cursor = None;
626  }
627  if (color_map != None) {
628  XFreeColormap(fb_x11.display, color_map);
629  color_map = None;
630  }
631  if (wm_intern_hints != None) {
632  XDeleteProperty(fb_x11.display, fb_x11.window, wm_intern_hints);
633  wm_intern_hints = None;
634  }
635  if (fb_x11.window != None) {
636  XDestroyWindow(fb_x11.display, fb_x11.window);
637  fb_x11.window = None;
638  }
639  if (fb_x11.fswindow != None) {
640  XDestroyWindow(fb_x11.display, fb_x11.fswindow);
641  fb_x11.fswindow = None;
642  }
643  if (fb_x11.wmwindow != None) {
644  XDestroyWindow(fb_x11.display, fb_x11.wmwindow);
645  fb_x11.wmwindow = None;
646  }
647  if (fb_x11.config) {
648  XRRFreeScreenConfigInfo(fb_x11.config);
649  fb_x11.config = NULL;
650  }
651  XCloseDisplay(fb_x11.display);
652  fb_x11.display = NULL;
653  }
654 }
655 
656 void fb_hX11Lock(void)
657 {
658  pthread_mutex_lock(&mutex);
659  XLockDisplay(fb_x11.display);
660 }
661 
662 void fb_hX11Unlock(void)
663 {
664  XUnlockDisplay(fb_x11.display);
665  pthread_mutex_unlock(&mutex);
666 }
667 
668 void fb_hX11SetPalette(int index, int r, int g, int b)
669 {
670  XColor color;
671 
672  if (fb_x11.visual->class == PseudoColor) {
673  color.pixel = index;
674  color.red = (r << 8) | r;
675  color.green = (g << 8) | g;
676  color.blue = (b << 8) | b;
677  color.flags = DoRed | DoGreen | DoBlue;
678  XStoreColors(fb_x11.display, color_map, &color, 1);
679  }
680 }
681 
683 {
684  usleep(1000000 / ((fb_x11.refresh_rate > 0) ? fb_x11.refresh_rate : 60));
685 }
686 
687 int fb_hX11GetMouse(int *x, int *y, int *z, int *buttons, int *clip)
688 {
689  Window root, child;
690  int root_x, root_y, win_x, win_y;
691  unsigned int buttons_mask;
692 
693  if ((!mouse_on) || (!has_focus))
694  return -1;
695 
696  /* prefer XQueryPointer to have a more responsive mouse position retrieval */
697  *z = mouse_wheel;
698  if (XQueryPointer(fb_x11.display, fb_x11.window, &root, &child, &root_x, &root_y, &win_x, &win_y, &buttons_mask)) {
699  *x = win_x;
700  *y = win_y;
701  *buttons = (buttons_mask & Button1Mask ? 0x1 : 0) |
702  (buttons_mask & Button3Mask ? 0x2 : 0) |
703  (buttons_mask & Button2Mask ? 0x4 : 0);
704  } else {
705  *x = mouse_x;
706  *y = mouse_y;
707  *buttons = mouse_buttons;
708  }
709 
710  *clip = fb_x11.mouse_clip;
711  return 0;
712 }
713 
714 void fb_hX11SetMouse(int x, int y, int show, int clip)
715 {
716  if ((x >= 0) && (has_focus)) {
717  mouse_on = TRUE;
718  mouse_x = MID(0, x, fb_x11.w - 1);
719  mouse_y = MID(0, y, fb_x11.h - 1) + fb_x11.display_offset;
720  XWarpPointer(fb_x11.display, None, fb_x11.window, 0, 0, 0, 0, mouse_x, mouse_y);
721  }
722  if ((show > 0) && (!cursor_shown)) {
723  XUndefineCursor(fb_x11.display, fb_x11.window);
724  XDefineCursor(fb_x11.display, fb_x11.window, arrow_cursor);
725  cursor_shown = TRUE;
726  }
727  else if ((show == 0) && (cursor_shown)) {
728  XUndefineCursor(fb_x11.display, fb_x11.window);
729  XDefineCursor(fb_x11.display, fb_x11.window, blank_cursor);
731  }
732  if (clip == 0) {
733  fb_x11.mouse_clip = FALSE;
734  XUngrabPointer(fb_x11.display, CurrentTime);
735  }
736  else if (clip > 0) {
737  fb_x11.mouse_clip = TRUE;
738  while (1) {
739  if (XGrabPointer(fb_x11.display, fb_x11.window, True, 0, GrabModeAsync, GrabModeAsync, fb_x11.window, None, CurrentTime) == GrabSuccess)
740  break;
741  usleep(100000);
742  }
743  }
744 }
745 
746 void fb_hX11SetWindowTitle(char *title)
747 {
748  XStoreName(fb_x11.display, fb_x11.wmwindow, title);
749 }
750 
751 int fb_hX11SetWindowPos(int x, int y)
752 {
753  Window window, root, parent, *children;
754  XWindowAttributes attribs = { 0 };
755  XEvent event;
756  unsigned int num_children;
757  int dx = 0, dy = 0;
758 
759  if (fb_x11.flags & DRIVER_FULLSCREEN)
760  return 0;
761  fb_hX11Lock();
762  parent = fb_x11.window;
763  do {
764  window = parent;
765  dx += attribs.x;
766  dy += attribs.y;
767  XGetWindowAttributes(fb_x11.display, window, &attribs);
768  XQueryTree(fb_x11.display, window, &root, &parent, &children, &num_children);
769  if (children) XFree(children);
770  } while (parent != root_window);
771  if (x == 0x80000000)
772  x = attribs.x;
773  else
774  x -= dx;
775  if (y == 0x80000000)
776  y = attribs.y;
777  else
778  y -= dy;
779 
780  if (fb_x11.flags & DRIVER_NO_FRAME)
781  XMoveWindow(fb_x11.display, fb_x11.window, x, y);
782  else
783  XMoveWindow(fb_x11.display, fb_x11.wmwindow, x, y);
784 
785  XClearWindow(fb_x11.display, fb_x11.wmwindow);
786 
787  /* remove any mouse motion events */
788  while (XCheckWindowEvent(fb_x11.display, fb_x11.window, PointerMotionMask, &event))
789  ;
790  fb_hX11Unlock();
791 
792  return ((attribs.x + dx) & 0xFFFF) | ((attribs.y + dy) << 16);
793 }
794 
795 int *fb_hX11FetchModes(int depth, int *size)
796 {
797  Display *dpy;
798  XRRScreenConfiguration *cfg;
799  XRRScreenSize *rr_sizes;
800  int i, *sizes = NULL;
801 
802  if ((depth != 8) && (depth != 15) && (depth != 16) && (depth != 24) && (depth != 32))
803  return NULL;
804 
805  if (fb_x11.display)
806  dpy = fb_x11.display;
807  else
808  dpy = XOpenDisplay(NULL);
809  if (!dpy)
810  return NULL;
811 
812  if (fb_x11.config)
813  cfg = fb_x11.config;
814  else
815  cfg = XRRGetScreenInfo(dpy, XDefaultRootWindow(dpy));
816  if (!cfg)
817  return NULL;
818 
819  rr_sizes = XRRConfigSizes(cfg, size);
820  if ((rr_sizes) && (*size > 0)) {
821  sizes = (int *)malloc(*size * sizeof(int));
822  for (i = 0; i < *size; i++)
823  sizes[i] = (rr_sizes[i].width << 16) | (rr_sizes[i].height);
824  }
825  if (!fb_x11.config)
826  XRRFreeScreenConfigInfo(cfg);
827  if (!fb_x11.display)
828  XCloseDisplay(dpy);
829 
830  return sizes;
831 }
832 
833 int fb_hX11ScreenInfo(ssize_t *width, ssize_t *height, ssize_t *depth, ssize_t *refresh)
834 {
835  XRRScreenConfiguration *cfg;
836  Display *dpy;
837  int dummy, version;
838 
839  dpy = XOpenDisplay(NULL);
840  if (!dpy)
841  return -1;
842 
843  *width = XDisplayWidth(dpy, XDefaultScreen(dpy));
844  *height = XDisplayHeight(dpy, XDefaultScreen(dpy));
845  *depth = XDefaultDepth(dpy, XDefaultScreen(dpy));
846  if (XRRQueryExtension(dpy, &dummy, &dummy) &&
847  XRRQueryVersion(dpy, &version, &dummy) && (version >= 1)) {
848  /* XRRGetScreenInfo() will fail if RandR extension isn't available */
849  cfg = XRRGetScreenInfo(dpy, XDefaultRootWindow(dpy));
850  if( cfg ) {
851  *refresh = XRRConfigCurrentRate(cfg);
852  XRRFreeScreenConfigInfo(cfg);
853  } else {
854  *refresh = 0;
855  }
856  } else {
857  *refresh = 0;
858  }
859 
860  XCloseDisplay(dpy);
861 
862  return 0;
863 }
864 
865 ssize_t fb_hGetWindowHandle(void)
866 {
867  return (fb_x11.display ? fb_x11.window : 0);
868 }
869 
870 #else
871 
872 ssize_t fb_hGetWindowHandle(void)
873 {
874  return 0;
875 }
876 
877 #endif