/* ScummVM - Scumm Interpreter * Copyright (C) 2001 Ludvig Strigeus * Copyright (C) 2001/2002 The ScummVM project * * 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; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * $Header$ * */ /* The bare pure X11 port done by Lionel 'BBrox' Ulmer */ #include "stdafx.h" #include "scumm.h" #include "mididrv.h" #include "gameDetector.h" #include #include #include #include #include #include #include #ifdef USE_XV_SCALING #include #include #endif #include #include #include #include #include #include #include class OSystem_X11 : public OSystem { public: // Set colors of the palette void set_palette(const byte *colors, uint start, uint num); // Set the size of the video bitmap. // Typically, 320x200 void init_size(uint w, uint h); // Draw a bitmap to screen. // The screen will not be updated to reflect the new bitmap void copy_rect(const byte *buf, int pitch, int x, int y, int w, int h); // Update the dirty areas of the screen void update_screen(); // Either show or hide the mouse cursor bool show_mouse(bool visible); // Set the position of the mouse cursor void set_mouse_pos(int x, int y); // Set the bitmap that's used when drawing the cursor. void set_mouse_cursor(const byte *buf, uint w, uint h, int hotspot_x, int hotspot_y); // Shaking is used in SCUMM. Set current shake position. void set_shake_pos(int shake_pos); // Get the number of milliseconds since the program was started. uint32 get_msecs(); // Delay for a specified amount of milliseconds void delay_msecs(uint msecs); // Create a thread void *create_thread(ThreadProc *proc, void *param); // Get the next event. // Returns true if an event was retrieved. bool poll_event(Event *event); // Set function that generates samples bool set_sound_proc(void *param, SoundProc *proc, byte sound); // Poll cdrom status // Returns true if cd audio is playing bool poll_cdrom(); // Play cdrom audio track void play_cdrom(int track, int num_loops, int start_frame, int end_frame); // Stop cdrom audio track void stop_cdrom(); // Update cdrom audio status void update_cdrom(); // Quit void quit(); // Set a parameter uint32 property(int param, Property *value); // Add a callback timer void set_timer(int timer, int (*callback)(int)); static OSystem *create(int gfx_mode, bool full_screen); private: OSystem_X11(); typedef struct { int x, y, w, h; } dirty_square; void create_empty_cursor(); void undraw_mouse(); void draw_mouse(); void update_screen_helper(const dirty_square * d, dirty_square * dout); unsigned char *local_fb; int window_width, window_height; int fb_width, fb_height; int scumm_x, scumm_y; #ifdef USE_XV_SCALING unsigned int *palette; #else unsigned short *palette; #endif bool _palette_changed; Display *display; int screen; Window window; GC black_gc; #ifdef USE_XV_SCALING XvImage *image; #else XImage *image; #endif pthread_t sound_thread; int fake_right_mouse; int report_presses; int current_shake_pos; int new_shake_pos; struct timeval start_time; enum { MAX_NUMBER_OF_DIRTY_SQUARES = 32, BAK_WIDTH = 40, BAK_HEIGHT = 40 }; dirty_square ds[MAX_NUMBER_OF_DIRTY_SQUARES]; int num_of_dirty_square; typedef struct { int x, y; int w, h; int hot_x, hot_y; } mouse_state; mouse_state old_state, cur_state; const byte *_ms_buf; byte _ms_backup[BAK_WIDTH * BAK_HEIGHT]; bool _mouse_drawn; bool _mouse_visible; unsigned int _timer_duration, _timer_next_expiry; bool _timer_active; int (*_timer_callback)(int); }; typedef struct { OSystem::SoundProc *sound_proc; void *param; byte format; } THREAD_PARAM; #undef CAPTURE_SOUND #define FRAG_SIZE 4096 static void *sound_and_music_thread(void *params) { /* Init sound */ int sound_fd, param, frag_size; unsigned char sound_buffer[FRAG_SIZE]; OSystem::SoundProc *sound_proc = ((THREAD_PARAM *) params)->sound_proc; void *proc_param = ((THREAD_PARAM *) params)->param; #ifdef CAPTURE_SOUND FILE *f = fopen("sound.raw", "wb"); #endif sound_fd = open("/dev/dsp", O_WRONLY); audio_buf_info info; if (sound_fd < 0) { error("Error opening sound device !\n"); exit(1); } param = 0; frag_size = FRAG_SIZE /* audio fragment size */ ; while (frag_size) { frag_size >>= 1; param++; } param--; param |= /* audio_fragment_num */ 3 << 16; if (ioctl(sound_fd, SNDCTL_DSP_SETFRAGMENT, ¶m) != 0) { error("Error in the SNDCTL_DSP_SETFRAGMENT ioctl !\n"); exit(1); } param = AFMT_S16_LE; if (ioctl(sound_fd, SNDCTL_DSP_SETFMT, ¶m) == -1) { perror("Error in the SNDCTL_DSP_SETFMT ioctl !\n"); exit(1); } if (param != AFMT_S16_LE) { error("AFMT_S16_LE not supported !\n"); exit(1); } param = 2; if (ioctl(sound_fd, SNDCTL_DSP_CHANNELS, ¶m) == -1) { error("Error in the SNDCTL_DSP_CHANNELS ioctl !\n"); exit(1); } if (param != 2) { error("Stereo mode not supported !\n"); exit(1); } param = 22050; if (ioctl(sound_fd, SNDCTL_DSP_SPEED, ¶m) == -1) { perror("Error in the SNDCTL_DSP_SPEED ioctl !\n"); exit(1); } if (param != 22050) { error("22050 kHz not supported !\n"); exit(1); } if (ioctl(sound_fd, SNDCTL_DSP_GETOSPACE, &info) != 0) { perror("SNDCTL_DSP_GETOSPACE"); exit(-1); } sched_yield(); while (1) { unsigned short *buf = (unsigned short *)sound_buffer; int size, written; sound_proc(proc_param, (byte *)sound_buffer, FRAG_SIZE >> 1); /* Now convert to stereo */ for (int i = ((FRAG_SIZE >> 2) - 1); i >= 0; i--) { buf[2 * i + 1] = buf[2 * i] = buf[i]; } #ifdef CAPTURE_SOUND fwrite(buf, 2, FRAG_SIZE >> 1, f); fflush(f); #endif size = FRAG_SIZE; while (size > 0) { written = write(sound_fd, sound_buffer, size); size -= written; } } return NULL; } /* Function used to hide the mouse cursor */ void OSystem_X11::create_empty_cursor() { XColor bg; Pixmap pixmapBits; Cursor cursor = None; static const char data[] = { 0 }; bg.red = bg.green = bg.blue = 0x0000; pixmapBits = XCreateBitmapFromData(display, XRootWindow(display, screen), data, 1, 1); if (pixmapBits) { cursor = XCreatePixmapCursor(display, pixmapBits, pixmapBits, &bg, &bg, 0, 0); XFreePixmap(display, pixmapBits); } XDefineCursor(display, window, cursor); } OSystem *OSystem_X11_create(void) { return OSystem_X11::create(0, 0); } OSystem *OSystem_X11::create(int gfx_mode, bool full_screen) { OSystem_X11 *syst = new OSystem_X11(); return syst; } OSystem_X11::OSystem_X11() { char buf[512]; XWMHints *wm_hints; XGCValues values; XTextProperty window_name; char *name = (char *)&buf; /* Some members initialization */ fake_right_mouse = 0; report_presses = 1; current_shake_pos = 0; new_shake_pos = 0; _palette_changed = false; num_of_dirty_square = MAX_NUMBER_OF_DIRTY_SQUARES; /* For the window title */ sprintf(buf, "ScummVM"); display = XOpenDisplay(NULL); if (display == NULL) { error("Could not open display !\n"); exit(1); } screen = DefaultScreen(display); window_width = 320; window_height = 200; scumm_x = 0; scumm_y = 0; window = XCreateSimpleWindow(display, XRootWindow(display, screen), 0, 0, 320, 200, 0, 0, 0); wm_hints = XAllocWMHints(); if (wm_hints == NULL) { error("Not enough memory to allocate Hints !\n"); exit(1); } wm_hints->flags = InputHint | StateHint; wm_hints->input = True; wm_hints->initial_state = NormalState; XStringListToTextProperty(&name, 1, &window_name); XSetWMProperties(display, window, &window_name, &window_name, NULL /* argv */ , 0 /* argc */ , NULL /* size hints */ , wm_hints, NULL /* class hints */ ); XFree(wm_hints); XSelectInput(display, window, ExposureMask | KeyPressMask | KeyReleaseMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask | StructureNotifyMask); values.foreground = BlackPixel(display, screen); black_gc = XCreateGC(display, window, GCForeground, &values); XMapWindow(display, window); XFlush(display); while (1) { XEvent event; XNextEvent(display, &event); switch (event.type) { case Expose: goto out_of_loop; } } out_of_loop: create_empty_cursor(); /* Initialize the timer routines */ _timer_active = false; /* And finally start the local timer */ gettimeofday(&start_time, NULL); } uint32 OSystem_X11::get_msecs() { struct timeval current_time; gettimeofday(¤t_time, NULL); return (uint32) (((current_time.tv_sec - start_time.tv_sec) * 1000) + ((current_time.tv_usec - start_time.tv_usec) / 1000)); } void OSystem_X11::init_size(uint w, uint h) { static XShmSegmentInfo shminfo; fb_width = w; fb_height = h; if ((fb_width != 320) || (fb_height != 200)) { /* We need to change the size of the X11 window */ XWindowChanges new_values; new_values.width = fb_width; new_values.height = fb_height; XConfigureWindow(display, window, CWWidth | CWHeight, &new_values); } #ifdef USE_XV_SCALING image = XvShmCreateImage(display, 65, 0x03, 0, fb_width, fb_height, &shminfo); shminfo.shmid = shmget(IPC_PRIVATE, image->data_size, IPC_CREAT | 0700); #else image = XShmCreateImage(display, DefaultVisual(display, screen), 16, ZPixmap, NULL, &shminfo, fb_width, fb_height); shminfo.shmid = shmget(IPC_PRIVATE, fb_width * fb_height * 2, IPC_CREAT | 0700); #endif shminfo.shmaddr = (char *)shmat(shminfo.shmid, 0, 0); image->data = shminfo.shmaddr; shminfo.readOnly = False; if (XShmAttach(display, &shminfo) == 0) { error("Could not attach shared memory segment !\n"); exit(1); } shmctl(shminfo.shmid, IPC_RMID, 0); /* Initialize the 'local' frame buffer and the palette */ local_fb = (unsigned char *)calloc(fb_width * fb_height, sizeof(unsigned char)); #ifdef USE_XV_SCALING palette = (unsigned int *)calloc(256, sizeof(unsigned int)); #else palette = (unsigned short *)calloc(256, sizeof(unsigned short)); #endif } bool OSystem_X11::set_sound_proc(void *param, SoundProc *proc, byte format) { static THREAD_PARAM thread_param; /* And finally start the music thread */ thread_param.param = param; thread_param.sound_proc = proc; thread_param.format = format; if (format == SOUND_16BIT) pthread_create(&sound_thread, NULL, sound_and_music_thread, (void *) &thread_param); else warning("Only support 16 bit sound for now. Disabling sound "); return true; } void OSystem_X11::set_palette(const byte *colors, uint start, uint num) { const byte *data = colors; #ifdef USE_XV_SCALING unsigned int *pal = &(palette[start]); #else unsigned short *pal = &(palette[start]); #endif do { #ifdef USE_XV_SCALING *pal++ = (data[0] << 16) | (data[1] << 8) | data[2]; #else *pal++ = ((data[0] & 0xF8) << 8) | ((data[1] & 0xFC) << 3) | (data[2] >> 3); #endif data += 4; num--; } while (num > 0); _palette_changed = true; } #define AddDirtyRec(xi,yi,wi,hi) \ if (num_of_dirty_square < MAX_NUMBER_OF_DIRTY_SQUARES) { \ ds[num_of_dirty_square].x = xi; \ ds[num_of_dirty_square].y = yi; \ ds[num_of_dirty_square].w = wi; \ ds[num_of_dirty_square].h = hi; \ num_of_dirty_square++; \ } void OSystem_X11::copy_rect(const byte *buf, int pitch, int x, int y, int w, int h) { unsigned char *dst; if (y < 0) { h += y; buf -= y * pitch; y = 0; } if (h > (fb_height - y)) { h = fb_height - y; } dst = local_fb + fb_width * y + x; if (h <= 0) return; if (_mouse_drawn) undraw_mouse(); AddDirtyRec(x, y, w, h); while (h-- > 0) { memcpy(dst, buf, w); dst += fb_width; buf += pitch; } } void OSystem_X11::update_screen_helper(const dirty_square * d, dirty_square * dout) { int x, y; unsigned char *ptr_src = local_fb + (fb_width * d->y) + d->x; #ifdef USE_XV_SCALING unsigned int *ptr_dst = ((unsigned int *)image->data) + (fb_width * d->y) + d->x; #else unsigned short *ptr_dst = ((unsigned short *)image->data) + (fb_width * d->y) + d->x; #endif for (y = 0; y < d->h; y++) { for (x = 0; x < d->w; x++) { *ptr_dst++ = palette[*ptr_src++]; } ptr_dst += fb_width - d->w; ptr_src += fb_width - d->w; } if (d->x < dout->x) dout->x = d->x; if (d->y < dout->y) dout->y = d->y; if ((d->x + d->w) > dout->w) dout->w = d->x + d->w; if ((d->y + d->h) > dout->h) dout->h = d->y + d->h; } void OSystem_X11::update_screen() { bool full_redraw = false; bool need_redraw = false; static const dirty_square ds_full = { 0, 0, fb_width, fb_height }; dirty_square dout = { fb_width, fb_height, 0, 0 }; /* First make sure the mouse is drawn, if it should be drawn. */ draw_mouse(); if (_palette_changed) { full_redraw = true; num_of_dirty_square = 0; _palette_changed = false; } else if (num_of_dirty_square >= MAX_NUMBER_OF_DIRTY_SQUARES) { full_redraw = true; num_of_dirty_square = 0; } if (full_redraw) { update_screen_helper(&ds_full, &dout); need_redraw = true; } else if (num_of_dirty_square > 0) { need_redraw = true; while (num_of_dirty_square > 0) { num_of_dirty_square--; update_screen_helper(&(ds[num_of_dirty_square]), &dout); } } if (current_shake_pos != new_shake_pos) { /* Redraw first the 'black borders' in case of resize */ if (current_shake_pos < new_shake_pos) XFillRectangle(display, window, black_gc, 0, current_shake_pos, window_width, new_shake_pos); else XFillRectangle(display, window, black_gc, 0, window_height - current_shake_pos, window_width, window_height - new_shake_pos); #ifndef USE_XV_SCALING XShmPutImage(display, window, DefaultGC(display, screen), image, 0, 0, scumm_x, scumm_y + new_shake_pos, fb_width, fb_height, 0); #endif current_shake_pos = new_shake_pos; } else if (need_redraw == true) { #ifdef USE_XV_SCALING XvShmPutImage(display, 65, window, DefaultGC(display, screen), image, 0, 0, fb_width, fb_height, 0, 0, window_width, window_height, 0); #else XShmPutImage(display, window, DefaultGC(display, screen), image, dout.x, dout.y, scumm_x + dout.x, scumm_y + dout.y + current_shake_pos, dout.w - dout.x, dout.h - dout.y, 0); #endif XFlush(display); } } bool OSystem_X11::show_mouse(bool visible) { if (_mouse_visible == visible) return visible; bool last = _mouse_visible; _mouse_visible = visible; if (visible) draw_mouse(); else undraw_mouse(); return last; } void OSystem_X11::quit() { exit(1); } void OSystem_X11::draw_mouse() { if (_mouse_drawn || !_mouse_visible) return; _mouse_drawn = true; int xdraw = cur_state.x - cur_state.hot_x; int ydraw = cur_state.y - cur_state.hot_y; int w = cur_state.w; int h = cur_state.h; int real_w; int real_h; int real_h_2; byte *dst; byte *dst2; const byte *buf = _ms_buf; byte *bak = _ms_backup; assert(w <= BAK_WIDTH && h <= BAK_HEIGHT); if (ydraw < 0) { real_h = h + ydraw; buf += (-ydraw) * w; ydraw = 0; } else { real_h = (ydraw + h) > fb_height ? (fb_height - ydraw) : h; } if (xdraw < 0) { real_w = w + xdraw; buf += (-xdraw); xdraw = 0; } else { real_w = (xdraw + w) > fb_width ? (fb_width - xdraw) : w; } dst = local_fb + (ydraw * fb_width) + xdraw; dst2 = dst; if ((real_h == 0) || (real_w == 0)) { _mouse_drawn = false; return; } AddDirtyRec(xdraw, ydraw, real_w, real_h); old_state.x = xdraw; old_state.y = ydraw; old_state.w = real_w; old_state.h = real_h; real_h_2 = real_h; while (real_h_2 > 0) { memcpy(bak, dst, real_w); bak += BAK_WIDTH; dst += fb_width; real_h_2--; } while (real_h > 0) { int width = real_w; while (width > 0) { byte color = *buf; if (color != 0xFF) { *dst2 = color; } buf++; dst2++; width--; } buf += w - real_w; dst2 += fb_width - real_w; real_h--; } } void OSystem_X11::undraw_mouse() { if (!_mouse_drawn) return; _mouse_drawn = false; int old_h = old_state.h; AddDirtyRec(old_state.x, old_state.y, old_state.w, old_state.h); byte *dst = local_fb + (old_state.y * fb_width) + old_state.x; byte *bak = _ms_backup; while (old_h > 0) { memcpy(dst, bak, old_state.w); bak += BAK_WIDTH; dst += fb_width; old_h--; } } void OSystem_X11::set_mouse_pos(int x, int y) { if ((x != cur_state.x) || (y != cur_state.y)) { cur_state.x = x; cur_state.y = y; undraw_mouse(); } } void OSystem_X11::set_mouse_cursor(const byte *buf, uint w, uint h, int hotspot_x, int hotspot_y) { cur_state.w = w; cur_state.h = h; cur_state.hot_x = hotspot_x; cur_state.hot_y = hotspot_y; _ms_buf = (byte*)buf; undraw_mouse(); } void OSystem_X11::set_shake_pos(int shake_pos) { new_shake_pos = shake_pos; } void *OSystem_X11::create_thread(ThreadProc *proc, void *param) { pthread_t *thread = (pthread_t *) malloc(sizeof(pthread_t)); if (pthread_create(thread, NULL, (void * (*)(void *)) proc, param)) return NULL; else return thread; } uint32 OSystem_X11::property(int param, Property *value) { switch (param) { case PROP_GET_SAMPLE_RATE: return 22050; case PROP_GET_FULLSCREEN: return 0; } warning("Property not implemented yet (%d) ", param); return 0; } bool OSystem_X11::poll_cdrom() { return false; } void OSystem_X11::play_cdrom(int track, int num_loops, int start_frame, int end_frame) { } void OSystem_X11::stop_cdrom() { } void OSystem_X11::update_cdrom() { } void OSystem_X11::delay_msecs(uint msecs) { usleep(msecs * 1000); } bool OSystem_X11::poll_event(Event *scumm_event) { /* First, handle timers */ uint32 current_msecs = get_msecs(); if (_timer_active && (current_msecs >= _timer_next_expiry)) { _timer_duration = _timer_callback(_timer_duration); _timer_next_expiry = current_msecs + _timer_duration; } while (XPending(display)) { XEvent event; XNextEvent(display, &event); switch (event.type) { case Expose:{ int real_w, real_h; int real_x, real_y; real_x = event.xexpose.x; real_y = event.xexpose.y; real_w = event.xexpose.width; real_h = event.xexpose.height; if (real_x < scumm_x) { real_w -= scumm_x - real_x; real_x = 0; } else { real_x -= scumm_x; } if (real_y < scumm_y) { real_h -= scumm_y - real_y; real_y = 0; } else { real_y -= scumm_y; } if ((real_h <= 0) || (real_w <= 0)) break; if ((real_x >= fb_width) || (real_y >= fb_height)) break; if ((real_x + real_w) >= fb_width) { real_w = fb_width - real_x; } if ((real_y + real_h) >= fb_height) { real_h = fb_height - real_y; } /* Compute the intersection of the expose event with the real ScummVM display zone */ AddDirtyRec(real_x, real_y, real_w, real_h); } break; case KeyPress: switch (event.xkey.keycode) { case 132: report_presses = 0; break; case 133: fake_right_mouse = 1; break; } break; case KeyRelease: { /* I am using keycodes here and NOT keysyms to be sure that even if the user remaps his iPAQ's keyboard, it will still work. */ int keycode = -1; int ascii = -1; switch (event.xkey.keycode) { case 9: /* Escape on my PC */ case 130: /* Calendar on the iPAQ */ keycode = 27; break; case 71: /* F5 on my PC */ case 128: /* Record on the iPAQ */ keycode = 319; break; case 65: /* Space on my PC */ case 131: /* Schedule on the iPAQ */ keycode = 32; break; case 132: /* 'Q' on the iPAQ */ report_presses = 1; break; case 133: /* Arrow on the iPAQ */ fake_right_mouse = 0; break; default:{ KeySym xsym; xsym = XKeycodeToKeysym(display, event.xkey.keycode, 0); keycode = xsym; if ((xsym >= 'a') && (xsym <= 'z') && (event.xkey.state & 0x01)) xsym &= ~0x20; /* Handle shifted keys */ ascii = xsym; } } if (keycode != -1) { scumm_event->event_code = EVENT_KEYDOWN; scumm_event->kbd.keycode = keycode; scumm_event->kbd.ascii = (ascii != -1 ? ascii : keycode); scumm_event->kbd.flags = 0; return true; } } break; case ButtonPress: if (report_presses != 0) { if (event.xbutton.button == 1) { if (fake_right_mouse == 0) { scumm_event->event_code = EVENT_LBUTTONDOWN; } else { scumm_event->event_code = EVENT_RBUTTONDOWN; } } else if (event.xbutton.button == 3) scumm_event->event_code = EVENT_RBUTTONDOWN; scumm_event->mouse.x = event.xbutton.x - scumm_x; scumm_event->mouse.y = event.xbutton.y - scumm_y; return true; } break; case ButtonRelease: if (report_presses != 0) { if (event.xbutton.button == 1) { if (fake_right_mouse == 0) { scumm_event->event_code = EVENT_LBUTTONUP; } else { scumm_event->event_code = EVENT_RBUTTONUP; } } else if (event.xbutton.button == 3) scumm_event->event_code = EVENT_RBUTTONUP; scumm_event->mouse.x = event.xbutton.x - scumm_x; scumm_event->mouse.y = event.xbutton.y - scumm_y; return true; } break; case MotionNotify: scumm_event->event_code = EVENT_MOUSEMOVE; scumm_event->mouse.x = event.xmotion.x - scumm_x; scumm_event->mouse.y = event.xmotion.y - scumm_y; return true; case ConfigureNotify:{ if ((window_width != event.xconfigure.width) || (window_height != event.xconfigure.height)) { window_width = event.xconfigure.width; window_height = event.xconfigure.height; scumm_x = (window_width - fb_width) / 2; scumm_y = (window_height - fb_height) / 2; XFillRectangle(display, window, black_gc, 0, 0, window_width, window_height); } } break; default: printf("Unhandled event : %d\n", event.type); break; } } return false; } void OSystem_X11::set_timer(int timer, int (*callback)(int)) { if (callback != NULL) { _timer_duration = timer; _timer_next_expiry = get_msecs() + timer; _timer_callback = callback; _timer_active = true; } else { _timer_active = false; } }