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" tron@2189: #include "../network.h" tron@2189: #include "../variables.h" tron@2189: #include "sdl_v.h" tron@2174: #include tron@2174: tron@2174: static SDL_Surface *_sdl_screen; tron@2174: static bool _all_modes; tron@2174: tron@2174: #define MAX_DIRTY_RECTS 100 tron@2174: static SDL_Rect _dirty_rects[MAX_DIRTY_RECTS]; tron@2174: static int _num_dirty_rects; tron@2174: tron@2174: static void SdlVideoMakeDirty(int left, int top, int width, int height) tron@2174: { tron@2174: if (_num_dirty_rects < MAX_DIRTY_RECTS) { tron@2174: _dirty_rects[_num_dirty_rects].x = left; tron@2174: _dirty_rects[_num_dirty_rects].y = top; tron@2174: _dirty_rects[_num_dirty_rects].w = width; tron@2174: _dirty_rects[_num_dirty_rects].h = height; tron@2174: } tron@2174: _num_dirty_rects++; tron@2174: } tron@2174: tron@2226: static void UpdatePalette(uint start, uint count) tron@2174: { tron@2226: SDL_Color pal[256]; tron@2174: uint i; tron@2174: 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@2174: pal[i].unused = 0; tron@2174: } tron@2174: tron@2226: SDL_CALL SDL_SetColors(_sdl_screen, pal, start, count); tron@2174: } tron@2174: tron@2174: static void InitPalette(void) tron@2174: { tron@2174: UpdatePalette(0, 256); tron@2174: } tron@2174: tron@2174: static void CheckPaletteAnim(void) tron@2174: { tron@2226: if (_pal_last_dirty != -1) { tron@2226: UpdatePalette(_pal_first_dirty, _pal_last_dirty - _pal_first_dirty + 1); tron@2174: _pal_last_dirty = -1; tron@2174: } tron@2174: } tron@2174: tron@2174: static void DrawSurfaceToScreen(void) tron@2174: { tron@2174: int n = _num_dirty_rects; tron@2174: if (n != 0) { tron@2174: _num_dirty_rects = 0; tron@2174: if (n > MAX_DIRTY_RECTS) tron@2174: SDL_CALL SDL_UpdateRect(_sdl_screen, 0, 0, 0, 0); tron@2174: else tron@2174: SDL_CALL SDL_UpdateRects(_sdl_screen, n, _dirty_rects); tron@2174: } tron@2174: } tron@2174: tron@2174: static const uint16 default_resolutions[][2] = { tron@2174: { 640, 480}, tron@2174: { 800, 600}, tron@2174: {1024, 768}, tron@2174: {1152, 864}, tron@2174: {1280, 800}, tron@2174: {1280, 960}, tron@2174: {1280, 1024}, tron@2174: {1400, 1050}, tron@2174: {1600, 1200}, tron@2174: {1680, 1050}, tron@2174: {1920, 1200} tron@2174: }; tron@2174: tron@2174: static void GetVideoModes(void) tron@2174: { tron@2174: int i; tron@2174: SDL_Rect **modes; tron@2174: tron@2174: modes = SDL_CALL SDL_ListModes(NULL, SDL_SWSURFACE + (_fullscreen ? SDL_FULLSCREEN : 0)); tron@2174: tron@2174: if (modes == NULL) tron@2174: error("sdl: no modes available"); tron@2174: tron@2174: _all_modes = (modes == (void*)-1); tron@2174: tron@2174: if (_all_modes) { tron@2174: // all modes available, put some default ones here tron@2174: memcpy(_resolutions, default_resolutions, sizeof(default_resolutions)); tron@2174: _num_resolutions = lengthof(default_resolutions); tron@2174: } else { tron@2174: int n = 0; tron@2174: for (i = 0; modes[i]; i++) { tron@2174: int w = modes[i]->w; tron@2174: int h = modes[i]->h; tron@2174: if (IS_INT_INSIDE(w, 640, MAX_SCREEN_WIDTH + 1) && tron@2174: IS_INT_INSIDE(h, 480, MAX_SCREEN_HEIGHT + 1)) { tron@2174: int j; tron@2174: for (j = 0; j < n; j++) { tron@2174: if (_resolutions[j][0] == w && _resolutions[j][1] == h) break; tron@2174: } tron@2174: tron@2174: if (j == n) { tron@2174: _resolutions[j][0] = w; tron@2174: _resolutions[j][1] = h; tron@2174: if (++n == lengthof(_resolutions)) break; tron@2174: } tron@2174: } tron@2174: } tron@2174: _num_resolutions = n; tron@2174: SortResolutions(_num_resolutions); tron@2174: } tron@2174: } tron@2174: tron@4501: static void GetAvailableVideoMode(int *w, int *h) tron@2174: { tron@2174: int i; tron@2174: int best; tron@2174: uint delta; tron@2174: tron@2174: // all modes available? tron@4501: if (_all_modes) return; tron@2174: tron@2174: // is the wanted mode among the available modes? tron@2174: for (i = 0; i != _num_resolutions; i++) { tron@4501: if (*w == _resolutions[i][0] && *h == _resolutions[i][1]) return; tron@2174: } tron@2174: tron@2174: // use the closest possible resolution tron@2174: best = 0; tron@2174: delta = abs((_resolutions[0][0] - *w) * (_resolutions[0][1] - *h)); tron@2174: for (i = 1; i != _num_resolutions; ++i) { tron@2174: uint newdelta = abs((_resolutions[i][0] - *w) * (_resolutions[i][1] - *h)); tron@2174: if (newdelta < delta) { tron@2174: best = i; tron@2174: delta = newdelta; tron@2174: } tron@2174: } tron@2174: *w = _resolutions[best][0]; tron@2174: *h = _resolutions[best][1]; tron@2174: } tron@2174: tron@2174: extern const char _openttd_revision[]; tron@2174: 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: tron@2174: static bool CreateMainSurface(int w, int h) tron@2174: { Darkvater@4256: SDL_Surface *newscreen, *icon; tron@2174: char caption[50]; tron@2174: tron@2174: GetAvailableVideoMode(&w, &h); tron@2174: tron@2210: DEBUG(driver, 1) ("sdl: using mode %dx%d", w, h); tron@2174: 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: tron@2174: // DO NOT CHANGE TO HWSURFACE, IT DOES NOT WORK tron@2174: newscreen = SDL_CALL SDL_SetVideoMode(w, h, 8, SDL_SWSURFACE | SDL_HWPALETTE | (_fullscreen ? SDL_FULLSCREEN : SDL_RESIZABLE)); tron@2174: if (newscreen == NULL) tron@2174: return false; tron@2174: tron@2174: _screen.width = newscreen->w; tron@2174: _screen.height = newscreen->h; tron@2174: _screen.pitch = newscreen->pitch; tron@2174: tron@2174: _sdl_screen = newscreen; tron@2174: InitPalette(); tron@2174: tron@2174: snprintf(caption, sizeof(caption), "OpenTTD %s", _openttd_revision); tron@2174: SDL_CALL SDL_WM_SetCaption(caption, caption); tron@2174: SDL_CALL SDL_ShowCursor(0); tron@2174: tron@2174: GameSizeChanged(); tron@2174: tron@2174: return true; tron@2174: } tron@2174: tron@2174: typedef struct VkMapping { tron@2174: uint16 vk_from; tron@2174: byte vk_count; tron@2174: byte map_to; tron@2174: } VkMapping; tron@2174: tron@2174: #define AS(x, z) {x, 0, z} tron@2174: #define AM(x, y, z, w) {x, y - x, z} tron@2174: tron@2174: static const VkMapping _vk_mapping[] = { tron@2174: // Pageup stuff + up/down tron@2174: 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), tron@2174: rubidium@4434: AS(SDLK_HOME, WKC_HOME), rubidium@4434: AS(SDLK_END, WKC_END), tron@2174: rubidium@4434: AS(SDLK_INSERT, WKC_INSERT), rubidium@4434: AS(SDLK_DELETE, WKC_DELETE), tron@2174: tron@2174: // Map letters & digits tron@2174: AM(SDLK_a, SDLK_z, 'A', 'Z'), tron@2174: AM(SDLK_0, SDLK_9, '0', '9'), tron@2174: rubidium@4434: AS(SDLK_ESCAPE, WKC_ESC), rubidium@4434: AS(SDLK_PAUSE, WKC_PAUSE), rubidium@4434: AS(SDLK_BACKSPACE, WKC_BACKSPACE), tron@2174: rubidium@4434: AS(SDLK_SPACE, WKC_SPACE), rubidium@4434: AS(SDLK_RETURN, WKC_RETURN), rubidium@4434: AS(SDLK_TAB, WKC_TAB), tron@2174: tron@2174: // Function keys tron@2174: AM(SDLK_F1, SDLK_F12, WKC_F1, WKC_F12), tron@2174: tron@2174: // Numeric part. tron@2174: // What is the virtual keycode for numeric enter?? tron@2174: 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) tron@2174: }; tron@2174: tron@2174: static uint32 ConvertSdlKeyIntoMy(SDL_keysym *sym) tron@2174: { tron@2174: const VkMapping *map; tron@2174: uint key = 0; tron@4507: tron@2174: for (map = _vk_mapping; map != endof(_vk_mapping); ++map) { tron@2174: if ((uint)(sym->sym - map->vk_from) <= map->vk_count) { tron@2174: key = sym->sym - map->vk_from + map->map_to; tron@2174: break; tron@2174: } tron@2174: } tron@2174: tron@2174: // check scancode for BACKQUOTE key, because we want the key left of "1", not anything else (on non-US keyboards) tron@2174: #if defined(WIN32) || defined(__OS2__) tron@2174: if (sym->scancode == 41) key |= WKC_BACKQUOTE; tron@2174: #elif defined(__APPLE__) tron@2174: if (sym->scancode == 10) key |= WKC_BACKQUOTE; tron@2174: #elif defined(__MORPHOS__) tron@2174: if (sym->scancode == 0) key |= WKC_BACKQUOTE; // yes, that key is code '0' under MorphOS :) tron@2174: #elif defined(__BEOS__) tron@2174: if (sym->scancode == 17) key |= WKC_BACKQUOTE; tron@2174: #elif defined(__SVR4) && defined(__sun) tron@2174: if (sym->scancode == 60) key |= WKC_BACKQUOTE; tron@2174: if (sym->scancode == 49) key |= WKC_BACKSPACE; tron@2174: #elif defined(__sgi__) tron@2174: if (sym->scancode == 22) key |= WKC_BACKQUOTE; tron@2174: #else tron@2174: if (sym->scancode == 41) key |= WKC_BACKQUOTE; // Linux console tron@2174: if (sym->scancode == 49) key |= WKC_BACKQUOTE; tron@2174: #endif tron@2174: tron@2174: // META are the command keys on mac tron@4507: if (sym->mod & KMOD_META) key |= WKC_META; tron@2174: 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@2174: // these two lines really help porting hotkey combos. Uncomment to use -- Bjarni tron@4507: #if 0 tron@4507: printf("scancode character pressed %d\n", sym->scancode); tron@4507: printf("unicode character pressed %d\n", sym->unicode); tron@4507: #endif tron@2174: return (key << 16) + sym->unicode; tron@2174: } tron@2174: tron@2174: static int PollEvent(void) tron@2174: { tron@2174: SDL_Event ev; tron@2174: tron@4507: if (!SDL_CALL SDL_PollEvent(&ev)) return -2; tron@2174: tron@2174: 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; tron@2174: } tron@4507: break; tron@2174: 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; tron@2174: tron@4507: case SDL_BUTTON_RIGHT: tron@4507: _right_button_down = true; tron@4507: _right_button_clicked = true; tron@4507: break; tron@2174: tron@4507: case SDL_BUTTON_WHEELUP: _cursor.wheel--; break; tron@4507: case SDL_BUTTON_WHEELDOWN: _cursor.wheel++; break; tron@2174: tron@4507: default: break; tron@4507: } 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: } 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 { tron@4507: _pressed_key = ConvertSdlKeyIntoMy(&ev.key.keysym); tron@4507: } 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: } tron@2174: } tron@2174: return -1; tron@2174: } tron@2174: tron@2174: static const char *SdlVideoStart(const char * const *parm) tron@2174: { tron@2174: char buf[30]; tron@2174: tron@2174: const char *s = SdlOpen(SDL_INIT_VIDEO); tron@2174: if (s != NULL) return s; tron@2174: tron@2174: SDL_CALL SDL_VideoDriverName(buf, 30); tron@2210: DEBUG(driver, 1) ("sdl: using driver '%s'", buf); tron@2174: tron@2174: GetVideoModes(); tron@2174: CreateMainSurface(_cur_resolution[0], _cur_resolution[1]); tron@2174: MarkWholeScreenDirty(); tron@2174: tron@2174: SDL_CALL SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); tron@2174: SDL_CALL SDL_EnableUNICODE(1); tron@2174: return NULL; tron@2174: } tron@2174: tron@2174: static void SdlVideoStop(void) tron@2174: { tron@2174: SdlClose(SDL_INIT_VIDEO); tron@2174: } tron@2174: tron@2228: static void SdlVideoMainLoop(void) tron@2174: { tron@2174: uint32 next_tick = SDL_CALL SDL_GetTicks() + 30; tron@2174: uint32 cur_ticks; tron@2174: uint32 pal_tick = 0; tron@2174: int i; tron@2174: uint32 mod; tron@2174: int numkeys; tron@2174: Uint8 *keys; tron@2174: tron@2174: for (;;) { tron@2174: InteractiveRandom(); // randomness tron@2174: tron@2174: while ((i = PollEvent()) == -1) {} tron@2228: if (_exit_game) return; tron@2174: tron@2174: mod = SDL_CALL SDL_GetModState(); tron@2174: keys = SDL_CALL SDL_GetKeyState(&numkeys); tron@2174: #if defined(_DEBUG) tron@2174: if (_shift_pressed) tron@2174: #else tron@2174: if (keys[SDLK_TAB]) tron@2174: #endif tron@2174: { rubidium@4536: if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2; tron@2174: } else if (_fast_forward & 2) { tron@2174: _fast_forward = 0; tron@2174: } tron@2174: tron@2174: cur_ticks = SDL_CALL SDL_GetTicks(); tron@2174: if ((_fast_forward && !_pause) || cur_ticks > next_tick) tron@2174: next_tick = cur_ticks; tron@2174: tron@2174: if (cur_ticks == next_tick) { tron@2174: next_tick += 30; tron@2174: tron@4508: _ctrl_pressed = !!(mod & KMOD_CTRL); tron@4508: _shift_pressed = !!(mod & KMOD_SHIFT); tron@2664: #ifdef _DEBUG tron@2174: _dbg_screen_rect = !!(mod & KMOD_CAPS); tron@2664: #endif tron@2174: tron@2174: // determine which directional keys are down tron@2174: _dirkeys = tron@2174: (keys[SDLK_LEFT] ? 1 : 0) | tron@2174: (keys[SDLK_UP] ? 2 : 0) | tron@2174: (keys[SDLK_RIGHT] ? 4 : 0) | tron@2174: (keys[SDLK_DOWN] ? 8 : 0); tron@2174: GameLoop(); tron@2174: tron@2174: _screen.dst_ptr = _sdl_screen->pixels; tron@2174: UpdateWindows(); tron@2174: if (++pal_tick > 4) { tron@2174: CheckPaletteAnim(); tron@2174: pal_tick = 1; tron@2174: } tron@2174: DrawSurfaceToScreen(); tron@2174: } else { tron@2174: SDL_CALL SDL_Delay(1); tron@2174: _screen.dst_ptr = _sdl_screen->pixels; tron@2174: DrawTextMessage(); tron@2174: DrawMouseCursor(); tron@2174: DrawSurfaceToScreen(); tron@2174: } tron@2174: } tron@2174: } tron@2174: tron@2174: static bool SdlVideoChangeRes(int w, int h) tron@2174: { tron@2174: return CreateMainSurface(w, h); tron@2174: } tron@2174: tron@2174: static void SdlVideoFullScreen(bool full_screen) tron@2174: { tron@2174: _fullscreen = full_screen; tron@2174: GetVideoModes(); // get the list of available video modes tron@4507: if (!_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: } tron@2174: } tron@2174: tron@2174: const HalVideoDriver _sdl_video_driver = { tron@2174: SdlVideoStart, tron@2174: SdlVideoStop, tron@2174: SdlVideoMakeDirty, tron@2174: SdlVideoMainLoop, tron@2174: SdlVideoChangeRes, tron@2174: SdlVideoFullScreen, tron@2174: }; tron@2189: tron@2189: #endif