2 * MACDRV Cocoa application class
4 * Copyright 2011, 2012, 2013 Ken Thomases for CodeWeavers Inc.
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.
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.
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
21 #import <Carbon/Carbon.h>
24 #import "cocoa_event.h"
25 #import "cocoa_window.h"
31 @interface WineApplication ()
33 @property (readwrite, copy, nonatomic) NSEvent* lastFlagsChanged;
38 @implementation WineApplication
40 @synthesize keyboardType, lastFlagsChanged;
41 @synthesize orderedWineWindows;
48 eventQueues = [[NSMutableArray alloc] init];
49 eventQueuesLock = [[NSLock alloc] init];
51 keyWindows = [[NSMutableArray alloc] init];
52 orderedWineWindows = [[NSMutableArray alloc] init];
54 if (!eventQueues || !eventQueuesLock || !keyWindows || !orderedWineWindows)
65 [orderedWineWindows release];
67 [eventQueues release];
68 [eventQueuesLock release];
72 - (void) transformProcessToForeground
74 if ([self activationPolicy] != NSApplicationActivationPolicyRegular)
82 [self setActivationPolicy:NSApplicationActivationPolicyRegular];
83 [self activateIgnoringOtherApps:YES];
85 mainMenu = [[[NSMenu alloc] init] autorelease];
87 submenu = [[[NSMenu alloc] initWithTitle:@"Wine"] autorelease];
88 bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleNameKey];
89 if ([bundleName length])
90 title = [NSString stringWithFormat:@"Quit %@", bundleName];
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];
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];
110 [self setMainMenu:mainMenu];
111 [self setWindowsMenu:submenu];
115 - (BOOL) registerEventQueue:(WineEventQueue*)queue
117 [eventQueuesLock lock];
118 [eventQueues addObject:queue];
119 [eventQueuesLock unlock];
123 - (void) unregisterEventQueue:(WineEventQueue*)queue
125 [eventQueuesLock lock];
126 [eventQueues removeObjectIdenticalTo:queue];
127 [eventQueuesLock unlock];
130 - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns
132 eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC);
135 - (double) ticksForEventTime:(NSTimeInterval)eventTime
137 return (eventTime + eventTimeAdjustment) * 1000;
140 /* Invalidate old focus offers across all queues. */
141 - (void) invalidateGotFocusEvents
143 WineEventQueue* queue;
147 [eventQueuesLock lock];
148 for (queue in eventQueues)
150 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS)
153 [eventQueuesLock unlock];
156 - (void) windowGotFocus:(WineWindow*)window
160 [NSApp invalidateGotFocusEvents];
162 event.type = WINDOW_GOT_FOCUS;
163 event.window = (macdrv_window)[window retain];
164 event.window_got_focus.serial = windowFocusSerial;
166 event.window_got_focus.tried_windows = [triedWindows retain];
168 event.window_got_focus.tried_windows = [[NSMutableSet alloc] init];
169 [window.queue postEvent:&event];
172 - (void) windowRejectedFocusEvent:(const macdrv_event*)event
174 if (event->window_got_focus.serial == windowFocusSerial)
176 triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows;
177 [triedWindows addObject:(WineWindow*)event->window];
178 for (NSWindow* window in [keyWindows arrayByAddingObjectsFromArray:[self orderedWindows]])
180 if (![triedWindows containsObject:window] && [window canBecomeKeyWindow])
182 [window makeKeyWindow];
190 - (void) keyboardSelectionDidChange
192 TISInputSourceRef inputSource;
194 inputSource = TISCopyCurrentKeyboardLayoutInputSource();
198 uchr = TISGetInputSourceProperty(inputSource,
199 kTISPropertyUnicodeKeyLayoutData);
203 WineEventQueue* queue;
205 event.type = KEYBOARD_CHANGED;
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);
211 if (event.keyboard_changed.uchr)
213 [eventQueuesLock lock];
215 for (queue in eventQueues)
217 CFRetain(event.keyboard_changed.uchr);
218 [queue postEvent:&event];
221 [eventQueuesLock unlock];
223 CFRelease(event.keyboard_changed.uchr);
227 CFRelease(inputSource);
231 - (CGFloat) primaryScreenHeight
233 if (!primaryScreenHeightValid)
235 NSArray* screens = [NSScreen screens];
238 primaryScreenHeight = NSHeight([[screens objectAtIndex:0] frame]);
239 primaryScreenHeightValid = TRUE;
242 return 1280; /* arbitrary value */
245 return primaryScreenHeight;
248 - (NSPoint) flippedMouseLocation:(NSPoint)point
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;
257 - (void) wineWindow:(WineWindow*)window
258 ordered:(NSWindowOrderingMode)order
259 relativeTo:(WineWindow*)otherWindow
267 [orderedWineWindows removeObjectIdenticalTo:window];
270 index = [orderedWineWindows indexOfObjectIdenticalTo:otherWindow];
271 if (index == NSNotFound)
277 for (otherWindow in orderedWineWindows)
279 if ([otherWindow levelWhenActive] <= [window levelWhenActive])
284 [orderedWineWindows insertObject:window atIndex:index];
289 [orderedWineWindows removeObjectIdenticalTo:window];
292 index = [orderedWineWindows indexOfObjectIdenticalTo:otherWindow];
293 if (index == NSNotFound)
294 index = [orderedWineWindows count];
299 for (otherWindow in orderedWineWindows)
301 if ([otherWindow levelWhenActive] < [window levelWhenActive])
306 [orderedWineWindows insertObject:window atIndex:index];
315 - (void) sendDisplaysChanged
318 WineEventQueue* queue;
320 event.type = DISPLAYS_CHANGED;
323 [eventQueuesLock lock];
324 for (queue in eventQueues)
325 [queue postEvent:&event];
326 [eventQueuesLock unlock];
331 * ---------- NSApplication method overrides ----------
333 - (void) sendEvent:(NSEvent*)anEvent
335 if ([anEvent type] == NSFlagsChanged)
336 self.lastFlagsChanged = anEvent;
338 [super sendEvent:anEvent];
343 * ---------- NSApplicationDelegate methods ----------
345 - (void)applicationDidBecomeActive:(NSNotification *)notification
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]];
354 - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
356 primaryScreenHeightValid = FALSE;
357 [self sendDisplaysChanged];
360 - (void)applicationDidResignActive:(NSNotification *)notification
363 WineEventQueue* queue;
365 [self invalidateGotFocusEvents];
367 event.type = APP_DEACTIVATED;
370 [eventQueuesLock lock];
371 for (queue in eventQueues)
372 [queue postEvent:&event];
373 [eventQueuesLock unlock];
376 - (void)applicationWillFinishLaunching:(NSNotification *)notification
378 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
380 [nc addObserverForName:NSWindowDidBecomeKeyNotification
383 usingBlock:^(NSNotification *note){
384 NSWindow* window = [note object];
385 [keyWindows removeObjectIdenticalTo:window];
386 [keyWindows insertObject:window atIndex:0];
389 [nc addObserverForName:NSWindowWillCloseNotification
391 queue:[NSOperationQueue mainQueue]
392 usingBlock:^(NSNotification *note){
393 NSWindow* window = [note object];
394 [keyWindows removeObjectIdenticalTo:window];
395 [orderedWineWindows removeObjectIdenticalTo:window];
399 selector:@selector(keyboardSelectionDidChange)
400 name:NSTextInputContextKeyboardSelectionDidChangeNotification
403 /* The above notification isn't sent unless the NSTextInputContext
404 class has initialized itself. Poke it. */
405 [NSTextInputContext self];
407 self.keyboardType = LMGetKbdType();
410 - (void)applicationWillResignActive:(NSNotification *)notification
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];
422 /***********************************************************************
425 * Run a block on the main thread synchronously.
427 void OnMainThread(dispatch_block_t block)
429 dispatch_sync(dispatch_get_main_queue(), block);
432 /***********************************************************************
435 * Run a block on the main thread asynchronously.
437 void OnMainThreadAsync(dispatch_block_t block)
439 dispatch_async(dispatch_get_main_queue(), block);
442 /***********************************************************************
445 void LogError(const char* func, NSString* format, ...)
448 va_start(args, format);
449 LogErrorv(func, format, args);
453 /***********************************************************************
456 void LogErrorv(const char* func, NSString* format, va_list args)
458 NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
459 fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
463 /***********************************************************************
464 * macdrv_window_rejected_focus
466 * Pass focus to the next window that hasn't already rejected this same
467 * WINDOW_GOT_FOCUS event.
469 void macdrv_window_rejected_focus(const macdrv_event *event)
472 [NSApp windowRejectedFocusEvent:event];
476 /***********************************************************************
477 * macdrv_get_keyboard_layout
479 * Returns the keyboard layout uchr data.
481 CFDataRef macdrv_copy_keyboard_layout(CGEventSourceKeyboardType* keyboard_type, int* is_iso)
483 __block CFDataRef result = NULL;
486 TISInputSourceRef inputSource;
488 inputSource = TISCopyCurrentKeyboardLayoutInputSource();
491 CFDataRef uchr = TISGetInputSourceProperty(inputSource,
492 kTISPropertyUnicodeKeyLayoutData);
493 result = CFDataCreateCopy(NULL, uchr);
494 CFRelease(inputSource);
496 *keyboard_type = ((WineApplication*)NSApp).keyboardType;
497 *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
504 /***********************************************************************
507 * Play the beep sound configured by the user in System Preferences.
509 void macdrv_beep(void)