Merge branch 'master' of http://www-dev.cockos.com/wdl/WDL into mergecockos
[wdl/wdl-ol.git] / WDL / swell / swell-misc.mm
blobe0698a4400db729c6d967f555d5f62c1b3652aee
1 #ifndef SWELL_PROVIDED_BY_APP
3 //#import <Carbon/Carbon.h>
4 #import <Cocoa/Cocoa.h>
5 #include "swell.h"
6 #include "swell-internal.h"
8 #include "../mutex.h"
10 HWND g_swell_only_timerhwnd;
12 @implementation SWELL_TimerFuncTarget
14 -(id) initWithId:(UINT_PTR)tid hwnd:(HWND)h callback:(TIMERPROC)cb
16   if ((self = [super init]))
17   {
18     m_hwnd=h;
19     m_cb=cb;
20     m_timerid = tid;
21   }
22   return self;
24 -(void)SWELL_Timer:(id)sender
26   if (g_swell_only_timerhwnd && m_hwnd != g_swell_only_timerhwnd) return;
27   
28   m_cb(m_hwnd,WM_TIMER,m_timerid,GetTickCount());
30 @end
32 @implementation SWELL_DataHold
33 -(id) initWithVal:(void *)val
35   if ((self = [super init]))
36   {
37     m_data=val;
38   }
39   return self;
41 -(void *) getValue
43   return m_data;
45 @end
47 void SWELL_CFStringToCString(const void *str, char *buf, int buflen)
49   NSString *s = (NSString *)str;
50   if (buflen>0) *buf=0;
51   if (buflen <= 1 || !s || [s getCString:buf maxLength:buflen encoding:NSUTF8StringEncoding]) return; // should always work, I'd hope (ambiguous documentation ugh)
52   
53   NSData *data = [s dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
54   if (!data)
55   {
56     [s getCString:buf maxLength:buflen encoding:NSASCIIStringEncoding];
57     return;
58   }
59   int len = [data length];
60   if (len > buflen-1) len=buflen-1;
61   [data getBytes:buf length:len];
62   buf[len]=0;
65 void *SWELL_CStringToCFString(const char *str)
67   if (!str) str="";
68   void *ret;
69   
70   ret=(void *)CFStringCreateWithCString(NULL,str,kCFStringEncodingUTF8);
71   if (ret) return ret;
72   ret=(void*)CFStringCreateWithCString(NULL,str,kCFStringEncodingASCII);
73   return ret;
76 void SWELL_MakeProcessFront(HANDLE h)
78   SWELL_InternalObjectHeader_NSTask *buf = (SWELL_InternalObjectHeader_NSTask*)h;
79   if (buf && buf->hdr.type == INTERNAL_OBJECT_NSTASK && buf->task)
80   {
81     ProcessSerialNumber psn;
82     
83     int pid=[(id)buf->task processIdentifier];
84     // try for 1sec to get the PSN, if it fails
85     int n = 20;
86     while (GetProcessForPID(pid,&psn) != noErr && n-- > 0)
87     {
88       Sleep(50);
89     }
90     if (n>0) SetFrontProcess(&psn);
91   }
94 void SWELL_ReleaseNSTask(void *p)
96   NSTask *a =(NSTask*)p;
97   [a release];
99 DWORD SWELL_WaitForNSTask(void *p, DWORD msTO)
101   NSTask *a =(NSTask*)p;
102   DWORD t = msTO ? GetTickCount()+msTO : 0;
103   do 
104   {
105     if (![a isRunning]) return WAIT_OBJECT_0;
106     if (t) Sleep(1);
107   }
108   while (GetTickCount()<t);
110   return [a isRunning] ? WAIT_TIMEOUT : WAIT_OBJECT_0;
113 HANDLE SWELL_CreateProcessIO(const char *exe, int nparams, const char **params, bool redirectIO)
115   NSString *ex = (NSString *)SWELL_CStringToCFString(exe);
116   NSMutableArray *ar = [[NSMutableArray alloc] initWithCapacity:nparams];
118   int x;
119   for (x=0;x <nparams;x++)
120   {
121     NSString *s = (NSString *)SWELL_CStringToCFString(params[x]?params[x]:"");
122     [ar addObject:s];
123     [s release];
124   }
126   NSTask *tsk = NULL;
127   
128   @try {
129     tsk = [[NSTask alloc] init];
130     [tsk setLaunchPath:ex];
131     [tsk setArguments:ar];
132     if (redirectIO)
133     {
134       [tsk setStandardInput:[NSPipe pipe]];
135       [tsk setStandardOutput:[NSPipe pipe]];
136       [tsk setStandardError:[NSPipe pipe]];
137     }
138     [tsk launch];
139   }
140   @catch (NSException *exception) { 
141     [tsk release];
142     tsk=0;
143   }
144   @catch (id ex) {
145   }
147   if (tsk) [tsk retain];
148   [ex release];
149   [ar release];
150   if (!tsk) return NULL;
151   
152   SWELL_InternalObjectHeader_NSTask *buf = (SWELL_InternalObjectHeader_NSTask*)malloc(sizeof(SWELL_InternalObjectHeader_NSTask));
153   buf->hdr.type = INTERNAL_OBJECT_NSTASK;
154   buf->hdr.count=1;
155   buf->task = tsk;
156   return buf;
159 HANDLE SWELL_CreateProcess(const char *exe, int nparams, const char **params)
161   return SWELL_CreateProcessIO(exe,nparams,params,false);
165 int SWELL_GetProcessExitCode(HANDLE hand)
167   int rv=0;
168   SWELL_InternalObjectHeader_NSTask *hdr=(SWELL_InternalObjectHeader_NSTask*)hand;
169   if (!hdr || hdr->hdr.type != INTERNAL_OBJECT_NSTASK || !hdr->task) return -1;
170   @try {
171     if ([(NSTask *)hdr->task isRunning]) rv=-3;
172     else rv = [(NSTask *)hdr->task terminationStatus];
173   }
174   @catch (id ex) { 
175     rv=-2;
176   }
177   return rv;
180 int SWELL_TerminateProcess(HANDLE hand)
182   int rv=0;
183   SWELL_InternalObjectHeader_NSTask *hdr=(SWELL_InternalObjectHeader_NSTask*)hand;
184   if (!hdr || hdr->hdr.type != INTERNAL_OBJECT_NSTASK || !hdr->task) return -1;
185   @try
186   {
187     [(NSTask *)hdr->task terminate];
188   }
189   @catch (id ex) {
190     rv=-2;
191   }
192   return rv;
194 int SWELL_ReadWriteProcessIO(HANDLE hand, int w/*stdin,stdout,stderr*/, char *buf, int bufsz)
196   SWELL_InternalObjectHeader_NSTask *hdr=(SWELL_InternalObjectHeader_NSTask*)hand;
197   if (!hdr || hdr->hdr.type != INTERNAL_OBJECT_NSTASK || !hdr->task) return 0;
198   NSTask *tsk = (NSTask*)hdr->task;
199   NSPipe *pipe = NULL;
200   switch (w)
201   {
202     case 0: pipe = [tsk standardInput]; break;
203     case 1: pipe = [tsk standardOutput]; break;
204     case 2: pipe = [tsk standardError]; break;
205   }
206   if (!pipe || ![pipe isKindOfClass:[NSPipe class]]) return 0;
208   NSFileHandle *fh = w!=0 ? [pipe fileHandleForReading] : [pipe fileHandleForWriting];
209   if (!fh) return 0;
210   if (w==0)
211   {
212     if (bufsz>0)
213     {
214       NSData *d = [NSData dataWithBytes:buf length:bufsz];
215       @try
216       {
217         if (d) [fh writeData:d];
218         else bufsz=0;
219       }
220       @catch (id ex) { bufsz=0; }
222       return bufsz;
223     }
224   }
225   else 
226   {
227     NSData *d = NULL;
228     @try
229     {
230       d = [fh readDataOfLength:(bufsz < 1 ? 32768 : bufsz)];
231     }
232     @catch (id ex) { }
234     if (!d || bufsz < 1) return d ? [d length] : 0;
235     int l = [d length];
236     if (l > bufsz) l = bufsz;
237     [d getBytes:buf length:l];
238     return l;
239   }
241   return 0;
245 @implementation SWELL_ThreadTmp
246 -(void)bla:(id)obj
248   if (a) 
249   {
250     DWORD (*func)(void *);
251     *(void **)(&func) = a;
252     func(b);
253   }
254   [NSThread exit];
256 @end
258 void SWELL_EnsureMultithreadedCocoa()
260   static int a;
261   if (!a)
262   {
263     a++;
264     if (![NSThread isMultiThreaded]) // force cocoa into multithreaded mode
265     {
266       SWELL_ThreadTmp *t=[[SWELL_ThreadTmp alloc] init]; 
267       t->a=0;
268       t->b=0;
269       [NSThread detachNewThreadSelector:@selector(bla:) toTarget:t withObject:t];
270       ///      [t release];
271     }
272   }
275 void CreateThreadNS(void *TA, DWORD stackSize, DWORD (*ThreadProc)(LPVOID), LPVOID parm, DWORD cf, DWORD *tidOut)
277   SWELL_ThreadTmp *t=[[SWELL_ThreadTmp alloc] init]; 
278   t->a=(void*)ThreadProc;
279   t->b=parm;
280   return [NSThread detachNewThreadSelector:@selector(bla:) toTarget:t withObject:t];
284 // used by swell.cpp (lazy these should go elsewhere)
285 void *SWELL_InitAutoRelease()
287   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
288   return (void *)pool;
290 void SWELL_QuitAutoRelease(void *p)
292   if (p)
293     [(NSAutoreleasePool*)p release];
296 void SWELL_RunEvents()
298   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
299   int x=100;
300   while (x-- > 0)
301   {
302     NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate dateWithTimeIntervalSinceNow:0.001] inMode:NSDefaultRunLoopMode dequeue:YES];
303     if (!event) break;
304     [NSApp sendEvent:event];
305   }
306   [pool release];
309 // timer stuff
310 typedef struct TimerInfoRec
312   UINT_PTR timerid;
313   HWND hwnd;
314   NSTimer *timer;
315   struct TimerInfoRec *_next;
316 } TimerInfoRec;
317 static TimerInfoRec *m_timer_list;
318 static WDL_Mutex m_timermutex;
319 static pthread_t m_pmq_mainthread;
320 static void SWELL_pmq_settimer(HWND h, UINT_PTR timerid, UINT rate, TIMERPROC tProc);
322 UINT_PTR SetTimer(HWND hwnd, UINT_PTR timerid, UINT rate, TIMERPROC tProc)
324   if (!hwnd && !tProc) return 0; // must have either callback or hwnd
325   
326   if (hwnd && !timerid) return 0;
327   
328   if (timerid != ~(UINT_PTR)0 && m_pmq_mainthread && pthread_self()!=m_pmq_mainthread)
329   {   
330     SWELL_pmq_settimer(hwnd,timerid,(rate==(UINT)-1)?((UINT)-2):rate,tProc);
331     return timerid;
332   }
333   
334   
335   if (hwnd && ![(id)hwnd respondsToSelector:@selector(SWELL_Timer:)])
336   {
337     if (![(id)hwnd isKindOfClass:[NSWindow class]]) return 0;
338     hwnd=(HWND)[(id)hwnd contentView];
339     if (![(id)hwnd respondsToSelector:@selector(SWELL_Timer:)]) return 0;
340   }
341   
342   WDL_MutexLock lock(&m_timermutex);
343   TimerInfoRec *rec=NULL;
344   if (hwnd||timerid)
345   {
346     rec = m_timer_list;
347     while (rec)
348     {
349       if (rec->timerid == timerid && rec->hwnd == hwnd) // works for both kinds
350         break;
351       rec=rec->_next;
352     }
353   }
354   
355   bool recAdd=false;
356   if (!rec) 
357   {
358     rec=(TimerInfoRec*)malloc(sizeof(TimerInfoRec));
359     recAdd=true;
360   }
361   else 
362   {
363     [rec->timer invalidate];
364     rec->timer=0;
365   }
366   
367   rec->timerid=timerid;
368   rec->hwnd=hwnd;
369   
370   if (!hwnd || tProc)
371   {
372     // set timer to this unique ptr
373     if (!hwnd) timerid = rec->timerid = (UINT_PTR)rec;
374     
375     SWELL_TimerFuncTarget *t = [[SWELL_TimerFuncTarget alloc] initWithId:timerid hwnd:hwnd callback:tProc];
376     rec->timer = [NSTimer scheduledTimerWithTimeInterval:(wdl_max(rate,1)*0.001) target:t selector:@selector(SWELL_Timer:)
377                                                 userInfo:t repeats:YES];
378     [t release];
379     
380   }
381   else
382   {
383     SWELL_DataHold *t=[[SWELL_DataHold alloc] initWithVal:(void *)timerid];
384     rec->timer = [NSTimer scheduledTimerWithTimeInterval:(wdl_max(rate,1)*0.001) target:(id)hwnd selector:@selector(SWELL_Timer:)
385                                                 userInfo:t repeats:YES];
386     
387     [t release];
388   }
389   [[NSRunLoop currentRunLoop] addTimer:rec->timer forMode:(NSString*)kCFRunLoopCommonModes];
390   
391   if (recAdd)
392   {
393     rec->_next=m_timer_list;
394     m_timer_list=rec;
395   }
396   
397   return timerid;
399 void SWELL_RunRunLoop(int ms)
401   [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:ms*0.001]];
403 void SWELL_RunRunLoopEx(int ms, HWND hwndOnlyTimer)
405   HWND h=g_swell_only_timerhwnd;
406   g_swell_only_timerhwnd = hwndOnlyTimer;
407   [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:ms*0.001]];
408   if (g_swell_only_timerhwnd == hwndOnlyTimer) g_swell_only_timerhwnd = h;
411 BOOL KillTimer(HWND hwnd, UINT_PTR timerid)
413   if (!hwnd && !timerid) return FALSE;
414   
415   WDL_MutexLock lock(&m_timermutex);
416   if (timerid != ~(UINT_PTR)0 && m_pmq_mainthread && pthread_self()!=m_pmq_mainthread)
417   {
418     SWELL_pmq_settimer(hwnd,timerid,~(UINT)0,NULL);
419     return TRUE;
420   }
421   BOOL rv=FALSE;
422   
423   // don't allow removing all global timers
424   if (timerid!=~(UINT_PTR)0 || hwnd) 
425   {
426     TimerInfoRec *rec = m_timer_list, *lrec=NULL;
427     while (rec)
428     {
429       
430       if (rec->hwnd == hwnd && (timerid==~(UINT_PTR)0 || rec->timerid == timerid))
431       {
432         TimerInfoRec *nrec = rec->_next;
433         
434         // remove self from list
435         if (lrec) lrec->_next = nrec;
436         else m_timer_list = nrec;
437         
438         [rec->timer invalidate];
439         free(rec);
440         
441         rv=TRUE;
442         if (timerid!=~(UINT_PTR)0) break;
443         
444         rec=nrec;
445       }
446       else 
447       {
448         lrec=rec;
449         rec=rec->_next;
450       }
451     }
452   }
453   return rv;
458 ///////// PostMessage emulation
460 BOOL PostMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
462   id del=[NSApp delegate];
463   if (del && [del respondsToSelector:@selector(swellPostMessage:msg:wp:lp:)])
464     return !![(SWELL_DelegateExtensions*)del swellPostMessage:hwnd msg:message wp:wParam lp:lParam];
465   return FALSE;
468 void SWELL_MessageQueue_Clear(HWND h)
470   id del=[NSApp delegate];
471   if (del && [del respondsToSelector:@selector(swellPostMessageClearQ:)])
472     [(SWELL_DelegateExtensions*)del swellPostMessageClearQ:h];
477 // implementation of postmessage stuff
481 typedef struct PMQ_rec
483   HWND hwnd;
484   UINT msg;
485   WPARAM wParam;
486   LPARAM lParam;
487   
488   struct PMQ_rec *next;
489   bool is_special_timer; // if set, then msg=interval(-1 for kill),wParam=timer id, lParam = timerproc
490 } PMQ_rec;
492 static WDL_Mutex *m_pmq_mutex;
493 static PMQ_rec *m_pmq, *m_pmq_empty, *m_pmq_tail;
494 static int m_pmq_size;
495 static id m_pmq_timer;
496 #define MAX_POSTMESSAGE_SIZE 1024
498 void SWELL_Internal_PostMessage_Init()
500   if (m_pmq_mutex) return;
501   id del = [NSApp delegate];
502   if (!del || ![del respondsToSelector:@selector(swellPostMessageTick:)]) return;
503   
504   m_pmq_mainthread=pthread_self();
505   m_pmq_mutex = new WDL_Mutex;
506   
507   m_pmq_timer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:(id)del selector:@selector(swellPostMessageTick:) userInfo:nil repeats:YES];
508   [[NSRunLoop currentRunLoop] addTimer:m_pmq_timer forMode:(NSString*)kCFRunLoopCommonModes];
509   //  [ release];
510   // set a timer to the delegate
513 #ifndef SWELL_JUST_FOR_THREADING
514 void SWELL_MessageQueue_Flush()
516   if (!m_pmq_mutex) return;
517   
518   m_pmq_mutex->Enter();
519   int max_amt = m_pmq_size;
520   PMQ_rec *p=m_pmq;
521   if (p)
522   {
523     m_pmq = p->next;
524     if (m_pmq_tail == p) m_pmq_tail=NULL;
525     m_pmq_size--;
526   }
527   m_pmq_mutex->Leave();
528   
529   // process out queue
530   while (p)
531   {
532     if (p->is_special_timer)
533     {
534       if (p->msg == ~(UINT)0) KillTimer(p->hwnd,p->wParam);
535       else SetTimer(p->hwnd,p->wParam,p->msg,(TIMERPROC)p->lParam);
536     }
537     else
538     {
539       SendMessage(p->hwnd,p->msg,p->wParam,p->lParam); 
540     }
541     
542     m_pmq_mutex->Enter();
543     // move this message to empty list
544     p->next=m_pmq_empty;
545     m_pmq_empty = p;
547     // get next queued message (if within limits)
548     p = (--max_amt > 0) ? m_pmq : NULL;
549     if (p)
550     {
551       m_pmq = p->next;
552       if (m_pmq_tail == p) m_pmq_tail=NULL;
553       m_pmq_size--;
554     }
555     m_pmq_mutex->Leave();
556   }
558 #endif
560 void SWELL_Internal_PMQ_ClearAllMessages(HWND hwnd)
562   if (!m_pmq_mutex) return;
563   
564   m_pmq_mutex->Enter();
565   PMQ_rec *p=m_pmq;
566   PMQ_rec *lastrec=NULL;
567   while (p)
568   {
569     if (hwnd && p->hwnd != hwnd) { lastrec=p; p=p->next; }
570     else
571     {
572       PMQ_rec *next=p->next; 
573       
574       p->next=m_pmq_empty; // add p to empty list
575       m_pmq_empty=p;
576       m_pmq_size--;
577       
578       
579       if (p==m_pmq_tail) m_pmq_tail=lastrec; // update tail
580       
581       if (lastrec)  p = lastrec->next = next;
582       else p = m_pmq = next;
583     }
584   }
585   m_pmq_mutex->Leave();
588 static void SWELL_pmq_settimer(HWND h, UINT_PTR timerid, UINT rate, TIMERPROC tProc)
590   if (!h||!m_pmq_mutex) return;
591   WDL_MutexLock lock(m_pmq_mutex);
592   
593   PMQ_rec *rec=m_pmq;
594   while (rec)
595   {
596     if (rec->is_special_timer && rec->hwnd == h && rec->wParam == timerid)
597     {
598       rec->msg = rate; // adjust to new rate
599       rec->lParam = (LPARAM)tProc;
600       return;
601     }
602     rec=rec->next;
603   }  
604   
605   rec=m_pmq_empty;
606   if (rec) m_pmq_empty=rec->next;
607   else rec=(PMQ_rec*)malloc(sizeof(PMQ_rec));
608   rec->next=0;
609   rec->hwnd=h;
610   rec->msg=rate;
611   rec->wParam=timerid;
612   rec->lParam=(LPARAM)tProc;
613   rec->is_special_timer=true;
614   
615   if (m_pmq_tail) m_pmq_tail->next=rec;
616   else 
617   {
618     PMQ_rec *p=m_pmq;
619     while (p && p->next) p=p->next; // shouldnt happen unless m_pmq is NULL As well but why not for safety
620     if (p) p->next=rec;
621     else m_pmq=rec;
622   }
623   m_pmq_tail=rec;
624   m_pmq_size++;
627 BOOL SWELL_Internal_PostMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
629   if (!hwnd||!m_pmq_mutex) return FALSE;
630   if (![(id)hwnd respondsToSelector:@selector(swellCanPostMessage)]) return FALSE;
631   
632   BOOL ret=FALSE;
633   m_pmq_mutex->Enter();
634   
635   if ((m_pmq_empty||m_pmq_size<MAX_POSTMESSAGE_SIZE) && [(id)hwnd swellCanPostMessage])
636   {
637     PMQ_rec *rec=m_pmq_empty;
638     if (rec) m_pmq_empty=rec->next;
639     else rec=(PMQ_rec*)malloc(sizeof(PMQ_rec));
640     rec->next=0;
641     rec->hwnd=hwnd;
642     rec->msg=msg;
643     rec->wParam=wParam;
644     rec->lParam=lParam;
645     rec->is_special_timer=false;
646     
647     if (m_pmq_tail) m_pmq_tail->next=rec;
648     else 
649     {
650       PMQ_rec *p=m_pmq;
651       while (p && p->next) p=p->next; // shouldnt happen unless m_pmq is NULL As well but why not for safety
652       if (p) p->next=rec;
653       else m_pmq=rec;
654     }
655     m_pmq_tail=rec;
656     m_pmq_size++;
657     
658     ret=TRUE;
659   }
660   
661   m_pmq_mutex->Leave();
662   
663   return ret;
667 static bool s_rightclickemulate=true;
669 bool IsRightClickEmulateEnabled()
671   return s_rightclickemulate;
674 void SWELL_EnableRightClickEmulate(BOOL enable)
676   s_rightclickemulate=enable;
679 int g_swell_terminating;
680 void SWELL_PostQuitMessage(void *sender)
682   g_swell_terminating=true;
684   [NSApp terminate:(id)sender];
688 #ifndef MAC_OS_X_VERSION_10_9
689 typedef uint64_t NSActivityOptions;
690 enum
692   NSActivityIdleDisplaySleepDisabled = (1ULL << 40),
693   NSActivityIdleSystemSleepDisabled = (1ULL << 20),
694   NSActivitySuddenTerminationDisabled = (1ULL << 14),
695   NSActivityAutomaticTerminationDisabled = (1ULL << 15),
696   NSActivityUserInitiated = (0x00FFFFFFULL | NSActivityIdleSystemSleepDisabled),
697   NSActivityUserInitiatedAllowingIdleSystemSleep = (NSActivityUserInitiated & ~NSActivityIdleSystemSleepDisabled),
698   NSActivityBackground = 0x000000FFULL,
699   NSActivityLatencyCritical = 0xFF00000000ULL,
703 @interface NSProcessInfo (reaperhostadditions)
704 - (id<NSObject>)beginActivityWithOptions:(NSActivityOptions)options reason:(NSString *)reason;
705 - (void)endActivity:(id<NSObject>)activity;
707 @end
709 #endif
711 void SWELL_DisableAppNap(int disable)
713   if (!g_swell_terminating && floor(NSFoundationVersionNumber) > 945.00) // 10.9+
714   {
715     static int cnt;
716     static id obj;
718     cnt += disable;
720     @try
721     {
722       if (cnt > 0)
723       {
724         if (!obj)
725         {
726           const NSActivityOptions  v = NSActivityLatencyCritical |  NSActivityIdleSystemSleepDisabled;
728           // beginActivityWithOptions returns an autoreleased object
729           obj = [[NSProcessInfo processInfo] beginActivityWithOptions:v reason:@"SWELL_DisableAppNap"];
730           if (obj) [obj retain];
731         }
732       }
733       else
734       {
735         id a = obj;
736         if (a)
737         {
738           // in case we crash somehow, dont' want obj sticking around pointing to a stale object
739           obj = NULL;
740           [[NSProcessInfo processInfo] endActivity:a];
741           [a release]; // apparently releasing this is enough, without the endActivity call, but the docs are quite vague
742         }
743       }
744     }
745     @catch (NSException *exception) {
746     }
747     @catch (id ex) {
748     }
749   }
753 int SWELL_GetOSXVersion()
755   static SInt32 v;
756   if (!v)
757   {
758     if (NSAppKitVersionNumber >= 1266.0) 
759     {
760       if (NSAppKitVersionNumber >= 1404.0)
761         v = 0x10b0;
762       else
763         v = 0x10a0; // 10.10+ Gestalt(gsv) return 0x109x, so we bump this to 0x10a0
764     }
765     else 
766     {
767       SInt32 a = 0x1040;
768       Gestalt(gestaltSystemVersion,&a);
769       v=a;
770     }
771   }
772   return v;
775 #endif