(svn r28004) -Update from Eints:
[openttd.git] / src / video / cocoa / event.mm
blob1298961dce958b5cbe20963d7304dba5bc1f9d7e
1 /* $Id$ */
3 /*
4  * This file is part of OpenTTD.
5  * OpenTTD 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, version 2.
6  * OpenTTD 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.
7  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8  */
10 /******************************************************************************
11  *                             Cocoa video driver                             *
12  * Known things left to do:                                                   *
13  *  Nothing at the moment.                                                    *
14  ******************************************************************************/
16 #ifdef WITH_COCOA
18 #include "../../stdafx.h"
20 #define Rect  OTTDRect
21 #define Point OTTDPoint
22 #import <Cocoa/Cocoa.h>
23 #undef Rect
24 #undef Point
26 #include "../../openttd.h"
27 #include "../../debug.h"
28 #include "../../os/macosx/splash.h"
29 #include "../../settings_type.h"
30 #include "../../core/geometry_type.hpp"
31 #include "cocoa_v.h"
32 #include "cocoa_keys.h"
33 #include "../../blitter/factory.hpp"
34 #include "../../gfx_func.h"
35 #include "../../network/network.h"
36 #include "../../core/random_func.hpp"
37 #include "../../core/math_func.hpp"
38 #include "../../texteff.hpp"
39 #include "../../window_func.h"
41 #import <sys/time.h> /* gettimeofday */
43 /**
44  * Important notice regarding all modifications!!!!!!!
45  * There are certain limitations because the file is objective C++.
46  * gdb has limitations.
47  * C++ and objective C code can't be joined in all cases (classes stuff).
48  * Read http://developer.apple.com/releasenotes/Cocoa/Objective-C++.html for more information.
49  */
52 /* Right Mouse Button Emulation enum */
53 enum RightMouseButtonEmulationState {
54         RMBE_COMMAND,
55         RMBE_CONTROL,
56         RMBE_OFF,
60 static unsigned int _current_mods;
61 static bool _tab_is_down;
62 static bool _emulating_right_button;
63 #if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
64 static float _current_magnification;
65 #endif
66 #ifdef _DEBUG
67 static uint32 _tEvent;
68 #endif
71 /* Support for touch gestures is only available starting with the
72  * 10.6 SDK, even if it says that support starts in fact with 10.5.2.
73  * Replicate the needed stuff for older SDKs. */
74 #if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 && MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6)
75 static const NSUInteger NSEventTypeMagnify    = 30;
76 static const NSUInteger NSEventTypeEndGesture = 20;
78 @interface NSEvent ()
79 /* This message is valid for events of type NSEventTypeMagnify, on 10.5.2 or later */
80 - (CGFloat)magnification WEAK_IMPORT_ATTRIBUTE;
81 @end
82 #endif
85 static uint32 GetTick()
87         struct timeval tim;
89         gettimeofday(&tim, NULL);
90         return tim.tv_usec / 1000 + tim.tv_sec * 1000;
93 static void QZ_WarpCursor(int x, int y)
95         assert(_cocoa_subdriver != NULL);
97         /* Only allow warping when in foreground */
98         if (![ NSApp isActive ]) return;
100         NSPoint p = NSMakePoint(x, y);
101         CGPoint cgp = _cocoa_subdriver->PrivateLocalToCG(&p);
103         /* this is the magic call that fixes cursor "freezing" after warp */
104         CGSetLocalEventsSuppressionInterval(0.0);
105         /* Do the actual warp */
106         CGWarpMouseCursorPosition(cgp);
110 static void QZ_CheckPaletteAnim()
112         if (_cur_palette.count_dirty != 0) {
113                 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
115                 switch (blitter->UsePaletteAnimation()) {
116                         case Blitter::PALETTE_ANIMATION_VIDEO_BACKEND:
117                                 _cocoa_subdriver->UpdatePalette(_cur_palette.first_dirty, _cur_palette.count_dirty);
118                                 break;
120                         case Blitter::PALETTE_ANIMATION_BLITTER:
121                                 blitter->PaletteAnimate(_cur_palette);
122                                 break;
124                         case Blitter::PALETTE_ANIMATION_NONE:
125                                 break;
127                         default:
128                                 NOT_REACHED();
129                 }
130                 _cur_palette.count_dirty = 0;
131         }
136 struct VkMapping {
137         unsigned short vk_from;
138         byte map_to;
141 #define AS(x, z) {x, z}
143 static const VkMapping _vk_mapping[] = {
144         AS(QZ_BACKQUOTE,  WKC_BACKQUOTE), // key left of '1'
145         AS(QZ_BACKQUOTE2, WKC_BACKQUOTE), // some keyboards have it on another scancode
147         /* Pageup stuff + up/down */
148         AS(QZ_PAGEUP,   WKC_PAGEUP),
149         AS(QZ_PAGEDOWN, WKC_PAGEDOWN),
151         AS(QZ_UP,    WKC_UP),
152         AS(QZ_DOWN,  WKC_DOWN),
153         AS(QZ_LEFT,  WKC_LEFT),
154         AS(QZ_RIGHT, WKC_RIGHT),
156         AS(QZ_HOME, WKC_HOME),
157         AS(QZ_END,  WKC_END),
159         AS(QZ_INSERT, WKC_INSERT),
160         AS(QZ_DELETE, WKC_DELETE),
162         /* Letters. QZ_[a-z] is not in numerical order so we can't use AM(...) */
163         AS(QZ_a, 'A'),
164         AS(QZ_b, 'B'),
165         AS(QZ_c, 'C'),
166         AS(QZ_d, 'D'),
167         AS(QZ_e, 'E'),
168         AS(QZ_f, 'F'),
169         AS(QZ_g, 'G'),
170         AS(QZ_h, 'H'),
171         AS(QZ_i, 'I'),
172         AS(QZ_j, 'J'),
173         AS(QZ_k, 'K'),
174         AS(QZ_l, 'L'),
175         AS(QZ_m, 'M'),
176         AS(QZ_n, 'N'),
177         AS(QZ_o, 'O'),
178         AS(QZ_p, 'P'),
179         AS(QZ_q, 'Q'),
180         AS(QZ_r, 'R'),
181         AS(QZ_s, 'S'),
182         AS(QZ_t, 'T'),
183         AS(QZ_u, 'U'),
184         AS(QZ_v, 'V'),
185         AS(QZ_w, 'W'),
186         AS(QZ_x, 'X'),
187         AS(QZ_y, 'Y'),
188         AS(QZ_z, 'Z'),
189         /* Same thing for digits */
190         AS(QZ_0, '0'),
191         AS(QZ_1, '1'),
192         AS(QZ_2, '2'),
193         AS(QZ_3, '3'),
194         AS(QZ_4, '4'),
195         AS(QZ_5, '5'),
196         AS(QZ_6, '6'),
197         AS(QZ_7, '7'),
198         AS(QZ_8, '8'),
199         AS(QZ_9, '9'),
201         AS(QZ_ESCAPE,    WKC_ESC),
202         AS(QZ_PAUSE,     WKC_PAUSE),
203         AS(QZ_BACKSPACE, WKC_BACKSPACE),
205         AS(QZ_SPACE,  WKC_SPACE),
206         AS(QZ_RETURN, WKC_RETURN),
207         AS(QZ_TAB,    WKC_TAB),
209         /* Function keys */
210         AS(QZ_F1,  WKC_F1),
211         AS(QZ_F2,  WKC_F2),
212         AS(QZ_F3,  WKC_F3),
213         AS(QZ_F4,  WKC_F4),
214         AS(QZ_F5,  WKC_F5),
215         AS(QZ_F6,  WKC_F6),
216         AS(QZ_F7,  WKC_F7),
217         AS(QZ_F8,  WKC_F8),
218         AS(QZ_F9,  WKC_F9),
219         AS(QZ_F10, WKC_F10),
220         AS(QZ_F11, WKC_F11),
221         AS(QZ_F12, WKC_F12),
223         /* Numeric part */
224         AS(QZ_KP0,         '0'),
225         AS(QZ_KP1,         '1'),
226         AS(QZ_KP2,         '2'),
227         AS(QZ_KP3,         '3'),
228         AS(QZ_KP4,         '4'),
229         AS(QZ_KP5,         '5'),
230         AS(QZ_KP6,         '6'),
231         AS(QZ_KP7,         '7'),
232         AS(QZ_KP8,         '8'),
233         AS(QZ_KP9,         '9'),
234         AS(QZ_KP_DIVIDE,   WKC_NUM_DIV),
235         AS(QZ_KP_MULTIPLY, WKC_NUM_MUL),
236         AS(QZ_KP_MINUS,    WKC_NUM_MINUS),
237         AS(QZ_KP_PLUS,     WKC_NUM_PLUS),
238         AS(QZ_KP_ENTER,    WKC_NUM_ENTER),
239         AS(QZ_KP_PERIOD,   WKC_NUM_DECIMAL),
241         /* Other non-letter keys */
242         AS(QZ_SLASH,        WKC_SLASH),
243         AS(QZ_SEMICOLON,    WKC_SEMICOLON),
244         AS(QZ_EQUALS,       WKC_EQUALS),
245         AS(QZ_LEFTBRACKET,  WKC_L_BRACKET),
246         AS(QZ_BACKSLASH,    WKC_BACKSLASH),
247         AS(QZ_RIGHTBRACKET, WKC_R_BRACKET),
249         AS(QZ_QUOTE,   WKC_SINGLEQUOTE),
250         AS(QZ_COMMA,   WKC_COMMA),
251         AS(QZ_MINUS,   WKC_MINUS),
252         AS(QZ_PERIOD,  WKC_PERIOD)
256 static uint32 QZ_MapKey(unsigned short sym)
258         uint32 key = 0;
260         for (const VkMapping *map = _vk_mapping; map != endof(_vk_mapping); ++map) {
261                 if (sym == map->vk_from) {
262                         key = map->map_to;
263                         break;
264                 }
265         }
267         if (_current_mods & NSShiftKeyMask)     key |= WKC_SHIFT;
268         if (_current_mods & NSControlKeyMask)   key |= (_settings_client.gui.right_mouse_btn_emulation != RMBE_CONTROL ? WKC_CTRL : WKC_META);
269         if (_current_mods & NSAlternateKeyMask) key |= WKC_ALT;
270         if (_current_mods & NSCommandKeyMask)   key |= (_settings_client.gui.right_mouse_btn_emulation != RMBE_CONTROL ? WKC_META : WKC_CTRL);
272         return key;
275 static bool QZ_KeyEvent(unsigned short keycode, unsigned short unicode, BOOL down)
277         bool interpret_keys = true;
279         switch (keycode) {
280                 case QZ_UP:    SB(_dirkeys, 1, 1, down); break;
281                 case QZ_DOWN:  SB(_dirkeys, 3, 1, down); break;
282                 case QZ_LEFT:  SB(_dirkeys, 0, 1, down); break;
283                 case QZ_RIGHT: SB(_dirkeys, 2, 1, down); break;
285                 case QZ_TAB: _tab_is_down = down; break;
287                 case QZ_RETURN:
288                 case QZ_f:
289                         if (down && (_current_mods & NSCommandKeyMask)) {
290                                 VideoDriver::GetInstance()->ToggleFullscreen(!_fullscreen);
291                         }
292                         break;
294                 case QZ_v:
295                         if (down && EditBoxInGlobalFocus() && (_current_mods & (NSCommandKeyMask | NSControlKeyMask))) {
296                                 HandleKeypress(WKC_CTRL | 'V', unicode);
297                         }
298                         break;
299                 case QZ_u:
300                         if (down && EditBoxInGlobalFocus() && (_current_mods & (NSCommandKeyMask | NSControlKeyMask))) {
301                                 HandleKeypress(WKC_CTRL | 'U', unicode);
302                         }
303                         break;
304         }
306         if (down) {
307                 uint32 pressed_key = QZ_MapKey(keycode);
309                 static bool console = false;
311                 /* The second backquote may have a character, which we don't want to interpret. */
312                 if (pressed_key == WKC_BACKQUOTE && (console || unicode == 0)) {
313                         if (!console) {
314                                 /* Backquote is a dead key, require a double press for hotkey behaviour (i.e. console). */
315                                 console = true;
316                                 return true;
317                         } else {
318                                 /* Second backquote, don't interpret as text input. */
319                                 interpret_keys = false;
320                         }
321                 }
322                 console = false;
324                 /* Don't handle normal characters if an edit box has the focus. */
325                 if (!EditBoxInGlobalFocus() || IsInsideMM(pressed_key & ~WKC_SPECIAL_KEYS, WKC_F1, WKC_PAUSE + 1)) {
326                         HandleKeypress(pressed_key, unicode);
327                 }
328                 DEBUG(driver, 2, "cocoa_v: QZ_KeyEvent: %x (%x), down, mapping: %x", keycode, unicode, pressed_key);
329         } else {
330                 DEBUG(driver, 2, "cocoa_v: QZ_KeyEvent: %x (%x), up", keycode, unicode);
331         }
333         return interpret_keys;
336 static void QZ_DoUnsidedModifiers(unsigned int newMods)
338         const int mapping[] = { QZ_CAPSLOCK, QZ_LSHIFT, QZ_LCTRL, QZ_LALT, QZ_LMETA };
340         if (_current_mods == newMods) return;
342         /* Iterate through the bits, testing each against the current modifiers */
343         for (unsigned int i = 0, bit = NSAlphaShiftKeyMask; bit <= NSCommandKeyMask; bit <<= 1, ++i) {
344                 unsigned int currentMask, newMask;
346                 currentMask = _current_mods & bit;
347                 newMask     = newMods & bit;
349                 if (currentMask && currentMask != newMask) { // modifier up event
350                         /* If this was Caps Lock, we need some additional voodoo to make SDL happy (is this needed in ottd?) */
351                         if (bit == NSAlphaShiftKeyMask) QZ_KeyEvent(mapping[i], 0, YES);
352                         QZ_KeyEvent(mapping[i], 0, NO);
353                 } else if (newMask && currentMask != newMask) { // modifier down event
354                         QZ_KeyEvent(mapping[i], 0, YES);
355                         /* If this was Caps Lock, we need some additional voodoo to make SDL happy (is this needed in ottd?) */
356                         if (bit == NSAlphaShiftKeyMask) QZ_KeyEvent(mapping[i], 0, NO);
357                 }
358         }
360         _current_mods = newMods;
363 static void QZ_MouseMovedEvent(int x, int y)
365         if (_cursor.UpdateCursorPosition(x, y, false)) {
366                 QZ_WarpCursor(_cursor.pos.x, _cursor.pos.y);
367         }
368         HandleMouseEvents();
372 static void QZ_MouseButtonEvent(int button, BOOL down)
374         switch (button) {
375                 case 0:
376                         if (down) {
377                                 _left_button_down = true;
378                         } else {
379                                 _left_button_down = false;
380                                 _left_button_clicked = false;
381                         }
382                         HandleMouseEvents();
383                         break;
385                 case 1:
386                         if (down) {
387                                 _right_button_down = true;
388                                 _right_button_clicked = true;
389                         } else {
390                                 _right_button_down = false;
391                         }
392                         HandleMouseEvents();
393                         break;
394         }
400 static bool QZ_PollEvent()
402         assert(_cocoa_subdriver != NULL);
404 #ifdef _DEBUG
405         uint32 et0 = GetTick();
406 #endif
407         NSEvent *event = [ NSApp nextEventMatchingMask:NSAnyEventMask
408                                 untilDate:[ NSDate distantPast ]
409                                 inMode:NSDefaultRunLoopMode dequeue:YES ];
410 #ifdef _DEBUG
411         _tEvent += GetTick() - et0;
412 #endif
414         if (event == nil) return false;
415         if (!_cocoa_subdriver->IsActive()) {
416                 [ NSApp sendEvent:event ];
417                 return true;
418         }
420         QZ_DoUnsidedModifiers( [ event modifierFlags ] );
422         NSString *chars;
423         NSPoint  pt;
424         switch ([ event type ]) {
425                 case NSMouseMoved:
426                 case NSOtherMouseDragged:
427                 case NSLeftMouseDragged:
428                         pt = _cocoa_subdriver->GetMouseLocation(event);
429                         if (!_cocoa_subdriver->MouseIsInsideView(&pt) && !_emulating_right_button) {
430                                 [ NSApp sendEvent:event ];
431                                 break;
432                         }
434                         QZ_MouseMovedEvent((int)pt.x, (int)pt.y);
435                         break;
437                 case NSRightMouseDragged:
438                         pt = _cocoa_subdriver->GetMouseLocation(event);
439                         QZ_MouseMovedEvent((int)pt.x, (int)pt.y);
440                         break;
442                 case NSLeftMouseDown:
443                 {
444                         uint32 keymask = 0;
445                         if (_settings_client.gui.right_mouse_btn_emulation == RMBE_COMMAND) keymask |= NSCommandKeyMask;
446                         if (_settings_client.gui.right_mouse_btn_emulation == RMBE_CONTROL) keymask |= NSControlKeyMask;
448                         pt = _cocoa_subdriver->GetMouseLocation(event);
450                         if (!([ event modifierFlags ] & keymask) || !_cocoa_subdriver->MouseIsInsideView(&pt)) {
451                                 [ NSApp sendEvent:event ];
452                         }
454                         QZ_MouseMovedEvent((int)pt.x, (int)pt.y);
456                         /* Right mouse button emulation */
457                         if ([ event modifierFlags ] & keymask) {
458                                 _emulating_right_button = true;
459                                 QZ_MouseButtonEvent(1, YES);
460                         } else {
461                                 QZ_MouseButtonEvent(0, YES);
462                         }
463                         break;
464                 }
465                 case NSLeftMouseUp:
466                         [ NSApp sendEvent:event ];
468                         pt = _cocoa_subdriver->GetMouseLocation(event);
470                         QZ_MouseMovedEvent((int)pt.x, (int)pt.y);
472                         /* Right mouse button emulation */
473                         if (_emulating_right_button) {
474                                 _emulating_right_button = false;
475                                 QZ_MouseButtonEvent(1, NO);
476                         } else {
477                                 QZ_MouseButtonEvent(0, NO);
478                         }
479                         break;
481                 case NSRightMouseDown:
482                         pt = _cocoa_subdriver->GetMouseLocation(event);
483                         if (!_cocoa_subdriver->MouseIsInsideView(&pt)) {
484                                 [ NSApp sendEvent:event ];
485                                 break;
486                         }
488                         QZ_MouseMovedEvent((int)pt.x, (int)pt.y);
489                         QZ_MouseButtonEvent(1, YES);
490                         break;
492                 case NSRightMouseUp:
493                         pt = _cocoa_subdriver->GetMouseLocation(event);
494                         if (!_cocoa_subdriver->MouseIsInsideView(&pt)) {
495                                 [ NSApp sendEvent:event ];
496                                 break;
497                         }
499                         QZ_MouseMovedEvent((int)pt.x, (int)pt.y);
500                         QZ_MouseButtonEvent(1, NO);
501                         break;
503 #if 0
504                 /* This is not needed since openttd currently only use two buttons */
505                 case NSOtherMouseDown:
506                         pt = QZ_GetMouseLocation(event);
507                         if (!QZ_MouseIsInsideView(&pt)) {
508                                 [ NSApp sendEvent:event ];
509                                 break;
510                         }
512                         QZ_MouseMovedEvent((int)pt.x, (int)pt.y);
513                         QZ_MouseButtonEvent([ event buttonNumber ], YES);
514                         break;
516                 case NSOtherMouseUp:
517                         pt = QZ_GetMouseLocation(event);
518                         if (!QZ_MouseIsInsideView(&pt)) {
519                                 [ NSApp sendEvent:event ];
520                                 break;
521                         }
523                         QZ_MouseMovedEvent((int)pt.x, (int)pt.y);
524                         QZ_MouseButtonEvent([ event buttonNumber ], NO);
525                         break;
526 #endif
528                 case NSKeyDown: {
529                         /* Quit, hide and minimize */
530                         switch ([ event keyCode ]) {
531                                 case QZ_q:
532                                 case QZ_h:
533                                 case QZ_m:
534                                         if ([ event modifierFlags ] & NSCommandKeyMask) {
535                                                 [ NSApp sendEvent:event ];
536                                         }
537                                         break;
538                         }
540                         chars = [ event characters ];
541                         unsigned short unicode = [ chars length ] > 0 ? [ chars characterAtIndex:0 ] : 0;
542                         if (EditBoxInGlobalFocus()) {
543                                 if (QZ_KeyEvent([ event keyCode ], unicode, YES)) {
544                                         [ _cocoa_subdriver->cocoaview interpretKeyEvents:[ NSArray arrayWithObject:event ] ];
545                                 }
546                         } else {
547                                 QZ_KeyEvent([ event keyCode ], unicode, YES);
548                                 for (uint i = 1; i < [ chars length ]; i++) {
549                                         QZ_KeyEvent(0, [ chars characterAtIndex:i ], YES);
550                                 }
551                         }
552                         break;
553                 }
555                 case NSKeyUp:
556                         /* Quit, hide and minimize */
557                         switch ([ event keyCode ]) {
558                                 case QZ_q:
559                                 case QZ_h:
560                                 case QZ_m:
561                                         if ([ event modifierFlags ] & NSCommandKeyMask) {
562                                                 [ NSApp sendEvent:event ];
563                                         }
564                                         break;
565                         }
567                         chars = [ event characters ];
568                         QZ_KeyEvent([ event keyCode ], [ chars length ] ? [ chars characterAtIndex:0 ] : 0, NO);
569                         break;
571                 case NSScrollWheel:
572                         if ([ event deltaY ] > 0.0) { /* Scroll up */
573                                 _cursor.wheel--;
574                         } else if ([ event deltaY ] < 0.0) { /* Scroll down */
575                                 _cursor.wheel++;
576                         } /* else: deltaY was 0.0 and we don't want to do anything */
578                         /* Set the scroll count for scrollwheel scrolling */
579                         _cursor.h_wheel -= (int)([ event deltaX ] * 5 * _settings_client.gui.scrollwheel_multiplier);
580                         _cursor.v_wheel -= (int)([ event deltaY ] * 5 * _settings_client.gui.scrollwheel_multiplier);
581                         break;
583 #if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
584                 case NSEventTypeMagnify:
585                         /* Pinch open or close gesture. */
586                         _current_magnification += [ event magnification ] * 5.0f;
588                         while (_current_magnification >= 1.0f) {
589                                 _current_magnification -= 1.0f;
590                                 _cursor.wheel++;
591                                 HandleMouseEvents();
592                         }
593                         while (_current_magnification <= -1.0f) {
594                                 _current_magnification += 1.0f;
595                                 _cursor.wheel--;
596                                 HandleMouseEvents();
597                         }
598                         break;
600                 case NSEventTypeEndGesture:
601                         /* Gesture ended. */
602                         _current_magnification = 0.0f;
603                         break;
604 #endif
606                 case NSCursorUpdate:
607                 case NSMouseEntered:
608                 case NSMouseExited:
609                         /* Catch these events if the cursor is dragging. During dragging, we reset
610                          * the mouse position programmatically, which would trigger OS X to show
611                          * the default arrow cursor if the events are propagated. */
612                         if (_cursor.fix_at) break;
613                         FALLTHROUGH;
615                 default:
616                         [ NSApp sendEvent:event ];
617         }
619         return true;
623 void QZ_GameLoop()
625         uint32 cur_ticks = GetTick();
626         uint32 last_cur_ticks = cur_ticks;
627         uint32 next_tick = cur_ticks + MILLISECONDS_PER_TICK;
629 #ifdef _DEBUG
630         uint32 et0 = GetTick();
631         uint32 st = 0;
632 #endif
634         DisplaySplashImage();
635         QZ_CheckPaletteAnim();
636         _cocoa_subdriver->Draw(true);
637         CSleep(1);
639         for (int i = 0; i < 2; i++) GameLoop();
641         UpdateWindows();
642         QZ_CheckPaletteAnim();
643         _cocoa_subdriver->Draw();
644         CSleep(1);
646         /* Set the proper OpenTTD palette which got spoilt by the splash
647          * image when using 8bpp blitter */
648         GfxInitPalettes();
649         QZ_CheckPaletteAnim();
650         _cocoa_subdriver->Draw(true);
652         for (;;) {
653                 uint32 prev_cur_ticks = cur_ticks; // to check for wrapping
654                 InteractiveRandom(); // randomness
656                 while (QZ_PollEvent()) {}
658                 if (_exit_game) break;
660 #if defined(_DEBUG)
661                 if (_current_mods & NSShiftKeyMask)
662 #else
663                 if (_tab_is_down)
664 #endif
665                 {
666                         if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2;
667                 } else if (_fast_forward & 2) {
668                         _fast_forward = 0;
669                 }
671                 cur_ticks = GetTick();
672                 if (cur_ticks >= next_tick || (_fast_forward && !_pause_mode) || cur_ticks < prev_cur_ticks) {
673                         _realtime_tick += cur_ticks - last_cur_ticks;
674                         last_cur_ticks = cur_ticks;
675                         next_tick = cur_ticks + MILLISECONDS_PER_TICK;
677                         bool old_ctrl_pressed = _ctrl_pressed;
679                         _ctrl_pressed = !!(_current_mods & ( _settings_client.gui.right_mouse_btn_emulation != RMBE_CONTROL ? NSControlKeyMask : NSCommandKeyMask));
680                         _shift_pressed = !!(_current_mods & NSShiftKeyMask);
682                         if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
684                         GameLoop();
686                         UpdateWindows();
687                         QZ_CheckPaletteAnim();
688                         _cocoa_subdriver->Draw();
689                 } else {
690 #ifdef _DEBUG
691                         uint32 st0 = GetTick();
692 #endif
693                         CSleep(1);
694 #ifdef _DEBUG
695                         st += GetTick() - st0;
696 #endif
697                         NetworkDrawChatMessage();
698                         DrawMouseCursor();
699                         _cocoa_subdriver->Draw();
700                 }
701         }
703 #ifdef _DEBUG
704         uint32 et = GetTick();
706         DEBUG(driver, 1, "cocoa_v: nextEventMatchingMask took %i ms total", _tEvent);
707         DEBUG(driver, 1, "cocoa_v: game loop took %i ms total (%i ms without sleep)", et - et0, et - et0 - st);
708         DEBUG(driver, 1, "cocoa_v: (nextEventMatchingMask total)/(game loop total) is %f%%", (double)_tEvent / (double)(et - et0) * 100);
709         DEBUG(driver, 1, "cocoa_v: (nextEventMatchingMask total)/(game loop without sleep total) is %f%%", (double)_tEvent / (double)(et - et0 - st) * 100);
710 #endif
713 #endif /* WITH_COCOA */