/* Sarien - A Sierra AGI resource interpreter engine * Copyright (C) 1999-2001 Stuart George and Claudio Matsuoka * * $Id: ph.c,v 1.13 2001/09/22 22:18:23 jpenner Exp $ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; see docs/COPYING for further details. * * Photon graphics module by Jeremy Penner, jeremy@astra.mb.ca */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sarien.h" #include "graphics.h" #include "keyboard.h" #include "agi.h" typedef struct { PhImage_t *behind_img; PhPoint_t pos; struct mouse sarien_mouse; } ph_mouse_t; #define PH_MOUSE_CURSOR_W 10 #define PH_MOUSE_CURSOR_H 16 #define PH_MOUSE_TRANS_COL 2 extern struct sarien_options opt; extern struct gfx_driver *gfx; static int scale = 1; static int key_control = 0; static int key_alt = 0; static PgColor_t ph_pal[32]; static int __argc; static char **__argv; static PgDisplaySettings_t oldvidmode; static PdDirectContext_t *dc; static ph_mouse_t ph_mouse; #define KEY_QUEUE_SIZE 16 static int key_queue[KEY_QUEUE_SIZE]; static int key_queue_start = 0; static int key_queue_end = 0; #define key_enqueue(k) do { key_queue[key_queue_end++] = (k); \ key_queue_end %= KEY_QUEUE_SIZE; } while (0) #define key_dequeue(k) do { (k) = key_queue[key_queue_start++]; \ key_queue_start %= KEY_QUEUE_SIZE; } while (0) static int init_vidmode (void); static int deinit_vidmode (void); static void ph_put_block (int, int, int, int); static void ph_put_pixels (int, int, int, UINT8*); static int ph_keypress (void); static int ph_get_key (void); static void ph_new_timer (void); static void ph_update_mouse (void); void * photon_thread (void *); static void ph_raw_draw_cb (PtWidget_t *, PhTile_t *); static int ph_keypress_cb (PtWidget_t *, void *, PtCallbackInfo_t *); static int ph_cause_damage (void *, int, void *, size_t); static int ph_mouse_cb (PtWidget_t *widget, void *data, PtCallbackInfo_t *cb); static int ph_close_cb (PtWidget_t *widget, void *data, PtCallbackInfo_t *cb); static void ph_setup_mouse_image (void); static void ph_show_mouse_image (void); static void ph_hide_mouse_image (void); static pthread_t ph_tid; static PhImage_t *phimage; static pthread_barrier_t barrier; static pthread_mutex_t mut_image = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t mut_mouse = PTHREAD_MUTEX_INITIALIZER; static PtWidget_t *rawwidget; static int ph_coid; static int ph_chid; static char ph_cursor_img_data[] = { 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0,15, 0, 2, 2, 2, 2, 2, 2, 2, 0,15,15, 0, 2, 2, 2, 2, 2, 2, 0,15,15,15, 0, 2, 2, 2, 2, 2, 0,15,15,15,15, 0, 2, 2, 2, 2, 0,15,15,15,15,15, 0, 2, 2, 2, 0,15,15,15,15,15,15, 0, 2, 2, 0,15,15,15,15,15,15,15, 0, 2, 0,15,15,15,15,15,15,15,15, 0, 0,15,15,15,15,15, 0, 0, 0, 0, 0,15,15, 0,15,15, 0, 2, 2, 2, 0,15, 0, 2, 0,15,15, 0, 2, 2, 0, 0, 2, 2, 0,15,15, 0, 2, 2, 0, 2, 2, 2, 2, 0,15,15, 0, 2, 2, 2, 2, 2, 2, 0,15,15, 0, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2 }; static struct gfx_driver GFX_ph = { init_vidmode, deinit_vidmode, ph_put_block, ph_put_pixels, ph_new_timer, ph_keypress, ph_get_key }; int init_machine (int argc, char **argv) { gfx = &GFX_ph; __argc = argc; __argv = argv; scale = opt.scale; return err_OK; } int deinit_machine (void) { return err_OK; } static int init_vidmode (void) { int i; fprintf (stderr, "ph: Photon support by jeremy@astra.mb.ca\n"); for (i = 0; i < 32; i ++) { ph_pal[i] = (palette[i * 3] << 18) + (palette[i * 3 + 1] << 10) + (palette[i * 3 + 2] << 2); } pthread_barrier_init(&barrier, NULL, 2); pthread_create (&ph_tid, NULL, photon_thread, (void*)getpid()); pthread_barrier_wait (&barrier); /* Wait for thread to create a channel to send to */ ph_coid = ConnectAttach (0, 0, ph_chid, _NTO_SIDE_CHANNEL, 0); return err_OK; } static int deinit_vidmode () { fprintf (stderr, "ph: deiniting video mode\n"); PtEnter(0); pthread_mutex_lock (&mut_image); PhReleaseImage(phimage); phimage = NULL; pthread_mutex_unlock (&mut_image); if (opt.fullscreen) { PdDirectStop (dc); PdReleaseDirectContext(dc); PgSetVideoMode (&oldvidmode); } PtLeave(0); return err_OK; } /* put a block onto the screen */ static void ph_put_block (int x1, int y1, int x2, int y2) { PhRect_t rect; if (x1 >= GFX_WIDTH) x1 = GFX_WIDTH - 1; if (y1 >= GFX_HEIGHT) y1 = GFX_HEIGHT - 1; if (x2 >= GFX_WIDTH) x2 = GFX_WIDTH - 1; if (y2 >= GFX_HEIGHT) y2 = GFX_HEIGHT - 1; if (scale > 1) { x1 *= scale; y1 *= scale; x2 = x2 * scale + scale - 1; y2 = y2 * scale + scale - 1; } rect.ul.x = x1; rect.ul.y = y1; rect.lr.x = x2; rect.lr.y = y2; MsgSend (ph_coid, &rect, sizeof (rect), NULL, 0); } /* put pixel routine */ static void ph_put_pixels (int x, int y, int w, UINT8 *p) { register int i, j; pthread_mutex_lock (&mut_image); if (!phimage) return; switch (scale) { case 1: while (w--) PiSetPixel (phimage, x++, y, *p++); break; case 2: x <<= 1; y <<= 1; while (w--) { int c = *p++; PiSetPixel (phimage, x, y, c); PiSetPixel (phimage, x++, y + 1, c); PiSetPixel (phimage, x, y, c); PiSetPixel (phimage, x++, y + 1, c); } break; default: x *= scale; y *= scale; while (w--) { int c = *p++; for (i = 0; i < scale; i++) { for (j = 0; j < scale; j++) PiSetPixel (phimage, x + i, y + j, c); } x += scale; } } pthread_mutex_unlock (&mut_image); } static int ph_keypress () { int retcode; pthread_sleepon_lock(); /* Watch those threads! This ain't atomic. */ retcode = key_queue_start != key_queue_end ? TRUE : FALSE; pthread_sleepon_unlock(); ph_update_mouse(); return (retcode); } static int ph_get_key (void) { int k; pthread_sleepon_lock (); while (key_queue_start == key_queue_end) /* block */ pthread_sleepon_wait(key_queue); key_dequeue (k); pthread_sleepon_unlock (); ph_update_mouse(); return k; } static void ph_new_timer () { struct timeval tv; struct timezone tz; static double msec = 0.0; double m; gettimeofday (&tv, &tz); m = 1000.0 * tv.tv_sec + tv.tv_usec / 1000.0; while (m - msec < 42) { usleep (5000); gettimeofday (&tv, &tz); m = 1000.0 * tv.tv_sec + tv.tv_usec / 1000.0; } msec = m; } static void ph_update_mouse(void) { pthread_mutex_lock (&mut_mouse); mouse = ph_mouse.sarien_mouse; pthread_mutex_unlock (&mut_mouse); } /* PHOTON THREAD */ void *photon_thread (void *pidarg) { PtArg_t args[8]; PtWidget_t *window; PhRect_t rect; PhDim_t dim; PhArea_t area; PhRid_t rid; PgVideoModes_t modelist; PgVideoModeInfo_t modeinfo; PgDisplaySettings_t modesettings; PhDrawContext_t *olddc; PtRawCallback_t keycb[] = {{ Ph_EV_KEY , ph_keypress_cb, NULL }, { Ph_EV_BUT_PRESS, ph_mouse_cb, NULL }, { Ph_EV_BUT_RELEASE, ph_mouse_cb, NULL }, { Ph_EV_PTR_MOTION_NOBUTTON, ph_mouse_cb, NULL }, { Ph_EV_PTR_MOTION_BUTTON, ph_mouse_cb, NULL }}; PtCallback_t closecb[] = { { ph_close_cb, NULL } }; int recpid = (int)pidarg; int index; char found; dim.w = GFX_WIDTH * scale; dim.h = GFX_HEIGHT * scale; PtSetArg(&args[0], Pt_ARG_WINDOW_TITLE, "Sarien for Photon", 0); PtSetArg(&args[1], Pt_ARG_WINDOW_RENDER_FLAGS, Pt_FALSE, Ph_WM_RENDER_RESIZE | Ph_WM_RENDER_MAX); PtSetArg(&args[2], Pt_ARG_DIM, &dim, sizeof (dim)); PtSetArg(&args[3], Pt_ARG_WINDOW_MANAGED_FLAGS, Pt_FALSE, Ph_WM_CLOSE); PtSetArg(&args[4], Pt_ARG_WINDOW_NOTIFY_FLAGS, Pt_TRUE, Ph_WM_CLOSE); PtSetArg(&args[5], Pt_CB_WINDOW, closecb, 1); window = PtAppInit( NULL, NULL, NULL, 6, args); if (!window) { fprintf (stderr, "ph: Unable to create main widget!"); exit (-1); } ph_chid = PhChannelAttach (0, -1, NULL); phimage = PhCreateImage(NULL, GFX_WIDTH * scale, GFX_HEIGHT * scale, Pg_IMAGE_PALETTE_BYTE, ph_pal, 32, 1); if (phimage == NULL) { fprintf (stderr, "ph: can't create image\n"); exit (-1); } PtAppAddInput (NULL, recpid, ph_cause_damage, NULL); area.size = dim; if (!opt.fullscreen) { area.pos.x = 0; area.pos.y = 0; } else { PhWindowQueryVisible (Ph_QUERY_INPUT_GROUP | Ph_QUERY_EXACT, 0, 0, &rect); area.pos.x = rect.ul.x; area.pos.y = rect.ul.y; } PtSetArg(&args[0], Pt_ARG_AREA, &area, 0); PtSetArg(&args[1], Pt_ARG_FLAGS, Pt_TRUE, Pt_GETS_FOCUS); if (!opt.fullscreen) { PtSetArg(&args[2], Pt_ARG_RAW_DRAW_F, ph_raw_draw_cb, 0); rawwidget = PtCreateWidget (PtRaw, window, 3, args); } else { PtSetArg(&args[2], Pt_ARG_REGION_INFRONT, Ph_DEV_RID, 0); PtSetArg(&args[3], Pt_ARG_REGION_OPAQUE, Pt_TRUE, Ph_EV_KEY | Ph_EV_BUT_PRESS | Ph_EV_BUT_RELEASE | Ph_EV_PTR_MOTION_NOBUTTON | Ph_EV_PTR_MOTION_BUTTON); PtSetArg(&args[4], Pt_ARG_REGION_FLAGS, Pt_TRUE, Ph_FOLLOW_IG_SIZE | Ph_FORCE_FRONT); PtSetArg(&args[5], Pt_ARG_REGION_FIELDS, Pt_TRUE, Ph_REGION_FLAGS | Ph_REGION_EV_OPAQUE | Ph_REGION_IN_FRONT); rawwidget = PtCreateWidget (PtRegion, Pt_NO_PARENT, 6, args); PtRealizeWidget (rawwidget); } PtAddEventHandlers (rawwidget, keycb, 5); if (opt.fullscreen) { ph_setup_mouse_image(); olddc = PhDCGetCurrent(); PdGetDevices(&rid, 1); PdSetTargetDevice (olddc, rid); PgGetVideoMode(&oldvidmode); PgGetVideoModeList (&modelist); found = 0; for (index = 0; index < modelist.num_modes; index ++) { PgGetVideoModeInfo(modelist.modes[index], &modeinfo); printf("%ux%ux%u\n", modeinfo.width, modeinfo.height, modeinfo.bits_per_pixel); #if 0 if (modeinfo.width == GFX_WIDTH * scale && modeinfo.height == GFX_HEIGHT * scale && modeinfo.bits_per_pixel == 8) #endif if (modeinfo.width == 640 && modeinfo.height == 480 && modeinfo.bits_per_pixel == 8) { modesettings.refresh = 0; modesettings.mode = modelist.modes[index]; found = 1; } } if (!found) { fprintf (stderr, "Unable to initialize %ux%ux8\n", GFX_WIDTH * scale, GFX_HEIGHT * scale); exit(0); } PgSetVideoMode(&modesettings); dc = PdCreateDirectContext(); PdDirectStart(dc); } pthread_barrier_wait (&barrier); /* sync with original thread */ PtRealizeWidget(window); PtMainLoop(); return (0); } static void ph_draw_image (PhRect_t *area) { PgSetPalette( ph_pal, 0, 0, 32, Pg_PALSET_SOFT, 0); pthread_mutex_lock (&mut_image); pthread_mutex_lock (&mut_mouse); if (!phimage) return; if (opt.fullscreen) { if (area->ul.x > ph_mouse.pos.x) area->ul.x = ph_mouse.pos.x; if (area->ul.y > ph_mouse.pos.y) area->ul.y = ph_mouse.pos.y; if (area->lr.x < ph_mouse.pos.x + PH_MOUSE_CURSOR_W) area->lr.x = ph_mouse.pos.x + PH_MOUSE_CURSOR_W; if (area->lr.y < ph_mouse.pos.y + PH_MOUSE_CURSOR_H) area->lr.y = ph_mouse.pos.y + PH_MOUSE_CURSOR_H; } ph_show_mouse_image(); PgDrawPhImageRectmx (&(area->ul), phimage, area, 0); PgFlush(); ph_hide_mouse_image(); pthread_mutex_unlock (&mut_mouse); pthread_mutex_unlock (&mut_image); } static void ph_raw_draw_cb (PtWidget_t *widget, PhTile_t *damage) { PhRect_t raw_canvas; PhRect_t draw_area; PtCalcCanvas (widget, &raw_canvas); PtClipAdd (widget, &raw_canvas); draw_area = damage->rect; if (draw_area.ul.x < raw_canvas.ul.x) draw_area.ul.x = raw_canvas.ul.x; if (draw_area.ul.y < raw_canvas.ul.y) draw_area.ul.y = raw_canvas.ul.y; if (draw_area.lr.x > raw_canvas.lr.x) draw_area.lr.x = raw_canvas.lr.x; if (draw_area.lr.x > raw_canvas.lr.x) draw_area.lr.x = raw_canvas.lr.x; ph_draw_image (&draw_area); PtClipRemove(); } static int ph_keypress_cb (PtWidget_t *widget, void *data, PtCallbackInfo_t *cb) { PhKeyEvent_t *key_event; int key = 0; key_event = PhGetData(cb->event); if (key_event->key_flags & Pk_KF_Key_Down) { if (key_control || key_alt) key = key_event->key_cap; else key = key_event->key_sym; switch (key) { case Pk_Shift_L: case Pk_Shift_R: key = 0; break; case Pk_Control_L: key_control |= 1; key = 0; break; case Pk_Control_R: key_control |= 2; key = 0; break; case Pk_Alt_L: key_alt |= 1; key = 0; break; case Pk_Alt_R: key_alt |= 2; key = 0; break; case Pk_Up: case Pk_KP_8: key = KEY_UP; break; case Pk_Left: case Pk_KP_4: key = KEY_LEFT; break; case Pk_Down: case Pk_KP_2: key = KEY_DOWN; break; case Pk_Right: case Pk_KP_6: key = KEY_RIGHT; break; case Pk_Home: case Pk_KP_7: key = KEY_UP_LEFT; break; case Pk_Pg_Up: case Pk_KP_9: key = KEY_UP_RIGHT; break; case Pk_Pg_Down: case Pk_KP_3: key = KEY_DOWN_RIGHT; break; case Pk_End: case Pk_KP_1: key = KEY_DOWN_LEFT; break; case Pk_KP_Enter: key = KEY_ENTER; break; case Pk_KP_Add: key = '+'; break; case Pk_KP_Subtract: key = '-'; break; case Pk_Tab: case Pk_KP_Tab: key = 0x0009; break; case Pk_F1: key = 0x3b00; break; case Pk_F2: key = 0x3c00; break; case Pk_F3: key = 0x3d00; break; case Pk_F4: key = 0x3e00; break; case Pk_F5: key = 0x3f00; break; case Pk_F6: key = 0x4000; break; case Pk_F7: key = 0x4100; break; case Pk_F8: key = 0x4200; break; case Pk_F9: key = 0x4300; break; case Pk_F10: key = 0x4400; break; case Pk_F11: key = KEY_STATUSLN; break; case Pk_F12: key = KEY_PRIORITY; break; case Pk_Return: key = 0x0d; break; case Pk_Escape: key = 0x1b; break; case Pk_BackSpace: key = KEY_BACKSPACE; break; default: if (!isalpha (key)) break; if (key_control) key = (key & ~0x20) - 0x40; else if (key_alt) key = scancode_table[(key & ~0x20) - 0x41] << 8; break; } } else { key = 0; switch (key_event->key_cap) { case Pk_Control_L: key_control &= ~1; break; case Pk_Control_R: key_control &= ~2; break; case Pk_Alt_L: key_alt &= ~1; break; case Pk_Alt_R: key_alt &= ~2; break; } } if (key) { pthread_sleepon_lock(); key_enqueue (key); pthread_sleepon_signal (key_queue); pthread_sleepon_unlock(); } return (Pt_CONSUME); } static int ph_mouse_cb (PtWidget_t *widget, void *data, PtCallbackInfo_t *cb) { PhPointerEvent_t *mouse_event; PhRect_t area; mouse_event = PhGetData(cb->event); /* Don't modify global variables from the Photon thread! */ pthread_mutex_lock (&mut_mouse); if (cb->event->type == Ph_EV_BUT_PRESS) { ph_mouse.sarien_mouse.button = TRUE; pthread_sleepon_lock(); key_enqueue ((mouse_event->buttons == Ph_BUTTON_SELECT) ? BUTTON_LEFT : BUTTON_RIGHT); pthread_sleepon_signal (key_queue); pthread_sleepon_unlock(); } /* If all buttons released */ if (cb->event->type == Ph_EV_BUT_RELEASE) ph_mouse.sarien_mouse.button = FALSE; ph_mouse.sarien_mouse.x = PhGetRects(cb->event)->ul.x / opt.scale; ph_mouse.sarien_mouse.y = PhGetRects(cb->event)->ul.y / opt.scale; #if 0 /* FIXME: Photon driver doesn't support this flag yet */ if (opt.fixratio) ph_mouse.sarien_mouse.y = ph_mouse.sarien_mouse.y * 5 / 6; #endif if (opt.fullscreen) { // Include old mouse position when updating screen. area.ul = ph_mouse.pos; area.lr.x = ph_mouse.pos.x + PH_MOUSE_CURSOR_W; area.lr.y = ph_mouse.pos.y + PH_MOUSE_CURSOR_H; ph_mouse.pos = PhGetRects(cb->event)->ul; pthread_mutex_unlock (&mut_mouse); ph_draw_image(&area); } else { pthread_mutex_unlock (&mut_mouse); } return (Pt_CONSUME); } static int ph_close_cb (PtWidget_t *widget, void *data, PtCallbackInfo_t *cb) { deinit_machine(); deinit_vidmode(); PtExit(0); return (Pt_CONTINUE); } static int ph_cause_damage (void *data, int rcvid, void *message, size_t mbsize) { PhRect_t *rect; rect = message; if (opt.fullscreen) ph_draw_image (rect); else PtDamageExtent (rawwidget, rect); MsgReply(rcvid, EOK, NULL, 0); return (Pt_CONTINUE); } static void ph_setup_mouse_image(void) { PhDim_t imgsize = { PH_MOUSE_CURSOR_W, PH_MOUSE_CURSOR_H }; ph_mouse.behind_img = PhCreateImage (NULL, imgsize.w, imgsize.h, Pg_IMAGE_PALETTE_BYTE, ph_pal, 32, 1); } void ph_show_mouse_image(void) { static PmMemoryContext_t *behindmc = NULL; int i, j, apos; PhDim_t imgsize = { PH_MOUSE_CURSOR_W, PH_MOUSE_CURSOR_H }; PhPoint_t pos = { 0, 0 }; PhRect_t area; if (!opt.fullscreen) return; if (behindmc == NULL) behindmc = PmMemCreateMC(ph_mouse.behind_img, &imgsize, &pos); area.ul = ph_mouse.pos; area.lr.x = ph_mouse.pos.x + PH_MOUSE_CURSOR_W; area.lr.y = ph_mouse.pos.y + PH_MOUSE_CURSOR_H; PmMemStart (behindmc); PgDrawPhImageRectmx (&pos, phimage, &area, 0); PmMemFlush (behindmc, ph_mouse.behind_img); PmMemStop (behindmc); for (i = 0; i < PH_MOUSE_CURSOR_W; i ++) { for (j = 0; j < PH_MOUSE_CURSOR_H; j ++) { apos = i + (j * PH_MOUSE_CURSOR_W); if (ph_cursor_img_data[apos] != PH_MOUSE_TRANS_COL) PiSetPixel (phimage, i + ph_mouse.pos.x, j + ph_mouse.pos.y, ph_cursor_img_data[apos]); } } } void ph_hide_mouse_image (void) { static PmMemoryContext_t *imgmc = NULL; PhPoint_t pos = { 0, 0 }; if (!opt.fullscreen) return; if (imgmc == NULL) imgmc = PmMemCreateMC (phimage, &(phimage->size), &pos); PmMemStart (imgmc); PgDrawPhImage (&(ph_mouse.pos), ph_mouse.behind_img, 0); PmMemFlush (imgmc, phimage); PmMemStop (imgmc); }