tron@2186: /* $Id$ */ tron@2186: tron@2189: #include "../stdafx.h" tron@2189: tron@2189: #ifdef WITH_SDL tron@2189: tron@2189: #include "../openttd.h" tron@2189: #include "../debug.h" tron@2189: #include "../functions.h" tron@2189: #include "../gfx.h" tron@2189: #include "../macros.h" tron@2189: #include "../sdl.h" tron@2189: #include "../window.h" celestar@5642: #include "../network/network.h" tron@2189: #include "../variables.h" tron@2189: #include "sdl_v.h" truelight@0: #include truelight@0: truelight@0: static SDL_Surface *_sdl_screen; truelight@0: static bool _all_modes; truelight@0: truelight@0: #define MAX_DIRTY_RECTS 100 truelight@0: static SDL_Rect _dirty_rects[MAX_DIRTY_RECTS]; truelight@0: static int _num_dirty_rects; truelight@0: truelight@0: static void SdlVideoMakeDirty(int left, int top, int width, int height) truelight@0: { truelight@0: if (_num_dirty_rects < MAX_DIRTY_RECTS) { truelight@0: _dirty_rects[_num_dirty_rects].x = left; truelight@0: _dirty_rects[_num_dirty_rects].y = top; truelight@0: _dirty_rects[_num_dirty_rects].w = width; truelight@0: _dirty_rects[_num_dirty_rects].h = height; truelight@0: } truelight@0: _num_dirty_rects++; truelight@0: } truelight@0: tron@2226: static void UpdatePalette(uint start, uint count) tron@423: { tron@2226: SDL_Color pal[256]; truelight@0: uint i; truelight@0: tron@2226: for (i = 0; i != count; i++) { tron@2226: pal[i].r = _cur_palette[start + i].r; tron@2226: pal[i].g = _cur_palette[start + i].g; tron@2226: pal[i].b = _cur_palette[start + i].b; tron@1991: pal[i].unused = 0; truelight@0: } truelight@0: tron@2226: SDL_CALL SDL_SetColors(_sdl_screen, pal, start, count); truelight@0: } truelight@0: tron@423: static void InitPalette(void) tron@423: { truelight@0: UpdatePalette(0, 256); truelight@0: } truelight@0: tron@423: static void CheckPaletteAnim(void) truelight@0: { tron@2226: if (_pal_last_dirty != -1) { tron@2226: UpdatePalette(_pal_first_dirty, _pal_last_dirty - _pal_first_dirty + 1); truelight@0: _pal_last_dirty = -1; truelight@0: } dominik@18: } dominik@18: tron@423: static void DrawSurfaceToScreen(void) dominik@18: { tron@423: int n = _num_dirty_rects; tron@423: if (n != 0) { truelight@0: _num_dirty_rects = 0; truelight@0: if (n > MAX_DIRTY_RECTS) truelight@0: SDL_CALL SDL_UpdateRect(_sdl_screen, 0, 0, 0, 0); tron@423: else truelight@0: SDL_CALL SDL_UpdateRects(_sdl_screen, n, _dirty_rects); truelight@0: } truelight@0: } truelight@0: truelight@0: static const uint16 default_resolutions[][2] = { tron@423: { 640, 480}, tron@423: { 800, 600}, tron@423: {1024, 768}, tron@423: {1152, 864}, darkvater@1028: {1280, 800}, tron@423: {1280, 960}, tron@423: {1280, 1024}, tron@423: {1400, 1050}, darkvater@1028: {1600, 1200}, darkvater@1028: {1680, 1050}, darkvater@1028: {1920, 1200} truelight@0: }; truelight@0: tron@423: static void GetVideoModes(void) tron@423: { truelight@0: int i; truelight@0: SDL_Rect **modes; truelight@0: truelight@0: modes = SDL_CALL SDL_ListModes(NULL, SDL_SWSURFACE + (_fullscreen ? SDL_FULLSCREEN : 0)); truelight@0: tron@423: if (modes == NULL) darkvater@245: error("sdl: no modes available"); dominik@119: darkvater@245: _all_modes = (modes == (void*)-1); darkvater@245: darkvater@245: if (_all_modes) { truelight@0: // all modes available, put some default ones here truelight@0: memcpy(_resolutions, default_resolutions, sizeof(default_resolutions)); darkvater@245: _num_resolutions = lengthof(default_resolutions); truelight@0: } else { dominik@128: int n = 0; tron@423: for (i = 0; modes[i]; i++) { dominik@128: int w = modes[i]->w; dominik@128: int h = modes[i]->h; darkvater@306: if (IS_INT_INSIDE(w, 640, MAX_SCREEN_WIDTH + 1) && darkvater@306: IS_INT_INSIDE(h, 480, MAX_SCREEN_HEIGHT + 1)) { dominik@128: int j; Darkvater@1806: for (j = 0; j < n; j++) { Darkvater@1806: if (_resolutions[j][0] == w && _resolutions[j][1] == h) break; Darkvater@1806: } Darkvater@1806: dominik@128: if (j == n) { Darkvater@1806: _resolutions[j][0] = w; Darkvater@1806: _resolutions[j][1] = h; darkvater@245: if (++n == lengthof(_resolutions)) break; dominik@128: } truelight@0: } truelight@0: } truelight@0: _num_resolutions = n; Darkvater@1806: SortResolutions(_num_resolutions); truelight@0: } truelight@0: } truelight@0: tron@4501: static void GetAvailableVideoMode(int *w, int *h) truelight@0: { truelight@0: int i; darkvater@306: int best; darkvater@306: uint delta; truelight@0: truelight@0: // all modes available? tron@4501: if (_all_modes) return; truelight@0: truelight@0: // is the wanted mode among the available modes? darkvater@306: for (i = 0; i != _num_resolutions; i++) { tron@4501: if (*w == _resolutions[i][0] && *h == _resolutions[i][1]) return; truelight@0: } truelight@0: darkvater@306: // use the closest possible resolution darkvater@306: best = 0; darkvater@306: delta = abs((_resolutions[0][0] - *w) * (_resolutions[0][1] - *h)); darkvater@306: for (i = 1; i != _num_resolutions; ++i) { darkvater@306: uint newdelta = abs((_resolutions[i][0] - *w) * (_resolutions[i][1] - *h)); darkvater@306: if (newdelta < delta) { darkvater@306: best = i; darkvater@306: delta = newdelta; darkvater@306: } darkvater@306: } darkvater@306: *w = _resolutions[best][0]; darkvater@306: *h = _resolutions[best][1]; truelight@0: } truelight@0: Darkvater@4256: #ifndef ICON_DIR Darkvater@4256: #define ICON_DIR "media" Darkvater@4256: #endif Darkvater@4256: Darkvater@4256: #ifdef WIN32 Darkvater@4256: /* Let's redefine the LoadBMP macro with because we are dynamically Darkvater@4256: * loading SDL and need to 'SDL_CALL' all functions */ Darkvater@4256: #undef SDL_LoadBMP Darkvater@4256: #define SDL_LoadBMP(file) SDL_LoadBMP_RW(SDL_CALL SDL_RWFromFile(file, "rb"), 1) Darkvater@4256: #endif Darkvater@4256: truelight@0: static bool CreateMainSurface(int w, int h) truelight@0: { Darkvater@4840: extern const char _openttd_revision[]; Darkvater@4256: SDL_Surface *newscreen, *icon; Darkvater@1474: char caption[50]; truelight@0: truelight@0: GetAvailableVideoMode(&w, &h); truelight@0: Darkvater@5568: DEBUG(driver, 1, "SDL: using mode %dx%d", w, h); truelight@0: Darkvater@4256: /* Give the application an icon */ Darkvater@4256: icon = SDL_CALL SDL_LoadBMP(ICON_DIR PATHSEP "openttd.32.bmp"); Darkvater@4256: if (icon != NULL) { Darkvater@4256: /* Get the colourkey, which will be magenta */ Darkvater@4256: uint32 rgbmap = SDL_CALL SDL_MapRGB(icon->format, 255, 0, 255); tron@4507: Darkvater@4256: SDL_CALL SDL_SetColorKey(icon, SDL_SRCCOLORKEY, rgbmap); Darkvater@4256: SDL_CALL SDL_WM_SetIcon(icon, NULL); Darkvater@4256: SDL_CALL SDL_FreeSurface(icon); Darkvater@4256: } Darkvater@4256: darkvater@36: // DO NOT CHANGE TO HWSURFACE, IT DOES NOT WORK tron@423: newscreen = SDL_CALL SDL_SetVideoMode(w, h, 8, SDL_SWSURFACE | SDL_HWPALETTE | (_fullscreen ? SDL_FULLSCREEN : SDL_RESIZABLE)); tron@423: if (newscreen == NULL) truelight@0: return false; truelight@0: darkvater@306: _screen.width = newscreen->w; darkvater@306: _screen.height = newscreen->h; darkvater@306: _screen.pitch = newscreen->pitch; darkvater@306: truelight@0: _sdl_screen = newscreen; truelight@0: InitPalette(); truelight@0: Darkvater@1474: snprintf(caption, sizeof(caption), "OpenTTD %s", _openttd_revision); dominik@902: SDL_CALL SDL_WM_SetCaption(caption, caption); truelight@0: SDL_CALL SDL_ShowCursor(0); truelight@0: darkvater@306: GameSizeChanged(); truelight@0: truelight@0: return true; truelight@0: } truelight@0: tron@423: typedef struct VkMapping { truelight@0: uint16 vk_from; truelight@0: byte vk_count; truelight@0: byte map_to; truelight@0: } VkMapping; truelight@0: tron@423: #define AS(x, z) {x, 0, z} tron@423: #define AM(x, y, z, w) {x, y - x, z} truelight@0: truelight@0: static const VkMapping _vk_mapping[] = { truelight@0: // Pageup stuff + up/down tron@423: AM(SDLK_PAGEUP, SDLK_PAGEDOWN, WKC_PAGEUP, WKC_PAGEDOWN), rubidium@4434: AS(SDLK_UP, WKC_UP), rubidium@4434: AS(SDLK_DOWN, WKC_DOWN), rubidium@4434: AS(SDLK_LEFT, WKC_LEFT), rubidium@4434: AS(SDLK_RIGHT, WKC_RIGHT), truelight@0: rubidium@4434: AS(SDLK_HOME, WKC_HOME), rubidium@4434: AS(SDLK_END, WKC_END), truelight@0: rubidium@4434: AS(SDLK_INSERT, WKC_INSERT), rubidium@4434: AS(SDLK_DELETE, WKC_DELETE), truelight@0: truelight@0: // Map letters & digits tron@423: AM(SDLK_a, SDLK_z, 'A', 'Z'), tron@423: AM(SDLK_0, SDLK_9, '0', '9'), truelight@0: rubidium@4434: AS(SDLK_ESCAPE, WKC_ESC), rubidium@4434: AS(SDLK_PAUSE, WKC_PAUSE), rubidium@4434: AS(SDLK_BACKSPACE, WKC_BACKSPACE), truelight@0: rubidium@4434: AS(SDLK_SPACE, WKC_SPACE), rubidium@4434: AS(SDLK_RETURN, WKC_RETURN), rubidium@4434: AS(SDLK_TAB, WKC_TAB), truelight@0: truelight@0: // Function keys tron@423: AM(SDLK_F1, SDLK_F12, WKC_F1, WKC_F12), truelight@0: truelight@0: // Numeric part. truelight@0: // What is the virtual keycode for numeric enter?? tron@423: AM(SDLK_KP0, SDLK_KP9, WKC_NUM_0, WKC_NUM_9), rubidium@4434: AS(SDLK_KP_DIVIDE, WKC_NUM_DIV), rubidium@4434: AS(SDLK_KP_MULTIPLY, WKC_NUM_MUL), rubidium@4434: AS(SDLK_KP_MINUS, WKC_NUM_MINUS), rubidium@4434: AS(SDLK_KP_PLUS, WKC_NUM_PLUS), rubidium@4434: AS(SDLK_KP_ENTER, WKC_NUM_ENTER), rubidium@4434: AS(SDLK_KP_PERIOD, WKC_NUM_DECIMAL) truelight@0: }; truelight@0: truelight@0: static uint32 ConvertSdlKeyIntoMy(SDL_keysym *sym) truelight@0: { tron@2026: const VkMapping *map; tron@423: uint key = 0; tron@4507: tron@423: for (map = _vk_mapping; map != endof(_vk_mapping); ++map) { tron@423: if ((uint)(sym->sym - map->vk_from) <= map->vk_count) { tron@423: key = sym->sym - map->vk_from + map->map_to; truelight@0: break; truelight@0: } truelight@0: } dominik@129: dominik@129: // check scancode for BACKQUOTE key, because we want the key left of "1", not anything else (on non-US keyboards) truelight@810: #if defined(WIN32) || defined(__OS2__) Darkvater@5099: if (sym->scancode == 41) key = WKC_BACKQUOTE; tron@423: #elif defined(__APPLE__) Darkvater@5099: if (sym->scancode == 10) key = WKC_BACKQUOTE; tron@423: #elif defined(__MORPHOS__) Darkvater@5099: if (sym->scancode == 0) key = WKC_BACKQUOTE; // yes, that key is code '0' under MorphOS :) tron@435: #elif defined(__BEOS__) Darkvater@5099: if (sym->scancode == 17) key = WKC_BACKQUOTE; bjarni@570: #elif defined(__SVR4) && defined(__sun) Darkvater@5099: if (sym->scancode == 60) key = WKC_BACKQUOTE; Darkvater@5099: if (sym->scancode == 49) key = WKC_BACKSPACE; Darkvater@1810: #elif defined(__sgi__) Darkvater@5099: if (sym->scancode == 22) key = WKC_BACKQUOTE; darkvater@135: #else Darkvater@5099: if (sym->scancode == 49) key = WKC_BACKQUOTE; darkvater@135: #endif bjarni@572: truelight@0: // META are the command keys on mac tron@4507: if (sym->mod & KMOD_META) key |= WKC_META; truelight@0: if (sym->mod & KMOD_SHIFT) key |= WKC_SHIFT; tron@4507: if (sym->mod & KMOD_CTRL) key |= WKC_CTRL; tron@4507: if (sym->mod & KMOD_ALT) key |= WKC_ALT; tron@423: // these two lines really help porting hotkey combos. Uncomment to use -- Bjarni tron@4507: #if 0 Darkvater@5568: DEBUG(driver, 0, "Scancode character pressed %u", sym->scancode); Darkvater@5568: DEBUG(driver, 0, "Unicode character pressed %u", sym->unicode); tron@4507: #endif truelight@0: return (key << 16) + sym->unicode; truelight@0: } truelight@0: tron@423: static int PollEvent(void) tron@423: { truelight@0: SDL_Event ev; truelight@0: tron@4507: if (!SDL_CALL SDL_PollEvent(&ev)) return -2; truelight@0: tron@423: switch (ev.type) { tron@4507: case SDL_MOUSEMOTION: tron@4507: if (_cursor.fix_at) { tron@4507: int dx = ev.motion.x - _cursor.pos.x; tron@4507: int dy = ev.motion.y - _cursor.pos.y; tron@4507: if (dx != 0 || dy != 0) { tron@4507: _cursor.delta.x += dx; tron@4507: _cursor.delta.y += dy; tron@4507: SDL_CALL SDL_WarpMouse(_cursor.pos.x, _cursor.pos.y); tron@4507: } tron@4507: } else { tron@4507: _cursor.delta.x = ev.motion.x - _cursor.pos.x; tron@4507: _cursor.delta.y = ev.motion.y - _cursor.pos.y; tron@4507: _cursor.pos.x = ev.motion.x; tron@4507: _cursor.pos.y = ev.motion.y; tron@4507: _cursor.dirty = true; truelight@0: } Darkvater@5090: HandleMouseEvents(); tron@4507: break; truelight@0: tron@4507: case SDL_MOUSEBUTTONDOWN: tron@4508: if (_rightclick_emulate && SDL_CALL SDL_GetModState() & KMOD_CTRL) { tron@4507: ev.button.button = SDL_BUTTON_RIGHT; tron@4507: } Darkvater@3312: tron@4507: switch (ev.button.button) { tron@4507: case SDL_BUTTON_LEFT: tron@4507: _left_button_down = true; tron@4507: break; truelight@0: tron@4507: case SDL_BUTTON_RIGHT: tron@4507: _right_button_down = true; tron@4507: _right_button_clicked = true; tron@4507: break; Darkvater@1806: tron@4507: case SDL_BUTTON_WHEELUP: _cursor.wheel--; break; tron@4507: case SDL_BUTTON_WHEELDOWN: _cursor.wheel++; break; truelight@193: tron@4507: default: break; tron@4507: } Darkvater@5090: HandleMouseEvents(); tron@4507: break; tron@4507: tron@4507: case SDL_MOUSEBUTTONUP: tron@4507: if (_rightclick_emulate) { tron@4507: _right_button_down = false; tron@4507: _left_button_down = false; tron@4507: _left_button_clicked = false; tron@4507: } else if (ev.button.button == SDL_BUTTON_LEFT) { tron@4507: _left_button_down = false; tron@4507: _left_button_clicked = false; tron@4507: } else if (ev.button.button == SDL_BUTTON_RIGHT) { tron@4507: _right_button_down = false; tron@4507: } Darkvater@5090: HandleMouseEvents(); tron@4507: break; tron@4507: tron@4507: case SDL_ACTIVEEVENT: tron@4507: if (!(ev.active.state & SDL_APPMOUSEFOCUS)) break; tron@4507: tron@4507: if (ev.active.gain) { // mouse entered the window, enable cursor tron@4507: _cursor.in_window = true; tron@4507: } else { tron@4507: UndrawMouseCursor(); // mouse left the window, undraw cursor tron@4507: _cursor.in_window = false; tron@4507: } tron@4507: break; tron@4507: rubidium@4548: case SDL_QUIT: HandleExitGameRequest(); break; tron@4507: rubidium@4548: case SDL_KEYDOWN: /* Toggle full-screen on ALT + ENTER/F */ tron@4507: if ((ev.key.keysym.mod & (KMOD_ALT | KMOD_META)) && tron@4507: (ev.key.keysym.sym == SDLK_RETURN || ev.key.keysym.sym == SDLK_f)) { tron@4507: ToggleFullScreen(!_fullscreen); tron@4507: } else { Darkvater@5086: HandleKeypress(ConvertSdlKeyIntoMy(&ev.key.keysym)); tron@4507: } tron@4507: break; tron@4507: tron@4507: case SDL_VIDEORESIZE: { tron@4507: int w = clamp(ev.resize.w, 64, MAX_SCREEN_WIDTH); tron@4507: int h = clamp(ev.resize.h, 64, MAX_SCREEN_HEIGHT); tron@4507: ChangeResInGame(w, h); tron@4507: break; tron@4507: } truelight@0: } truelight@0: return -1; truelight@0: } truelight@0: tron@1301: static const char *SdlVideoStart(const char * const *parm) truelight@0: { tron@423: char buf[30]; truelight@0: tron@423: const char *s = SdlOpen(SDL_INIT_VIDEO); tron@423: if (s != NULL) return s; truelight@0: truelight@0: SDL_CALL SDL_VideoDriverName(buf, 30); Darkvater@5568: DEBUG(driver, 1, "SDL: using driver '%s'", buf); truelight@0: truelight@0: GetVideoModes(); truelight@0: CreateMainSurface(_cur_resolution[0], _cur_resolution[1]); truelight@0: MarkWholeScreenDirty(); truelight@0: truelight@0: SDL_CALL SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); truelight@0: SDL_CALL SDL_EnableUNICODE(1); truelight@0: return NULL; truelight@0: } truelight@0: tron@423: static void SdlVideoStop(void) truelight@0: { truelight@0: SdlClose(SDL_INIT_VIDEO); truelight@0: } truelight@0: tron@2228: static void SdlVideoMainLoop(void) truelight@0: { celestar@5648: uint32 cur_ticks = SDL_CALL SDL_GetTicks(); celestar@5648: uint32 next_tick = cur_ticks + 30; tron@423: uint32 pal_tick = 0; truelight@0: uint32 mod; truelight@0: int numkeys; truelight@0: Uint8 *keys; truelight@0: tron@423: for (;;) { celestar@5648: uint32 prev_cur_ticks = cur_ticks; // to check for wrapping truelight@0: InteractiveRandom(); // randomness truelight@193: Darkvater@5089: while (PollEvent() == -1) {} tron@2228: if (_exit_game) return; truelight@0: dominik@18: mod = SDL_CALL SDL_GetModState(); truelight@193: keys = SDL_CALL SDL_GetKeyState(&numkeys); truelight@0: #if defined(_DEBUG) tron@423: if (_shift_pressed) truelight@0: #else celestar@5642: /* Speedup when pressing tab, except when using ALT+TAB celestar@5642: * to switch to another application */ celestar@5642: if (keys[SDLK_TAB] && (mod & KMOD_ALT) == 0) truelight@0: #endif tron@423: { rubidium@4536: if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2; tron@423: } else if (_fast_forward & 2) { truelight@0: _fast_forward = 0; truelight@0: } truelight@0: tron@423: cur_ticks = SDL_CALL SDL_GetTicks(); celestar@5648: if (cur_ticks >= next_tick || (_fast_forward && !_pause) || cur_ticks < prev_cur_ticks) { celestar@5648: next_tick = cur_ticks + 30; dominik@18: tron@4508: _ctrl_pressed = !!(mod & KMOD_CTRL); tron@4508: _shift_pressed = !!(mod & KMOD_SHIFT); tron@2664: #ifdef _DEBUG dominik@302: _dbg_screen_rect = !!(mod & KMOD_CAPS); tron@2664: #endif truelight@193: dominik@18: // determine which directional keys are down truelight@193: _dirkeys = tron@423: (keys[SDLK_LEFT] ? 1 : 0) | tron@423: (keys[SDLK_UP] ? 2 : 0) | tron@423: (keys[SDLK_RIGHT] ? 4 : 0) | tron@423: (keys[SDLK_DOWN] ? 8 : 0); dominik@18: GameLoop(); dominik@18: celestar@5650: _screen.dst_ptr = (Pixel*)_sdl_screen->pixels; dominik@18: UpdateWindows(); tron@423: if (++pal_tick > 4) { tron@423: CheckPaletteAnim(); tron@423: pal_tick = 1; truelight@0: } tron@2026: DrawSurfaceToScreen(); truelight@0: } else { dominik@18: SDL_CALL SDL_Delay(1); celestar@5650: _screen.dst_ptr = (Pixel*)_sdl_screen->pixels; truelight@543: DrawTextMessage(); dominik@18: DrawMouseCursor(); dominik@18: DrawSurfaceToScreen(); truelight@0: } truelight@0: } truelight@0: } truelight@0: truelight@0: static bool SdlVideoChangeRes(int w, int h) truelight@0: { tron@2169: return CreateMainSurface(w, h); truelight@0: } truelight@0: Darkvater@1829: static void SdlVideoFullScreen(bool full_screen) Darkvater@1806: { Darkvater@1808: _fullscreen = full_screen; Darkvater@1806: GetVideoModes(); // get the list of available video modes rubidium@5217: if (_num_resolutions == 0 || !_video_driver->change_resolution(_cur_resolution[0], _cur_resolution[1])) { tron@4507: // switching resolution failed, put back full_screen to original status tron@4507: _fullscreen ^= true; tron@4507: } Darkvater@1806: } Darkvater@1806: truelight@0: const HalVideoDriver _sdl_video_driver = { truelight@0: SdlVideoStart, truelight@0: SdlVideoStop, truelight@0: SdlVideoMakeDirty, truelight@0: SdlVideoMainLoop, truelight@0: SdlVideoChangeRes, Darkvater@1829: SdlVideoFullScreen, truelight@0: }; tron@2189: tron@2189: #endif