]> git.saurik.com Git - wxWidgets.git/blob - src/mac/corefoundation/hid.cpp
Fix for unexplained change in socket behavior where failed connections throw a CONNEC...
[wxWidgets.git] / src / mac / corefoundation / hid.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: hid.cpp
3 // Purpose: DARWIN HID layer for WX Implementation
4 // Author: Ryan Norton
5 // Modified by:
6 // Created: 11/11/2003
7 // RCS-ID: $Id$
8 // Copyright: (c) Ryan Norton
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 // ===========================================================================
13 // declarations
14 // ===========================================================================
15
16 // ---------------------------------------------------------------------------
17 // headers
18 // ---------------------------------------------------------------------------
19
20 #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
21 #pragma implementation "hid.h"
22 #endif
23
24 // For compilers that support precompilation, includes "wx.h".
25 #include "wx/wxprec.h"
26
27 #ifdef __BORLANDC__
28 #pragma hdrstop
29 #endif
30
31 //DARWIN _ONLY_
32 #ifdef __DARWIN__
33
34 #include "wx/mac/corefoundation/hid.h"
35 #include "wx/string.h"
36 #include "wx/log.h"
37 #include "wx/mac/corefoundation/cfstring.h"
38
39
40 // ---------------------------------------------------------------------------
41 // assertion macros
42 // ---------------------------------------------------------------------------
43
44 #define wxFORCECHECK_MSG(arg, msg) \
45 {\
46 if (arg) \
47 {\
48 wxLogSysError(wxString::Format(wxT("Message:%s\nHID: %s failed!"), wxT(msg), wxT(#arg)));\
49 return false;\
50 }\
51 }
52 #define wxIOCHECK(arg, msg) wxFORCECHECK_MSG(arg != kIOReturnSuccess, msg)
53 #define wxKERNCHECK(arg, msg) wxFORCECHECK_MSG(arg != KERN_SUCCESS, msg)
54 #define wxSCHECK(arg, msg) wxFORCECHECK_MSG(arg != S_OK, msg)
55
56 #ifdef __WXDEBUG___
57 # define wxVERIFY(arg) wxASSERT(arg)
58 #else
59 # define wxVERIFY(arg) arg
60 #endif
61
62 /*
63 void CFShowTypeIDDescription(CFTypeRef pData)
64 {
65 if(!pData)
66 {
67 wxASSERT(false);
68 return;
69 }
70
71 wxMessageBox(
72 CFStringGetCStringPtr(
73 CFCopyTypeIDDescription(CFGetTypeID(pData)),CFStringGetSystemEncoding()
74 )
75 );
76 }
77 */
78
79 // ============================================================================
80 // implementation
81 // ============================================================================
82
83 // ---------------------------------------------------------------------------
84 // wxHIDDevice
85 // ---------------------------------------------------------------------------
86
87 bool wxHIDDevice::Create (int nClass, int nType, int nDev)
88 {
89 //Create the mach port
90 wxIOCHECK(IOMasterPort(bootstrap_port, &m_pPort), "Could not create mach port");
91
92 //Dictionary that will hold first
93 //the matching dictionary for determining which kind of devices we want,
94 //then later some registry properties from an iterator (see below)
95 CFMutableDictionaryRef pDictionary;
96
97 //Create a dictionary
98 //The call to IOServiceMatching filters down the
99 //the services we want to hid services (and also eats the
100 //dictionary up for us (consumes one reference))
101 wxVERIFY((pDictionary = IOServiceMatching(kIOHIDDeviceKey)) != NULL );
102
103 //Here we'll filter down the services to what we want
104 if (nType != -1)
105 {
106 CFNumberRef pType = CFNumberCreate(kCFAllocatorDefault,
107 kCFNumberIntType, &nType);
108 CFDictionarySetValue(pDictionary, CFSTR(kIOHIDPrimaryUsageKey), pType);
109 CFRelease(pType);
110 }
111 if (nClass != -1)
112 {
113 CFNumberRef pClass = CFNumberCreate(kCFAllocatorDefault,
114 kCFNumberIntType, &nClass);
115 CFDictionarySetValue(pDictionary, CFSTR(kIOHIDPrimaryUsagePageKey), pClass);
116 CFRelease(pClass);
117 }
118
119 //Now get the maching services
120 io_iterator_t pIterator;
121 wxIOCHECK(IOServiceGetMatchingServices(m_pPort, pDictionary, &pIterator), "No Matching HID Services");
122 wxASSERT_MSG(pIterator != 0, wxT("No devices found!"));
123
124 //Now we iterate through them
125 io_object_t pObject;
126 while ( (pObject = IOIteratorNext(pIterator)) != 0)
127 {
128 if(--nDev != 0)
129 continue;
130
131 wxVERIFY(IORegistryEntryCreateCFProperties(pObject, &pDictionary,
132 kCFAllocatorDefault, kNilOptions) == KERN_SUCCESS);
133
134 //Just for sanity :)
135 wxASSERT(CFGetTypeID(CFDictionaryGetValue(pDictionary, CFSTR(kIOHIDProductKey))) == CFStringGetTypeID());
136
137 /*
138 kIOHIDTransportKey;
139 kIOHIDVendorIDKey;
140 kIOHIDProductIDKey;
141 kIOHIDVersionNumberKey;
142 kIOHIDManufacturerKey;
143 kIOHIDSerialNumberKey;
144 if !kIOHIDLocationIDKey
145 kUSBDevicePropertyLocationID
146 kIOHIDPrimaryUsageKey
147 kIOHIDPrimaryUsagePageKey
148 idProduct
149 idVendor
150 USB Product Name
151 */
152 //Get [product] name
153 m_szProductName = wxMacCFStringHolder( (CFStringRef) CFDictionaryGetValue(pDictionary, CFSTR(kIOHIDProductKey)), false ).AsString();
154
155 CFNumberRef nref = (CFNumberRef) CFDictionaryGetValue(pDictionary, CFSTR(kIOHIDProductIDKey));
156
157 if (nref)
158 CFNumberGetValue(
159 nref,
160 kCFNumberIntType,
161 &m_nProductId
162 );
163
164 nref = (CFNumberRef) CFDictionaryGetValue(pDictionary, CFSTR(kIOHIDVendorIDKey));
165 if (nref)
166 CFNumberGetValue(
167 nref,
168 kCFNumberIntType,
169 &m_nManufacturerId
170 );
171
172 //Create the interface (good grief - long function names!)
173 SInt32 nScore;
174 IOCFPlugInInterface** ppPlugin;
175 wxIOCHECK(IOCreatePlugInInterfaceForService(pObject, kIOHIDDeviceUserClientTypeID,
176 kIOCFPlugInInterfaceID, &ppPlugin, &nScore), "");
177
178 //Now, the final thing we can check before we fall back to asserts
179 //(because the dtor only checks if the device is ok, so if anything
180 //fails from now on the dtor will delete the device anyway, so we can't break from this).
181
182 //Get the HID interface from the plugin to the mach port
183 wxSCHECK((*ppPlugin)->QueryInterface(ppPlugin,
184 CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), (void**) &m_ppDevice), "");
185
186 //release the plugin
187 (*ppPlugin)->Release(ppPlugin);
188
189 //open the HID interface...
190 wxVERIFY((*m_ppDevice)->open(m_ppDevice, 0) == S_OK);
191
192 //
193 //Now the hard part - in order to scan things we need "cookies" -
194 //
195 wxCFArray CookieArray = CFDictionaryGetValue(pDictionary, CFSTR(kIOHIDElementKey));
196 BuildCookies(CookieArray);
197
198 //cleanup
199 CFRelease(pDictionary);
200 IOObjectRelease(pObject);
201 break;
202 }
203 //iterator cleanup
204 IOObjectRelease(pIterator);
205
206 return true;
207 }//end Create()
208
209 int wxHIDDevice::GetCount (int nClass, int nType)
210 {
211 mach_port_t m_pPort;
212
213 //Create the mach port
214 wxIOCHECK(IOMasterPort(bootstrap_port, &m_pPort), "Could not create mach port");
215
216 //Dictionary that will hold first
217 //the matching dictionary for determining which kind of devices we want,
218 //then later some registry properties from an iterator (see below)
219 CFMutableDictionaryRef pDictionary;
220
221 //Create a dictionary
222 //The call to IOServiceMatching filters down the
223 //the services we want to hid services (and also eats the
224 //dictionary up for us (consumes one reference))
225 wxVERIFY((pDictionary = IOServiceMatching(kIOHIDDeviceKey)) != NULL );
226
227 //Here we'll filter down the services to what we want
228 if (nType != -1)
229 {
230 CFNumberRef pType = CFNumberCreate(kCFAllocatorDefault,
231 kCFNumberIntType, &nType);
232 CFDictionarySetValue(pDictionary, CFSTR(kIOHIDPrimaryUsageKey), pType);
233 CFRelease(pType);
234 }
235 if (nClass != -1)
236 {
237 CFNumberRef pClass = CFNumberCreate(kCFAllocatorDefault,
238 kCFNumberIntType, &nClass);
239 CFDictionarySetValue(pDictionary, CFSTR(kIOHIDPrimaryUsagePageKey), pClass);
240 CFRelease(pClass);
241 }
242
243 //Now get the maching services
244 io_iterator_t pIterator;
245 wxIOCHECK(IOServiceGetMatchingServices(m_pPort, pDictionary, &pIterator), "No Matching HID Services");
246
247 if(pIterator == NULL)
248 return 0;
249
250 //Now we iterate through them
251 io_object_t pObject;
252
253 int nCount = 0;
254
255 while ( (pObject = IOIteratorNext(pIterator)) != 0)
256 ++nCount;
257
258 //iterator cleanup
259 IOObjectRelease(pIterator);
260
261 return nCount;
262 }//end Create()
263
264 void wxHIDDevice::AddCookie(CFTypeRef Data, int i)
265 {
266 CFNumberGetValue(
267 (CFNumberRef) CFDictionaryGetValue ( (CFDictionaryRef) Data
268 , CFSTR(kIOHIDElementCookieKey)
269 ),
270 kCFNumberIntType,
271 &m_pCookies[i]
272 );
273 }
274
275 void wxHIDDevice::AddCookieInQueue(CFTypeRef Data, int i)
276 {
277 AddCookie(Data, i);
278 wxVERIFY((*m_ppQueue)->addElement(m_ppQueue, m_pCookies[i], 0) == S_OK);//3rd Param flags (none yet)
279 }
280
281 void wxHIDDevice::InitCookies(size_t dwSize, bool bQueue)
282 {
283 m_pCookies = new IOHIDElementCookie[dwSize];
284 if (bQueue)
285 {
286 wxASSERT( m_ppQueue == NULL);
287 wxVERIFY( (m_ppQueue = (*m_ppDevice)->allocQueue(m_ppDevice)) != NULL);
288 wxVERIFY( (*m_ppQueue)->create(m_ppQueue, 0, 512) == S_OK); //Param 2, flags, none yet
289 }
290 }
291
292 bool wxHIDDevice::IsActive(int nIndex)
293 {
294 wxASSERT(m_pCookies[nIndex] != NULL);
295 IOHIDEventStruct Event;
296 (*m_ppDevice)->getElementValue(m_ppDevice, m_pCookies[nIndex], &Event);
297 /*
298 wxString ss;
299 ss << _T("[") << (int) m_pCookies[nIndex] << _T("] = ") << Event.value << _T(" SIZE:") << Event.longValueSize;
300
301 wxLogDebug(ss);
302 */
303 return !!Event.value;
304 }
305
306 bool wxHIDDevice::HasElement(int nIndex)
307 {
308 return m_pCookies[nIndex] != NULL;
309 }
310
311 wxHIDDevice::~wxHIDDevice()
312 {
313 if (m_ppDevice != NULL)
314 {
315 if (m_ppQueue != NULL)
316 {
317 (*m_ppQueue)->stop(m_ppQueue);
318 (*m_ppQueue)->dispose(m_ppQueue);
319 (*m_ppQueue)->Release(m_ppQueue);
320 }
321 (*m_ppDevice)->close(m_ppDevice);
322 (*m_ppDevice)->Release(m_ppDevice);
323 mach_port_deallocate(mach_task_self(), m_pPort);
324 }
325
326 if (m_pCookies != NULL)
327 {
328 delete [] m_pCookies;
329 }
330 }
331
332 // ---------------------------------------------------------------------------
333 // wxHIDKeyboard
334 // ---------------------------------------------------------------------------
335
336 enum
337 {
338 WXK_RSHIFT = 400,
339 WXK_RALT,
340 WXK_RCONTROL,
341 WXK_RMENU
342
343 };
344
345 bool wxHIDKeyboard::Create()
346 {
347 return wxHIDDevice::Create(kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard);
348 }
349
350 void wxHIDKeyboard::BuildCookies(wxCFArray& Array)
351 {
352 Array = CFDictionaryGetValue((CFDictionaryRef)Array[0], CFSTR(kIOHIDElementKey));
353 InitCookies(500);
354 int i,
355 nUsage;
356 bool bEOTriggered = false;
357 for (i = 0; i < Array.Count(); ++i)
358 {
359 CFNumberGetValue(
360 (CFNumberRef) CFDictionaryGetValue((CFDictionaryRef) Array[i], CFSTR(kIOHIDElementUsageKey)),
361 kCFNumberLongType, &nUsage);
362
363 //
364 // OK, this is strange - basically this kind of strange -
365 // Starting from 0xEO these elements (like shift) appear twice in
366 // the array! The ones at the end are bogus I guess - the funny part
367 // is that besides the fact that the ones at the front have a Unit
368 // and UnitExponent key with a value of 0 and a different cookie value,
369 // there is no discernable difference between the two...
370 //
371 // Will the real shift please stand up?
372 //
373 // Something to spend a support request on, if I had one, LOL.
374 //
375 if(nUsage == 0xE0)
376 {
377 if(bEOTriggered)
378 break;
379 bEOTriggered = true;
380 }
381 /*
382 wxString msg;
383 int cookie;
384 CFNumberGetValue(
385 (CFNumberRef) CFDictionaryGetValue ( (CFDictionaryRef) Array[i]
386 , CFSTR(kIOHIDElementCookieKey)
387 ),
388 kCFNumberIntType,
389 &cookie
390 );
391
392 msg << wxT("KEY:") << nUsage << wxT("COOKIE:") << cookie;
393 wxLogDebug(msg);
394 */
395
396 if (nUsage >= kHIDUsage_KeyboardA && nUsage <= kHIDUsage_KeyboardZ)
397 AddCookie(Array[i], 'A' + (nUsage - kHIDUsage_KeyboardA) );
398 else if (nUsage >= kHIDUsage_Keyboard1 && nUsage <= kHIDUsage_Keyboard9)
399 AddCookie(Array[i], '1' + (nUsage - kHIDUsage_Keyboard1) );
400 else if (nUsage >= kHIDUsage_KeyboardF1 && nUsage <= kHIDUsage_KeyboardF12)
401 AddCookie(Array[i], WXK_F1 + (nUsage - kHIDUsage_KeyboardF1) );
402 else if (nUsage >= kHIDUsage_KeyboardF13 && nUsage <= kHIDUsage_KeyboardF24)
403 AddCookie(Array[i], WXK_F13 + (nUsage - kHIDUsage_KeyboardF13) );
404 else if (nUsage >= kHIDUsage_Keypad1 && nUsage <= kHIDUsage_Keypad9)
405 AddCookie(Array[i], WXK_NUMPAD1 + (nUsage - kHIDUsage_Keypad1) );
406 else switch (nUsage)
407 {
408 //0's (wx & ascii go 0-9, but HID goes 1-0)
409 case kHIDUsage_Keyboard0:
410 AddCookie(Array[i],'0');
411 break;
412 case kHIDUsage_Keypad0:
413 AddCookie(Array[i],WXK_NUMPAD0);
414 break;
415
416 //Basic
417 case kHIDUsage_KeyboardReturnOrEnter:
418 AddCookie(Array[i], WXK_RETURN);
419 break;
420 case kHIDUsage_KeyboardEscape:
421 AddCookie(Array[i], WXK_ESCAPE);
422 break;
423 case kHIDUsage_KeyboardDeleteOrBackspace:
424 AddCookie(Array[i], WXK_BACK);
425 break;
426 case kHIDUsage_KeyboardTab:
427 AddCookie(Array[i], WXK_TAB);
428 break;
429 case kHIDUsage_KeyboardSpacebar:
430 AddCookie(Array[i], WXK_SPACE);
431 break;
432 case kHIDUsage_KeyboardPageUp:
433 AddCookie(Array[i], WXK_PRIOR);
434 break;
435 case kHIDUsage_KeyboardEnd:
436 AddCookie(Array[i], WXK_END);
437 break;
438 case kHIDUsage_KeyboardPageDown:
439 AddCookie(Array[i], WXK_NEXT);
440 break;
441 case kHIDUsage_KeyboardRightArrow:
442 AddCookie(Array[i], WXK_RIGHT);
443 break;
444 case kHIDUsage_KeyboardLeftArrow:
445 AddCookie(Array[i], WXK_LEFT);
446 break;
447 case kHIDUsage_KeyboardDownArrow:
448 AddCookie(Array[i], WXK_DOWN);
449 break;
450 case kHIDUsage_KeyboardUpArrow:
451 AddCookie(Array[i], WXK_UP);
452 break;
453
454 //LEDS
455 case kHIDUsage_KeyboardCapsLock:
456 AddCookie(Array[i],WXK_CAPITAL);
457 break;
458 case kHIDUsage_KeypadNumLock:
459 AddCookie(Array[i],WXK_NUMLOCK);
460 break;
461 case kHIDUsage_KeyboardScrollLock:
462 AddCookie(Array[i],WXK_SCROLL);
463 break;
464
465 //Menu keys, Shift, other specials
466 case kHIDUsage_KeyboardLeftControl:
467 AddCookie(Array[i],WXK_CONTROL);
468 break;
469 case kHIDUsage_KeyboardLeftShift:
470 AddCookie(Array[i],WXK_SHIFT);
471 break;
472 case kHIDUsage_KeyboardLeftAlt:
473 AddCookie(Array[i],WXK_ALT);
474 break;
475 case kHIDUsage_KeyboardLeftGUI:
476 AddCookie(Array[i],WXK_MENU);
477 break;
478 case kHIDUsage_KeyboardRightControl:
479 AddCookie(Array[i],WXK_RCONTROL);
480 break;
481 case kHIDUsage_KeyboardRightShift:
482 AddCookie(Array[i],WXK_RSHIFT);
483 break;
484 case kHIDUsage_KeyboardRightAlt:
485 AddCookie(Array[i],WXK_RALT);
486 break;
487 case kHIDUsage_KeyboardRightGUI:
488 AddCookie(Array[i],WXK_RMENU);
489 break;
490
491 //Default
492 default:
493 //not in wx keycodes - do nothing....
494 break;
495 }
496 }
497 }//end buildcookies
498
499 //
500 // wxGetKeyState
501 //
502
503 #include "wx/utils.h"
504 #include "wx/module.h"
505
506 class wxHIDModule : public wxModule
507 {
508 DECLARE_DYNAMIC_CLASS(wxHIDModule)
509
510 public:
511 static wxHIDKeyboard* sm_keyboard;
512
513 virtual bool OnInit()
514 {
515 sm_keyboard = NULL;
516 return true;
517 }
518 virtual void OnExit()
519 {
520 if (sm_keyboard)
521 delete sm_keyboard;
522 }
523 };
524
525 IMPLEMENT_DYNAMIC_CLASS(wxHIDModule, wxModule)
526
527 wxHIDKeyboard* wxHIDModule::sm_keyboard;
528
529 bool wxGetKeyState (wxKeyCode key)
530 {
531 wxASSERT_MSG(key != WXK_LBUTTON && key != WXK_RBUTTON && key !=
532 WXK_MBUTTON, wxT("can't use wxGetKeyState() for mouse buttons"));
533
534 if (!wxHIDModule::sm_keyboard)
535 {
536 wxHIDModule::sm_keyboard = new wxHIDKeyboard();
537 bool bOK = wxHIDModule::sm_keyboard->Create();
538 wxASSERT(bOK);
539 if(!bOK)
540 {
541 delete wxHIDModule::sm_keyboard;
542 wxHIDModule::sm_keyboard = NULL;
543 return false;
544 }
545 }
546
547 switch(key)
548 {
549 case WXK_SHIFT:
550 return wxHIDModule::sm_keyboard->IsActive(WXK_SHIFT) ||
551 wxHIDModule::sm_keyboard->IsActive(WXK_RSHIFT);
552 break;
553 case WXK_ALT:
554 return wxHIDModule::sm_keyboard->IsActive(WXK_ALT) ||
555 wxHIDModule::sm_keyboard->IsActive(WXK_RALT);
556 break;
557 case WXK_CONTROL:
558 return wxHIDModule::sm_keyboard->IsActive(WXK_CONTROL) ||
559 wxHIDModule::sm_keyboard->IsActive(WXK_RCONTROL);
560 break;
561 case WXK_MENU:
562 return wxHIDModule::sm_keyboard->IsActive(WXK_MENU) ||
563 wxHIDModule::sm_keyboard->IsActive(WXK_RMENU);
564 break;
565 default:
566 return wxHIDModule::sm_keyboard->IsActive(key);
567 break;
568 }
569 }
570
571 //
572 // TODO: Find a better file to put this in
573 //
574 #include "wx/intl.h"
575
576 #ifdef __WXCOCOA__
577 #include <CoreFoundation/CoreFoundation.h>
578 #include <ApplicationServices/ApplicationServices.h>
579 #else
580 #include "wx/mac/private.h"
581 #include "LaunchServices.h"
582 #endif
583
584 #include "wx/uri.h"
585 #include "wx/mac/corefoundation/cfstring.h"
586
587 long wxMacExecute(wxChar **argv,
588 int flags,
589 wxProcess *process)
590 {
591 CFIndex cfiCount = 0;
592 //get count
593 for(wxChar** argvcopy = argv; *argvcopy != NULL ; ++argvcopy)
594 {
595 ++cfiCount;
596 }
597
598 if(cfiCount == 0) //no file to launch?
599 {
600 wxLogDebug(wxT("wxMacExecute No file to launch!"));
601 return -1;
602 }
603
604 CFURLRef cfurlApp = CFURLCreateWithString(
605 kCFAllocatorDefault,
606 wxMacCFStringHolder(*argv++, wxLocale::GetSystemEncoding()),
607 NULL);
608 wxASSERT(cfurlApp);
609
610 CFBundleRef cfbApp = CFBundleCreate(kCFAllocatorDefault, cfurlApp);
611 if(!cfbApp)
612 {
613 wxLogDebug(wxT("wxMacExecute Bad bundle"));
614 CFRelease(cfurlApp);
615 return -1;
616 }
617
618
619 UInt32 dwBundleType, dwBundleCreator;
620 CFBundleGetPackageInfo(cfbApp, &dwBundleType, &dwBundleCreator);
621
622 //Only call wxMacExecute for .app bundles - others could be actual unix programs
623 if(dwBundleType != 'APPL')
624 {
625 CFRelease(cfurlApp);
626 return -1;
627 }
628
629 //
630 // We have a good bundle - so let's launch it!
631 //
632
633 CFMutableArrayRef cfaFiles = CFArrayCreateMutable(kCFAllocatorDefault, cfiCount - 1, NULL);
634
635 wxASSERT(cfaFiles);
636
637 if(--cfiCount)
638 {
639 for( ; *argv != NULL ; ++argv)
640 {
641 // wxLogDebug(*argv);
642 wxString sCurrentFile;
643
644 if(wxURI(*argv).IsReference())
645 sCurrentFile = wxString(wxT("file://")) + *argv;
646 else
647 sCurrentFile = *argv;
648
649 CFURLRef cfurlCurrentFile = CFURLCreateWithString(
650 kCFAllocatorDefault,
651 wxMacCFStringHolder(sCurrentFile, wxLocale::GetSystemEncoding()),
652 NULL);
653 wxASSERT(cfurlCurrentFile);
654
655 CFArrayAppendValue(
656 cfaFiles,
657 cfurlCurrentFile
658 );
659 }
660 }
661
662 LSLaunchURLSpec launchspec;
663 launchspec.appURL = cfurlApp;
664 launchspec.itemURLs = cfaFiles;
665 launchspec.passThruParams = NULL; //AEDesc*
666 launchspec.launchFlags = kLSLaunchDefaults | kLSLaunchDontSwitch; //TODO: Possibly be smarter with flags
667 launchspec.asyncRefCon = NULL;
668
669 OSStatus status = LSOpenFromURLSpec(&launchspec,
670 NULL); //2nd is CFURLRef* really launched
671
672 //cleanup
673 CFRelease(cfurlApp);
674 CFRelease(cfaFiles);
675
676 //check for error
677 if(status != noErr)
678 {
679 wxLogDebug(wxString::Format(wxT("wxMacExecute ERROR: %d")), (int)status);
680 return -1;
681 }
682 return 0; //success
683 }
684
685 #endif //__DARWIN__