1 /*
2  * video_x.c
3  *
4  * display screen management
5  */
6 
7 /* $Id: video_x.c,v 1.26 2000/09/16 16:01:59 nyef Exp $ */
8 
9 #include <X11/Xlib.h>
10 #include <X11/keysym.h>
11 #include <X11/Intrinsic.h>
12 #include <X11/StringDefs.h>
13 #include <X11/extensions/XShm.h>
14 #include <sys/shm.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include "video.h"
18 #include "tool.h"
19 #include "ui.h"
20 
21 /* the joypad (only one, I could care less about more) */
22 struct joypad *ui_joypad;
23 
24 /* the keypad (only one, I could care less about more) */
25 struct keypad *ui_keypad;
26 
27 int video_active = 0;
28 int deblevel = 0;
29 
30 Display *display;
31 Window nes_window;
32 GC nes_gc;
33 Colormap nes_cmap;
34 Screen *nes_screen;
35 
36 int buffer_x;
37 int buffer_y;
38 
39 struct image_buffer {
40     int is_shm;
41     char *data;
42     XImage *image;
43 } image_buffers[2], *cur_image_buffer;
44 
45 typedef void ((*xlatfunc)(void *, int));
46 
47 xlatfunc vidxlate;
48 void vidxlate16 (char *dest, int size);
49 typedef void (*video_setpal_t)(int, int *r, int *g, int *b);
50 video_setpal_t video_setpal_true;
51 void init_xlate8(int colors, int *red, int *green, int *blue);
52 void init_xlate16(int colors, int *red, int *green, int *blue);
53 void init_xlate32(int colors, int *red, int *green, int *blue);
54 unsigned char *vid_pre_xlat;
55 
56 /* FIXME: ui_x.c interface -- move to separate header. */
57 extern Widget drawbox;
58 extern Widget manager;
59 void ui_set_drawbox_size(int width, int height);
60 
61 void do_key_event(Widget, XtPointer, XEvent *, Boolean *);
62 
63 /*
64  * Thanks to Sam Lantinga of SDL and the GDK team for XShm references.
65  */
66 int use_shm = 0;
67 int shm_error = 0;
68 int (*old_x_handler)(Display *, XErrorEvent *);
69 int alloc_shm_surface(struct image_buffer *, int, int, int, int);
70 int shm_error_handler(Display*, XErrorEvent *);
71 
video_init()72 void video_init()
73 {
74     int is_local_display;
75 
76     display = XtDisplay(drawbox);
77     nes_screen = XtScreen(drawbox);
78     nes_window = XtWindow(drawbox);
79 
80     ui_set_drawbox_size(256, 240);
81 
82     nes_gc = DefaultGCOfScreen(nes_screen);
83     XtVaGetValues(drawbox, XtNcolormap, &nes_cmap, NULL);
84 
85     XtAddEventHandler(manager, KeyPressMask | KeyReleaseMask, False, do_key_event, NULL);
86 
87     XFlush(display);
88 
89     if (0 == strncmp(XDisplayName(NULL), ":", 1)) {
90 	is_local_display = True;
91     } else {
92 	is_local_display = False;
93     }
94 
95     if (is_local_display) {
96 	use_shm = XShmQueryExtension(display);
97     } else {
98 	use_shm = 0;
99     }
100 
101 #ifdef FORCE_NOXSHM
102     use_shm = 0;
103 #endif
104 
105     if (DefaultDepthOfScreen(nes_screen) == 8) {
106 	video_setpal_true = init_xlate8;
107     } else if (DefaultDepthOfScreen(nes_screen) == 16) {
108 	video_setpal_true = init_xlate16;
109     } else if (DefaultDepthOfScreen(nes_screen) == 24) {
110 	video_setpal_true = init_xlate32;
111     } else {
112 	fprintf(stderr, "display depth %d unsupported.\n", DefaultDepthOfScreen(nes_screen));
113 	exit(1);
114     }
115 }
116 
video_shutdown(void)117 void video_shutdown(void)
118 {
119 }
120 
video_allocate_image_buffer(struct image_buffer * buffer,int bit_depth,int bytes_per_pixel,int x,int y)121 void video_allocate_image_buffer(struct image_buffer *buffer, int bit_depth, int bytes_per_pixel, int x, int y)
122 {
123     if (use_shm) {
124 	if (alloc_shm_surface(buffer, bit_depth, bytes_per_pixel, x, y)) {
125 	    return;
126 	} else {
127 	    deb_printf("shm failure, falling back.\n");
128 	    use_shm = 0;
129 	}
130     }
131 
132     buffer->data = malloc(x * y * bytes_per_pixel);
133     buffer->image = XCreateImage(display, DefaultVisualOfScreen(nes_screen),
134 				 bit_depth, ZPixmap, 0, buffer->data, x, y, 8, 0);
135     buffer->is_shm = 0;
136 }
137 
video_setsize(int x,int y)138 void video_setsize(int x, int y)
139 {
140     buffer_x = x;
141     buffer_y = y;
142 
143     ui_set_drawbox_size(x, y);
144 
145     if (DefaultDepthOfScreen(nes_screen) == 8) {
146 	video_allocate_image_buffer(&image_buffers[0], 8, 1, x, y);
147 	video_allocate_image_buffer(&image_buffers[1], 8, 1, x, y);
148     } else if (DefaultDepthOfScreen(nes_screen) == 16) {
149 	video_allocate_image_buffer(&image_buffers[0], 16, 2, x, y);
150 	video_allocate_image_buffer(&image_buffers[1], 16, 2, x, y);
151     } else if (DefaultDepthOfScreen(nes_screen) == 24) {
152 	video_allocate_image_buffer(&image_buffers[0], 24, 4, x, y);
153 	video_allocate_image_buffer(&image_buffers[1], 24, 4, x, y);
154     }
155 
156     cur_image_buffer = &image_buffers[0];
157 }
158 
shm_error_handler(Display * d,XErrorEvent * e)159 int shm_error_handler(Display* d, XErrorEvent* e)
160 {
161     if (BadAccess == e->error_code) {
162 	shm_error = 1;
163 	return 0;
164     }
165 
166     return old_x_handler(d, e);
167 }
168 
alloc_shm_surface(struct image_buffer * buffer,int depth,int bytes_per_pixel,int width,int height)169 int alloc_shm_surface(struct image_buffer *buffer, int depth, int bytes_per_pixel, int width, int height)
170 {
171     XShmSegmentInfo *shm_info;
172     Screen *screen = XtScreen(drawbox);
173 
174     shm_info = calloc(1, sizeof(XShmSegmentInfo));
175 
176     shm_info->shmid = shmget(IPC_PRIVATE, width * height * bytes_per_pixel,
177 			     IPC_CREAT | 0777);
178 
179     if (shm_info->shmid < 0) {
180 	perror("darcnes: shmget()");
181 	use_shm = 0;
182 	return 0;
183     }
184 
185     shm_info->shmaddr = (char *)shmat(shm_info->shmid, 0, 0);
186     shm_info->readOnly = False;
187 
188     if ((char *) -1 == shm_info->shmaddr) {
189 	perror("darcnes: shmat()");
190 
191 	/* destroy the SHM block now, before we forget */
192 	if (shmctl(shm_info->shmid, IPC_RMID, NULL) < 0) {
193 	    perror("darcnes: shmctl(RMID)");
194 	    /* No, this isn't fatal enough to cause us to quit */
195 	}
196 
197 	use_shm = 0;
198 	return 0;
199     }
200 
201     shm_error = 0;
202     /*
203      * We set this to grab anything bad that happens during the
204      * attach and (especially) sync calls.
205      */
206     old_x_handler = XSetErrorHandler(shm_error_handler);
207     XShmAttach(display, shm_info);
208     XSync(display, False);
209     /* And then reset the old handler here. */
210     XSetErrorHandler(old_x_handler);
211 
212     /* Automatically destroy surface on detach. */
213     if (shmctl(shm_info->shmid, IPC_RMID, NULL) < 0) {
214 	perror("darcnes: shmctl(RMID)");
215 	/* No, this isn't fatal enough to cause us to quit */
216     }
217 
218     if (shm_error) {
219 	fprintf(stderr, "darcnes: error during SHM attach.\n");
220 	shmdt(shm_info->shmaddr);
221 	use_shm = 0;
222 	return 0;
223     }
224 
225     buffer->data = shm_info->shmaddr;
226     buffer->image = XShmCreateImage(display, DefaultVisualOfScreen(screen), depth,
227 				    ZPixmap, shm_info->shmaddr, shm_info, width, height);
228     buffer->is_shm = 1;
229 
230     return 1;
231 }
232 
video_display_buffer(void)233 void video_display_buffer(void)
234 {
235     if (vidxlate) {
236 	vidxlate(cur_image_buffer->data, buffer_x * buffer_y);
237     }
238 
239     if (cur_image_buffer->is_shm) {
240 	XShmPutImage(display, nes_window, nes_gc, cur_image_buffer->image,
241 		     0, 0, 0, 0, buffer_x, buffer_y, False);
242     } else {
243 	XPutImage(display, nes_window, nes_gc, cur_image_buffer->image, 0, 0, 0, 0, buffer_x, buffer_y);
244     }
245 
246     XFlush(display);
247 
248     if (cur_image_buffer == &image_buffers[0]) {
249 	cur_image_buffer = &image_buffers[1];
250     } else {
251 	cur_image_buffer = &image_buffers[0];
252     }
253 }
254 
255 /* joypad emulation */
256 
ui_register_joypad(struct joypad * pad)257 int ui_register_joypad(struct joypad *pad)
258 {
259     if (ui_joypad) {
260 	return 0;
261     }
262 
263     ui_joypad = pad;
264     return 1;
265 }
266 
ui_update_joypad(struct joypad * pad)267 void ui_update_joypad(struct joypad *pad)
268 {
269     /* NOTE: does nothing. may want to do stuff when using real gamepads */
270 }
271 
video_events(void)272 void video_events(void)
273 {
274 }
275 
276 char joypad_key_state[12];
277 
278 struct {
279     KeySym keysym;
280     int state;
281 } joypad_keys[] = {
282     {XK_s, 8}, {XK_S, 8},
283     {XK_a, 9}, {XK_A, 9},
284     {XK_bracketleft, 10}, {XK_braceleft, 10},
285     {XK_bracketright, 11}, {XK_braceright, 11},
286     {XK_Up, 0}, {XK_KP_Up, 4},
287     {XK_Down, 1}, {XK_KP_Down, 5},
288     {XK_Left, 2}, {XK_KP_Left, 6},
289     {XK_Right, 3}, {XK_KP_Right, 7},
290 };
291 
handle_joypad_key(int type,KeySym keysym)292 void handle_joypad_key(int type, KeySym keysym)
293 {
294     int i;
295 
296     for (i = (sizeof(joypad_keys) / sizeof(joypad_keys[0])) - 1; i >= 0; i--) {
297 	if (keysym == joypad_keys[i].keysym) {
298 	    if (type == KeyPress) {
299 		joypad_key_state[joypad_keys[i].state] = 1;
300 	    } else {
301 		joypad_key_state[joypad_keys[i].state] = 0;
302 	    }
303 	}
304     }
305 
306     if (!ui_joypad) {
307 	return;
308     }
309 
310     switch (ui_joypad->button_template->num_buttons) {
311 	/* NOTE: all cases fall through */
312     case 8:
313 	if (joypad_key_state[11]) {
314 	    ui_joypad->data |= ui_joypad->button_template->buttons[7];
315 	} else {
316 	    ui_joypad->data &= ~ui_joypad->button_template->buttons[7];
317 	}
318 
319     case 7:
320 	if (joypad_key_state[10]) {
321 	    ui_joypad->data |= ui_joypad->button_template->buttons[6];
322 	} else {
323 	    ui_joypad->data &= ~ui_joypad->button_template->buttons[6];
324 	}
325 
326     case 6:
327 	if (joypad_key_state[9]) {
328 	    ui_joypad->data |= ui_joypad->button_template->buttons[5];
329 	} else {
330 	    ui_joypad->data &= ~ui_joypad->button_template->buttons[5];
331 	}
332 
333     case 5:
334 	if (joypad_key_state[8]) {
335 	    ui_joypad->data |= ui_joypad->button_template->buttons[4];
336 	} else {
337 	    ui_joypad->data &= ~ui_joypad->button_template->buttons[4];
338 	}
339 
340     case 4:
341 	if (joypad_key_state[7] || joypad_key_state[3]) {
342 	    ui_joypad->data |= ui_joypad->button_template->buttons[3];
343 	} else {
344 	    ui_joypad->data &= ~ui_joypad->button_template->buttons[3];
345 	}
346 
347     case 3:
348 	if (joypad_key_state[6] || joypad_key_state[2]) {
349 	    ui_joypad->data |= ui_joypad->button_template->buttons[2];
350 	} else {
351 	    ui_joypad->data &= ~ui_joypad->button_template->buttons[2];
352 	}
353 
354     case 2:
355 	if (joypad_key_state[5] || joypad_key_state[1]) {
356 	    ui_joypad->data |= ui_joypad->button_template->buttons[1];
357 	} else {
358 	    ui_joypad->data &= ~ui_joypad->button_template->buttons[1];
359 	}
360 
361     case 1:
362 	if (joypad_key_state[4] || joypad_key_state[0]) {
363 	    ui_joypad->data |= ui_joypad->button_template->buttons[0];
364 	} else {
365 	    ui_joypad->data &= ~ui_joypad->button_template->buttons[0];
366 	}
367 
368     default: break;
369     }
370 }
371 
372 /* keypad emulation */
373 
keypad_register(struct keypad * pad)374 int keypad_register(struct keypad *pad)
375 {
376     if (ui_keypad) {
377 	return 0;
378     }
379 
380     ui_keypad = pad;
381     return 1;
382 }
383 
keypad_update(struct keypad * pad)384 void keypad_update(struct keypad *pad)
385 {
386     /* NOTE: does nothing. may want to do stuff when using real keypads */
387 }
388 
389 u16 keypad_data;
390 
391 struct {
392     KeySym keysym;
393     u16 state;
394 } keypad_keys[12] = {
395     {XK_KP_0, KPD_0},
396     {XK_KP_1, KPD_1},
397     {XK_KP_2, KPD_2},
398     {XK_KP_3, KPD_3},
399     {XK_KP_4, KPD_4},
400     {XK_KP_5, KPD_5},
401     {XK_KP_6, KPD_6},
402     {XK_KP_7, KPD_7},
403     {XK_KP_8, KPD_8},
404     {XK_KP_9, KPD_9},
405     {XK_KP_Multiply, KPD_STAR},
406     {XK_KP_Add, KPD_HASH},
407 };
408 
handle_keypad_key(int type,KeySym keysym)409 void handle_keypad_key(int type, KeySym keysym)
410 {
411     int i;
412 
413     for (i = (sizeof(keypad_keys) / sizeof(keypad_keys[0])) - 1; i >= 0; i--) {
414 	if (keysym == keypad_keys[i].keysym) {
415 	    if (type == KeyPress) {
416 		keypad_data |= keypad_keys[i].state;
417 	    } else {
418 		keypad_data &= ~keypad_keys[i].state;
419 	    }
420 	}
421     }
422 
423     if (!ui_keypad) {
424 	return;
425     }
426 
427     ui_keypad->data = keypad_data;
428 }
429 
430 /* keyboard emulation */
431 
432 typedef void (*keyhook)(Display *display, XEvent *event);
433 
434 keyhook ui_keyhook;
435 
ui_set_keyboard_hook(keyhook hook)436 void ui_set_keyboard_hook(keyhook hook)
437 {
438     ui_keyhook = hook;
439 }
440 
441 /* key event handler */
442 
do_key_event(Widget w,XtPointer client_data,XEvent * event,Boolean * continue_dispatch)443 void do_key_event(Widget w, XtPointer client_data, XEvent *event, Boolean *continue_dispatch)
444 {
445     KeySym keysym;
446     Modifiers dummy;
447 
448     XtTranslateKeycode(display, event->xkey.keycode, event->xkey.state, &dummy, &keysym);
449 
450     if (ui_keyhook) {
451 	ui_keyhook(display, event);
452     }
453 
454     handle_joypad_key(event->type, keysym);
455     handle_keypad_key(event->type, keysym);
456 }
457 
458 short *xlatepal;
459 
vidxlate16(char * dest,int size)460 void vidxlate16(char *dest, int size)
461 {
462     int i;
463 
464     for (i = size; i > 0; i--) {
465 	*(((short *)dest)+(i-1)) = xlatepal[(int) *(dest+(i-1))];
466     }
467 }
468 
469 long *xlatepal2;
470 
vidxlate32(char * dest,int size)471 void vidxlate32(char *dest, int size)
472 {
473     int i;
474 
475     for (i = size; i > 0; i--) {
476 	*(((long *)dest)+(i-1)) = xlatepal2[(int) *(dest+(i-1))];
477     }
478 }
479 
video_get_vbp(int line)480 unsigned char *video_get_vbp(int line)
481 {
482     return cur_image_buffer->data + (line * buffer_x);
483 }
484 
485 /* #include "nespal.c" */
486 
init_xlate8(int colors,int * red,int * green,int * blue)487 void init_xlate8(int colors, int *red, int *green, int *blue)
488 {
489     int i;
490     XColor color;
491 
492     vid_pre_xlat = malloc(colors);
493     vidxlate = NULL;
494 
495     for (i = 0; i < colors; i++) {
496 	color.flags = DoRed | DoGreen | DoBlue;
497 	color.red = red[i];
498 	color.green = green[i];
499 	color.blue = blue[i];
500 	color.red += color.red << 8;
501 	color.green += color.green << 8;
502 	color.blue += color.blue << 8;
503 	if (XAllocColor(display, nes_cmap, &color)) {
504 	    vid_pre_xlat[i] = color.pixel;
505 	}
506     }
507 }
508 
init_xlate16(int colors,int * red,int * green,int * blue)509 void init_xlate16(int colors, int *red, int *green, int *blue)
510 {
511     int i;
512     XColor color;
513 
514     xlatepal = malloc(colors << 1);
515     vid_pre_xlat = malloc(colors);
516     vidxlate = (xlatfunc)&vidxlate16;
517 
518     for (i = 0; i < colors; i++) {
519 	vid_pre_xlat[i] = i;
520 	color.flags = DoRed | DoGreen | DoBlue;
521 	color.red = red[i];
522 	color.green = green[i];
523 	color.blue = blue[i];
524 	color.red += color.red << 8;
525 	color.green += color.green << 8;
526 	color.blue += color.blue << 8;
527 	if (XAllocColor(display, nes_cmap, &color)) {
528 	    xlatepal[i] = color.pixel;
529 	}
530     }
531 }
532 
init_xlate32(int colors,int * red,int * green,int * blue)533 void init_xlate32(int colors, int *red, int *green, int *blue)
534 {
535     int i;
536     XColor color;
537 
538     xlatepal2 = malloc(colors << 2);
539     vid_pre_xlat = malloc(colors);
540     vidxlate = (xlatfunc)&vidxlate32;
541 
542     for (i = 0; i < colors; i++) {
543 	vid_pre_xlat[i] = i;
544 	color.flags = DoRed | DoGreen | DoBlue;
545 	color.red = red[i];
546 	color.green = green[i];
547 	color.blue = blue[i];
548 	color.red += color.red << 8;
549 	color.green += color.green << 8;
550 	color.blue += color.blue << 8;
551 	if (XAllocColor(display, nes_cmap, &color)) {
552 	    xlatepal2[i] = color.pixel;
553 	}
554     }
555 }
556 
video_setpal(int num_colors,int * red,int * green,int * blue)557 void video_setpal(int num_colors, int *red, int *green, int *blue)
558 {
559     video_setpal_true(num_colors, red, green, blue);
560 }
561 
562 /*
563  * $Log: video_x.c,v $
564  * Revision 1.26  2000/09/16 16:01:59  nyef
565  * removed some (unused) errno stuff
566  *
567  * Revision 1.25  2000/05/15 01:22:50  nyef
568  * changed to work with new video interface
569  *
570  * Revision 1.24  2000/05/07 00:25:38  nyef
571  * fixed to work with new joypad interface names
572  *
573  * Revision 1.23  2000/03/12 21:43:02  nyef
574  * disabled F1 (debugger) and ESC (quit) keys (they didn't work)
575  *
576  * Revision 1.22  2000/03/06 00:39:33  nyef
577  * added keyboard emulation hook
578  *
579  * Revision 1.21  2000/02/20 22:00:32  nyef
580  * added 24/32 bpp support
581  *
582  * Revision 1.20  2000/02/20 17:12:50  nyef
583  * cleaned up the SHM support a bit
584  *
585  * Revision 1.19  2000/02/20 07:34:50  nyef
586  * cleaned up some of the image buffer manipulation code
587  *
588  * Revision 1.18  2000/01/01 03:22:38  nyef
589  * added preliminary keypad implementation
590  *
591  * Revision 1.17  1999/12/31 16:50:30  nyef
592  * changed how the joypad was implemented, especially wrt X keyboard events
593  *
594  * Revision 1.16  1999/06/05 02:41:39  nyef
595  * converted to use new joypad interface
596  *
597  * Revision 1.15  1999/02/15 03:33:54  nyef
598  * added patch from Michael Vance for XShm
599  *
600  * Revision 1.14  1999/02/13 22:23:51  nyef
601  * added support for the keypad arrow keys
602  *
603  * Revision 1.13  1999/02/07 17:55:51  nyef
604  * removed double-buffering code
605  * added video_setsize() interface
606  *
607  * Revision 1.12  1999/01/05 04:28:41  nyef
608  * hacked up an interface to allow setting the palette.
609  *
610  * Revision 1.11  1998/08/29 22:09:37  nyef
611  * switched to a double-buffered video system. added routines to support
612  * the PPU writing directly into the video buffers. eliminated the 8-bit
613  * vidxlate routine. changed vidxlate16 to operate using only one buffer.
614  * changed the video buffer size to a symbolic constant.
615  *
616  * Revision 1.10  1998/08/29 14:52:29  nyef
617  * added an identity vid_pre_xlat table for use in 16-bitplane modes.
618  *
619  * Revision 1.9  1998/08/28 03:02:16  nyef
620  * added a "vid_pre_xlat" table so that some of the color translation
621  * could be done by the PPU. also moved the 8-bit color translation to
622  * the PPU and set the 8-bit xlatfinc to be memcpy. (maybe I should
623  * double buffer the display?)
624  *
625  * Revision 1.8  1998/08/15 15:45:34  nyef
626  * reworked vidxlate16 to use a normal loop and to copy longs.
627  * this should be slightly faster on a pentium.
628  *
629  * Revision 1.7  1998/08/15 01:08:15  nyef
630  * changed unsupported display depth message to indicate current depth.
631  *
632  * Revision 1.6  1998/08/02 02:03:08  nyef
633  * preliminary GUIfication completed.
634  *
635  * Revision 1.5  1998/08/01 23:12:45  nyef
636  * removed the checks for the symbol "GRAPHICS".
637  * removed two commented-out printf statements in the input handler.
638  *
639  * Revision 1.4  1998/08/01 00:57:13  nyef
640  * moved old palette values into nespal.c
641  *
642  * Revision 1.3  1998/07/26 13:22:55  nyef
643  * changed vidxlate16 tu use pointer notation and duff's device.
644  *
645  * Revision 1.2  1998/07/18 18:54:27  nyef
646  * changed vidxlate16 to copy shorts instead of chars for a massive speed increase
647  *   in 16 bitplane mode.
648  *
649  * Revision 1.1  1998/07/11 22:19:25  nyef
650  * Initial revision
651  *
652  */
653