rnd-20040814-1-src
[rocksndiamonds.git] / src / libem / init.c
1 /* 2000-08-10T18:03:54Z
2  *
3  * open X11 display and sound
4  */
5
6 #include <X11/Xlib.h>
7 #include <X11/Xutil.h>
8 #include <X11/Xatom.h>
9 #include <X11/keysym.h>
10 #include <X11/cursorfont.h>
11 #include <X11/xpm.h>
12 #include <sys/types.h>
13 #include <sys/wait.h>
14 #include <unistd.h>
15 #include <stdlib.h>
16 #include <stdio.h>
17 #include <signal.h>
18 #include <string.h>
19 #include <errno.h>
20
21 #include "../libgame/platform.h"
22
23 #include "global.h"
24 #include "display.h"
25 #include "sample.h"
26
27 Display *display;
28 Window xwindow;
29
30 Pixmap screenPixmap;
31 Pixmap scorePixmap;
32 Pixmap spriteBitmap;
33
34 Pixmap objPixmap;
35 Pixmap objmaskBitmap;
36 Pixmap botPixmap;
37 Pixmap botmaskBitmap;
38 Pixmap sprPixmap;
39 Pixmap sprmaskBitmap;
40 Pixmap ttlPixmap;
41 Pixmap ttlmaskBitmap;
42
43 GC screenGC;
44 GC scoreGC;
45 GC spriteGC;
46 GC antsGC;
47
48 Atom deleteAtom;
49
50 KeySym lastKeySym;
51
52 KeyCode northKeyCode[3];
53 KeyCode eastKeyCode[3];
54 KeyCode southKeyCode[3];
55 KeyCode westKeyCode[3];
56 KeyCode fireKeyCode[3];
57 KeyCode escKeyCode[1];
58
59 char play[SAMPLE_MAX];
60
61 static int sound_pid = -1;
62 int sound_pipe[2] = { -1, -1 }; /* for communication */
63 short *sound_data[SAMPLE_MAX]; /* pointer to sound data */
64 long sound_length[SAMPLE_MAX]; /* length of sound data */
65
66 static Screen *defaultScreen;
67 static Visual *defaultVisual;
68 static Colormap defaultColourmap;
69 static Window defaultRootWindow;
70 static unsigned int screenDepth;
71 static unsigned int screenWidth;
72 static unsigned int screenHeight;
73 static unsigned long screenBlackPixel;
74 static unsigned long screenWhitePixel;
75
76 static XSizeHints sizeHints;
77 static XSetWindowAttributes setWindowAttributes;
78 static XWMHints wmHints;
79 static XVisualInfo visualInfo;
80 static XGCValues gcValues;
81
82 static Colormap privateColourmap;
83 static Cursor cursor;
84 static XColor *privateColours;
85 static unsigned char *privateFlags;
86 static int privateNumColours;
87
88 static XColor redColour;
89 static XColor whiteColour;
90 static int gotRed;
91 static int gotWhite;
92
93 static Pixmap xpmPixmaps[4];
94 static Pixmap xpmBitmaps[4];
95 static XpmAttributes xpmAttributes[4];
96 static int xpmGot[4];
97
98 static int xpmAllocColourFunc(Display *, Colormap, char *, XColor *, void *);
99 static int xpmFreeColoursFunc(Display *, Colormap, unsigned long *, int, void *);
100
101 static KeyCode keycodes[16];
102
103 static const char *xpmNames[4] = { "object.xpm", "score.xpm", "sprite.xpm", "title.xpm" };
104 static const int xpmCloseness[4] = { 10000, 10000, 40000, 50000 };
105 static const KeySym keysyms[16] = {
106         XK_Up, XK_KP_Up, XK_r, /* north */
107         XK_Right, XK_KP_Right, XK_g, /* east */
108         XK_Down, XK_KP_Down, XK_f, /* south */
109         XK_Left, XK_KP_Left, XK_d, /* west */
110         XK_Shift_L, XK_Control_R, XK_space, /* fire */
111         XK_Escape /* escape */
112 };
113 static const char *sound_names[SAMPLE_MAX] = {
114         "00.blank.au","01.roll.au","02.stone.au","03.nut.au","04.crack.au",
115         "05.bug.au","06.tank.au","07.android.au","08.spring.au","09.slurp.au",
116         "10.eater.au","11.alien.au","12.collect.au","13.diamond.au","14.squash.au",
117         "15.drip.au","16.push.au","17.dirt.au","18.acid.au","19.ball.au",
118         "20.grow.au","21.wonder.au","22.door.au","23.exit.au","24.dynamite.au",
119         "25.tick.au","26.press.au","27.wheel.au","28.boom.au","29.time.au",
120         "30.die.au"
121 };
122 static const int sound_volume[SAMPLE_MAX] = {
123         20,100,100,100,100,20,20,100,100,100,
124         50,100,100,100,100,100,100,100,100,100,
125         100,20,100,100,100,100,100,20,100,100,
126         100
127 };
128
129 int open_all(void)
130 {
131         char name[MAXNAME+2];
132         void *dummyptr;
133         int dummyint;
134         int i;
135
136         display = XOpenDisplay(arg_display);
137         if(display == 0) {
138                 fprintf(stderr, "%s: \"%s\": %s: %s\n", progname, XDisplayName(arg_display), "failed to open display", strerror(errno));
139                 return(1);
140         }
141
142         defaultScreen = DefaultScreenOfDisplay(display);
143         defaultVisual = DefaultVisualOfScreen(defaultScreen);
144         defaultColourmap = DefaultColormapOfScreen(defaultScreen);
145         defaultRootWindow = RootWindowOfScreen(defaultScreen);
146         screenDepth = DefaultDepthOfScreen(defaultScreen);
147         screenWidth = WidthOfScreen(defaultScreen);
148         screenHeight = HeightOfScreen(defaultScreen);
149         screenBlackPixel = BlackPixelOfScreen(defaultScreen);
150         screenWhitePixel = WhitePixelOfScreen(defaultScreen);
151
152         if(arg_install) {
153                 visualInfo.visualid = XVisualIDFromVisual(defaultVisual);
154                 dummyptr = XGetVisualInfo(display, VisualIDMask, &visualInfo, &dummyint);
155                 if(dummyptr == 0) {
156                         fprintf(stderr, "%s: \"%s\": %s: %s\n", progname, XDisplayName(arg_display), "failed to get visual info", strerror(errno));
157                         return(1);
158                 }
159                 memcpy(&visualInfo, dummyptr, sizeof(visualInfo));
160                 XFree(dummyptr);
161
162                 if(visualInfo.class != PseudoColor) {
163                         fprintf(stderr, "%s: \"%s\": %s\n", progname, XDisplayName(arg_display), "private colourmap only supported for pseudocolour display");
164                         return(1);
165                 }
166
167                 privateColourmap = XCreateColormap(display, defaultRootWindow, defaultVisual, AllocAll);
168                 if(privateColourmap == 0) {
169                         fprintf(stderr, "%s: \"%s\": %s: %s\n", progname, XDisplayName(arg_display), "failed to create colourmap", strerror(errno));
170                         return(1);
171                 }
172
173                 privateNumColours = visualInfo.colormap_size;
174
175                 privateColours = malloc(privateNumColours * sizeof(XColor));
176                 if(privateColours == 0) {
177                         fprintf(stderr, "%s: %s (%d): %s\n", progname, "malloc failed", privateNumColours * sizeof(XColor), strerror(errno));
178                         return(1);
179                 }
180                 for(dummyint = 0; dummyint < privateNumColours; dummyint++) privateColours[dummyint].pixel = dummyint;
181                 XQueryColors(display, defaultColourmap, privateColours, privateNumColours);
182                 XStoreColors(display, privateColourmap, privateColours, privateNumColours);
183
184                 privateFlags = malloc(privateNumColours);
185                 if(privateFlags == 0) {
186                         fprintf(stderr, "%s: %s (%d): %s\n", progname, "malloc failed", privateNumColours, strerror(errno));
187                         return(1);
188                 }
189                 memset(privateFlags, 0, privateNumColours);
190                 privateFlags[0] = 1; /* first two entries (black and white) are already allocated */
191                 privateFlags[1] = 1;
192         }
193
194         sizeHints.flags = PSize | PMinSize | PMaxSize;
195         sizeHints.width = 20 * TILEX;
196         sizeHints.height = 12 * TILEY + SCOREY;
197         sizeHints.min_width = sizeHints.max_width = sizeHints.width;
198         sizeHints.min_height = sizeHints.max_height = sizeHints.height;
199         if(arg_geometry) {
200                 dummyint = XWMGeometry(display, XScreenNumberOfScreen(defaultScreen), arg_geometry, 0, 2, &sizeHints, &sizeHints.x, &sizeHints.y, &dummyint, &dummyint, &sizeHints.win_gravity);
201                 if(dummyint & (XValue | YValue)) sizeHints.flags |= USPosition | PWinGravity;
202         }
203
204         xwindow = XCreateWindow(display, defaultRootWindow, sizeHints.x, sizeHints.y, sizeHints.width, sizeHints.height, 2, screenDepth, InputOutput, CopyFromParent, 0, 0);
205         if(xwindow == 0) {
206                 fprintf(stderr, "%s: \"%s\": %s: %s\n", progname, XDisplayName(arg_display), "failed to open window", strerror(errno));
207                 return(1);
208         }
209
210         setWindowAttributes.background_pixel = screenBlackPixel;
211         setWindowAttributes.border_pixel = screenWhitePixel;
212         setWindowAttributes.backing_store = NotUseful;
213         setWindowAttributes.override_redirect = False;
214         setWindowAttributes.event_mask = KeyPressMask | EnterWindowMask | LeaveWindowMask | ExposureMask;
215         setWindowAttributes.colormap = privateColourmap ? privateColourmap : defaultColourmap;
216         XChangeWindowAttributes(display, xwindow, CWBackPixel | CWBorderPixel | CWBackingStore | CWOverrideRedirect | CWEventMask | CWColormap, &setWindowAttributes);
217
218         XStoreName(display, xwindow, "Emerald Mine");
219
220         wmHints.flags = InputHint | StateHint;
221         wmHints.input = True;
222         wmHints.initial_state = NormalState;
223         XSetWMHints(display, xwindow, &wmHints);
224
225         XSetWMNormalHints(display, xwindow, &sizeHints);
226
227         deleteAtom = XInternAtom(display, "WM_DELETE_WINDOW", False);
228         XSetWMProtocols(display, xwindow, &deleteAtom, 1);
229
230         cursor = XCreateFontCursor(display, XC_trek);
231         if(cursor) XDefineCursor(display, xwindow, cursor);
232
233         XMapWindow(display, xwindow);
234
235         for(i = 0; i < 4; i++) {
236                 name[MAXNAME] = 0;
237                 if(arg_basedir) {
238                         snprintf(name, MAXNAME+2, "%s/%s/%s", arg_basedir, EM_GFX_DIR, xpmNames[i]);
239                 } else {
240                         snprintf(name, MAXNAME+2, "%s/%s", EM_GFX_DIR, xpmNames[i]);
241                 }
242                 if(name[MAXNAME]) snprintf_overflow("read graphics/ files");
243
244                 xpmAttributes[i].valuemask = XpmColormap | XpmReturnAllocPixels | XpmExactColors | XpmCloseness | XpmAllocColor | XpmFreeColors;
245                 xpmAttributes[i].colormap = privateColourmap ? privateColourmap : defaultColourmap;
246                 xpmAttributes[i].exactColors = False;
247                 xpmAttributes[i].closeness = xpmCloseness[i];
248                 xpmAttributes[i].alloc_color = xpmAllocColourFunc;
249                 xpmAttributes[i].free_colors = xpmFreeColoursFunc;
250                 dummyint = XpmReadFileToPixmap(display, xwindow, name, &xpmPixmaps[i], &xpmBitmaps[i], &xpmAttributes[i]);
251                 if(dummyint) {
252                         fprintf(stderr, "%s: \"%s\": \"%s\": %s: %s: %s\n", progname, XDisplayName(arg_display), name, "failed to read xpm", XpmGetErrorString(dummyint), strerror(errno));
253                         return(1);
254                 }
255                 xpmGot[i] = 1;
256         }
257
258         objPixmap = xpmPixmaps[0];
259         botPixmap = xpmPixmaps[1];
260         sprPixmap = xpmPixmaps[2];
261         ttlPixmap = xpmPixmaps[3];
262         objmaskBitmap = xpmBitmaps[0];
263         botmaskBitmap = xpmBitmaps[1];
264         sprmaskBitmap = xpmBitmaps[2];
265         ttlmaskBitmap = xpmBitmaps[3];
266
267         screenPixmap = XCreatePixmap(display, xwindow, 22 * TILEX, 14 * TILEY, screenDepth);
268         if(screenPixmap == 0) {
269                 fprintf(stderr, "%s: \"%s\": %s: %s\n", progname, XDisplayName(arg_display), "failed to create pixmap", strerror(errno));
270                 return(1);
271         }
272
273         scorePixmap = XCreatePixmap(display, xwindow, 20 * TILEX, SCOREY, screenDepth);
274         if(scorePixmap == 0) {
275                 fprintf(stderr, "%s: \"%s\": %s: %s\n", progname, XDisplayName(arg_display), "failed to create pixmap", strerror(errno));
276                 return(1);
277         }
278
279         spriteBitmap = XCreatePixmap(display, xwindow, TILEX, TILEY, 1);
280         if(spriteBitmap == 0) {
281                 fprintf(stderr, "%s: \"%s\": %s: %s\n", progname, XDisplayName(arg_display), "failed to create pixmap", strerror(errno));
282                 return(1);
283         }
284
285         redColour.pixel = screenWhitePixel;
286         whiteColour.pixel = screenBlackPixel;
287         gotRed = (xpmAllocColourFunc(display, privateColourmap ? privateColourmap : defaultColourmap, "red", &redColour, 0) > 0);
288         gotWhite = (xpmAllocColourFunc(display, privateColourmap ? privateColourmap : defaultColourmap, "white", &whiteColour, 0) > 0);
289
290         gcValues.graphics_exposures = False;
291         screenGC = XCreateGC(display, screenPixmap, GCGraphicsExposures, &gcValues);
292         if(screenGC == 0) {
293                 fprintf(stderr, "%s: \"%s\": %s: %s\n", progname, XDisplayName(arg_display), "failed to create graphics context", strerror(errno));
294                 return(1);
295         }
296
297         gcValues.graphics_exposures = False;
298         scoreGC = XCreateGC(display, scorePixmap, GCGraphicsExposures, &gcValues);
299         if(scoreGC == 0) {
300                 fprintf(stderr, "%s: \"%s\": %s: %s\n", progname, XDisplayName(arg_display), "failed to create graphics context", strerror(errno));
301                 return(1);
302         }
303
304         gcValues.function = objmaskBitmap ? GXcopyInverted : sprmaskBitmap ? GXcopy : GXset;
305         gcValues.graphics_exposures = False;
306         spriteGC = XCreateGC(display, spriteBitmap, GCFunction | GCGraphicsExposures, &gcValues);
307         if(spriteGC == 0) {
308                 fprintf(stderr, "%s: \"%s\": %s: %s\n", progname, XDisplayName(arg_display), "failed to create graphics context", strerror(errno));
309                 return(1);
310         }
311
312         gcValues.foreground = redColour.pixel;
313         gcValues.background = whiteColour.pixel;
314         gcValues.line_style = LineDoubleDash;
315         gcValues.graphics_exposures = False;
316         antsGC = XCreateGC(display, screenPixmap, GCForeground | GCBackground | GCLineStyle | GCGraphicsExposures, &gcValues);
317         if(antsGC == 0) {
318                 fprintf(stderr, "%s: \"%s\": %s: %s\n", progname, XDisplayName(arg_display), "failed to create graphics context", strerror(errno));
319                 return(1);
320         }
321
322         for(i = 0; i < 16; i++) {
323                 keycodes[i] = XKeysymToKeycode(display, keysyms[i]);
324         }
325         for(i = 0; i < 3; i++) northKeyCode[i] = keycodes[i + 0];
326         for(i = 0; i < 3; i++) eastKeyCode[i] = keycodes[i + 3];
327         for(i = 0; i < 3; i++) southKeyCode[i] = keycodes[i + 6];
328         for(i = 0; i < 3; i++) westKeyCode[i] = keycodes[i + 9];
329         for(i = 0; i < 3; i++) fireKeyCode[i] = keycodes[i + 12];
330         for(i = 0; i < 1; i++) escKeyCode[i] = keycodes[i + 15];
331
332 #if defined(PLATFORM_LINUX) || defined(PLATFORM_BSD)
333         if(arg_silence == 0) {
334                 for(i = 0; i < SAMPLE_MAX; i++) {
335                         name[MAXNAME] = 0;
336                         if(arg_basedir) {
337                                 snprintf(name, MAXNAME+2, "%s/%s/%s", arg_basedir, EM_SND_DIR, sound_names[i]);
338                         } else {
339                                 snprintf(name, MAXNAME+2, "%s/%s", EM_SND_DIR, sound_names[i]);
340                         }
341                         if(name[MAXNAME]) snprintf_overflow("read sounds/ directory");
342
343                         if(read_sample(name, &sound_data[i], &sound_length[i])) return(1);
344
345                         {
346                                 short *ptr, *stop;
347                                 int mult = sound_volume[i] * 65536 / (100 * MIXER_MAX);
348                                 stop = sound_data[i] + sound_length[i];
349                                 for(ptr = sound_data[i]; ptr < stop; ptr++) *ptr = (*ptr * mult) / 65536;
350                         }
351                 }
352
353                 if(pipe(sound_pipe) == -1) {
354                         fprintf(stderr, "%s: %s: %s\n", progname, "unable to create sound pipe", strerror(errno));
355                         return(1);
356                 }
357                 sound_pid = fork();
358                 if(sound_pid == -1) {
359                         fprintf(stderr, "%s: %s: %s\n", progname, "unable to fork sound thread", strerror(errno));
360                         return(1);
361                 }
362                 close(sound_pipe[sound_pid == 0]); sound_pipe[sound_pid == 0] = -1;
363                 if(sound_pid == 0) _exit(sound_thread());
364                 signal(SIGPIPE, SIG_IGN); /* dont crash if sound process dies */
365         }
366 #endif /* defined(PLATFORM_LINUX) || defined(PLATFORM_BSD) */
367
368         return(0);
369 }
370
371 void close_all(void)
372 {
373         int i;
374
375         if(sound_pid != -1) {
376                 kill(sound_pid, SIGTERM);
377                 waitpid(sound_pid, 0, 0);
378         }
379         if(sound_pipe[0] != -1) close(sound_pipe[0]);
380         if(sound_pipe[1] != -1) close(sound_pipe[1]);
381         for(i = 0; i < SAMPLE_MAX; i++) if(sound_data[i]) free(sound_data[i]);
382
383         for(i = 0; i < 4; i++) if(xpmPixmaps[i]) XFreePixmap(display, xpmPixmaps[i]);
384         for(i = 0; i < 4; i++) if(xpmBitmaps[i]) XFreePixmap(display, xpmBitmaps[i]);
385         for(i = 0; i < 4; i++) if(xpmGot[i]) {
386                 xpmFreeColoursFunc(display, xpmAttributes[i].colormap, xpmAttributes[i].alloc_pixels, xpmAttributes[i].nalloc_pixels, 0);
387                 XpmFreeAttributes(&xpmAttributes[i]);
388         }
389         if(gotRed) xpmFreeColoursFunc(display, privateColourmap ? privateColourmap : defaultColourmap, &redColour.pixel, 1, 0);
390         if(gotWhite) xpmFreeColoursFunc(display, privateColourmap ? privateColourmap : defaultColourmap, &whiteColour.pixel, 1, 0);
391         if(screenGC) XFreeGC(display, screenGC);
392         if(scoreGC) XFreeGC(display, scoreGC);
393         if(spriteGC) XFreeGC(display, spriteGC);
394         if(antsGC) XFreeGC(display, antsGC);
395         if(screenPixmap) XFreePixmap(display, screenPixmap);
396         if(scorePixmap) XFreePixmap(display, scorePixmap);
397         if(spriteBitmap) XFreePixmap(display, spriteBitmap);
398         if(xwindow) XDestroyWindow(display, xwindow);
399         if(cursor) XFreeCursor(display, cursor);
400         if(privateColourmap) XFreeColormap(display, privateColourmap);
401         if(privateColours) free(privateColours);
402         if(privateFlags) free(privateFlags);
403         if(display) XCloseDisplay(display);
404 }
405
406 /* ---------------------------------------------------------------------- */
407
408 void sound_play(void)
409 {
410         if(sound_pipe[1] != -1) {
411                 if(write(sound_pipe[1], &play, sizeof(play)) == -1) {
412                         fprintf(stderr, "%s: %s: %s\n", progname, "write sound", strerror(errno));
413                         if(sound_pipe[0] != -1) { close(sound_pipe[0]); sound_pipe[0] = -1; }
414                         if(sound_pipe[1] != -1) { close(sound_pipe[1]); sound_pipe[1] = -1; }
415                 }
416         }
417         memset(play, 0, sizeof(play));
418 }
419
420 /* ---------------------------------------------------------------------- */
421
422 static int xpmAllocColourFunc(Display *display, Colormap colourmap, char *colourname, XColor *xcolour, void *closure)
423 {
424         int i, match;
425         int r,g,b;
426         long best, sum;
427
428         if(colourname) if(XParseColor(display, colourmap, colourname, xcolour) == 0) return(-1); /* invalid name */
429         if(colourmap != privateColourmap) return(XAllocColor(display, colourmap, xcolour) != 0);
430
431 /* first try to find an exact match */
432         match = -1;
433         for(i = 0; i < privateNumColours; i++) {
434                 if(privateColours[i].red == xcolour->red && privateColours[i].green == xcolour->green && privateColours[i].blue == xcolour->blue) match = i;
435         }
436         if(match != -1) {
437                 privateFlags[match] = 1;
438                 xcolour->pixel = privateColours[match].pixel;
439                 return(1);
440         }
441
442 /* then find an unallocated colour that is close to what we want */
443         match = -1;
444         best = 1000000;
445         for(i = 0; i < privateNumColours; i++) {
446                 if(privateFlags[i]) continue; /* skip if it is already allocated */
447                 r = (privateColours[i].red - xcolour->red) / 256;
448                 g = (privateColours[i].green - xcolour->green) / 256;
449                 b = (privateColours[i].blue - xcolour->blue) / 256;
450                 sum = r * r + g * g + b * b;
451                 if(sum < best) {
452                         best = sum;
453                         match = i;
454                 }
455         }
456         if(match != -1) {
457                 privateFlags[match] = 1;
458                 privateColours[match].red = xcolour->red;
459                 privateColours[match].green = xcolour->green;
460                 privateColours[match].blue = xcolour->blue;
461                 XStoreColor(display, colourmap, &privateColours[match]);
462                 xcolour->pixel = privateColours[match].pixel;
463                 return(1); /* found a close match */
464         }
465
466 /* if all else fails, just find the closest colour and return it */
467         match = -1;
468         best = 1000000;
469         for(i = 0; i < privateNumColours; i++) {
470                 r = (privateColours[i].red - xcolour->red) / 256;
471                 g = (privateColours[i].green - xcolour->green) / 256;
472                 b = (privateColours[i].blue - xcolour->blue) / 256;
473                 sum = r * r + g * g + b * b;
474                 if(sum < best) {
475                         best = sum;
476                         match = i;
477                 }
478         }
479         if(match != -1) {
480                 xcolour->red = privateColours[match].red;
481                 xcolour->green = privateColours[match].green;
482                 xcolour->blue = privateColours[match].blue;
483                 xcolour->pixel = privateColours[match].pixel;
484                 return(1); /* best we could do */
485         }
486         return(0); /* still didnt find one, give up */
487 }
488
489 static int xpmFreeColoursFunc(Display *display, Colormap colourmap, unsigned long *pixels, int npixels, void *closure)
490 {
491         if(colourmap != privateColourmap) XFreeColors(display, colourmap, pixels, npixels, 0);
492         return(1); /* non-zero for success */
493 }