PageRenderTime 591ms CodeModel.GetById 180ms app.highlight 276ms RepoModel.GetById 128ms app.codeStats 0ms

/indra/newview/llappviewermacosx.cpp

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