fe022c5bf3f8ddb1f50d9d8c7b776c4f7694894b
[rocksndiamonds.git] / src / libgame / x11.c
1 /***********************************************************
2 * Artsoft Retro-Game Library                               *
3 *----------------------------------------------------------*
4 * (c) 1994-2006 Artsoft Entertainment                      *
5 *               Holger Schemel                             *
6 *               Detmolder Strasse 189                      *
7 *               33604 Bielefeld                            *
8 *               Germany                                    *
9 *               e-mail: info@artsoft.org                   *
10 *----------------------------------------------------------*
11 * x11.c                                                    *
12 ***********************************************************/
13
14 #include "system.h"
15 #include "pcx.h"
16 #include "misc.h"
17 #include "setup.h"
18
19
20 #if defined(TARGET_X11)
21
22 static void X11InitDisplay();
23 static DrawWindow *X11InitWindow();
24
25 static int X11DebugErrorHandler(Display *display, XErrorEvent *event)
26 {
27   int x = 0;
28
29   return 1 / x;         /* !!! crash program to give backtrace in gdb !!! */
30 }
31
32 void X11InitVideoDisplay(void)
33 {
34   /* initialize X11 video */
35   X11InitDisplay();
36
37   /* set default X11 depth */
38   video.default_depth = XDefaultDepth(display, screen);
39 }
40
41 void X11InitVideoBuffer(DrawBuffer **backbuffer, DrawWindow **window)
42 {
43   *window = X11InitWindow();
44
45   XMapWindow(display, (*window)->drawable);
46   FlushDisplay();
47
48   /* create additional (off-screen) buffer for double-buffering */
49   *backbuffer = CreateBitmap(video.width, video.height, video.depth);
50 }
51
52 static void X11InitDisplay()
53 {
54 #if !defined(PLATFORM_MSDOS)
55   XVisualInfo vinfo_template, *vinfo;
56   int num_visuals;
57 #endif
58   unsigned int depth;
59
60   /* connect to X server */
61   if (!(display = XOpenDisplay(options.display_name)))
62     Error(ERR_EXIT, "cannot connect to X server %s",
63           XDisplayName(options.display_name));
64
65   if (options.debug_x11_sync)
66   {
67     Error(ERR_WARN, "running in X11 synchronous mode (debug only)");
68
69     XSynchronize(display, True);
70     XSetErrorHandler(X11DebugErrorHandler);
71   }
72
73   screen = DefaultScreen(display);
74   visual = DefaultVisual(display, screen);
75   depth  = DefaultDepth(display, screen);
76   cmap   = DefaultColormap(display, screen);
77
78 #if !defined(PLATFORM_MSDOS)
79   /* look for good enough visual */
80   vinfo_template.screen = screen;
81   vinfo_template.class = (depth == 8 ? PseudoColor : TrueColor);
82   vinfo_template.depth = depth;
83   if ((vinfo = XGetVisualInfo(display, VisualScreenMask | VisualClassMask |
84                               VisualDepthMask, &vinfo_template, &num_visuals)))
85   {
86     visual = vinfo->visual;
87     XFree((void *)vinfo);
88   }
89
90   /* got appropriate visual? */
91   if (depth < 8)
92     Error(ERR_EXIT, "X11 display not supported (less than 8 bits per pixel)");
93   else if ((depth ==8 && visual->class != PseudoColor) ||
94            (depth > 8 && visual->class != TrueColor &&
95             visual->class != DirectColor))
96     Error(ERR_EXIT, "X11 display not supported (inappropriate visual)");
97 #endif /* !PLATFORM_MSDOS */
98 }
99
100 static DrawWindow *X11InitWindow()
101 {
102   DrawWindow *new_window = CreateBitmapStruct();
103   unsigned int border_width = 4;
104   XGCValues gc_values;
105   unsigned long gc_valuemask;
106 #if !defined(PLATFORM_MSDOS)
107   XTextProperty windowName, iconName;
108   Pixmap icon_pixmap, iconmask_pixmap;
109   unsigned int icon_width, icon_height;
110   int icon_hot_x, icon_hot_y;
111   XSizeHints size_hints;
112   XWMHints wm_hints;
113   XClassHint class_hints;
114   char *window_name = program.window_title;
115   char *icon_name = program.window_title;
116   long window_event_mask;
117   Atom proto_atom = None, delete_atom = None;
118 #endif
119   int screen_width, screen_height;
120   int win_xpos, win_ypos;
121   unsigned long pen_fg = WhitePixel(display, screen);
122   unsigned long pen_bg = BlackPixel(display, screen);
123   const int width = video.width, height = video.height;
124   int i;
125
126   screen_width = XDisplayWidth(display, screen);
127   screen_height = XDisplayHeight(display, screen);
128
129   win_xpos = (screen_width - width) / 2;
130   win_ypos = (screen_height - height) / 2;
131
132   new_window->drawable = XCreateSimpleWindow(display,
133                                              RootWindow(display, screen),
134                                              win_xpos, win_ypos,
135                                              width, height, border_width,
136                                              pen_fg, pen_bg);
137
138 #if !defined(PLATFORM_MSDOS)
139   proto_atom = XInternAtom(display, "WM_PROTOCOLS", FALSE);
140   delete_atom = XInternAtom(display, "WM_DELETE_WINDOW", FALSE);
141   if ((proto_atom != None) && (delete_atom != None))
142     XChangeProperty(display, new_window->drawable, proto_atom, XA_ATOM, 32,
143                     PropModePrepend, (unsigned char *) &delete_atom, 1);
144
145   if (XReadBitmapFile(display, new_window->drawable,
146                       getCustomImageFilename(program.x11_icon_filename),
147                       &icon_width, &icon_height, &icon_pixmap,
148                       &icon_hot_x, &icon_hot_y) != BitmapSuccess)
149     Error(ERR_EXIT, "cannot read icon bitmap file '%s'",
150           program.x11_icon_filename);
151
152   if (XReadBitmapFile(display, new_window->drawable,
153                       getCustomImageFilename(program.x11_iconmask_filename),
154                       &icon_width, &icon_height, &iconmask_pixmap,
155                       &icon_hot_x, &icon_hot_y) != BitmapSuccess)
156     Error(ERR_EXIT, "cannot read icon bitmap file '%s'",
157           program.x11_iconmask_filename);
158
159   size_hints.width  = size_hints.min_width  = size_hints.max_width  = width;
160   size_hints.height = size_hints.min_height = size_hints.max_height = height;
161   size_hints.flags = PSize | PMinSize | PMaxSize;
162
163   if (win_xpos || win_ypos)
164   {
165     size_hints.x = win_xpos;
166     size_hints.y = win_ypos;
167     size_hints.flags |= PPosition;
168   }
169
170   if (!XStringListToTextProperty(&window_name, 1, &windowName))
171     Error(ERR_EXIT, "structure allocation for windowName failed");
172
173   if (!XStringListToTextProperty(&icon_name, 1, &iconName))
174     Error(ERR_EXIT, "structure allocation for iconName failed");
175
176   wm_hints.initial_state = NormalState;
177   wm_hints.input = True;
178   wm_hints.icon_pixmap = icon_pixmap;
179   wm_hints.icon_mask = iconmask_pixmap;
180   wm_hints.flags = StateHint | IconPixmapHint | IconMaskHint | InputHint;
181
182   class_hints.res_name = program.command_basename;
183   class_hints.res_class = program.program_title;
184
185   XSetWMProperties(display, new_window->drawable, &windowName, &iconName, 
186                    NULL, 0, &size_hints, &wm_hints, 
187                    &class_hints);
188
189   XFree(windowName.value);
190   XFree(iconName.value);
191
192   /* Select event types wanted */
193   window_event_mask =
194     ExposureMask | StructureNotifyMask | FocusChangeMask |
195     ButtonPressMask | ButtonReleaseMask |
196     PointerMotionMask | PointerMotionHintMask |
197     KeyPressMask | KeyReleaseMask;
198
199   XSelectInput(display, new_window->drawable, window_event_mask);
200 #endif
201
202   /* create GC for drawing with window depth and background color (black) */
203   gc_values.graphics_exposures = False;
204   gc_values.foreground = pen_bg;
205   gc_values.background = pen_bg;
206   gc_valuemask = GCGraphicsExposures | GCForeground | GCBackground;
207   new_window->gc =
208     XCreateGC(display, new_window->drawable, gc_valuemask, &gc_values);
209
210   /* create GCs for line drawing (black and white) */
211   for (i = 0; i < 2; i++)
212   {
213     gc_values.graphics_exposures = False;
214     gc_values.foreground = (i ? pen_fg : pen_bg);
215     gc_values.background = pen_bg;
216     gc_values.line_width = 4;
217     gc_values.line_style = LineSolid;
218     gc_values.cap_style = CapRound;
219     gc_values.join_style = JoinRound;
220
221     gc_valuemask = GCGraphicsExposures | GCForeground | GCBackground |
222                    GCLineWidth | GCLineStyle | GCCapStyle | GCJoinStyle;
223     new_window->line_gc[i] =
224       XCreateGC(display, new_window->drawable, gc_valuemask, &gc_values);
225   }
226
227   return new_window;
228 }
229
230 void X11ZoomBitmap(Bitmap *src_bitmap, Bitmap *dst_bitmap)
231 {
232 #if defined(TARGET_ALLEGRO)
233   AllegroZoomBitmap(src_bitmap->drawable, dst_bitmap->drawable,
234                     src_bitmap->width, src_bitmap->height,
235                     dst_bitmap->width, dst_bitmap->height);
236 #else
237   ZoomPixmap(display, src_bitmap->gc,
238              src_bitmap->drawable, dst_bitmap->drawable,
239              src_bitmap->width, src_bitmap->height,
240              dst_bitmap->width, dst_bitmap->height);
241 #endif
242 }
243
244 static void SetImageDimensions(Bitmap *bitmap)
245 {
246 #if defined(TARGET_ALLEGRO)
247   BITMAP *allegro_bitmap = (BITMAP *)(bitmap->drawable);
248
249   bitmap->width  = allegro_bitmap->w;
250   bitmap->height = allegro_bitmap->h;
251 #else
252   Window root;
253   int x, y;
254   unsigned int border_width, depth;
255
256   XGetGeometry(display, bitmap->drawable, &root, &x, &y,
257                &bitmap->width, &bitmap->height, &border_width, &depth);
258 #endif
259 }
260
261 Bitmap *X11LoadImage(char *filename)
262 {
263   Bitmap *new_bitmap = CreateBitmapStruct();
264   char *error = "Read_PCX_to_Pixmap(): %s '%s'";
265   int pcx_err;
266   XGCValues clip_gc_values;
267   unsigned long clip_gc_valuemask;
268
269   pcx_err = Read_PCX_to_Pixmap(display, window->drawable, window->gc, filename,
270                                &new_bitmap->drawable, &new_bitmap->clip_mask);
271   switch(pcx_err)
272   {
273     case PCX_Success:
274       break;
275     case PCX_OpenFailed:
276       SetError(error, "cannot open PCX file", filename);
277       return NULL;
278     case PCX_ReadFailed:
279       SetError(error, "cannot read PCX file", filename);
280       return NULL;
281     case PCX_FileInvalid:
282       SetError(error, "invalid PCX file", filename);
283       return NULL;
284     case PCX_NoMemory:
285       SetError(error, "not enough memory for PCX file", filename);
286       return NULL;
287     case PCX_ColorFailed:
288       SetError(error, "cannot get colors for PCX file", filename);
289       return NULL;
290     case PCX_OtherError:
291       /* this should already have called SetError() */
292       return NULL;
293     default:
294       SetError(error, "unknown error reading PCX file", filename);
295       return NULL;
296   }
297
298   if (!new_bitmap->drawable)
299   {
300     SetError("X11LoadImage(): cannot get graphics for '%s'", filename);
301     return NULL;
302   }
303
304   if (!new_bitmap->clip_mask)
305   {
306     SetError("X11LoadImage(): cannot get clipmask for '%s'", filename);
307     return NULL;
308   }
309
310   clip_gc_values.graphics_exposures = False;
311   clip_gc_values.clip_mask = new_bitmap->clip_mask;
312   clip_gc_valuemask = GCGraphicsExposures | GCClipMask;
313   new_bitmap->stored_clip_gc = XCreateGC(display, window->drawable,
314                                          clip_gc_valuemask, &clip_gc_values);
315
316   /* set GraphicContext inheritated from Window */
317   new_bitmap->gc = window->gc;
318
319   /* set image width and height */
320   SetImageDimensions(new_bitmap);
321
322   return new_bitmap;
323 }
324
325 void X11CreateBitmapContent(Bitmap *new_bitmap,
326                             int width, int height, int depth)
327 {
328   Pixmap pixmap;
329
330   if ((pixmap = XCreatePixmap(display, window->drawable, width, height, depth))
331       == None)
332     Error(ERR_EXIT, "cannot create pixmap");
333
334   new_bitmap->drawable = pixmap;
335
336   if (window == NULL)
337     Error(ERR_EXIT, "Window GC needed for Bitmap -- create Window first");
338
339   new_bitmap->gc = window->gc;
340
341   new_bitmap->line_gc[0] = window->line_gc[0];
342   new_bitmap->line_gc[1] = window->line_gc[1];
343 }
344
345 void X11FreeBitmapPointers(Bitmap *bitmap)
346 {
347   /* The X11 version seems to have a memory leak here -- although
348      "XFreePixmap()" is called, the corresponding memory seems not
349      to be freed (according to "ps"). The SDL version apparently
350      does not have this problem. */
351
352   if (bitmap->drawable)
353     XFreePixmap(display, bitmap->drawable);
354   if (bitmap->clip_mask)
355     XFreePixmap(display, bitmap->clip_mask);
356   if (bitmap->stored_clip_gc)
357     XFreeGC(display, bitmap->stored_clip_gc);
358   /* the other GCs are only pointers to GCs used elsewhere */
359   bitmap->drawable = None;
360   bitmap->clip_mask = None;
361   bitmap->stored_clip_gc = None;
362 }
363
364 void X11CopyArea(Bitmap *src_bitmap, Bitmap *dst_bitmap,
365                  int src_x, int src_y, int width, int height,
366                  int dst_x, int dst_y, int mask_mode)
367 {
368   XCopyArea(display, src_bitmap->drawable, dst_bitmap->drawable,
369             (mask_mode == BLIT_MASKED ? src_bitmap->clip_gc : dst_bitmap->gc),
370             src_x, src_y, width, height, dst_x, dst_y);
371 }
372
373 void X11FillRectangle(Bitmap *bitmap, int x, int y,
374                       int width, int height, Pixel color)
375 {
376   XSetForeground(display, bitmap->gc, color);
377   XFillRectangle(display, bitmap->drawable, bitmap->gc, x, y, width, height);
378 }
379
380 void X11FadeRectangle(Bitmap *bitmap_cross, int x, int y, int width, int height,
381                       int fade_mode, int fade_delay, int post_delay,
382                       void (*draw_border_function)(void))
383 {
384   /* fading currently not supported -- simply copy backbuffer to screen */
385
386   if (fade_mode == FADE_MODE_FADE_OUT)
387     X11FillRectangle(backbuffer, x, y, width, height, BLACK_PIXEL);
388
389   if (draw_border_function != NULL)
390     draw_border_function();
391
392   X11CopyArea(backbuffer, window, x, y, width, height, x, y, BLIT_OPAQUE);
393
394   /* as we currently cannot use the fade delay, also do not use post delay */
395 }
396
397 void X11DrawSimpleLine(Bitmap *bitmap, int from_x, int from_y,
398                        int to_x, int to_y, Pixel color)
399 {
400   XSetForeground(display, bitmap->gc, color);
401   XDrawLine(display, bitmap->drawable, bitmap->gc, from_x, from_y, to_x, to_y);
402 }
403
404 Pixel X11GetPixel(Bitmap *bitmap, int x, int y)
405 {
406   XImage *pixel_image;
407   Pixel pixel_value;
408
409   pixel_image = XGetImage(display, bitmap->drawable, x, y, 1, 1,
410                           AllPlanes, ZPixmap);
411   pixel_value = XGetPixel(pixel_image, 0, 0);
412
413   X11DestroyImage(pixel_image);
414
415   return pixel_value;
416 }
417
418 #if defined(TARGET_X11_NATIVE)
419 Pixel X11GetPixelFromRGB(unsigned int color_r, unsigned int color_g,
420                          unsigned int color_b)
421 {
422   XColor xcolor;
423   Pixel pixel;
424
425   xcolor.flags = DoRed | DoGreen | DoBlue;
426   xcolor.red = (color_r << 8);
427   xcolor.green = (color_g << 8);
428   xcolor.blue = (color_b << 8);
429
430   XAllocColor(display, cmap, &xcolor);
431   pixel = xcolor.pixel;
432
433   return pixel;
434 }
435 #endif  /* TARGET_X11_NATIVE */
436
437 void X11DestroyImage(XImage *ximage)
438 {
439 #if defined(TARGET_X11_NATIVE)
440   /* this seems to be needed for OS/2, but does not hurt on other platforms */
441   if (ximage->data != NULL)
442   {
443     free(ximage->data);
444     ximage->data = NULL;
445   }
446 #endif  /* TARGET_X11_NATIVE */
447
448   XDestroyImage(ximage);
449 }
450
451
452 /* ------------------------------------------------------------------------- */
453 /* mouse pointer functions                                                   */
454 /* ------------------------------------------------------------------------- */
455
456 #if defined(TARGET_X11_NATIVE)
457
458 static Cursor create_cursor(struct MouseCursorInfo *cursor_info)
459 {
460   Pixmap pixmap_data, pixmap_mask;
461   XColor color_fg, color_bg;
462   Cursor cursor;
463
464   /* shape and mask are single plane pixmaps */
465   pixmap_data =
466     XCreatePixmapFromBitmapData(display, window->drawable, cursor_info->data,
467                                 cursor_info->width, cursor_info->height,
468                                 1, 0, 1);
469   pixmap_mask =
470     XCreatePixmapFromBitmapData(display, window->drawable, cursor_info->mask,
471                                 cursor_info->width, cursor_info->height,
472                                 1, 0, 1);
473
474   XParseColor(display, cmap, "black", &color_fg);
475   XParseColor(display, cmap, "white", &color_bg);
476
477   cursor = XCreatePixmapCursor(display, pixmap_data, pixmap_mask,
478                                &color_fg, &color_bg,
479                                cursor_info->hot_x, cursor_info->hot_y);
480
481   return cursor;
482 }
483
484 void X11SetMouseCursor(struct MouseCursorInfo *cursor_info)
485 {
486   static struct MouseCursorInfo *last_cursor_info = NULL;
487   static Cursor cursor_default = None;
488   static Cursor cursor_current = None;
489
490   if (cursor_info != NULL && cursor_info != last_cursor_info)
491   {
492     cursor_current = create_cursor(cursor_info);
493     last_cursor_info = cursor_info;
494   }
495
496   XDefineCursor(display, window->drawable,
497                 cursor_info ? cursor_current : cursor_default);
498 }
499 #endif  /* TARGET_X11_NATIVE */
500
501 #endif /* TARGET_X11 */