cocoa_main.m 4.95 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
/*
 * 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>
22 23
#include <mach/mach.h>
#include <mach/mach_time.h>
24 25 26 27 28 29 30 31 32 33 34 35 36 37

#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,
};


38 39 40 41
struct cocoa_app_startup_info {
    NSConditionLock*    lock;
    unsigned long long  tickcount;
    uint64_t            uptime_ns;
42
    BOOL                success;
43 44 45
};


46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
/***********************************************************************
 *              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)
{
63 64
    struct cocoa_app_startup_info* startup_info = info;
    NSConditionLock* lock = startup_info->lock;
65
    BOOL created_app = FALSE;
66 67 68

    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

69 70 71 72 73 74 75 76 77 78 79 80 81
    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;
    }
82 83 84 85 86 87 88 89 90 91

    /* 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];

92 93 94 95 96
    if (created_app && startup_info->success)
    {
        /* Never returns */
        [NSApp run];
    }
97 98 99 100 101 102 103 104 105 106
}


/***********************************************************************
 *              macdrv_start_cocoa_app
 *
 * Tells the main thread to transform itself into a Cocoa application.
 *
 * Returns 0 on success, non-zero on failure.
 */
107
int macdrv_start_cocoa_app(unsigned long long tickcount)
108 109 110
{
    int ret = -1;
    CFRunLoopSourceRef source;
111 112 113
    struct cocoa_app_startup_info startup_info;
    uint64_t uptime_mach = mach_absolute_time();
    mach_timebase_info_data_t mach_timebase;
114 115 116 117 118 119 120 121 122 123 124
    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];

125 126
    startup_info.lock = [[NSConditionLock alloc] initWithCondition:COCOA_APP_NOT_RUNNING];
    startup_info.tickcount = tickcount;
127
    startup_info.success = FALSE;
128 129 130 131

    mach_timebase_info(&mach_timebase);
    startup_info.uptime_ns = uptime_mach * mach_timebase.numer / mach_timebase.denom;

132 133
    timeLimit = [NSDate dateWithTimeIntervalSinceNow:5];

134
    source_context.info = &startup_info;
135 136 137
    source_context.perform = run_cocoa_app;
    source = CFRunLoopSourceCreate(NULL, 0, &source_context);

138
    if (source && startup_info.lock && timeLimit)
139 140 141 142 143
    {
        CFRunLoopAddSource(CFRunLoopGetMain(), source, kCFRunLoopCommonModes);
        CFRunLoopSourceSignal(source);
        CFRunLoopWakeUp(CFRunLoopGetMain());

144
        if ([startup_info.lock lockWhenCondition:COCOA_APP_RUNNING beforeDate:timeLimit])
145
        {
146
            [startup_info.lock unlock];
147
            ret = !startup_info.success;
148 149 150 151 152
        }
    }

    if (source)
        CFRelease(source);
153
    [startup_info.lock release];
154 155 156
    [pool release];
    return ret;
}