winemac: Generate KEY_PRESS/RELEASE events from Cocoa key events.
[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
42     - (id) init
43     {
44         self = [super init];
45         if (self != nil)
46         {
47             eventQueues = [[NSMutableArray alloc] init];
48             eventQueuesLock = [[NSLock alloc] init];
49
50             keyWindows = [[NSMutableArray alloc] init];
51
52             if (!eventQueues || !eventQueuesLock || !keyWindows)
53             {
54                 [self release];
55                 return nil;
56             }
57         }
58         return self;
59     }
60
61     - (void) dealloc
62     {
63         [keyWindows release];
64         [eventQueues release];
65         [eventQueuesLock release];
66         [super dealloc];
67     }
68
69     - (void) transformProcessToForeground
70     {
71         if ([self activationPolicy] != NSApplicationActivationPolicyRegular)
72         {
73             NSMenu* mainMenu;
74             NSMenu* submenu;
75             NSString* bundleName;
76             NSString* title;
77             NSMenuItem* item;
78
79             [self setActivationPolicy:NSApplicationActivationPolicyRegular];
80             [self activateIgnoringOtherApps:YES];
81
82             mainMenu = [[[NSMenu alloc] init] autorelease];
83
84             submenu = [[[NSMenu alloc] initWithTitle:@"Wine"] autorelease];
85             bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleNameKey];
86             if ([bundleName length])
87                 title = [NSString stringWithFormat:@"Quit %@", bundleName];
88             else
89                 title = @"Quit";
90             item = [submenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
91             [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask];
92             item = [[[NSMenuItem alloc] init] autorelease];
93             [item setTitle:@"Wine"];
94             [item setSubmenu:submenu];
95             [mainMenu addItem:item];
96
97             submenu = [[[NSMenu alloc] initWithTitle:@"Window"] autorelease];
98             [submenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@""];
99             [submenu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""];
100             [submenu addItem:[NSMenuItem separatorItem]];
101             [submenu addItemWithTitle:@"Bring All to Front" action:@selector(arrangeInFront:) keyEquivalent:@""];
102             item = [[[NSMenuItem alloc] init] autorelease];
103             [item setTitle:@"Window"];
104             [item setSubmenu:submenu];
105             [mainMenu addItem:item];
106
107             [self setMainMenu:mainMenu];
108             [self setWindowsMenu:submenu];
109         }
110     }
111
112     - (BOOL) registerEventQueue:(WineEventQueue*)queue
113     {
114         [eventQueuesLock lock];
115         [eventQueues addObject:queue];
116         [eventQueuesLock unlock];
117         return TRUE;
118     }
119
120     - (void) unregisterEventQueue:(WineEventQueue*)queue
121     {
122         [eventQueuesLock lock];
123         [eventQueues removeObjectIdenticalTo:queue];
124         [eventQueuesLock unlock];
125     }
126
127     - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns
128     {
129         eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC);
130     }
131
132     - (double) ticksForEventTime:(NSTimeInterval)eventTime
133     {
134         return (eventTime + eventTimeAdjustment) * 1000;
135     }
136
137     /* Invalidate old focus offers across all queues. */
138     - (void) invalidateGotFocusEvents
139     {
140         WineEventQueue* queue;
141
142         windowFocusSerial++;
143
144         [eventQueuesLock lock];
145         for (queue in eventQueues)
146         {
147             [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS)
148                                    forWindow:nil];
149         }
150         [eventQueuesLock unlock];
151     }
152
153     - (void) windowGotFocus:(WineWindow*)window
154     {
155         macdrv_event event;
156
157         [NSApp invalidateGotFocusEvents];
158
159         event.type = WINDOW_GOT_FOCUS;
160         event.window = (macdrv_window)[window retain];
161         event.window_got_focus.serial = windowFocusSerial;
162         if (triedWindows)
163             event.window_got_focus.tried_windows = [triedWindows retain];
164         else
165             event.window_got_focus.tried_windows = [[NSMutableSet alloc] init];
166         [window.queue postEvent:&event];
167     }
168
169     - (void) windowRejectedFocusEvent:(const macdrv_event*)event
170     {
171         if (event->window_got_focus.serial == windowFocusSerial)
172         {
173             triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows;
174             [triedWindows addObject:(WineWindow*)event->window];
175             for (NSWindow* window in [keyWindows arrayByAddingObjectsFromArray:[self orderedWindows]])
176             {
177                 if (![triedWindows containsObject:window] && [window canBecomeKeyWindow])
178                 {
179                     [window makeKeyWindow];
180                     break;
181                 }
182             }
183             triedWindows = nil;
184         }
185     }
186
187     - (void) keyboardSelectionDidChange
188     {
189         TISInputSourceRef inputSource;
190
191         inputSource = TISCopyCurrentKeyboardLayoutInputSource();
192         if (inputSource)
193         {
194             CFDataRef uchr;
195             uchr = TISGetInputSourceProperty(inputSource,
196                     kTISPropertyUnicodeKeyLayoutData);
197             if (uchr)
198             {
199                 macdrv_event event;
200                 WineEventQueue* queue;
201
202                 event.type = KEYBOARD_CHANGED;
203                 event.window = NULL;
204                 event.keyboard_changed.keyboard_type = self.keyboardType;
205                 event.keyboard_changed.iso_keyboard = (KBGetLayoutType(self.keyboardType) == kKeyboardISO);
206                 event.keyboard_changed.uchr = CFDataCreateCopy(NULL, uchr);
207
208                 if (event.keyboard_changed.uchr)
209                 {
210                     [eventQueuesLock lock];
211
212                     for (queue in eventQueues)
213                     {
214                         CFRetain(event.keyboard_changed.uchr);
215                         [queue postEvent:&event];
216                     }
217
218                     [eventQueuesLock unlock];
219
220                     CFRelease(event.keyboard_changed.uchr);
221                 }
222             }
223
224             CFRelease(inputSource);
225         }
226     }
227
228
229     /*
230      * ---------- NSApplication method overrides ----------
231      */
232     - (void) sendEvent:(NSEvent*)anEvent
233     {
234         if ([anEvent type] == NSFlagsChanged)
235             self.lastFlagsChanged = anEvent;
236
237         [super sendEvent:anEvent];
238     }
239
240
241     /*
242      * ---------- NSApplicationDelegate methods ----------
243      */
244     - (void)applicationDidResignActive:(NSNotification *)notification
245     {
246         macdrv_event event;
247         WineEventQueue* queue;
248
249         [self invalidateGotFocusEvents];
250
251         event.type = APP_DEACTIVATED;
252         event.window = NULL;
253
254         [eventQueuesLock lock];
255         for (queue in eventQueues)
256             [queue postEvent:&event];
257         [eventQueuesLock unlock];
258     }
259
260     - (void)applicationWillFinishLaunching:(NSNotification *)notification
261     {
262         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
263
264         [nc addObserverForName:NSWindowDidBecomeKeyNotification
265                         object:nil
266                          queue:nil
267                     usingBlock:^(NSNotification *note){
268             NSWindow* window = [note object];
269             [keyWindows removeObjectIdenticalTo:window];
270             [keyWindows insertObject:window atIndex:0];
271         }];
272
273         [nc addObserverForName:NSWindowWillCloseNotification
274                         object:nil
275                          queue:[NSOperationQueue mainQueue]
276                     usingBlock:^(NSNotification *note){
277             NSWindow* window = [note object];
278             [keyWindows removeObjectIdenticalTo:window];
279         }];
280
281         [nc addObserver:self
282                selector:@selector(keyboardSelectionDidChange)
283                    name:NSTextInputContextKeyboardSelectionDidChangeNotification
284                  object:nil];
285
286         /* The above notification isn't sent unless the NSTextInputContext
287            class has initialized itself.  Poke it. */
288         [NSTextInputContext self];
289
290         self.keyboardType = LMGetKbdType();
291     }
292
293 @end
294
295 /***********************************************************************
296  *              OnMainThread
297  *
298  * Run a block on the main thread synchronously.
299  */
300 void OnMainThread(dispatch_block_t block)
301 {
302     dispatch_sync(dispatch_get_main_queue(), block);
303 }
304
305 /***********************************************************************
306  *              OnMainThreadAsync
307  *
308  * Run a block on the main thread asynchronously.
309  */
310 void OnMainThreadAsync(dispatch_block_t block)
311 {
312     dispatch_async(dispatch_get_main_queue(), block);
313 }
314
315 /***********************************************************************
316  *              LogError
317  */
318 void LogError(const char* func, NSString* format, ...)
319 {
320     va_list args;
321     va_start(args, format);
322     LogErrorv(func, format, args);
323     va_end(args);
324 }
325
326 /***********************************************************************
327  *              LogErrorv
328  */
329 void LogErrorv(const char* func, NSString* format, va_list args)
330 {
331     NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
332     fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
333     [message release];
334 }
335
336 /***********************************************************************
337  *              macdrv_window_rejected_focus
338  *
339  * Pass focus to the next window that hasn't already rejected this same
340  * WINDOW_GOT_FOCUS event.
341  */
342 void macdrv_window_rejected_focus(const macdrv_event *event)
343 {
344     OnMainThread(^{
345         [NSApp windowRejectedFocusEvent:event];
346     });
347 }
348
349 /***********************************************************************
350  *              macdrv_get_keyboard_layout
351  *
352  * Returns the keyboard layout uchr data.
353  */
354 CFDataRef macdrv_copy_keyboard_layout(CGEventSourceKeyboardType* keyboard_type, int* is_iso)
355 {
356     __block CFDataRef result = NULL;
357
358     OnMainThread(^{
359         TISInputSourceRef inputSource;
360
361         inputSource = TISCopyCurrentKeyboardLayoutInputSource();
362         if (inputSource)
363         {
364             CFDataRef uchr = TISGetInputSourceProperty(inputSource,
365                                 kTISPropertyUnicodeKeyLayoutData);
366             result = CFDataCreateCopy(NULL, uchr);
367             CFRelease(inputSource);
368
369             *keyboard_type = ((WineApplication*)NSApp).keyboardType;
370             *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
371         }
372     });
373
374     return result;
375 }