/indra/newview/llappviewermacosx.cpp

https://bitbucket.org/lindenlab/viewer-beta/ · C++ · 577 lines · 392 code · 88 blank · 97 comment · 61 complexity · bd42bb260f41ab070a855dcaf114b6e8 MD5 · raw file

  1. /**
  2. * @file llappviewermacosx.cpp
  3. * @brief The LLAppViewerMacOSX class definitions
  4. *
  5. * $LicenseInfo:firstyear=2007&license=viewerlgpl$
  6. * Second Life Viewer Source Code
  7. * Copyright (C) 2010, Linden Research, Inc.
  8. *
  9. * This library is free software; you can redistribute it and/or
  10. * modify it under the terms of the GNU Lesser General Public
  11. * License as published by the Free Software Foundation;
  12. * version 2.1 of the License only.
  13. *
  14. * This library is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  17. * Lesser General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU Lesser General Public
  20. * License along with this library; if not, write to the Free Software
  21. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  22. *
  23. * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
  24. * $/LicenseInfo$
  25. */
  26. #include "llviewerprecompiledheaders.h"
  27. #if !defined LL_DARWIN
  28. #error "Use only with Mac OS X"
  29. #endif
  30. #include "llappviewermacosx.h"
  31. #include "llcommandlineparser.h"
  32. #include "llmemtype.h"
  33. #include "llviewernetwork.h"
  34. #include "llviewercontrol.h"
  35. #include "llmd5.h"
  36. #include "llfloaterworldmap.h"
  37. #include "llurldispatcher.h"
  38. #include <Carbon/Carbon.h>
  39. #include "lldir.h"
  40. #include <signal.h>
  41. #include <CoreAudio/CoreAudio.h> // for systemwide mute
  42. class LLMediaCtrl; // for LLURLDispatcher
  43. namespace
  44. {
  45. // The command line args stored.
  46. // They are not used immediately by the app.
  47. int gArgC;
  48. char** gArgV;
  49. bool sCrashReporterIsRunning = false;
  50. OSErr AEQuitHandler(const AppleEvent *messagein, AppleEvent *reply, long refIn)
  51. {
  52. OSErr result = noErr;
  53. LLAppViewer::instance()->userQuit();
  54. return(result);
  55. }
  56. }
  57. int main( int argc, char **argv )
  58. {
  59. LLMemType mt1(LLMemType::MTYPE_STARTUP);
  60. #if LL_SOLARIS && defined(__sparc)
  61. asm ("ta\t6"); // NOTE: Make sure memory alignment is enforced on SPARC
  62. #endif
  63. // Set the working dir to <bundle>/Contents/Resources
  64. if (chdir(gDirUtilp->getAppRODataDir().c_str()) == -1)
  65. {
  66. llwarns << "Could not change directory to "
  67. << gDirUtilp->getAppRODataDir() << ": " << strerror(errno)
  68. << llendl;
  69. }
  70. LLAppViewerMacOSX* viewer_app_ptr = new LLAppViewerMacOSX();
  71. viewer_app_ptr->setErrorHandler(LLAppViewer::handleViewerCrash);
  72. // Store off the command line args for use later.
  73. gArgC = argc;
  74. gArgV = argv;
  75. bool ok = viewer_app_ptr->init();
  76. if(!ok)
  77. {
  78. llwarns << "Application init failed." << llendl;
  79. return -1;
  80. }
  81. // Run the application main loop
  82. if(!LLApp::isQuitting())
  83. {
  84. viewer_app_ptr->mainLoop();
  85. }
  86. if (!LLApp::isError())
  87. {
  88. //
  89. // We don't want to do cleanup here if the error handler got called -
  90. // the assumption is that the error handler is responsible for doing
  91. // app cleanup if there was a problem.
  92. //
  93. viewer_app_ptr->cleanup();
  94. }
  95. delete viewer_app_ptr;
  96. viewer_app_ptr = NULL;
  97. return 0;
  98. }
  99. LLAppViewerMacOSX::LLAppViewerMacOSX()
  100. {
  101. }
  102. LLAppViewerMacOSX::~LLAppViewerMacOSX()
  103. {
  104. }
  105. bool LLAppViewerMacOSX::init()
  106. {
  107. return LLAppViewer::init();
  108. }
  109. // MacOSX may add and addition command line arguement for the process serial number.
  110. // The option takes a form like '-psn_0_12345'. The following method should be able to recognize
  111. // and either ignore or return a pair of values for the option.
  112. // look for this method to be added to the parser in parseAndStoreResults.
  113. std::pair<std::string, std::string> parse_psn(const std::string& s)
  114. {
  115. if (s.find("-psn_") == 0)
  116. {
  117. // *FIX:Mani Not sure that the value makes sense.
  118. // fix it once the actual -psn_XXX syntax is known.
  119. return std::make_pair("psn", s.substr(5));
  120. }
  121. else
  122. {
  123. return std::make_pair(std::string(), std::string());
  124. }
  125. }
  126. bool LLAppViewerMacOSX::initParseCommandLine(LLCommandLineParser& clp)
  127. {
  128. // The next two lines add the support for parsing the mac -psn_XXX arg.
  129. clp.addOptionDesc("psn", NULL, 1, "MacOSX process serial number");
  130. clp.setCustomParser(parse_psn);
  131. // First read in the args from arguments txt.
  132. const char* filename = "arguments.txt";
  133. llifstream ifs(filename, llifstream::binary);
  134. if (!ifs.is_open())
  135. {
  136. llwarns << "Unable to open file" << filename << llendl;
  137. return false;
  138. }
  139. if(clp.parseCommandLineFile(ifs) == false)
  140. {
  141. return false;
  142. }
  143. // Then parse the user's command line, so that any --url arg can appear last
  144. // Succesive calls to clp.parse... will NOT override earlier options.
  145. if(clp.parseCommandLine(gArgC, gArgV) == false)
  146. {
  147. return false;
  148. }
  149. // Get the user's preferred language string based on the Mac OS localization mechanism.
  150. // To add a new localization:
  151. // go to the "Resources" section of the project
  152. // get info on "language.txt"
  153. // in the "General" tab, click the "Add Localization" button
  154. // create a new localization for the language you're adding
  155. // set the contents of the new localization of the file to the string corresponding to our localization
  156. // (i.e. "en", "ja", etc. Use the existing ones as a guide.)
  157. CFURLRef url = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("language"), CFSTR("txt"), NULL);
  158. char path[MAX_PATH];
  159. if(CFURLGetFileSystemRepresentation(url, false, (UInt8 *)path, sizeof(path)))
  160. {
  161. std::string lang;
  162. if(_read_file_into_string(lang, path)) /* Flawfinder: ignore*/
  163. {
  164. LLControlVariable* c = gSavedSettings.getControl("SystemLanguage");
  165. if(c)
  166. {
  167. c->setValue(lang, false);
  168. }
  169. }
  170. }
  171. CFRelease(url);
  172. return true;
  173. }
  174. // *FIX:Mani It would be nice to provide a clean interface to get the
  175. // default_unix_signal_handler for the LLApp class.
  176. extern void default_unix_signal_handler(int, siginfo_t *, void *);
  177. bool LLAppViewerMacOSX::restoreErrorTrap()
  178. {
  179. // This method intends to reinstate signal handlers.
  180. // *NOTE:Mani It was found that the first execution of a shader was overriding
  181. // our initial signal handlers somehow.
  182. // This method will be called (at least) once per mainloop execution.
  183. // *NOTE:Mani The signals used below are copied over from the
  184. // setup_signals() func in LLApp.cpp
  185. // LLApp could use some way of overriding that func, but for this viewer
  186. // fix I opt to avoid affecting the server code.
  187. // Set up signal handlers that may result in program termination
  188. //
  189. struct sigaction act;
  190. struct sigaction old_act;
  191. act.sa_sigaction = default_unix_signal_handler;
  192. sigemptyset( &act.sa_mask );
  193. act.sa_flags = SA_SIGINFO;
  194. unsigned int reset_count = 0;
  195. #define SET_SIG(S) sigaction(SIGABRT, &act, &old_act); \
  196. if((unsigned int)act.sa_sigaction != (unsigned int) old_act.sa_sigaction) \
  197. ++reset_count;
  198. // Synchronous signals
  199. SET_SIG(SIGABRT)
  200. SET_SIG(SIGALRM)
  201. SET_SIG(SIGBUS)
  202. SET_SIG(SIGFPE)
  203. SET_SIG(SIGHUP)
  204. SET_SIG(SIGILL)
  205. SET_SIG(SIGPIPE)
  206. SET_SIG(SIGSEGV)
  207. SET_SIG(SIGSYS)
  208. SET_SIG(LL_HEARTBEAT_SIGNAL)
  209. SET_SIG(LL_SMACKDOWN_SIGNAL)
  210. // Asynchronous signals that are normally ignored
  211. SET_SIG(SIGCHLD)
  212. SET_SIG(SIGUSR2)
  213. // Asynchronous signals that result in attempted graceful exit
  214. SET_SIG(SIGHUP)
  215. SET_SIG(SIGTERM)
  216. SET_SIG(SIGINT)
  217. // Asynchronous signals that result in core
  218. SET_SIG(SIGQUIT)
  219. #undef SET_SIG
  220. return reset_count == 0;
  221. }
  222. static OSStatus CarbonEventHandler(EventHandlerCallRef inHandlerCallRef,
  223. EventRef inEvent,
  224. void* inUserData)
  225. {
  226. ProcessSerialNumber psn;
  227. GetEventParameter(inEvent,
  228. kEventParamProcessID,
  229. typeProcessSerialNumber,
  230. NULL,
  231. sizeof(psn),
  232. NULL,
  233. &psn);
  234. if( GetEventKind(inEvent) == kEventAppTerminated )
  235. {
  236. Boolean matching_psn = FALSE;
  237. OSErr os_result = SameProcess(&psn, (ProcessSerialNumber*)inUserData, &matching_psn);
  238. if(os_result >= 0 && matching_psn)
  239. {
  240. sCrashReporterIsRunning = false;
  241. QuitApplicationEventLoop();
  242. }
  243. }
  244. return noErr;
  245. }
  246. void LLAppViewerMacOSX::handleCrashReporting(bool reportFreeze)
  247. {
  248. // This used to use fork&exec, but is switched to LSOpenApplication to
  249. // Make sure the crash reporter launches in front of the SL window.
  250. std::string command_str;
  251. //command_str = "open Second Life.app/Contents/Resources/mac-crash-logger.app";
  252. command_str = "mac-crash-logger.app/Contents/MacOS/mac-crash-logger";
  253. FSRef appRef;
  254. Boolean isDir = 0;
  255. OSStatus os_result = FSPathMakeRef((UInt8*)command_str.c_str(),
  256. &appRef,
  257. &isDir);
  258. if(os_result >= 0)
  259. {
  260. LSApplicationParameters appParams;
  261. memset(&appParams, 0, sizeof(appParams));
  262. appParams.version = 0;
  263. appParams.flags = kLSLaunchNoParams | kLSLaunchStartClassic;
  264. appParams.application = &appRef;
  265. if(reportFreeze)
  266. {
  267. // Make sure freeze reporting launches the crash logger synchronously, lest
  268. // Log files get changed by SL while the logger is running.
  269. // *NOTE:Mani A better way - make a copy of the data that the crash reporter will send
  270. // and let SL go about its business. This way makes the mac work like windows and linux
  271. // and is the smallest patch for the issue.
  272. sCrashReporterIsRunning = false;
  273. ProcessSerialNumber o_psn;
  274. static EventHandlerRef sCarbonEventsRef = NULL;
  275. static const EventTypeSpec kEvents[] =
  276. {
  277. { kEventClassApplication, kEventAppTerminated }
  278. };
  279. // Install the handler to detect crash logger termination
  280. InstallEventHandler(GetApplicationEventTarget(),
  281. (EventHandlerUPP) CarbonEventHandler,
  282. GetEventTypeCount(kEvents),
  283. kEvents,
  284. &o_psn,
  285. &sCarbonEventsRef
  286. );
  287. // Remove, temporarily the quit handler - which has *crash* behavior before
  288. // the mainloop gets running!
  289. AERemoveEventHandler(kCoreEventClass,
  290. kAEQuitApplication,
  291. NewAEEventHandlerUPP(AEQuitHandler),
  292. false);
  293. // Launch the crash reporter.
  294. os_result = LSOpenApplication(&appParams, &o_psn);
  295. if(os_result >= 0)
  296. {
  297. sCrashReporterIsRunning = true;
  298. }
  299. while(sCrashReporterIsRunning)
  300. {
  301. RunApplicationEventLoop();
  302. }
  303. // Re-install the apps quit handler.
  304. AEInstallEventHandler(kCoreEventClass,
  305. kAEQuitApplication,
  306. NewAEEventHandlerUPP(AEQuitHandler),
  307. 0,
  308. false);
  309. // Remove the crash reporter quit handler.
  310. RemoveEventHandler(sCarbonEventsRef);
  311. }
  312. else
  313. {
  314. appParams.flags |= kLSLaunchAsync;
  315. clear_signals();
  316. ProcessSerialNumber o_psn;
  317. os_result = LSOpenApplication(&appParams, &o_psn);
  318. }
  319. }
  320. }
  321. std::string LLAppViewerMacOSX::generateSerialNumber()
  322. {
  323. char serial_md5[MD5HEX_STR_SIZE]; // Flawfinder: ignore
  324. serial_md5[0] = 0;
  325. // JC: Sample code from http://developer.apple.com/technotes/tn/tn1103.html
  326. CFStringRef serialNumber = NULL;
  327. io_service_t platformExpert = IOServiceGetMatchingService(kIOMasterPortDefault,
  328. IOServiceMatching("IOPlatformExpertDevice"));
  329. if (platformExpert) {
  330. serialNumber = (CFStringRef) IORegistryEntryCreateCFProperty(platformExpert,
  331. CFSTR(kIOPlatformSerialNumberKey),
  332. kCFAllocatorDefault, 0);
  333. IOObjectRelease(platformExpert);
  334. }
  335. if (serialNumber)
  336. {
  337. char buffer[MAX_STRING]; // Flawfinder: ignore
  338. if (CFStringGetCString(serialNumber, buffer, MAX_STRING, kCFStringEncodingASCII))
  339. {
  340. LLMD5 md5( (unsigned char*)buffer );
  341. md5.hex_digest(serial_md5);
  342. }
  343. CFRelease(serialNumber);
  344. }
  345. return serial_md5;
  346. }
  347. static AudioDeviceID get_default_audio_output_device(void)
  348. {
  349. AudioDeviceID device = 0;
  350. UInt32 size = sizeof(device);
  351. AudioObjectPropertyAddress device_address = { kAudioHardwarePropertyDefaultOutputDevice,
  352. kAudioObjectPropertyScopeGlobal,
  353. kAudioObjectPropertyElementMaster };
  354. OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &device_address, 0, NULL, &size, &device);
  355. if(err != noErr)
  356. {
  357. LL_DEBUGS("SystemMute") << "Couldn't get default audio output device (0x" << std::hex << err << ")" << LL_ENDL;
  358. }
  359. return device;
  360. }
  361. //virtual
  362. void LLAppViewerMacOSX::setMasterSystemAudioMute(bool new_mute)
  363. {
  364. AudioDeviceID device = get_default_audio_output_device();
  365. if(device != 0)
  366. {
  367. UInt32 mute = new_mute;
  368. AudioObjectPropertyAddress device_address = { kAudioDevicePropertyMute,
  369. kAudioDevicePropertyScopeOutput,
  370. kAudioObjectPropertyElementMaster };
  371. OSStatus err = AudioObjectSetPropertyData(device, &device_address, 0, NULL, sizeof(mute), &mute);
  372. if(err != noErr)
  373. {
  374. LL_INFOS("SystemMute") << "Couldn't set audio mute property (0x" << std::hex << err << ")" << LL_ENDL;
  375. }
  376. }
  377. }
  378. //virtual
  379. bool LLAppViewerMacOSX::getMasterSystemAudioMute()
  380. {
  381. // Assume the system isn't muted
  382. UInt32 mute = 0;
  383. AudioDeviceID device = get_default_audio_output_device();
  384. if(device != 0)
  385. {
  386. UInt32 size = sizeof(mute);
  387. AudioObjectPropertyAddress device_address = { kAudioDevicePropertyMute,
  388. kAudioDevicePropertyScopeOutput,
  389. kAudioObjectPropertyElementMaster };
  390. OSStatus err = AudioObjectGetPropertyData(device, &device_address, 0, NULL, &size, &mute);
  391. if(err != noErr)
  392. {
  393. LL_DEBUGS("SystemMute") << "Couldn't get audio mute property (0x" << std::hex << err << ")" << LL_ENDL;
  394. }
  395. }
  396. return (mute != 0);
  397. }
  398. OSErr AEGURLHandler(const AppleEvent *messagein, AppleEvent *reply, long refIn)
  399. {
  400. OSErr result = noErr;
  401. DescType actualType;
  402. char buffer[1024]; // Flawfinder: ignore
  403. Size size;
  404. result = AEGetParamPtr (
  405. messagein,
  406. keyDirectObject,
  407. typeCString,
  408. &actualType,
  409. (Ptr)buffer,
  410. sizeof(buffer),
  411. &size);
  412. if(result == noErr)
  413. {
  414. std::string url = buffer;
  415. // Safari 3.2 silently mangles secondlife:///app/ URLs into
  416. // secondlife:/app/ (only one leading slash).
  417. // Fix them up to meet the URL specification. JC
  418. const std::string prefix = "secondlife:/app/";
  419. std::string test_prefix = url.substr(0, prefix.length());
  420. LLStringUtil::toLower(test_prefix);
  421. if (test_prefix == prefix)
  422. {
  423. url.replace(0, prefix.length(), "secondlife:///app/");
  424. }
  425. LLMediaCtrl* web = NULL;
  426. const bool trusted_browser = false;
  427. LLURLDispatcher::dispatch(url, "", web, trusted_browser);
  428. }
  429. return(result);
  430. }
  431. OSStatus simpleDialogHandler(EventHandlerCallRef handler, EventRef event, void *userdata)
  432. {
  433. OSStatus result = eventNotHandledErr;
  434. OSStatus err;
  435. UInt32 evtClass = GetEventClass(event);
  436. UInt32 evtKind = GetEventKind(event);
  437. WindowRef window = (WindowRef)userdata;
  438. if((evtClass == kEventClassCommand) && (evtKind == kEventCommandProcess))
  439. {
  440. HICommand cmd;
  441. err = GetEventParameter(event, kEventParamDirectObject, typeHICommand, NULL, sizeof(cmd), NULL, &cmd);
  442. if(err == noErr)
  443. {
  444. switch(cmd.commandID)
  445. {
  446. case kHICommandOK:
  447. QuitAppModalLoopForWindow(window);
  448. result = noErr;
  449. break;
  450. case kHICommandCancel:
  451. QuitAppModalLoopForWindow(window);
  452. result = userCanceledErr;
  453. break;
  454. }
  455. }
  456. }
  457. return(result);
  458. }
  459. void init_apple_menu(const char* product)
  460. {
  461. // Load up a proper menu bar.
  462. {
  463. OSStatus err;
  464. IBNibRef nib = NULL;
  465. // NOTE: DO NOT translate or brand this string. It's an internal name in the .nib file, and MUST match exactly.
  466. err = CreateNibReference(CFSTR("SecondLife"), &nib);
  467. if(err == noErr)
  468. {
  469. // NOTE: DO NOT translate or brand this string. It's an internal name in the .nib file, and MUST match exactly.
  470. SetMenuBarFromNib(nib, CFSTR("MenuBar"));
  471. }
  472. if(nib != NULL)
  473. {
  474. DisposeNibReference(nib);
  475. }
  476. }
  477. // Install a handler for 'gurl' AppleEvents. This is how secondlife:// URLs get passed to the viewer.
  478. if(AEInstallEventHandler('GURL', 'GURL', NewAEEventHandlerUPP(AEGURLHandler),0, false) != noErr)
  479. {
  480. // Couldn't install AppleEvent handler. This error shouldn't be fatal.
  481. llinfos << "Couldn't install 'GURL' AppleEvent handler. Continuing..." << llendl;
  482. }
  483. // Install a handler for 'quit' AppleEvents. This makes quitting the application from the dock work.
  484. if(AEInstallEventHandler(kCoreEventClass, kAEQuitApplication, NewAEEventHandlerUPP(AEQuitHandler),0, false) != noErr)
  485. {
  486. // Couldn't install AppleEvent handler. This error shouldn't be fatal.
  487. llinfos << "Couldn't install Quit AppleEvent handler. Continuing..." << llendl;
  488. }
  489. }