| /* |
| * MACDRV Cocoa initialization code |
| * |
| * Copyright 2011, 2012, 2013 Ken Thomases for CodeWeavers Inc. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library 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. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
| */ |
| |
| #import <AppKit/AppKit.h> |
| #include <mach/mach.h> |
| #include <mach/mach_time.h> |
| |
| #include "macdrv_cocoa.h" |
| #import "cocoa_app.h" |
| |
| |
| /* Condition values for an NSConditionLock. Used to signal between run_cocoa_app |
| and macdrv_start_cocoa_app so the latter knows when the former is running |
| the application event loop. */ |
| enum { |
| COCOA_APP_NOT_RUNNING, |
| COCOA_APP_RUNNING, |
| }; |
| |
| |
| struct cocoa_app_startup_info { |
| NSConditionLock* lock; |
| unsigned long long tickcount; |
| uint64_t uptime_ns; |
| BOOL success; |
| }; |
| |
| |
| /*********************************************************************** |
| * run_cocoa_app |
| * |
| * Transforms the main thread from merely idling in its run loop to |
| * being a Cocoa application running its event loop. |
| * |
| * This will be the perform callback of a custom run loop source that |
| * will be scheduled in the main thread's run loop from a secondary |
| * thread by macdrv_start_cocoa_app. This function communicates that |
| * it has successfully started the application by changing the condition |
| * of a shared NSConditionLock, passed in via the info parameter. |
| * |
| * This function never returns. It's the new permanent home of the |
| * main thread. |
| */ |
| static void run_cocoa_app(void* info) |
| { |
| struct cocoa_app_startup_info* startup_info = info; |
| NSConditionLock* lock = startup_info->lock; |
| BOOL created_app = FALSE; |
| |
| NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
| |
| if (!NSApp) |
| { |
| [WineApplication sharedApplication]; |
| created_app = TRUE; |
| } |
| |
| if ([NSApp respondsToSelector:@selector(setWineController:)]) |
| { |
| WineApplicationController* controller = [WineApplicationController sharedController]; |
| [NSApp setWineController:controller]; |
| [controller computeEventTimeAdjustmentFromTicks:startup_info->tickcount uptime:startup_info->uptime_ns]; |
| startup_info->success = TRUE; |
| } |
| |
| /* Retain the lock while we're using it, so macdrv_start_cocoa_app() |
| doesn't deallocate it in the middle of us unlocking it. */ |
| [lock retain]; |
| [lock lock]; |
| [lock unlockWithCondition:COCOA_APP_RUNNING]; |
| [lock release]; |
| |
| [pool release]; |
| |
| if (created_app && startup_info->success) |
| { |
| /* Never returns */ |
| [NSApp run]; |
| } |
| } |
| |
| |
| /*********************************************************************** |
| * macdrv_start_cocoa_app |
| * |
| * Tells the main thread to transform itself into a Cocoa application. |
| * |
| * Returns 0 on success, non-zero on failure. |
| */ |
| int macdrv_start_cocoa_app(unsigned long long tickcount) |
| { |
| int ret = -1; |
| CFRunLoopSourceRef source; |
| struct cocoa_app_startup_info startup_info; |
| uint64_t uptime_mach = mach_absolute_time(); |
| mach_timebase_info_data_t mach_timebase; |
| NSDate* timeLimit; |
| CFRunLoopSourceContext source_context = { 0 }; |
| |
| NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
| |
| /* Make sure Cocoa is in multi-threading mode by detaching a |
| do-nothing thread. */ |
| [NSThread detachNewThreadSelector:@selector(self) |
| toTarget:[NSThread class] |
| withObject:nil]; |
| |
| startup_info.lock = [[NSConditionLock alloc] initWithCondition:COCOA_APP_NOT_RUNNING]; |
| startup_info.tickcount = tickcount; |
| startup_info.success = FALSE; |
| |
| mach_timebase_info(&mach_timebase); |
| startup_info.uptime_ns = uptime_mach * mach_timebase.numer / mach_timebase.denom; |
| |
| timeLimit = [NSDate dateWithTimeIntervalSinceNow:5]; |
| |
| source_context.info = &startup_info; |
| source_context.perform = run_cocoa_app; |
| source = CFRunLoopSourceCreate(NULL, 0, &source_context); |
| |
| if (source && startup_info.lock && timeLimit) |
| { |
| CFRunLoopAddSource(CFRunLoopGetMain(), source, kCFRunLoopCommonModes); |
| CFRunLoopSourceSignal(source); |
| CFRunLoopWakeUp(CFRunLoopGetMain()); |
| |
| if ([startup_info.lock lockWhenCondition:COCOA_APP_RUNNING beforeDate:timeLimit]) |
| { |
| [startup_info.lock unlock]; |
| ret = !startup_info.success; |
| } |
| } |
| |
| if (source) |
| CFRelease(source); |
| [startup_info.lock release]; |
| [pool release]; |
| return ret; |
| } |