/* Residual - Virtual machine to run LucasArts' 3D adventure games * * Residual is the legal property of its developers, whose names * are too numerous to list here. Please refer to the AUTHORS * file distributed with this source distribution. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * This library 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 * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * * $URL$ * $Id$ * */ #include "common/debug.h" #include "mixer/mixer_intern.h" #include "engine/backend/sdl/driver_sdl.h" #include "engine/backend/default-timer.h" #ifdef _WIN32 #include #endif #include "icons/residual.xpm" #define SAMPLES_PER_SEC 22050 // NOTE: This is not a complete driver, it needs to be subclassed // to provide rendering functionality. // Fake SDLK_* values for joystick and mouse events enum { SDLK_JOY1_B1 = SDLK_LAST, SDLK_JOY1_B2, SDLK_JOY1_B3, SDLK_JOY1_B4, SDLK_JOY1_B5, SDLK_JOY1_B6, SDLK_JOY1_B7, SDLK_JOY1_B8, SDLK_JOY1_B9, SDLK_JOY1_B10, SDLK_JOY1_HLEFT, SDLK_JOY1_HUP, SDLK_JOY1_HRIGHT, SDLK_JOY1_HDOWN, SDLK_JOY2_B1, SDLK_JOY2_B2, SDLK_JOY2_B3, SDLK_JOY2_B4, SDLK_JOY2_B5, SDLK_JOY2_B6, SDLK_JOY2_B7, SDLK_JOY2_B8, SDLK_JOY2_B9, SDLK_JOY2_B10, SDLK_JOY2_HLEFT, SDLK_JOY2_HUP, SDLK_JOY2_HRIGHT, SDLK_JOY2_HDOWN, SDLK_MOUSE_B1, SDLK_MOUSE_B2, SDLK_MOUSE_B3, SDLK_MOUSE_B4, SDLK_AXIS_JOY1_X, SDLK_AXIS_JOY1_Y, SDLK_AXIS_JOY1_Z, SDLK_AXIS_JOY1_R, SDLK_AXIS_JOY1_U, SDLK_AXIS_JOY1_V, SDLK_AXIS_JOY2_X, SDLK_AXIS_JOY2_Y, SDLK_AXIS_JOY2_Z, SDLK_AXIS_JOY2_R, SDLK_AXIS_JOY2_U, SDLK_AXIS_JOY2_V, SDLK_AXIS_MOUSE_X, SDLK_AXIS_MOUSE_Y, SDLK_AXIS_MOUSE_Z, SDLK_EXTRA_LAST }; // Entries in the system.controls table static const Driver::ControlDescriptor controls[] = { { "KEY_ESCAPE", SDLK_ESCAPE }, { "KEY_1", SDLK_1 }, { "KEY_2", SDLK_2 }, { "KEY_3", SDLK_3 }, { "KEY_4", SDLK_4 }, { "KEY_5", SDLK_5 }, { "KEY_6", SDLK_6 }, { "KEY_7", SDLK_7 }, { "KEY_8", SDLK_8 }, { "KEY_9", SDLK_9 }, { "KEY_0", SDLK_0 }, { "KEY_MINUS", SDLK_MINUS }, { "KEY_EQUALS", SDLK_EQUALS }, { "KEY_BACK", SDLK_BACKSPACE }, { "KEY_TAB", SDLK_TAB }, { "KEY_Q", SDLK_q }, { "KEY_W", SDLK_w }, { "KEY_E", SDLK_e }, { "KEY_R", SDLK_r }, { "KEY_T", SDLK_t }, { "KEY_Y", SDLK_y }, { "KEY_U", SDLK_u }, { "KEY_I", SDLK_i }, { "KEY_O", SDLK_o }, { "KEY_P", SDLK_p }, { "KEY_LBRACKET", SDLK_LEFTBRACKET }, { "KEY_RBRACKET", SDLK_RIGHTBRACKET }, { "KEY_RETURN", SDLK_RETURN }, { "KEY_LCONTROL", SDLK_LCTRL }, { "KEY_A", SDLK_a }, { "KEY_S", SDLK_s }, { "KEY_D", SDLK_d }, { "KEY_F", SDLK_f }, { "KEY_G", SDLK_g }, { "KEY_H", SDLK_h }, { "KEY_J", SDLK_j }, { "KEY_K", SDLK_k }, { "KEY_L", SDLK_l }, { "KEY_SEMICOLON", SDLK_SEMICOLON }, { "KEY_APOSTROPHE", SDLK_QUOTE }, { "KEY_GRAVE", SDLK_BACKQUOTE }, { "KEY_LSHIFT", SDLK_LSHIFT }, { "KEY_BACKSLASH", SDLK_BACKSLASH }, { "KEY_Z", SDLK_z }, { "KEY_X", SDLK_x }, { "KEY_C", SDLK_c }, { "KEY_V", SDLK_v }, { "KEY_B", SDLK_b }, { "KEY_N", SDLK_n }, { "KEY_M", SDLK_m }, { "KEY_COMMA", SDLK_COMMA }, { "KEY_PERIOD", SDLK_PERIOD }, { "KEY_SLASH", SDLK_SLASH }, { "KEY_RSHIFT", SDLK_RSHIFT }, { "KEY_MULTIPLY", SDLK_KP_MULTIPLY }, { "KEY_LMENU", SDLK_LALT }, { "KEY_SPACE", SDLK_SPACE }, { "KEY_CAPITAL", SDLK_CAPSLOCK }, { "KEY_F1", SDLK_F1 }, { "KEY_F2", SDLK_F2 }, { "KEY_F3", SDLK_F3 }, { "KEY_F4", SDLK_F4 }, { "KEY_F5", SDLK_F5 }, { "KEY_F6", SDLK_F6 }, { "KEY_F7", SDLK_F7 }, { "KEY_F8", SDLK_F8 }, { "KEY_F9", SDLK_F9 }, { "KEY_F10", SDLK_F10 }, { "KEY_NUMLOCK", SDLK_NUMLOCK }, { "KEY_SCROLL", SDLK_SCROLLOCK }, { "KEY_NUMPAD7", SDLK_KP7 }, { "KEY_NUMPAD8", SDLK_KP8 }, { "KEY_NUMPAD9", SDLK_KP9 }, { "KEY_SUBTRACT", SDLK_KP_MINUS }, { "KEY_NUMPAD4", SDLK_KP4 }, { "KEY_NUMPAD5", SDLK_KP5 }, { "KEY_NUMPAD6", SDLK_KP6 }, { "KEY_ADD", SDLK_KP_PLUS }, { "KEY_NUMPAD1", SDLK_KP1 }, { "KEY_NUMPAD2", SDLK_KP2 }, { "KEY_NUMPAD3", SDLK_KP3 }, { "KEY_NUMPAD0", SDLK_KP0 }, { "KEY_DECIMAL", SDLK_KP_PERIOD }, { "KEY_F11", SDLK_F11 }, { "KEY_F12", SDLK_F12 }, { "KEY_F13", SDLK_F13 }, { "KEY_F14", SDLK_F14 }, { "KEY_F15", SDLK_F15 }, { "KEY_STOP", SDLK_BREAK }, { "KEY_NUMPADENTER", SDLK_KP_ENTER }, { "KEY_RCONTROL", SDLK_RCTRL }, { "KEY_DIVIDE", SDLK_KP_DIVIDE }, { "KEY_SYSRQ", SDLK_SYSREQ }, { "KEY_RMENU", SDLK_RALT }, { "KEY_HOME", SDLK_HOME }, { "KEY_UP", SDLK_UP }, { "KEY_PRIOR", SDLK_PAGEUP }, { "KEY_LEFT", SDLK_LEFT }, { "KEY_RIGHT", SDLK_RIGHT }, { "KEY_END", SDLK_END }, { "KEY_DOWN", SDLK_DOWN }, { "KEY_NEXT", SDLK_PAGEDOWN }, { "KEY_INSERT", SDLK_INSERT }, { "KEY_DELETE", SDLK_DELETE }, { "KEY_LWIN", SDLK_LSUPER }, { "KEY_RWIN", SDLK_RSUPER }, { "KEY_APPS", SDLK_MENU }, { "KEY_JOY1_B1", SDLK_JOY1_B1 }, { "KEY_JOY1_B2", SDLK_JOY1_B2 }, { "KEY_JOY1_B3", SDLK_JOY1_B3 }, { "KEY_JOY1_B4", SDLK_JOY1_B4 }, { "KEY_JOY1_B5", SDLK_JOY1_B5 }, { "KEY_JOY1_B6", SDLK_JOY1_B6 }, { "KEY_JOY1_B7", SDLK_JOY1_B7 }, { "KEY_JOY1_B8", SDLK_JOY1_B8 }, { "KEY_JOY1_B9", SDLK_JOY1_B9 }, { "KEY_JOY1_B10", SDLK_JOY1_B10 }, { "KEY_JOY1_HLEFT", SDLK_JOY1_HLEFT }, { "KEY_JOY1_HUP", SDLK_JOY1_HUP }, { "KEY_JOY1_HRIGHT", SDLK_JOY1_HRIGHT }, { "KEY_JOY1_HDOWN", SDLK_JOY1_HDOWN }, { "KEY_JOY2_B1", SDLK_JOY2_B1 }, { "KEY_JOY2_B2", SDLK_JOY2_B2 }, { "KEY_JOY2_B3", SDLK_JOY2_B3 }, { "KEY_JOY2_B4", SDLK_JOY2_B4 }, { "KEY_JOY2_B5", SDLK_JOY2_B5 }, { "KEY_JOY2_B6", SDLK_JOY2_B6 }, { "KEY_JOY2_B7", SDLK_JOY2_B7 }, { "KEY_JOY2_B8", SDLK_JOY2_B8 }, { "KEY_JOY2_B9", SDLK_JOY2_B9 }, { "KEY_JOY2_B10", SDLK_JOY2_B10 }, { "KEY_JOY2_HLEFT", SDLK_JOY2_HLEFT }, { "KEY_JOY2_HUP", SDLK_JOY2_HUP }, { "KEY_JOY2_HRIGHT", SDLK_JOY2_HRIGHT }, { "KEY_JOY2_HDOWN", SDLK_JOY2_HDOWN }, { "KEY_MOUSE_B1", SDLK_MOUSE_B1 }, { "KEY_MOUSE_B2", SDLK_MOUSE_B2 }, { "KEY_MOUSE_B3", SDLK_MOUSE_B3 }, { "KEY_MOUSE_B4", SDLK_MOUSE_B4 }, { "AXIS_JOY1_X", SDLK_AXIS_JOY1_X }, { "AXIS_JOY1_Y", SDLK_AXIS_JOY1_Y }, { "AXIS_JOY1_Z", SDLK_AXIS_JOY1_Z }, { "AXIS_JOY1_R", SDLK_AXIS_JOY1_R }, { "AXIS_JOY1_U", SDLK_AXIS_JOY1_U }, { "AXIS_JOY1_V", SDLK_AXIS_JOY1_V }, { "AXIS_JOY2_X", SDLK_AXIS_JOY2_X }, { "AXIS_JOY2_Y", SDLK_AXIS_JOY2_Y }, { "AXIS_JOY2_Z", SDLK_AXIS_JOY2_Z }, { "AXIS_JOY2_R", SDLK_AXIS_JOY2_R }, { "AXIS_JOY2_U", SDLK_AXIS_JOY2_U }, { "AXIS_JOY2_V", SDLK_AXIS_JOY2_V }, { "AXIS_MOUSE_X", SDLK_AXIS_MOUSE_X }, { "AXIS_MOUSE_Y", SDLK_AXIS_MOUSE_Y }, { "AXIS_MOUSE_Z", SDLK_AXIS_MOUSE_Z }, { NULL, 0 } }; // numupper provides conversion between number keys and their "upper case" const char numupper[] = {')', '!', '@', '#', '$', '%', '^', '&', '*', '('}; static int mapKey(SDLKey key, SDLMod mod, Uint16 unicode) { if (key >= SDLK_KP0 && key <= SDLK_KP9) { return key - SDLK_KP0 + '0'; } else if (unicode) { return unicode; } else if (key >= 'a' && key <= 'z' && mod & KMOD_SHIFT) { return key & ~0x20; } else if (key >= SDLK_0 && key <= SDLK_9 && mod & KMOD_SHIFT) { return numupper[key - SDLK_0]; } else if (key > 127) { return 0; } return key; } static byte SDLModToDriverKeyFlags(SDLMod mod) { byte b = 0; if (mod & KMOD_SHIFT) b |= Driver::KBD_SHIFT; if (mod & KMOD_ALT) b |= Driver::KBD_ALT; if (mod & KMOD_CTRL) b |= Driver::KBD_CTRL; return b; } static Uint32 timer_handler(Uint32 interval, void *param) { ((DefaultTimerManager *)param)->handler(); return interval; } Common::TimerManager *DriverSDL::getTimerManager() { assert(_timer); return _timer; } DriverSDL::DriverSDL() { _mixer = NULL; _timer = NULL; } DriverSDL::~DriverSDL() { SDL_RemoveTimer(_timerID); SDL_CloseAudio(); delete _mixer; delete _timer; } void DriverSDL::init() { _timer = new DefaultTimerManager(); _timerID = SDL_AddTimer(10, &timer_handler, _timer); #if !defined(MACOSX) setupIcon(); #endif } const char *DriverSDL::getVideoDeviceName() { return "SDL Video Device"; } const Driver::ControlDescriptor *DriverSDL::listControls() { return controls; } int DriverSDL::getNumControls() { return SDLK_EXTRA_LAST; } bool DriverSDL::controlIsAxis(int num) { return num >= SDLK_AXIS_JOY1_X && num <= SDLK_AXIS_MOUSE_Z; } float DriverSDL::getControlAxis(int num) { return 0; } bool DriverSDL::getControlState(int num) { if (num >= SDLK_JOY1_B1 && num <= SDLK_MOUSE_B4) return false; else { uint8 *keystate = SDL_GetKeyState(NULL); return keystate[num] != 0; } } bool DriverSDL::pollEvent(Event &event) { SDL_Event ev; byte b = 0; while(SDL_PollEvent(&ev)) { if (ev.type == SDL_KEYDOWN && (ev.key.keysym.sym == SDLK_RETURN || ev.key.keysym.sym == SDLK_KP_ENTER) && (ev.key.keysym.mod & KMOD_ALT)) { toggleFullscreenMode(); } switch(ev.type) { case SDL_KEYDOWN:{ event.kbd.flags = SDLModToDriverKeyFlags(SDL_GetModState()); // Alt-Return and Alt-Enter toggle full screen mode if (b == KBD_ALT && (ev.key.keysym.sym == SDLK_RETURN || ev.key.keysym.sym == SDLK_KP_ENTER)) { toggleFullscreenMode(); break; } event.type = EVENT_KEYDOWN; event.kbd.num = ev.key.keysym.sym; event.kbd.ascii = mapKey(ev.key.keysym.sym, ev.key.keysym.mod, ev.key.keysym.unicode); return true; } case SDL_KEYUP: { event.type = EVENT_KEYUP; event.kbd.num = ev.key.keysym.sym; event.kbd.ascii = mapKey(ev.key.keysym.sym, ev.key.keysym.mod, ev.key.keysym.unicode); b = event.kbd.flags = SDLModToDriverKeyFlags(SDL_GetModState()); return true; } case SDL_VIDEOEXPOSE: event.type = EVENT_REFRESH; return true; case SDL_QUIT: event.type = EVENT_QUIT; return true; } } return false; } uint32 DriverSDL::getMillis() { return SDL_GetTicks(); } void DriverSDL::delayMillis(uint msecs) { SDL_Delay(msecs); } Common::MutexRef DriverSDL::createMutex() { return (MutexRef)SDL_CreateMutex(); } void DriverSDL::lockMutex(MutexRef mutex) { SDL_mutexP((SDL_mutex *)mutex); } void DriverSDL::unlockMutex(MutexRef mutex) { SDL_mutexV((SDL_mutex *)mutex); } void DriverSDL::deleteMutex(MutexRef mutex) { SDL_DestroyMutex((SDL_mutex *)mutex); } void DriverSDL::mixCallback(void *sys, byte *samples, int len) { DriverSDL *this_ = (DriverSDL *)sys; assert(this_); if (this_->_mixer) this_->_mixer->mixCallback(samples, len); } void DriverSDL::setupMixer() { SDL_AudioSpec desired; SDL_AudioSpec obtained; // Determine the desired output sampling frequency. _samplesPerSec = 0; if (_samplesPerSec <= 0) _samplesPerSec = SAMPLES_PER_SEC; // Determine the sample buffer size. We want it to store enough data for // about 1/16th of a second. Note that it must be a power of two. // So e.g. at 22050 Hz, we request a sample buffer size of 2048. int samples = 8192; while (16 * samples >= _samplesPerSec) { samples >>= 1; } memset(&desired, 0, sizeof(desired)); desired.freq = _samplesPerSec; desired.format = AUDIO_S16SYS; desired.channels = 2; desired.samples = (uint16)samples; desired.callback = mixCallback; desired.userdata = this; // Create the mixer instance assert(!_mixer); _mixer = new Audio::MixerImpl(); assert(_mixer); if (SDL_OpenAudio(&desired, &obtained) != 0) { warning("Could not open audio device: %s", SDL_GetError()); _samplesPerSec = 0; _mixer->setReady(false); } else { _samplesPerSec = obtained.freq; _mixer->setOutputRate(_samplesPerSec); _mixer->setReady(true); SDL_PauseAudio(0); } } Audio::Mixer *DriverSDL::getMixer() { assert(_mixer); return _mixer; } /* This function sends the SDL signal to * go ahead and exit the game */ void DriverSDL::quit() { SDL_Event event; event.type = SDL_QUIT; if (SDL_PushEvent(&event) != 0) error("Unable to push exit event!"); } void DriverSDL::setupIcon() { int w, h, ncols, nbytes, i; unsigned int rgba[256], icon[32 * 32]; unsigned char mask[32][4]; sscanf(residual_icon[0], "%d %d %d %d", &w, &h, &ncols, &nbytes); if ((w != 32) || (h != 32) || (ncols > 255) || (nbytes > 1)) { warning("Could not load the icon (%d %d %d %d)", w, h, ncols, nbytes); return; } for (i = 0; i < ncols; i++) { unsigned char code; char color[32]; unsigned int col; sscanf(residual_icon[1 + i], "%c c %s", &code, color); if (!strcmp(color, "None")) col = 0x00000000; else if (!strcmp(color, "black")) col = 0xFF000000; else if (color[0] == '#') { sscanf(color + 1, "%06x", &col); col |= 0xFF000000; } else { warning("Could not load the icon (%d %s - %s) ", code, color, residual_icon[1 + i]); return; } rgba[code] = col; } memset(mask, 0, sizeof(mask)); for (h = 0; h < 32; h++) { const char *line = residual_icon[1 + ncols + h]; for (w = 0; w < 32; w++) { icon[w + 32 * h] = rgba[(int)line[w]]; if (rgba[(int)line[w]] & 0xFF000000) { mask[h][w >> 3] |= 1 << (7 - (w & 0x07)); } } } SDL_Surface *sdl_surf = SDL_CreateRGBSurfaceFrom(icon, 32, 32, 32, 32 * 4, 0xFF0000, 0x00FF00, 0x0000FF, 0xFF000000); SDL_WM_SetIcon(sdl_surf, (unsigned char *)mask); SDL_FreeSurface(sdl_surf); } #if defined (WIN32) int __stdcall WinMain(HINSTANCE /*hInst*/, HINSTANCE /*hPrevInst*/, LPSTR /*lpCmdLine*/, int /*iShowCmd*/) { SDL_SetModuleHandle(GetModuleHandle(NULL)); return main(__argc, __argv); } #endif int main(int argc, char *argv[]) { int res = residual_main(argc, argv); return res; }