ObjFW  Artifact [669a08ac5c]

Artifact 669a08ac5c5fda8c95f6ca331a20e19aa70eb01f767acb308662451e0b87f9fa:

  • File src/OFApplication.m — part of check-in [6dff0f5922] at 2017-01-07 02:34:39 on branch trunk — Always use "." for the decimal point

    This is achieved by replacing the locale's decimal point with "." after
    formatting and replacing "." with the locale's decimal point before
    parsing.

    To still use the decimal point from the locale for formatting, the new
    flag "," is introduced to formats. This is useful for just printing a
    string to the user that is not saved to a file or sent via a network.

    While this is an ugly hack, there is no better way to do this other than
    implementing the functionality of printf and strtod myself, as POSIX
    does not specify versions of these that take a locale as an argument.
    While this is a lot of work and error-prone, I will most likely end up
    doing this eventually.

    This commit also enables the locale in OFApplication to notice when
    things break. As a nice side effect, ofhttp now uses the locale's
    decimal point in its user interface. (user: js, size: 10856) [annotate] [blame] [check-ins using]


/*
 * 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 <locale.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

	setlocale(LC_ALL, "");

	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