|
1 /* $Id$ */ |
|
2 |
|
3 #include "../stdafx.h" |
|
4 |
|
5 #ifdef WITH_SDL |
|
6 |
|
7 #include "../openttd.h" |
|
8 #include "../debug.h" |
|
9 #include "../functions.h" |
|
10 #include "../gfx.h" |
|
11 #include "../macros.h" |
|
12 #include "../sdl.h" |
|
13 #include "../window.h" |
|
14 #include "../network/network.h" |
|
15 #include "../variables.h" |
|
16 #include "sdl_v.h" |
|
17 #include <SDL.h> |
|
18 |
|
19 static SDL_Surface *_sdl_screen; |
|
20 static bool _all_modes; |
|
21 |
|
22 #define MAX_DIRTY_RECTS 100 |
|
23 static SDL_Rect _dirty_rects[MAX_DIRTY_RECTS]; |
|
24 static int _num_dirty_rects; |
|
25 |
|
26 static void SdlVideoMakeDirty(int left, int top, int width, int height) |
|
27 { |
|
28 if (_num_dirty_rects < MAX_DIRTY_RECTS) { |
|
29 _dirty_rects[_num_dirty_rects].x = left; |
|
30 _dirty_rects[_num_dirty_rects].y = top; |
|
31 _dirty_rects[_num_dirty_rects].w = width; |
|
32 _dirty_rects[_num_dirty_rects].h = height; |
|
33 } |
|
34 _num_dirty_rects++; |
|
35 } |
|
36 |
|
37 static void UpdatePalette(uint start, uint count) |
|
38 { |
|
39 SDL_Color pal[256]; |
|
40 uint i; |
|
41 |
|
42 for (i = 0; i != count; i++) { |
|
43 pal[i].r = _cur_palette[start + i].r; |
|
44 pal[i].g = _cur_palette[start + i].g; |
|
45 pal[i].b = _cur_palette[start + i].b; |
|
46 pal[i].unused = 0; |
|
47 } |
|
48 |
|
49 SDL_CALL SDL_SetColors(_sdl_screen, pal, start, count); |
|
50 } |
|
51 |
|
52 static void InitPalette(void) |
|
53 { |
|
54 UpdatePalette(0, 256); |
|
55 } |
|
56 |
|
57 static void CheckPaletteAnim(void) |
|
58 { |
|
59 if (_pal_last_dirty != -1) { |
|
60 UpdatePalette(_pal_first_dirty, _pal_last_dirty - _pal_first_dirty + 1); |
|
61 _pal_last_dirty = -1; |
|
62 } |
|
63 } |
|
64 |
|
65 static void DrawSurfaceToScreen(void) |
|
66 { |
|
67 int n = _num_dirty_rects; |
|
68 if (n != 0) { |
|
69 _num_dirty_rects = 0; |
|
70 if (n > MAX_DIRTY_RECTS) |
|
71 SDL_CALL SDL_UpdateRect(_sdl_screen, 0, 0, 0, 0); |
|
72 else |
|
73 SDL_CALL SDL_UpdateRects(_sdl_screen, n, _dirty_rects); |
|
74 } |
|
75 } |
|
76 |
|
77 static const uint16 default_resolutions[][2] = { |
|
78 { 640, 480}, |
|
79 { 800, 600}, |
|
80 {1024, 768}, |
|
81 {1152, 864}, |
|
82 {1280, 800}, |
|
83 {1280, 960}, |
|
84 {1280, 1024}, |
|
85 {1400, 1050}, |
|
86 {1600, 1200}, |
|
87 {1680, 1050}, |
|
88 {1920, 1200} |
|
89 }; |
|
90 |
|
91 static void GetVideoModes(void) |
|
92 { |
|
93 int i; |
|
94 SDL_Rect **modes; |
|
95 |
|
96 modes = SDL_CALL SDL_ListModes(NULL, SDL_SWSURFACE + (_fullscreen ? SDL_FULLSCREEN : 0)); |
|
97 |
|
98 if (modes == NULL) |
|
99 error("sdl: no modes available"); |
|
100 |
|
101 _all_modes = (modes == (void*)-1); |
|
102 |
|
103 if (_all_modes) { |
|
104 // all modes available, put some default ones here |
|
105 memcpy(_resolutions, default_resolutions, sizeof(default_resolutions)); |
|
106 _num_resolutions = lengthof(default_resolutions); |
|
107 } else { |
|
108 int n = 0; |
|
109 for (i = 0; modes[i]; i++) { |
|
110 int w = modes[i]->w; |
|
111 int h = modes[i]->h; |
|
112 if (IS_INT_INSIDE(w, 640, MAX_SCREEN_WIDTH + 1) && |
|
113 IS_INT_INSIDE(h, 480, MAX_SCREEN_HEIGHT + 1)) { |
|
114 int j; |
|
115 for (j = 0; j < n; j++) { |
|
116 if (_resolutions[j][0] == w && _resolutions[j][1] == h) break; |
|
117 } |
|
118 |
|
119 if (j == n) { |
|
120 _resolutions[j][0] = w; |
|
121 _resolutions[j][1] = h; |
|
122 if (++n == lengthof(_resolutions)) break; |
|
123 } |
|
124 } |
|
125 } |
|
126 _num_resolutions = n; |
|
127 SortResolutions(_num_resolutions); |
|
128 } |
|
129 } |
|
130 |
|
131 static void GetAvailableVideoMode(int *w, int *h) |
|
132 { |
|
133 int i; |
|
134 int best; |
|
135 uint delta; |
|
136 |
|
137 // all modes available? |
|
138 if (_all_modes) return; |
|
139 |
|
140 // is the wanted mode among the available modes? |
|
141 for (i = 0; i != _num_resolutions; i++) { |
|
142 if (*w == _resolutions[i][0] && *h == _resolutions[i][1]) return; |
|
143 } |
|
144 |
|
145 // use the closest possible resolution |
|
146 best = 0; |
|
147 delta = abs((_resolutions[0][0] - *w) * (_resolutions[0][1] - *h)); |
|
148 for (i = 1; i != _num_resolutions; ++i) { |
|
149 uint newdelta = abs((_resolutions[i][0] - *w) * (_resolutions[i][1] - *h)); |
|
150 if (newdelta < delta) { |
|
151 best = i; |
|
152 delta = newdelta; |
|
153 } |
|
154 } |
|
155 *w = _resolutions[best][0]; |
|
156 *h = _resolutions[best][1]; |
|
157 } |
|
158 |
|
159 #ifndef ICON_DIR |
|
160 #define ICON_DIR "media" |
|
161 #endif |
|
162 |
|
163 #ifdef WIN32 |
|
164 /* Let's redefine the LoadBMP macro with because we are dynamically |
|
165 * loading SDL and need to 'SDL_CALL' all functions */ |
|
166 #undef SDL_LoadBMP |
|
167 #define SDL_LoadBMP(file) SDL_LoadBMP_RW(SDL_CALL SDL_RWFromFile(file, "rb"), 1) |
|
168 #endif |
|
169 |
|
170 static bool CreateMainSurface(int w, int h) |
|
171 { |
|
172 extern const char _openttd_revision[]; |
|
173 SDL_Surface *newscreen, *icon; |
|
174 char caption[50]; |
|
175 |
|
176 GetAvailableVideoMode(&w, &h); |
|
177 |
|
178 DEBUG(driver, 1, "SDL: using mode %dx%d", w, h); |
|
179 |
|
180 /* Give the application an icon */ |
|
181 icon = SDL_CALL SDL_LoadBMP(ICON_DIR PATHSEP "openttd.32.bmp"); |
|
182 if (icon != NULL) { |
|
183 /* Get the colourkey, which will be magenta */ |
|
184 uint32 rgbmap = SDL_CALL SDL_MapRGB(icon->format, 255, 0, 255); |
|
185 |
|
186 SDL_CALL SDL_SetColorKey(icon, SDL_SRCCOLORKEY, rgbmap); |
|
187 SDL_CALL SDL_WM_SetIcon(icon, NULL); |
|
188 SDL_CALL SDL_FreeSurface(icon); |
|
189 } |
|
190 |
|
191 // DO NOT CHANGE TO HWSURFACE, IT DOES NOT WORK |
|
192 newscreen = SDL_CALL SDL_SetVideoMode(w, h, 8, SDL_SWSURFACE | SDL_HWPALETTE | (_fullscreen ? SDL_FULLSCREEN : SDL_RESIZABLE)); |
|
193 if (newscreen == NULL) |
|
194 return false; |
|
195 |
|
196 _screen.width = newscreen->w; |
|
197 _screen.height = newscreen->h; |
|
198 _screen.pitch = newscreen->pitch; |
|
199 |
|
200 _sdl_screen = newscreen; |
|
201 InitPalette(); |
|
202 |
|
203 snprintf(caption, sizeof(caption), "OpenTTD %s", _openttd_revision); |
|
204 SDL_CALL SDL_WM_SetCaption(caption, caption); |
|
205 SDL_CALL SDL_ShowCursor(0); |
|
206 |
|
207 GameSizeChanged(); |
|
208 |
|
209 return true; |
|
210 } |
|
211 |
|
212 typedef struct VkMapping { |
|
213 uint16 vk_from; |
|
214 byte vk_count; |
|
215 byte map_to; |
|
216 } VkMapping; |
|
217 |
|
218 #define AS(x, z) {x, 0, z} |
|
219 #define AM(x, y, z, w) {x, y - x, z} |
|
220 |
|
221 static const VkMapping _vk_mapping[] = { |
|
222 // Pageup stuff + up/down |
|
223 AM(SDLK_PAGEUP, SDLK_PAGEDOWN, WKC_PAGEUP, WKC_PAGEDOWN), |
|
224 AS(SDLK_UP, WKC_UP), |
|
225 AS(SDLK_DOWN, WKC_DOWN), |
|
226 AS(SDLK_LEFT, WKC_LEFT), |
|
227 AS(SDLK_RIGHT, WKC_RIGHT), |
|
228 |
|
229 AS(SDLK_HOME, WKC_HOME), |
|
230 AS(SDLK_END, WKC_END), |
|
231 |
|
232 AS(SDLK_INSERT, WKC_INSERT), |
|
233 AS(SDLK_DELETE, WKC_DELETE), |
|
234 |
|
235 // Map letters & digits |
|
236 AM(SDLK_a, SDLK_z, 'A', 'Z'), |
|
237 AM(SDLK_0, SDLK_9, '0', '9'), |
|
238 |
|
239 AS(SDLK_ESCAPE, WKC_ESC), |
|
240 AS(SDLK_PAUSE, WKC_PAUSE), |
|
241 AS(SDLK_BACKSPACE, WKC_BACKSPACE), |
|
242 |
|
243 AS(SDLK_SPACE, WKC_SPACE), |
|
244 AS(SDLK_RETURN, WKC_RETURN), |
|
245 AS(SDLK_TAB, WKC_TAB), |
|
246 |
|
247 // Function keys |
|
248 AM(SDLK_F1, SDLK_F12, WKC_F1, WKC_F12), |
|
249 |
|
250 // Numeric part. |
|
251 // What is the virtual keycode for numeric enter?? |
|
252 AM(SDLK_KP0, SDLK_KP9, WKC_NUM_0, WKC_NUM_9), |
|
253 AS(SDLK_KP_DIVIDE, WKC_NUM_DIV), |
|
254 AS(SDLK_KP_MULTIPLY, WKC_NUM_MUL), |
|
255 AS(SDLK_KP_MINUS, WKC_NUM_MINUS), |
|
256 AS(SDLK_KP_PLUS, WKC_NUM_PLUS), |
|
257 AS(SDLK_KP_ENTER, WKC_NUM_ENTER), |
|
258 AS(SDLK_KP_PERIOD, WKC_NUM_DECIMAL) |
|
259 }; |
|
260 |
|
261 static uint32 ConvertSdlKeyIntoMy(SDL_keysym *sym) |
|
262 { |
|
263 const VkMapping *map; |
|
264 uint key = 0; |
|
265 |
|
266 for (map = _vk_mapping; map != endof(_vk_mapping); ++map) { |
|
267 if ((uint)(sym->sym - map->vk_from) <= map->vk_count) { |
|
268 key = sym->sym - map->vk_from + map->map_to; |
|
269 break; |
|
270 } |
|
271 } |
|
272 |
|
273 // check scancode for BACKQUOTE key, because we want the key left of "1", not anything else (on non-US keyboards) |
|
274 #if defined(WIN32) || defined(__OS2__) |
|
275 if (sym->scancode == 41) key = WKC_BACKQUOTE; |
|
276 #elif defined(__APPLE__) |
|
277 if (sym->scancode == 10) key = WKC_BACKQUOTE; |
|
278 #elif defined(__MORPHOS__) |
|
279 if (sym->scancode == 0) key = WKC_BACKQUOTE; // yes, that key is code '0' under MorphOS :) |
|
280 #elif defined(__BEOS__) |
|
281 if (sym->scancode == 17) key = WKC_BACKQUOTE; |
|
282 #elif defined(__SVR4) && defined(__sun) |
|
283 if (sym->scancode == 60) key = WKC_BACKQUOTE; |
|
284 if (sym->scancode == 49) key = WKC_BACKSPACE; |
|
285 #elif defined(__sgi__) |
|
286 if (sym->scancode == 22) key = WKC_BACKQUOTE; |
|
287 #else |
|
288 if (sym->scancode == 49) key = WKC_BACKQUOTE; |
|
289 #endif |
|
290 |
|
291 // META are the command keys on mac |
|
292 if (sym->mod & KMOD_META) key |= WKC_META; |
|
293 if (sym->mod & KMOD_SHIFT) key |= WKC_SHIFT; |
|
294 if (sym->mod & KMOD_CTRL) key |= WKC_CTRL; |
|
295 if (sym->mod & KMOD_ALT) key |= WKC_ALT; |
|
296 // these two lines really help porting hotkey combos. Uncomment to use -- Bjarni |
|
297 #if 0 |
|
298 DEBUG(driver, 0, "Scancode character pressed %u", sym->scancode); |
|
299 DEBUG(driver, 0, "Unicode character pressed %u", sym->unicode); |
|
300 #endif |
|
301 return (key << 16) + sym->unicode; |
|
302 } |
|
303 |
|
304 static int PollEvent(void) |
|
305 { |
|
306 SDL_Event ev; |
|
307 |
|
308 if (!SDL_CALL SDL_PollEvent(&ev)) return -2; |
|
309 |
|
310 switch (ev.type) { |
|
311 case SDL_MOUSEMOTION: |
|
312 if (_cursor.fix_at) { |
|
313 int dx = ev.motion.x - _cursor.pos.x; |
|
314 int dy = ev.motion.y - _cursor.pos.y; |
|
315 if (dx != 0 || dy != 0) { |
|
316 _cursor.delta.x += dx; |
|
317 _cursor.delta.y += dy; |
|
318 SDL_CALL SDL_WarpMouse(_cursor.pos.x, _cursor.pos.y); |
|
319 } |
|
320 } else { |
|
321 _cursor.delta.x = ev.motion.x - _cursor.pos.x; |
|
322 _cursor.delta.y = ev.motion.y - _cursor.pos.y; |
|
323 _cursor.pos.x = ev.motion.x; |
|
324 _cursor.pos.y = ev.motion.y; |
|
325 _cursor.dirty = true; |
|
326 } |
|
327 HandleMouseEvents(); |
|
328 break; |
|
329 |
|
330 case SDL_MOUSEBUTTONDOWN: |
|
331 if (_rightclick_emulate && SDL_CALL SDL_GetModState() & KMOD_CTRL) { |
|
332 ev.button.button = SDL_BUTTON_RIGHT; |
|
333 } |
|
334 |
|
335 switch (ev.button.button) { |
|
336 case SDL_BUTTON_LEFT: |
|
337 _left_button_down = true; |
|
338 break; |
|
339 |
|
340 case SDL_BUTTON_RIGHT: |
|
341 _right_button_down = true; |
|
342 _right_button_clicked = true; |
|
343 break; |
|
344 |
|
345 case SDL_BUTTON_WHEELUP: _cursor.wheel--; break; |
|
346 case SDL_BUTTON_WHEELDOWN: _cursor.wheel++; break; |
|
347 |
|
348 default: break; |
|
349 } |
|
350 HandleMouseEvents(); |
|
351 break; |
|
352 |
|
353 case SDL_MOUSEBUTTONUP: |
|
354 if (_rightclick_emulate) { |
|
355 _right_button_down = false; |
|
356 _left_button_down = false; |
|
357 _left_button_clicked = false; |
|
358 } else if (ev.button.button == SDL_BUTTON_LEFT) { |
|
359 _left_button_down = false; |
|
360 _left_button_clicked = false; |
|
361 } else if (ev.button.button == SDL_BUTTON_RIGHT) { |
|
362 _right_button_down = false; |
|
363 } |
|
364 HandleMouseEvents(); |
|
365 break; |
|
366 |
|
367 case SDL_ACTIVEEVENT: |
|
368 if (!(ev.active.state & SDL_APPMOUSEFOCUS)) break; |
|
369 |
|
370 if (ev.active.gain) { // mouse entered the window, enable cursor |
|
371 _cursor.in_window = true; |
|
372 } else { |
|
373 UndrawMouseCursor(); // mouse left the window, undraw cursor |
|
374 _cursor.in_window = false; |
|
375 } |
|
376 break; |
|
377 |
|
378 case SDL_QUIT: HandleExitGameRequest(); break; |
|
379 |
|
380 case SDL_KEYDOWN: /* Toggle full-screen on ALT + ENTER/F */ |
|
381 if ((ev.key.keysym.mod & (KMOD_ALT | KMOD_META)) && |
|
382 (ev.key.keysym.sym == SDLK_RETURN || ev.key.keysym.sym == SDLK_f)) { |
|
383 ToggleFullScreen(!_fullscreen); |
|
384 } else { |
|
385 HandleKeypress(ConvertSdlKeyIntoMy(&ev.key.keysym)); |
|
386 } |
|
387 break; |
|
388 |
|
389 case SDL_VIDEORESIZE: { |
|
390 int w = clamp(ev.resize.w, 64, MAX_SCREEN_WIDTH); |
|
391 int h = clamp(ev.resize.h, 64, MAX_SCREEN_HEIGHT); |
|
392 ChangeResInGame(w, h); |
|
393 break; |
|
394 } |
|
395 } |
|
396 return -1; |
|
397 } |
|
398 |
|
399 static const char *SdlVideoStart(const char * const *parm) |
|
400 { |
|
401 char buf[30]; |
|
402 |
|
403 const char *s = SdlOpen(SDL_INIT_VIDEO); |
|
404 if (s != NULL) return s; |
|
405 |
|
406 SDL_CALL SDL_VideoDriverName(buf, 30); |
|
407 DEBUG(driver, 1, "SDL: using driver '%s'", buf); |
|
408 |
|
409 GetVideoModes(); |
|
410 CreateMainSurface(_cur_resolution[0], _cur_resolution[1]); |
|
411 MarkWholeScreenDirty(); |
|
412 |
|
413 SDL_CALL SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); |
|
414 SDL_CALL SDL_EnableUNICODE(1); |
|
415 return NULL; |
|
416 } |
|
417 |
|
418 static void SdlVideoStop(void) |
|
419 { |
|
420 SdlClose(SDL_INIT_VIDEO); |
|
421 } |
|
422 |
|
423 static void SdlVideoMainLoop(void) |
|
424 { |
|
425 uint32 cur_ticks = SDL_CALL SDL_GetTicks(); |
|
426 uint32 next_tick = cur_ticks + 30; |
|
427 uint32 pal_tick = 0; |
|
428 uint32 mod; |
|
429 int numkeys; |
|
430 Uint8 *keys; |
|
431 |
|
432 for (;;) { |
|
433 uint32 prev_cur_ticks = cur_ticks; // to check for wrapping |
|
434 InteractiveRandom(); // randomness |
|
435 |
|
436 while (PollEvent() == -1) {} |
|
437 if (_exit_game) return; |
|
438 |
|
439 mod = SDL_CALL SDL_GetModState(); |
|
440 keys = SDL_CALL SDL_GetKeyState(&numkeys); |
|
441 #if defined(_DEBUG) |
|
442 if (_shift_pressed) |
|
443 #else |
|
444 /* Speedup when pressing tab, except when using ALT+TAB |
|
445 * to switch to another application */ |
|
446 if (keys[SDLK_TAB] && (mod & KMOD_ALT) == 0) |
|
447 #endif |
|
448 { |
|
449 if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2; |
|
450 } else if (_fast_forward & 2) { |
|
451 _fast_forward = 0; |
|
452 } |
|
453 |
|
454 cur_ticks = SDL_CALL SDL_GetTicks(); |
|
455 if (cur_ticks >= next_tick || (_fast_forward && !_pause) || cur_ticks < prev_cur_ticks) { |
|
456 next_tick = cur_ticks + 30; |
|
457 |
|
458 _ctrl_pressed = !!(mod & KMOD_CTRL); |
|
459 _shift_pressed = !!(mod & KMOD_SHIFT); |
|
460 #ifdef _DEBUG |
|
461 _dbg_screen_rect = !!(mod & KMOD_CAPS); |
|
462 #endif |
|
463 |
|
464 // determine which directional keys are down |
|
465 _dirkeys = |
|
466 (keys[SDLK_LEFT] ? 1 : 0) | |
|
467 (keys[SDLK_UP] ? 2 : 0) | |
|
468 (keys[SDLK_RIGHT] ? 4 : 0) | |
|
469 (keys[SDLK_DOWN] ? 8 : 0); |
|
470 GameLoop(); |
|
471 |
|
472 _screen.dst_ptr = _sdl_screen->pixels; |
|
473 UpdateWindows(); |
|
474 if (++pal_tick > 4) { |
|
475 CheckPaletteAnim(); |
|
476 pal_tick = 1; |
|
477 } |
|
478 DrawSurfaceToScreen(); |
|
479 } else { |
|
480 SDL_CALL SDL_Delay(1); |
|
481 _screen.dst_ptr = _sdl_screen->pixels; |
|
482 DrawTextMessage(); |
|
483 DrawMouseCursor(); |
|
484 DrawSurfaceToScreen(); |
|
485 } |
|
486 } |
|
487 } |
|
488 |
|
489 static bool SdlVideoChangeRes(int w, int h) |
|
490 { |
|
491 return CreateMainSurface(w, h); |
|
492 } |
|
493 |
|
494 static void SdlVideoFullScreen(bool full_screen) |
|
495 { |
|
496 _fullscreen = full_screen; |
|
497 GetVideoModes(); // get the list of available video modes |
|
498 if (_num_resolutions == 0 || !_video_driver->change_resolution(_cur_resolution[0], _cur_resolution[1])) { |
|
499 // switching resolution failed, put back full_screen to original status |
|
500 _fullscreen ^= true; |
|
501 } |
|
502 } |
|
503 |
|
504 const HalVideoDriver _sdl_video_driver = { |
|
505 SdlVideoStart, |
|
506 SdlVideoStop, |
|
507 SdlVideoMakeDirty, |
|
508 SdlVideoMainLoop, |
|
509 SdlVideoChangeRes, |
|
510 SdlVideoFullScreen, |
|
511 }; |
|
512 |
|
513 #endif |