/** * @file llappviewermacosx.cpp * @brief The LLAppViewerMacOSX class definitions * * $LicenseInfo:firstyear=2007&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, 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; * version 2.1 of the License only. * * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" #if !defined LL_DARWIN #error "Use only with Mac OS X" #endif #define LL_CARBON_CRASH_HANDLER 1 #include "llappviewermacosx.h" #include "llwindowmacosx-objc.h" #include "llcommandlineparser.h" #include "llviewernetwork.h" #include "llviewercontrol.h" #include "llmd5.h" #include "llfloaterworldmap.h" #include "llurldispatcher.h" #include <ApplicationServices/ApplicationServices.h> #ifdef LL_CARBON_CRASH_HANDLER #include <Carbon/Carbon.h> #endif #include "lldir.h" #include <signal.h> #include <CoreAudio/CoreAudio.h> // for systemwide mute class LLMediaCtrl; // for LLURLDispatcher namespace { // The command line args stored. // They are not used immediately by the app. int gArgC; char** gArgV; bool sCrashReporterIsRunning = false; LLAppViewerMacOSX* gViewerAppPtr; #ifdef LL_CARBON_CRASH_HANDLER OSErr AEQuitHandler(const AppleEvent *messagein, AppleEvent *reply, long refIn) { OSErr result = noErr; LLAppViewer::instance()->userQuit(); return(result); } #endif } bool initViewer() { #if LL_SOLARIS && defined(__sparc) asm ("ta\t6"); // NOTE: Make sure memory alignment is enforced on SPARC #endif // Set the working dir to <bundle>/Contents/Resources if (chdir(gDirUtilp->getAppRODataDir().c_str()) == -1) { LL_WARNS() << "Could not change directory to " << gDirUtilp->getAppRODataDir() << ": " << strerror(errno) << LL_ENDL; } gViewerAppPtr = new LLAppViewerMacOSX(); gViewerAppPtr->setErrorHandler(LLAppViewer::handleViewerCrash); bool ok = gViewerAppPtr->init(); if(!ok) { LL_WARNS() << "Application init failed." << LL_ENDL; } return ok; } void handleQuit() { LLAppViewer::instance()->userQuit(); } bool runMainLoop() { bool ret = LLApp::isQuitting(); if (!ret && gViewerAppPtr != NULL) { ret = gViewerAppPtr->mainLoop(); } else { ret = true; } return ret; } void cleanupViewer() { if(!LLApp::isError()) { gViewerAppPtr->cleanup(); } delete gViewerAppPtr; gViewerAppPtr = NULL; } int main( int argc, char **argv ) { // Store off the command line args for use later. gArgC = argc; gArgV = argv; return createNSApp(argc, (const char**)argv); } LLAppViewerMacOSX::LLAppViewerMacOSX() { } LLAppViewerMacOSX::~LLAppViewerMacOSX() { } bool LLAppViewerMacOSX::init() { return LLAppViewer::init(); } // MacOSX may add and addition command line arguement for the process serial number. // The option takes a form like '-psn_0_12345'. The following method should be able to recognize // and either ignore or return a pair of values for the option. // look for this method to be added to the parser in parseAndStoreResults. std::pair<std::string, std::string> parse_psn(const std::string& s) { if (s.find("-psn_") == 0) { // *FIX:Mani Not sure that the value makes sense. // fix it once the actual -psn_XXX syntax is known. return std::make_pair("psn", s.substr(5)); } else { return std::make_pair(std::string(), std::string()); } } bool LLAppViewerMacOSX::initParseCommandLine(LLCommandLineParser& clp) { // The next two lines add the support for parsing the mac -psn_XXX arg. clp.addOptionDesc("psn", NULL, 1, "MacOSX process serial number"); clp.setCustomParser(parse_psn); // parse the user's command line if(clp.parseCommandLine(gArgC, gArgV) == false) { return false; } // Get the user's preferred language string based on the Mac OS localization mechanism. // To add a new localization: // go to the "Resources" section of the project // get info on "language.txt" // in the "General" tab, click the "Add Localization" button // create a new localization for the language you're adding // set the contents of the new localization of the file to the string corresponding to our localization // (i.e. "en", "ja", etc. Use the existing ones as a guide.) CFURLRef url = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("language"), CFSTR("txt"), NULL); char path[MAX_PATH]; if(CFURLGetFileSystemRepresentation(url, false, (UInt8 *)path, sizeof(path))) { std::string lang; if(_read_file_into_string(lang, path)) /* Flawfinder: ignore*/ { LLControlVariable* c = gSavedSettings.getControl("SystemLanguage"); if(c) { c->setValue(lang, false); } } } CFRelease(url); return true; } // *FIX:Mani It would be nice to provide a clean interface to get the // default_unix_signal_handler for the LLApp class. extern void default_unix_signal_handler(int, siginfo_t *, void *); bool LLAppViewerMacOSX::restoreErrorTrap() { // This method intends to reinstate signal handlers. // *NOTE:Mani It was found that the first execution of a shader was overriding // our initial signal handlers somehow. // This method will be called (at least) once per mainloop execution. // *NOTE:Mani The signals used below are copied over from the // setup_signals() func in LLApp.cpp // LLApp could use some way of overriding that func, but for this viewer // fix I opt to avoid affecting the server code. // Set up signal handlers that may result in program termination // struct sigaction act; struct sigaction old_act; act.sa_sigaction = default_unix_signal_handler; sigemptyset( &act.sa_mask ); act.sa_flags = SA_SIGINFO; unsigned int reset_count = 0; #define SET_SIG(S) sigaction(SIGABRT, &act, &old_act); \ if((unsigned int)act.sa_sigaction != (unsigned int) old_act.sa_sigaction) \ ++reset_count; // Synchronous signals SET_SIG(SIGABRT) SET_SIG(SIGALRM) SET_SIG(SIGBUS) SET_SIG(SIGFPE) SET_SIG(SIGHUP) SET_SIG(SIGILL) SET_SIG(SIGPIPE) SET_SIG(SIGSEGV) SET_SIG(SIGSYS) SET_SIG(LL_HEARTBEAT_SIGNAL) SET_SIG(LL_SMACKDOWN_SIGNAL) // Asynchronous signals that are normally ignored SET_SIG(SIGCHLD) SET_SIG(SIGUSR2) // Asynchronous signals that result in attempted graceful exit SET_SIG(SIGHUP) SET_SIG(SIGTERM) SET_SIG(SIGINT) // Asynchronous signals that result in core SET_SIG(SIGQUIT) #undef SET_SIG return reset_count == 0; } #ifdef LL_CARBON_CRASH_HANDLER static OSStatus CarbonEventHandler(EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void* inUserData) { ProcessSerialNumber psn; GetEventParameter(inEvent, kEventParamProcessID, typeProcessSerialNumber, NULL, sizeof(psn), NULL, &psn); if( GetEventKind(inEvent) == kEventAppTerminated ) { Boolean matching_psn = FALSE; OSErr os_result = SameProcess(&psn, (ProcessSerialNumber*)inUserData, &matching_psn); if(os_result >= 0 && matching_psn) { sCrashReporterIsRunning = false; QuitApplicationEventLoop(); } } return noErr; } #endif void LLAppViewerMacOSX::handleCrashReporting(bool reportFreeze) { #ifdef LL_CARBON_CRASH_HANDLER // This used to use fork&exec, but is switched to LSOpenApplication to // Make sure the crash reporter launches in front of the SL window. std::string command_str; //command_str = "open Second Life.app/Contents/Resources/mac-crash-logger.app"; command_str = "mac-crash-logger.app/Contents/MacOS/mac-crash-logger"; CFURLRef urlRef = CFURLCreateFromFileSystemRepresentation(NULL, (UInt8*)command_str.c_str(), strlen(command_str.c_str()), FALSE); // FSRef apparently isn't deprecated. // There's other funcitonality that depends on it existing as well that isn't deprecated. // There doesn't seem to be much to directly verify what the status of FSRef is, outside of some documentation pointing at FSRef being valid, and other documentation pointing to everything in Files.h being deprecated. // We'll assume it isn't for now, since all non-deprecated functions that use it seem to assume similar. FSRef appRef; Boolean pathstatus = CFURLGetFSRef(urlRef, &appRef); OSStatus os_result = noErr; if(pathstatus == true) { LSApplicationParameters appParams; memset(&appParams, 0, sizeof(appParams)); appParams.version = 0; appParams.flags = kLSLaunchNoParams | kLSLaunchStartClassic; appParams.application = &appRef; if(reportFreeze) { // Make sure freeze reporting launches the crash logger synchronously, lest // Log files get changed by SL while the logger is running. // *NOTE:Mani A better way - make a copy of the data that the crash reporter will send // and let SL go about its business. This way makes the mac work like windows and linux // and is the smallest patch for the issue. sCrashReporterIsRunning = false; ProcessSerialNumber o_psn; static EventHandlerRef sCarbonEventsRef = NULL; static const EventTypeSpec kEvents[] = { { kEventClassApplication, kEventAppTerminated } }; // Install the handler to detect crash logger termination InstallEventHandler(GetApplicationEventTarget(), (EventHandlerUPP) CarbonEventHandler, GetEventTypeCount(kEvents), kEvents, &o_psn, &sCarbonEventsRef ); // Remove, temporarily the quit handler - which has *crash* behavior before // the mainloop gets running! AERemoveEventHandler(kCoreEventClass, kAEQuitApplication, NewAEEventHandlerUPP(AEQuitHandler), false); // Launch the crash reporter. os_result = LSOpenApplication(&appParams, &o_psn); if(os_result >= 0) { sCrashReporterIsRunning = true; } while(sCrashReporterIsRunning) { RunApplicationEventLoop(); } // Re-install the apps quit handler. AEInstallEventHandler(kCoreEventClass, kAEQuitApplication, NewAEEventHandlerUPP(AEQuitHandler), 0, false); // Remove the crash reporter quit handler. RemoveEventHandler(sCarbonEventsRef); } else { appParams.flags |= kLSLaunchAsync; clear_signals(); ProcessSerialNumber o_psn; os_result = LSOpenApplication(&appParams, &o_psn); } } #endif } std::string LLAppViewerMacOSX::generateSerialNumber() { char serial_md5[MD5HEX_STR_SIZE]; // Flawfinder: ignore serial_md5[0] = 0; // JC: Sample code from http://developer.apple.com/technotes/tn/tn1103.html CFStringRef serialNumber = NULL; io_service_t platformExpert = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice")); if (platformExpert) { serialNumber = (CFStringRef) IORegistryEntryCreateCFProperty(platformExpert, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, 0); IOObjectRelease(platformExpert); } if (serialNumber) { char buffer[MAX_STRING]; // Flawfinder: ignore if (CFStringGetCString(serialNumber, buffer, MAX_STRING, kCFStringEncodingASCII)) { LLMD5 md5( (unsigned char*)buffer ); md5.hex_digest(serial_md5); } CFRelease(serialNumber); } return serial_md5; } static AudioDeviceID get_default_audio_output_device(void) { AudioDeviceID device = 0; UInt32 size = sizeof(device); AudioObjectPropertyAddress device_address = { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &device_address, 0, NULL, &size, &device); if(err != noErr) { LL_DEBUGS("SystemMute") << "Couldn't get default audio output device (0x" << std::hex << err << ")" << LL_ENDL; } return device; } //virtual void LLAppViewerMacOSX::setMasterSystemAudioMute(bool new_mute) { AudioDeviceID device = get_default_audio_output_device(); if(device != 0) { UInt32 mute = new_mute; AudioObjectPropertyAddress device_address = { kAudioDevicePropertyMute, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; OSStatus err = AudioObjectSetPropertyData(device, &device_address, 0, NULL, sizeof(mute), &mute); if(err != noErr) { LL_INFOS("SystemMute") << "Couldn't set audio mute property (0x" << std::hex << err << ")" << LL_ENDL; } } } //virtual bool LLAppViewerMacOSX::getMasterSystemAudioMute() { // Assume the system isn't muted UInt32 mute = 0; AudioDeviceID device = get_default_audio_output_device(); if(device != 0) { UInt32 size = sizeof(mute); AudioObjectPropertyAddress device_address = { kAudioDevicePropertyMute, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; OSStatus err = AudioObjectGetPropertyData(device, &device_address, 0, NULL, &size, &mute); if(err != noErr) { LL_DEBUGS("SystemMute") << "Couldn't get audio mute property (0x" << std::hex << err << ")" << LL_ENDL; } } return (mute != 0); } void handleUrl(const char* url_utf8) { if (url_utf8) { std::string url = url_utf8; // Safari 3.2 silently mangles secondlife:///app/ URLs into // secondlife:/app/ (only one leading slash). // Fix them up to meet the URL specification. JC const std::string prefix = "secondlife:/app/"; std::string test_prefix = url.substr(0, prefix.length()); LLStringUtil::toLower(test_prefix); if (test_prefix == prefix) { url.replace(0, prefix.length(), "secondlife:///app/"); } LLMediaCtrl* web = NULL; const bool trusted_browser = false; LLURLDispatcher::dispatch(url, "", web, trusted_browser); } }