mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-27 20:28:27 +00:00
88fdb5a627
svn-id: r13796
1151 lines
28 KiB
C++
1151 lines
28 KiB
C++
/* 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 "common/stdafx.h"
|
|
#include "common/scummsys.h"
|
|
#include "common/system.h"
|
|
#include "common/util.h"
|
|
|
|
#include "backends/intern.h"
|
|
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
|
|
#include <sys/time.h>
|
|
#include <unistd.h>
|
|
|
|
#include <sys/ipc.h>
|
|
#include <sys/shm.h>
|
|
|
|
#include <X11/Xlib.h>
|
|
#include <X11/Xutil.h>
|
|
#include <X11/extensions/XShm.h>
|
|
|
|
#ifdef __linux__
|
|
#include <linux/soundcard.h>
|
|
#else
|
|
#include <sys/soundcard.h>
|
|
#endif
|
|
|
|
#include <sched.h>
|
|
#include <pthread.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <sys/ioctl.h>
|
|
|
|
class OSystem_X11:public OSystem {
|
|
public:
|
|
// Determine whether the backend supports the specified feature.
|
|
bool hasFeature(Feature f);
|
|
|
|
// En-/disable the specified feature.
|
|
void setFeatureState(Feature f, bool enable);
|
|
|
|
// Query the state of the specified feature.
|
|
bool getFeatureState(Feature f);
|
|
|
|
// Retrieve a list of all graphics modes supported by this backend.
|
|
const GraphicsMode *getSupportedGraphicsModes() const;
|
|
|
|
// Return the ID of the 'default' graphics mode.
|
|
int getDefaultGraphicsMode() const;
|
|
|
|
// Switch to the specified graphics mode.
|
|
bool setGraphicsMode(int mode);
|
|
|
|
// Determine which graphics mode is currently active.
|
|
int getGraphicsMode() const;
|
|
|
|
// Set colors of the palette
|
|
void setPalette(const byte *colors, uint start, uint num);
|
|
|
|
// Set the size of the video bitmap.
|
|
// Typically, 320x200
|
|
void initSize(uint w, uint h);
|
|
|
|
// Draw a bitmap to screen.
|
|
// The screen will not be updated to reflect the new bitmap
|
|
void copyRectToScreen(const byte *buf, int pitch, int x, int y, int w, int h);
|
|
|
|
void move_screen(int dx, int dy, int height);
|
|
|
|
// Update the dirty areas of the screen
|
|
void updateScreen();
|
|
|
|
// Either show or hide the mouse cursor
|
|
bool showMouse(bool visible);
|
|
|
|
// Set the position of the mouse cursor
|
|
void set_mouse_pos(int x, int y);
|
|
|
|
// Warp the mouse cursor. Where set_mouse_pos() only informs the
|
|
// backend of the mouse cursor's current position, this function
|
|
// actually moves the cursor to the specified position.
|
|
void warpMouse(int x, int y);
|
|
|
|
// Set the bitmap that's used when drawing the cursor.
|
|
void setMouseCursor(const byte *buf, uint w, uint h, int hotspot_x, int hotspot_y, byte keycolor);
|
|
|
|
// 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);
|
|
|
|
// Get the next event.
|
|
// Returns true if an event was retrieved.
|
|
bool poll_event(Event *event);
|
|
|
|
// Set function that generates samples
|
|
bool setSoundCallback(SoundProc proc, void *param);
|
|
void clearSoundCallback();
|
|
|
|
// Determine the output sample rate. Audio data provided by the sound
|
|
// callback will be played using this rate.
|
|
int getOutputSampleRate() const;
|
|
|
|
// Initialise the specified CD drive for audio playback.
|
|
bool openCD(int drive);
|
|
|
|
// 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 duration);
|
|
|
|
// Stop cdrom audio track
|
|
void stop_cdrom();
|
|
|
|
// Update cdrom audio status
|
|
void update_cdrom();
|
|
|
|
// Quit
|
|
void quit();
|
|
|
|
// Add a callback timer
|
|
void setTimerCallback(TimerProc callback, int interval);
|
|
|
|
// Mutex handling
|
|
MutexRef createMutex();
|
|
void lockMutex(MutexRef mutex);
|
|
void unlockMutex(MutexRef mutex);
|
|
void deleteMutex(MutexRef mutex);
|
|
|
|
// Overlay handling for the new menu system
|
|
void showOverlay();
|
|
void hideOverlay();
|
|
void clearOverlay();
|
|
void grabOverlay(int16 *, int);
|
|
void copyRectToOverlay(const int16 *, int, int, int, int, int);
|
|
virtual int16 getHeight();
|
|
virtual int16 getWidth();
|
|
|
|
// Set a window caption or any other comparable status display to the
|
|
// given value.
|
|
void setWindowCaption(const char *caption);
|
|
|
|
|
|
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 draw_mouse(dirty_square *dout);
|
|
void undraw_mouse();
|
|
void updateScreen_helper(const dirty_square * d, dirty_square * dout);
|
|
void blit_convert(const dirty_square * d, uint16 *dst, int pitch);
|
|
|
|
uint8 *local_fb;
|
|
uint16 *local_fb_overlay;
|
|
bool _overlay_visible;
|
|
|
|
int window_width, window_height;
|
|
int fb_width, fb_height;
|
|
int scumm_x, scumm_y;
|
|
|
|
uint16 *palette;
|
|
bool _palette_changed;
|
|
Display *display;
|
|
int screen, depth;
|
|
Window window;
|
|
GC black_gc;
|
|
XImage *image;
|
|
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
|
|
};
|
|
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;
|
|
byte *_ms_buf;
|
|
bool _mouse_visible;
|
|
bool _mouse_state_changed;
|
|
byte _mouseKeycolor;
|
|
|
|
uint32 _timer_duration, _timer_next_expiry;
|
|
bool _timer_active;
|
|
int (*_timer_callback) (int);
|
|
};
|
|
|
|
typedef struct {
|
|
OSystem::SoundProc sound_proc;
|
|
void *param;
|
|
} 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;
|
|
uint8 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 = SAMPLES_PER_SEC;
|
|
if (ioctl(sound_fd, SNDCTL_DSP_SPEED, ¶m) == -1) {
|
|
perror("Error in the SNDCTL_DSP_SPEED ioctl !\n");
|
|
exit(1);
|
|
}
|
|
if (param != SAMPLES_PER_SEC) {
|
|
error("%d kHz not supported !\n", SAMPLES_PER_SEC);
|
|
exit(1);
|
|
}
|
|
if (ioctl(sound_fd, SNDCTL_DSP_GETOSPACE, &info) != 0) {
|
|
perror("SNDCTL_DSP_GETOSPACE");
|
|
exit(-1);
|
|
}
|
|
|
|
sched_yield();
|
|
while (1) {
|
|
uint8 *buf = (uint8 *)sound_buffer;
|
|
int size, written;
|
|
|
|
sound_proc(proc_param, (byte *)sound_buffer, FRAG_SIZE);
|
|
#ifdef CAPTURE_SOUND
|
|
fwrite(buf, 2, FRAG_SIZE >> 1, f);
|
|
fflush(f);
|
|
#endif
|
|
size = FRAG_SIZE;
|
|
while (size > 0) {
|
|
written = write(sound_fd, buf, size);
|
|
buf += written;
|
|
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() {
|
|
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 = 0;
|
|
_overlay_visible = false;
|
|
_mouse_state_changed = true;
|
|
_mouse_visible = true;
|
|
_ms_buf = NULL;
|
|
cur_state.x = 0;
|
|
cur_state.y = 0;
|
|
cur_state.hot_x = 0;
|
|
cur_state.hot_y = 0;
|
|
cur_state.w = 0;
|
|
cur_state.h = 0;
|
|
|
|
/* For the window title */
|
|
sprintf(buf, "ScummVM");
|
|
|
|
display = XOpenDisplay(NULL);
|
|
if (display == NULL) {
|
|
error("Could not open display !\n");
|
|
exit(1);
|
|
}
|
|
|
|
if (XShmQueryExtension(display)!=True)
|
|
error("No Shared Memory Extension present");
|
|
|
|
screen = DefaultScreen(display);
|
|
depth = DefaultDepth(display,screen);
|
|
if (depth != 16)
|
|
error("Your screen depth is %ibit. Values other than 16bit are currently not supported", depth);
|
|
|
|
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);
|
|
}
|
|
|
|
bool OSystem_X11::hasFeature(Feature f) {
|
|
return false;
|
|
}
|
|
|
|
void OSystem_X11::setFeatureState(Feature f, bool enable) {
|
|
}
|
|
|
|
bool OSystem_X11::getFeatureState(Feature f) {
|
|
return false;
|
|
}
|
|
|
|
const OSystem::GraphicsMode *OSystem_X11::getSupportedGraphicsModes() const {
|
|
return NULL; // TODO / FIXME
|
|
}
|
|
|
|
int OSystem_X11::getDefaultGraphicsMode() const {
|
|
return 0;
|
|
}
|
|
|
|
bool OSystem_X11::setGraphicsMode(int mode) {
|
|
return (mode == 0);
|
|
}
|
|
|
|
int OSystem_X11::getGraphicsMode() const {
|
|
return 0;
|
|
}
|
|
|
|
|
|
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::initSize(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);
|
|
}
|
|
|
|
image = XShmCreateImage(display, DefaultVisual(display, screen), depth, ZPixmap, NULL, &shminfo, fb_width, fb_height);
|
|
if (!image)
|
|
error("Couldn't get image by XShmCreateImage()");
|
|
|
|
shminfo.shmid = shmget(IPC_PRIVATE, image->bytes_per_line * image->height, IPC_CREAT | 0700);
|
|
if (shminfo.shmid < 0)
|
|
error("Couldn't allocate image data by shmget()");
|
|
|
|
image->data = shminfo.shmaddr = (char *)shmat(shminfo.shmid, 0, 0);
|
|
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 = (uint8 *)calloc(fb_width * fb_height, sizeof(uint8));
|
|
local_fb_overlay = (uint16 *)calloc(fb_width * fb_height, sizeof(uint16));
|
|
palette = (uint16 *)calloc(256, sizeof(uint16));
|
|
}
|
|
|
|
bool OSystem_X11::setSoundCallback(SoundProc proc, void *param) {
|
|
static THREAD_PARAM thread_param;
|
|
|
|
/* And finally start the music thread */
|
|
thread_param.param = param;
|
|
thread_param.sound_proc = proc;
|
|
|
|
pthread_create(&sound_thread, NULL, sound_and_music_thread, (void *)&thread_param);
|
|
|
|
return true;
|
|
}
|
|
|
|
void OSystem_X11::clearSoundCallback() {
|
|
// TODO implement this...
|
|
// The sound_thread has to be stopped in a nice way. In particular,
|
|
// using pthread_kill would be a bad idea. Rather, use pthread_cancel,
|
|
// or maybe a global variable, to achieve this.
|
|
// This method shouldn't return until the sound thread really has stopped.
|
|
}
|
|
|
|
|
|
void OSystem_X11::setPalette(const byte *colors, uint start, uint num) {
|
|
const byte *data = colors;
|
|
uint16 *pal = &(palette[start]);
|
|
|
|
do {
|
|
*pal++ = ((data[0] & 0xF8) << 8) | ((data[1] & 0xFC) << 3) | (data[2] >> 3);
|
|
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::copyRectToScreen(const byte *buf, int pitch, int x, int y, int w, int h) {
|
|
uint8 *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;
|
|
|
|
AddDirtyRec(x, y, w, h);
|
|
while (h-- > 0) {
|
|
memcpy(dst, buf, w);
|
|
dst += fb_width;
|
|
buf += pitch;
|
|
}
|
|
}
|
|
|
|
void OSystem_X11::move_screen(int dx, int dy, int height) {
|
|
|
|
if ((dx == 0) && (dy == 0))
|
|
return;
|
|
|
|
if (dx == 0) {
|
|
// vertical movement
|
|
if (dy > 0) {
|
|
// move down
|
|
// copy from bottom to top
|
|
for (int y = height - 1; y >= dy; y--)
|
|
copyRectToScreen(local_fb + fb_width * (y - dy), fb_width, 0, y, fb_width, 1);
|
|
} else {
|
|
// move up
|
|
// copy from top to bottom
|
|
for (int y = 0; y < height + dx; y++)
|
|
copyRectToScreen(local_fb + fb_width * (y - dy), fb_width, 0, y, fb_width, 1);
|
|
}
|
|
} else if (dy == 0) {
|
|
// horizontal movement
|
|
if (dx > 0) {
|
|
// move right
|
|
// copy from right to left
|
|
for (int x = fb_width - 1; x >= dx; x--)
|
|
copyRectToScreen(local_fb + x - dx, fb_width, x, 0, 1, height);
|
|
} else {
|
|
// move left
|
|
// copy from left to right
|
|
for (int x = 0; x < fb_width; x++)
|
|
copyRectToScreen(local_fb + x - dx, fb_width, x, 0, 1, height);
|
|
}
|
|
} else {
|
|
// free movement
|
|
// not necessary for now
|
|
}
|
|
}
|
|
|
|
void OSystem_X11::blit_convert(const dirty_square * d, uint16 *dst, int pitch) {
|
|
uint8 *ptr_src = local_fb + (fb_width * d->y) + d->x;
|
|
uint16 *ptr_dst = dst + (fb_width * d->y) + d->x;
|
|
int x, y;
|
|
|
|
for (y = 0; y < d->h; y++) {
|
|
for (x = 0; x < d->w; x++) {
|
|
*ptr_dst++ = palette[*ptr_src++];
|
|
}
|
|
ptr_dst += pitch - d->w;
|
|
ptr_src += fb_width - d->w;
|
|
}
|
|
}
|
|
|
|
void OSystem_X11::updateScreen_helper(const dirty_square * d, dirty_square * dout) {
|
|
if (_overlay_visible == false) {
|
|
blit_convert(d, (uint16 *) image->data, fb_width);
|
|
} else {
|
|
uint16 *ptr_src = local_fb_overlay + (fb_width * d->y) + d->x;
|
|
uint16 *ptr_dst = ((uint16 *)image->data) + (fb_width * d->y) + d->x;
|
|
int y;
|
|
|
|
for (y = 0; y < d->h; y++) {
|
|
memcpy(ptr_dst, ptr_src, d->w * sizeof(uint16));
|
|
ptr_dst += fb_width;
|
|
ptr_src += fb_width;
|
|
}
|
|
}
|
|
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::updateScreen() {
|
|
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 };
|
|
|
|
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) {
|
|
updateScreen_helper(&ds_full, &dout);
|
|
need_redraw = true;
|
|
} else if ((num_of_dirty_square > 0) || (_mouse_state_changed == true)) {
|
|
need_redraw = true;
|
|
while (num_of_dirty_square > 0) {
|
|
num_of_dirty_square--;
|
|
updateScreen_helper(&(ds[num_of_dirty_square]), &dout);
|
|
}
|
|
}
|
|
|
|
/* Then 'overlay' the mouse on the image */
|
|
draw_mouse(&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);
|
|
XShmPutImage(display, window, DefaultGC(display, screen), image,
|
|
0, 0, scumm_x, scumm_y + new_shake_pos, fb_width, fb_height, 0);
|
|
current_shake_pos = new_shake_pos;
|
|
} else if (need_redraw == true) {
|
|
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);
|
|
XFlush(display);
|
|
}
|
|
}
|
|
|
|
bool OSystem_X11::showMouse(bool visible)
|
|
{
|
|
if (_mouse_visible == visible)
|
|
return visible;
|
|
|
|
bool last = _mouse_visible;
|
|
_mouse_visible = visible;
|
|
|
|
if ((visible == false) && (_mouse_state_changed == false)) {
|
|
undraw_mouse();
|
|
}
|
|
_mouse_state_changed = true;
|
|
|
|
return last;
|
|
}
|
|
|
|
void OSystem_X11::quit() {
|
|
exit(0);
|
|
}
|
|
|
|
void OSystem_X11::setWindowCaption(const char *caption) {
|
|
}
|
|
|
|
void OSystem_X11::undraw_mouse() {
|
|
AddDirtyRec(old_state.x, old_state.y, old_state.w, old_state.h);
|
|
}
|
|
|
|
void OSystem_X11::draw_mouse(dirty_square *dout) {
|
|
_mouse_state_changed = false;
|
|
|
|
if (_mouse_visible == false)
|
|
return;
|
|
|
|
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;
|
|
|
|
uint16 *dst;
|
|
const byte *buf = _ms_buf;
|
|
|
|
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 = ((uint16 *) image->data) + (ydraw * fb_width) + xdraw;
|
|
|
|
if ((real_h == 0) || (real_w == 0)) {
|
|
return;
|
|
}
|
|
|
|
|
|
if (xdraw < dout->x)
|
|
dout->x = xdraw;
|
|
if (ydraw < dout->y)
|
|
dout->y = ydraw;
|
|
if ((xdraw + real_w) > dout->w)
|
|
dout->w = xdraw + real_w;
|
|
if ((ydraw + real_h) > dout->h)
|
|
dout->h = ydraw + real_h;
|
|
|
|
old_state.x = xdraw;
|
|
old_state.y = ydraw;
|
|
old_state.w = real_w;
|
|
old_state.h = real_h;
|
|
|
|
while (real_h > 0) {
|
|
int width = real_w;
|
|
while (width > 0) {
|
|
byte color = *buf;
|
|
if (color != _mouseKeycolor) {
|
|
*dst = palette[color];
|
|
}
|
|
buf++;
|
|
dst++;
|
|
width--;
|
|
}
|
|
buf += w - real_w;
|
|
dst += fb_width - real_w;
|
|
real_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;
|
|
if (_mouse_state_changed == false) {
|
|
undraw_mouse();
|
|
}
|
|
_mouse_state_changed = true;
|
|
}
|
|
}
|
|
|
|
void OSystem_X11::warpMouse(int x, int y) {
|
|
set_mouse_pos(x, y);
|
|
}
|
|
|
|
void OSystem_X11::setMouseCursor(const byte *buf, uint w, uint h, int hotspot_x, int hotspot_y, byte keycolor) {
|
|
cur_state.w = w;
|
|
cur_state.h = h;
|
|
cur_state.hot_x = hotspot_x;
|
|
cur_state.hot_y = hotspot_y;
|
|
|
|
if (_ms_buf)
|
|
free(_ms_buf);
|
|
_ms_buf = malloc(w * h);
|
|
memcpy(_ms_buf, buf, w * h);
|
|
|
|
if (_mouse_state_changed == false) {
|
|
undraw_mouse();
|
|
}
|
|
_mouseKeycolor = keycolor;
|
|
_mouse_state_changed = true;
|
|
}
|
|
|
|
void OSystem_X11::set_shake_pos(int shake_pos) {
|
|
if (new_shake_pos != shake_pos) {
|
|
if (_mouse_state_changed == false) {
|
|
undraw_mouse();
|
|
}
|
|
_mouse_state_changed = true;
|
|
}
|
|
new_shake_pos = shake_pos;
|
|
}
|
|
|
|
int OSystem_X11::getOutputSampleRate() const {
|
|
return SAMPLES_PER_SEC;
|
|
}
|
|
|
|
bool OSystem_X11::openCD(int drive) {
|
|
return false;
|
|
}
|
|
|
|
bool OSystem_X11::poll_cdrom() {
|
|
return false;
|
|
}
|
|
|
|
void OSystem_X11::play_cdrom(int track, int num_loops, int start_frame, int duration) {
|
|
}
|
|
|
|
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:{
|
|
/* 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;
|
|
byte mode = 0;
|
|
|
|
if (event.xkey.state & 0x01)
|
|
mode |= KBD_SHIFT;
|
|
if (event.xkey.state & 0x04)
|
|
mode |= KBD_CTRL;
|
|
if (event.xkey.state & 0x08)
|
|
mode |= KBD_ALT;
|
|
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:
|
|
report_presses = 0;
|
|
break;
|
|
|
|
case 133:
|
|
fake_right_mouse = 1;
|
|
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 = mode;
|
|
return true;
|
|
}
|
|
}
|
|
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;
|
|
byte mode = 0;
|
|
|
|
if (event.xkey.state & 0x01)
|
|
mode |= KBD_SHIFT;
|
|
if (event.xkey.state & 0x04)
|
|
mode |= KBD_CTRL;
|
|
if (event.xkey.state & 0x08)
|
|
mode |= KBD_ALT;
|
|
switch (event.xkey.keycode) {
|
|
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_KEYUP;
|
|
scumm_event->kbd.keycode = keycode;
|
|
scumm_event->kbd.ascii = (ascii != -1 ? ascii : keycode);
|
|
scumm_event->kbd.flags = mode;
|
|
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;
|
|
set_mouse_pos(scumm_event->mouse.x, scumm_event->mouse.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::setTimerCallback(TimerProc callback, int interval) {
|
|
if (callback != NULL) {
|
|
_timer_duration = interval;
|
|
_timer_next_expiry = get_msecs() + interval;
|
|
_timer_callback = callback;
|
|
_timer_active = true;
|
|
} else {
|
|
_timer_active = false;
|
|
}
|
|
}
|
|
|
|
OSystem::MutexRef OSystem_X11::createMutex(void) {
|
|
pthread_mutex_t *mutex = (pthread_mutex_t *) malloc(sizeof(pthread_mutex_t));
|
|
pthread_mutex_init(mutex, NULL);
|
|
return (MutexRef)mutex;
|
|
}
|
|
|
|
void OSystem_X11::lockMutex(MutexRef mutex) {
|
|
pthread_mutex_lock((pthread_mutex_t *) mutex);
|
|
}
|
|
|
|
void OSystem_X11::unlockMutex(MutexRef mutex) {
|
|
pthread_mutex_unlock((pthread_mutex_t *) mutex);
|
|
}
|
|
|
|
void OSystem_X11::deleteMutex(MutexRef mutex) {
|
|
pthread_mutex_destroy((pthread_mutex_t *) mutex);
|
|
free(mutex);
|
|
}
|
|
|
|
void OSystem_X11::showOverlay() {
|
|
_overlay_visible = true;
|
|
}
|
|
|
|
void OSystem_X11::hideOverlay() {
|
|
_overlay_visible = false;
|
|
_palette_changed = true; // This is to force a full redraw to hide the overlay
|
|
}
|
|
|
|
void OSystem_X11::clearOverlay() {
|
|
if (_overlay_visible == false)
|
|
return;
|
|
dirty_square d = { 0, 0, fb_width, fb_height };
|
|
AddDirtyRec(0, 0, fb_width, fb_height);
|
|
blit_convert(&d, local_fb_overlay, fb_width);
|
|
}
|
|
|
|
void OSystem_X11::grabOverlay(int16 *dest, int pitch) {
|
|
if (_overlay_visible == false)
|
|
return;
|
|
|
|
dirty_square d = { 0, 0, fb_width, fb_height };
|
|
blit_convert(&d, (uint16 *) dest, pitch);
|
|
}
|
|
|
|
void OSystem_X11::copyRectToOverlay(const int16 *src, int pitch, int x, int y, int w, int h) {
|
|
if (_overlay_visible == false)
|
|
return;
|
|
uint16 *dst = local_fb_overlay + x + (y * fb_width);
|
|
AddDirtyRec(x, y, w, h);
|
|
while (h > 0) {
|
|
memcpy(dst, src, w * sizeof(*dst));
|
|
dst += fb_width;
|
|
src += pitch;
|
|
h--;
|
|
}
|
|
}
|
|
|
|
int16 OSystem_X11::getHeight() {
|
|
return fb_height;
|
|
}
|
|
|
|
int16 OSystem_X11::getWidth() {
|
|
return fb_width;
|
|
}
|
|
|