/*
* Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016
* Jonathan Schleifer <js@heap.zone>
*
* All rights reserved.
*
* This file is part of ObjFW. It may be distributed under the terms of the
* Q Public License 1.0, which can be found in the file LICENSE.QPL included in
* the packaging of this file.
*
* Alternatively, it may be distributed under the terms of the GNU General
* Public License, either version 2 or 3, which can be found in the file
* LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
* file.
*/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#import "OFApplication.h"
#import "OFString.h"
#import "OFArray.h"
#import "OFDictionary.h"
#import "OFSystemInfo.h"
#import "OFRunLoop.h"
#import "OFRunLoop+Private.h"
#import "OFThread.h"
#import "OFThread+Private.h"
#if defined(OF_MAC_OS_X)
# include <crt_externs.h>
#elif defined(OF_WINDOWS)
# include <windows.h>
extern int _CRT_glob;
extern void __wgetmainargs(int*, wchar_t***, wchar_t***, int, int*);
#elif !defined(OF_IOS)
extern char **environ;
#endif
#ifdef OF_PSP
# include <pspkerneltypes.h>
# include <psploadexec.h>
#endif
#ifdef OF_NINTENDO_DS
# define asm __asm__
# include <nds.h>
# undef asm
#endif
@interface OFApplication ()
- (void)OF_setArgumentCount: (int*)argc
andArgumentValues: (char**[])argv;
#ifdef OF_WINDOWS
- (void)OF_setArgumentCount: (int)argc
andWideArgumentValues: (wchar_t*[])argv;
#endif
- (void)OF_run;
@end
static OFApplication *app = nil;
static void
atexitHandler(void)
{
id <OFApplicationDelegate> delegate = [app delegate];
if ([delegate respondsToSelector: @selector(applicationWillTerminate)])
[delegate applicationWillTerminate];
[delegate release];
}
#define SIGNAL_HANDLER(sig) \
static void \
handle##sig(int signal) \
{ \
app->_##sig##Handler(app->_delegate, \
@selector(applicationDidReceive##sig)); \
}
SIGNAL_HANDLER(SIGINT)
#ifndef OF_WINDOWS
SIGNAL_HANDLER(SIGHUP)
SIGNAL_HANDLER(SIGUSR1)
SIGNAL_HANDLER(SIGUSR2)
#endif
#undef SIGNAL_HANDLER
int
of_application_main(int *argc, char **argv[], Class cls)
{
id <OFApplicationDelegate> delegate;
#ifdef OF_WINDOWS
wchar_t **wargv, **wenvp;
int wargc, si = 0;
#endif
if ([cls isSubclassOfClass: [OFApplication class]]) {
fprintf(stderr, "FATAL ERROR:\n Class %s is a subclass of "
"class OFApplication, but class\n %s was specified as "
"application delegate!\n Most likely, you wanted to "
"subclass OFObject instead or specified\n the wrong class "
"with OF_APPLICATION_DELEGATE().\n",
class_getName(cls), class_getName(cls));
exit(1);
}
app = [[OFApplication alloc] init];
[app OF_setArgumentCount: argc
andArgumentValues: argv];
#ifdef OF_WINDOWS
__wgetmainargs(&wargc, &wargv, &wenvp, _CRT_glob, &si);
[app OF_setArgumentCount: wargc
andWideArgumentValues: wargv];
#endif
delegate = [[cls alloc] init];
[app setDelegate: delegate];
[app OF_run];
[delegate release];
return 0;
}
@implementation OFApplication
@synthesize programName = _programName, arguments = _arguments;
@synthesize environment = _environment;
+ (OFApplication*)sharedApplication
{
return app;
}
+ (OFString*)programName
{
return [app programName];
}
+ (OFArray*)arguments
{
return [app arguments];
}
+ (OFDictionary*)environment
{
return [app environment];
}
+ (void)terminate
{
[self terminateWithStatus: EXIT_SUCCESS];
OF_UNREACHABLE
}
+ (void)terminateWithStatus: (int)status
{
#ifndef OF_PSP
exit(status);
#else
sceKernelExitGame();
OF_UNREACHABLE
#endif
}
- init
{
self = [super init];
@try {
void *pool;
OFMutableDictionary *environment;
#if defined(OF_MAC_OS_X)
char **env = *_NSGetEnviron();
#elif defined(OF_WINDOWS)
of_char16_t *env, *env0;
#elif !defined(OF_IOS)
char **env = environ;
#else
char *env;
#endif
environment = [[OFMutableDictionary alloc] init];
atexit(atexitHandler);
#if defined(OF_WINDOWS)
env = env0 = GetEnvironmentStringsW();
while (*env != 0) {
OFString *tmp, *key, *value;
size_t length, pos;
pool = objc_autoreleasePoolPush();
length = of_string_utf16_length(env);
tmp = [OFString stringWithUTF16String: env
length: length];
env += length + 1;
/*
* cmd.exe seems to add some special variables which
* start with a "=", even though variable names are not
* allowed to contain a "=".
*/
if ([tmp hasPrefix: @"="]) {
objc_autoreleasePoolPop(pool);
continue;
}
pos = [tmp rangeOfString: @"="].location;
if (pos == OF_NOT_FOUND) {
fprintf(stderr, "Warning: Invalid environment "
"variable: %s\n", [tmp UTF8String]);
continue;
}
key = [tmp substringWithRange: of_range(0, pos)];
value = [tmp substringWithRange:
of_range(pos + 1, [tmp length] - pos - 1)];
[environment setObject: value
forKey: key];
objc_autoreleasePoolPop(pool);
}
FreeEnvironmentStringsW(env0);
#elif !defined(OF_IOS)
if (env != NULL) {
for (; *env != NULL; env++) {
OFString *key, *value;
char *sep;
const of_string_encoding_t encoding =
[OFSystemInfo native8BitEncoding];
pool = objc_autoreleasePoolPush();
if ((sep = strchr(*env, '=')) == NULL) {
fprintf(stderr, "Warning: Invalid "
"environment variable: %s\n", *env);
continue;
}
key = [OFString
stringWithCString: *env
encoding: encoding
length: sep - *env];
value = [OFString
stringWithCString: sep + 1
encoding: encoding];
[environment setObject: value
forKey: key];
objc_autoreleasePoolPop(pool);
}
}
#else
/*
* iOS does not provide environ and Apple does not allow using
* _NSGetEnviron on iOS. Therefore, we just get a few common
* variables from the environment which applications might
* expect.
*/
pool = objc_autoreleasePoolPush();
if ((env = getenv("HOME")) != NULL) {
OFString *home = [[[OFString alloc]
initWithUTF8StringNoCopy: env
freeWhenDone: false] autorelease];
[environment setObject: home
forKey: @"HOME"];
}
if ((env = getenv("PATH")) != NULL) {
OFString *path = [[[OFString alloc]
initWithUTF8StringNoCopy: env
freeWhenDone: false] autorelease];
[environment setObject: path
forKey: @"PATH"];
}
if ((env = getenv("SHELL")) != NULL) {
OFString *shell = [[[OFString alloc]
initWithUTF8StringNoCopy: env
freeWhenDone: false] autorelease];
[environment setObject: shell
forKey: @"SHELL"];
}
if ((env = getenv("TMPDIR")) != NULL) {
OFString *tmpdir = [[[OFString alloc]
initWithUTF8StringNoCopy: env
freeWhenDone: false] autorelease];
[environment setObject: tmpdir
forKey: @"TMPDIR"];
}
if ((env = getenv("USER")) != NULL) {
OFString *user = [[[OFString alloc]
initWithUTF8StringNoCopy: env
freeWhenDone: false] autorelease];
[environment setObject: user
forKey: @"USER"];
}
objc_autoreleasePoolPop(pool);
#endif
[environment makeImmutable];
_environment = environment;
} @catch (id e) {
[self release];
@throw e;
}
return self;
}
- (void)dealloc
{
[_arguments release];
[_environment release];
[super dealloc];
}
- (void)OF_setArgumentCount: (int*)argc
andArgumentValues: (char***)argv
{
#ifndef OF_WINDOWS
void *pool = objc_autoreleasePoolPush();
OFMutableArray *arguments;
of_string_encoding_t encoding;
_argc = argc;
_argv = argv;
encoding = [OFSystemInfo native8BitEncoding];
# ifndef OF_NINTENDO_DS
if (*argc > 0) {
# else
if (__system_argv->argvMagic == ARGV_MAGIC &&
__system_argv->argc > 0) {
# endif
_programName = [[OFString alloc] initWithCString: (*argv)[0]
encoding: encoding];
arguments = [[OFMutableArray alloc] init];
_arguments = arguments;
for (int i = 1; i < *argc; i++)
[arguments addObject:
[OFString stringWithCString: (*argv)[i]
encoding: encoding]];
[arguments makeImmutable];
}
objc_autoreleasePoolPop(pool);
#else
_argc = argc;
_argv = argv;
#endif
}
#ifdef OF_WINDOWS
- (void)OF_setArgumentCount: (int)argc
andWideArgumentValues: (wchar_t**)argv
{
void *pool = objc_autoreleasePoolPush();
OFMutableArray *arguments;
if (argc > 0) {
_programName = [[OFString alloc] initWithUTF16String: argv[0]];
arguments = [[OFMutableArray alloc] init];
for (int i = 1; i < argc; i++)
[arguments addObject:
[OFString stringWithUTF16String: argv[i]]];
[arguments makeImmutable];
_arguments = arguments;
}
objc_autoreleasePoolPop(pool);
}
#endif
- (void)getArgumentCount: (int**)argc
andArgumentValues: (char****)argv
{
*argc = _argc;
*argv = _argv;
}
- (id <OFApplicationDelegate>)delegate
{
return _delegate;
}
- (void)setDelegate: (id <OFApplicationDelegate>)delegate
{
#ifdef HAVE_SIGACTION
struct sigaction sa = { .sa_flags = SA_RESTART };
sigemptyset(&sa.sa_mask);
# define REGISTER_SIGNAL(sig) \
if ([delegate respondsToSelector: \
@selector(applicationDidReceive##sig)]) { \
_##sig##Handler = (void(*)(id, SEL))[(id)delegate \
methodForSelector: \
@selector(applicationDidReceive##sig)]; \
\
sa.sa_handler = handle##sig; \
} else \
sa.sa_handler = SIG_DFL; \
\
OF_ENSURE(sigaction(sig, &sa, NULL) == 0);
#else
# define REGISTER_SIGNAL(sig) \
if ([delegate respondsToSelector: \
@selector(applicationDidReceive##sig)]) { \
_##sig##Handler = (void(*)(id, SEL))[(id)delegate \
methodForSelector: \
@selector(applicationDidReceive##sig)]; \
signal(sig, handle##sig); \
} else \
signal(sig, SIG_DFL);
#endif
_delegate = delegate;
REGISTER_SIGNAL(SIGINT)
#ifndef OF_WINDOWS
REGISTER_SIGNAL(SIGHUP)
REGISTER_SIGNAL(SIGUSR1)
REGISTER_SIGNAL(SIGUSR2)
#endif
#undef REGISTER_SIGNAL
}
- (void)OF_run
{
void *pool = objc_autoreleasePoolPush();
OFRunLoop *runLoop;
#ifdef OF_HAVE_THREADS
[OFThread OF_createMainThread];
runLoop = [OFRunLoop currentRunLoop];
#else
runLoop = [[[OFRunLoop alloc] init] autorelease];
#endif
[OFRunLoop OF_setMainRunLoop: runLoop];
objc_autoreleasePoolPop(pool);
/*
* Note: runLoop is still valid after the release of the pool, as
* OF_setMainRunLoop: retained it. However, we only have a weak
* reference to it now, whereas we had a strong reference before.
*/
pool = objc_autoreleasePoolPush();
[_delegate applicationDidFinishLaunching];
objc_autoreleasePoolPop(pool);
[runLoop run];
}
- (void)terminate
{
[[self class] terminate];
OF_UNREACHABLE
}
- (void)terminateWithStatus: (int)status
{
[[self class] terminateWithStatus: status];
OF_UNREACHABLE
}
@end