winemac: Implement DISPLAYS_CHANGED event for when display configuration has changed.
[wine] / dlls / winemac.drv / cocoa_app.m
1 /*
2  * MACDRV Cocoa application class
3  *
4  * Copyright 2011, 2012, 2013 Ken Thomases for CodeWeavers Inc.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 #import <Carbon/Carbon.h>
22
23 #import "cocoa_app.h"
24 #import "cocoa_event.h"
25 #import "cocoa_window.h"
26
27
28 int macdrv_err_on;
29
30
31 @interface WineApplication ()
32
33 @property (readwrite, copy, nonatomic) NSEvent* lastFlagsChanged;
34
35 @end
36
37
38 @implementation WineApplication
39
40     @synthesize keyboardType, lastFlagsChanged;
41     @synthesize orderedWineWindows;
42
43     - (id) init
44     {
45         self = [super init];
46         if (self != nil)
47         {
48             eventQueues = [[NSMutableArray alloc] init];
49             eventQueuesLock = [[NSLock alloc] init];
50
51             keyWindows = [[NSMutableArray alloc] init];
52             orderedWineWindows = [[NSMutableArray alloc] init];
53
54             if (!eventQueues || !eventQueuesLock || !keyWindows || !orderedWineWindows)
55             {
56                 [self release];
57                 return nil;
58             }
59         }
60         return self;
61     }
62
63     - (void) dealloc
64     {
65         [orderedWineWindows release];
66         [keyWindows release];
67         [eventQueues release];
68         [eventQueuesLock release];
69         [super dealloc];
70     }
71
72     - (void) transformProcessToForeground
73     {
74         if ([self activationPolicy] != NSApplicationActivationPolicyRegular)
75         {
76             NSMenu* mainMenu;
77             NSMenu* submenu;
78             NSString* bundleName;
79             NSString* title;
80             NSMenuItem* item;
81
82             [self setActivationPolicy:NSApplicationActivationPolicyRegular];
83             [self activateIgnoringOtherApps:YES];
84
85             mainMenu = [[[NSMenu alloc] init] autorelease];
86
87             submenu = [[[NSMenu alloc] initWithTitle:@"Wine"] autorelease];
88             bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleNameKey];
89             if ([bundleName length])
90                 title = [NSString stringWithFormat:@"Quit %@", bundleName];
91             else
92                 title = @"Quit";
93             item = [submenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
94             [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask];
95             item = [[[NSMenuItem alloc] init] autorelease];
96             [item setTitle:@"Wine"];
97             [item setSubmenu:submenu];
98             [mainMenu addItem:item];
99
100             submenu = [[[NSMenu alloc] initWithTitle:@"Window"] autorelease];
101             [submenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@""];
102             [submenu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""];
103             [submenu addItem:[NSMenuItem separatorItem]];
104             [submenu addItemWithTitle:@"Bring All to Front" action:@selector(arrangeInFront:) keyEquivalent:@""];
105             item = [[[NSMenuItem alloc] init] autorelease];
106             [item setTitle:@"Window"];
107             [item setSubmenu:submenu];
108             [mainMenu addItem:item];
109
110             [self setMainMenu:mainMenu];
111             [self setWindowsMenu:submenu];
112         }
113     }
114
115     - (BOOL) registerEventQueue:(WineEventQueue*)queue
116     {
117         [eventQueuesLock lock];
118         [eventQueues addObject:queue];
119         [eventQueuesLock unlock];
120         return TRUE;
121     }
122
123     - (void) unregisterEventQueue:(WineEventQueue*)queue
124     {
125         [eventQueuesLock lock];
126         [eventQueues removeObjectIdenticalTo:queue];
127         [eventQueuesLock unlock];
128     }
129
130     - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns
131     {
132         eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC);
133     }
134
135     - (double) ticksForEventTime:(NSTimeInterval)eventTime
136     {
137         return (eventTime + eventTimeAdjustment) * 1000;
138     }
139
140     /* Invalidate old focus offers across all queues. */
141     - (void) invalidateGotFocusEvents
142     {
143         WineEventQueue* queue;
144
145         windowFocusSerial++;
146
147         [eventQueuesLock lock];
148         for (queue in eventQueues)
149         {
150             [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS)
151                                    forWindow:nil];
152         }
153         [eventQueuesLock unlock];
154     }
155
156     - (void) windowGotFocus:(WineWindow*)window
157     {
158         macdrv_event event;
159
160         [NSApp invalidateGotFocusEvents];
161
162         event.type = WINDOW_GOT_FOCUS;
163         event.window = (macdrv_window)[window retain];
164         event.window_got_focus.serial = windowFocusSerial;
165         if (triedWindows)
166             event.window_got_focus.tried_windows = [triedWindows retain];
167         else
168             event.window_got_focus.tried_windows = [[NSMutableSet alloc] init];
169         [window.queue postEvent:&event];
170     }
171
172     - (void) windowRejectedFocusEvent:(const macdrv_event*)event
173     {
174         if (event->window_got_focus.serial == windowFocusSerial)
175         {
176             triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows;
177             [triedWindows addObject:(WineWindow*)event->window];
178             for (NSWindow* window in [keyWindows arrayByAddingObjectsFromArray:[self orderedWindows]])
179             {
180                 if (![triedWindows containsObject:window] && [window canBecomeKeyWindow])
181                 {
182                     [window makeKeyWindow];
183                     break;
184                 }
185             }
186             triedWindows = nil;
187         }
188     }
189
190     - (void) keyboardSelectionDidChange
191     {
192         TISInputSourceRef inputSource;
193
194         inputSource = TISCopyCurrentKeyboardLayoutInputSource();
195         if (inputSource)
196         {
197             CFDataRef uchr;
198             uchr = TISGetInputSourceProperty(inputSource,
199                     kTISPropertyUnicodeKeyLayoutData);
200             if (uchr)
201             {
202                 macdrv_event event;
203                 WineEventQueue* queue;
204
205                 event.type = KEYBOARD_CHANGED;
206                 event.window = NULL;
207                 event.keyboard_changed.keyboard_type = self.keyboardType;
208                 event.keyboard_changed.iso_keyboard = (KBGetLayoutType(self.keyboardType) == kKeyboardISO);
209                 event.keyboard_changed.uchr = CFDataCreateCopy(NULL, uchr);
210
211                 if (event.keyboard_changed.uchr)
212                 {
213                     [eventQueuesLock lock];
214
215                     for (queue in eventQueues)
216                     {
217                         CFRetain(event.keyboard_changed.uchr);
218                         [queue postEvent:&event];
219                     }
220
221                     [eventQueuesLock unlock];
222
223                     CFRelease(event.keyboard_changed.uchr);
224                 }
225             }
226
227             CFRelease(inputSource);
228         }
229     }
230
231     - (CGFloat) primaryScreenHeight
232     {
233         if (!primaryScreenHeightValid)
234         {
235             NSArray* screens = [NSScreen screens];
236             if ([screens count])
237             {
238                 primaryScreenHeight = NSHeight([[screens objectAtIndex:0] frame]);
239                 primaryScreenHeightValid = TRUE;
240             }
241             else
242                 return 1280; /* arbitrary value */
243         }
244
245         return primaryScreenHeight;
246     }
247
248     - (NSPoint) flippedMouseLocation:(NSPoint)point
249     {
250         /* This relies on the fact that Cocoa's mouse location points are
251            actually off by one (precisely because they were flipped from
252            Quartz screen coordinates using this same technique). */
253         point.y = [self primaryScreenHeight] - point.y;
254         return point;
255     }
256
257     - (void) wineWindow:(WineWindow*)window
258                 ordered:(NSWindowOrderingMode)order
259              relativeTo:(WineWindow*)otherWindow
260     {
261         NSUInteger index;
262
263         switch (order)
264         {
265             case NSWindowAbove:
266                 [window retain];
267                 [orderedWineWindows removeObjectIdenticalTo:window];
268                 if (otherWindow)
269                 {
270                     index = [orderedWineWindows indexOfObjectIdenticalTo:otherWindow];
271                     if (index == NSNotFound)
272                         index = 0;
273                 }
274                 else
275                 {
276                     index = 0;
277                     for (otherWindow in orderedWineWindows)
278                     {
279                         if ([otherWindow levelWhenActive] <= [window levelWhenActive])
280                             break;
281                         index++;
282                     }
283                 }
284                 [orderedWineWindows insertObject:window atIndex:index];
285                 [window release];
286                 break;
287             case NSWindowBelow:
288                 [window retain];
289                 [orderedWineWindows removeObjectIdenticalTo:window];
290                 if (otherWindow)
291                 {
292                     index = [orderedWineWindows indexOfObjectIdenticalTo:otherWindow];
293                     if (index == NSNotFound)
294                         index = [orderedWineWindows count];
295                 }
296                 else
297                 {
298                     index = 0;
299                     for (otherWindow in orderedWineWindows)
300                     {
301                         if ([otherWindow levelWhenActive] < [window levelWhenActive])
302                             break;
303                         index++;
304                     }
305                 }
306                 [orderedWineWindows insertObject:window atIndex:index];
307                 [window release];
308                 break;
309             case NSWindowOut:
310             default:
311                 break;
312         }
313     }
314
315     - (void) sendDisplaysChanged
316     {
317         macdrv_event event;
318         WineEventQueue* queue;
319
320         event.type = DISPLAYS_CHANGED;
321         event.window = NULL;
322
323         [eventQueuesLock lock];
324         for (queue in eventQueues)
325             [queue postEvent:&event];
326         [eventQueuesLock unlock];
327     }
328
329
330     /*
331      * ---------- NSApplication method overrides ----------
332      */
333     - (void) sendEvent:(NSEvent*)anEvent
334     {
335         if ([anEvent type] == NSFlagsChanged)
336             self.lastFlagsChanged = anEvent;
337
338         [super sendEvent:anEvent];
339     }
340
341
342     /*
343      * ---------- NSApplicationDelegate methods ----------
344      */
345     - (void)applicationDidBecomeActive:(NSNotification *)notification
346     {
347         [orderedWineWindows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
348             WineWindow* window = obj;
349             if ([window levelWhenActive] != [window level])
350                 [window setLevel:[window levelWhenActive]];
351         }];
352     }
353
354     - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
355     {
356         primaryScreenHeightValid = FALSE;
357         [self sendDisplaysChanged];
358     }
359
360     - (void)applicationDidResignActive:(NSNotification *)notification
361     {
362         macdrv_event event;
363         WineEventQueue* queue;
364
365         [self invalidateGotFocusEvents];
366
367         event.type = APP_DEACTIVATED;
368         event.window = NULL;
369
370         [eventQueuesLock lock];
371         for (queue in eventQueues)
372             [queue postEvent:&event];
373         [eventQueuesLock unlock];
374     }
375
376     - (void)applicationWillFinishLaunching:(NSNotification *)notification
377     {
378         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
379
380         [nc addObserverForName:NSWindowDidBecomeKeyNotification
381                         object:nil
382                          queue:nil
383                     usingBlock:^(NSNotification *note){
384             NSWindow* window = [note object];
385             [keyWindows removeObjectIdenticalTo:window];
386             [keyWindows insertObject:window atIndex:0];
387         }];
388
389         [nc addObserverForName:NSWindowWillCloseNotification
390                         object:nil
391                          queue:[NSOperationQueue mainQueue]
392                     usingBlock:^(NSNotification *note){
393             NSWindow* window = [note object];
394             [keyWindows removeObjectIdenticalTo:window];
395             [orderedWineWindows removeObjectIdenticalTo:window];
396         }];
397
398         [nc addObserver:self
399                selector:@selector(keyboardSelectionDidChange)
400                    name:NSTextInputContextKeyboardSelectionDidChangeNotification
401                  object:nil];
402
403         /* The above notification isn't sent unless the NSTextInputContext
404            class has initialized itself.  Poke it. */
405         [NSTextInputContext self];
406
407         self.keyboardType = LMGetKbdType();
408     }
409
410     - (void)applicationWillResignActive:(NSNotification *)notification
411     {
412         [orderedWineWindows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
413             WineWindow* window = obj;
414             NSInteger level = window.floating ? NSFloatingWindowLevel : NSNormalWindowLevel;
415             if ([window level] > level)
416                 [window setLevel:level];
417         }];
418     }
419
420 @end
421
422 /***********************************************************************
423  *              OnMainThread
424  *
425  * Run a block on the main thread synchronously.
426  */
427 void OnMainThread(dispatch_block_t block)
428 {
429     dispatch_sync(dispatch_get_main_queue(), block);
430 }
431
432 /***********************************************************************
433  *              OnMainThreadAsync
434  *
435  * Run a block on the main thread asynchronously.
436  */
437 void OnMainThreadAsync(dispatch_block_t block)
438 {
439     dispatch_async(dispatch_get_main_queue(), block);
440 }
441
442 /***********************************************************************
443  *              LogError
444  */
445 void LogError(const char* func, NSString* format, ...)
446 {
447     va_list args;
448     va_start(args, format);
449     LogErrorv(func, format, args);
450     va_end(args);
451 }
452
453 /***********************************************************************
454  *              LogErrorv
455  */
456 void LogErrorv(const char* func, NSString* format, va_list args)
457 {
458     NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
459     fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
460     [message release];
461 }
462
463 /***********************************************************************
464  *              macdrv_window_rejected_focus
465  *
466  * Pass focus to the next window that hasn't already rejected this same
467  * WINDOW_GOT_FOCUS event.
468  */
469 void macdrv_window_rejected_focus(const macdrv_event *event)
470 {
471     OnMainThread(^{
472         [NSApp windowRejectedFocusEvent:event];
473     });
474 }
475
476 /***********************************************************************
477  *              macdrv_get_keyboard_layout
478  *
479  * Returns the keyboard layout uchr data.
480  */
481 CFDataRef macdrv_copy_keyboard_layout(CGEventSourceKeyboardType* keyboard_type, int* is_iso)
482 {
483     __block CFDataRef result = NULL;
484
485     OnMainThread(^{
486         TISInputSourceRef inputSource;
487
488         inputSource = TISCopyCurrentKeyboardLayoutInputSource();
489         if (inputSource)
490         {
491             CFDataRef uchr = TISGetInputSourceProperty(inputSource,
492                                 kTISPropertyUnicodeKeyLayoutData);
493             result = CFDataCreateCopy(NULL, uchr);
494             CFRelease(inputSource);
495
496             *keyboard_type = ((WineApplication*)NSApp).keyboardType;
497             *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
498         }
499     });
500
501     return result;
502 }
503
504 /***********************************************************************
505  *              macdrv_beep
506  *
507  * Play the beep sound configured by the user in System Preferences.
508  */
509 void macdrv_beep(void)
510 {
511     OnMainThreadAsync(^{
512         NSBeep();
513     });
514 }