view libmlk-core/core/sys.c @ 298:196264679079

misc: remove usage of bool
author David Demelier <markand@malikania.fr>
date Wed, 10 Mar 2021 18:49:08 +0100
parents 5217c195c5b9
children f04b4ee04db3
line wrap: on
line source

/*
 * sys.c -- system routines
 *
 * Copyright (c) 2020 David Demelier <markand@malikania.fr>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "sysconfig.h"

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

#if defined(_WIN32)
#       include <shlwapi.h>
#       include <windows.h>
#else
#       include <sys/stat.h>
#       include <errno.h>
#       include <string.h>
#endif

#include <SDL.h>
#include <SDL_image.h>
#include <SDL_mixer.h>
#include <SDL_ttf.h>

#include "error.h"
#include "sound.h"
#include "sys.h"

static struct {
	char organization[128];
	char name[128];
} info = {
	.organization = "fr.malikania",
	.name = "molko"
};

static const char *paths[] = {
	[SYS_DIR_BIN]           = MOLKO_BINDIR,
	[SYS_DIR_DATA]          = MOLKO_DATADIR,
	[SYS_DIR_LOCALE]        = MOLKO_LOCALEDIR
};

static const char *abspaths[] = {
	[SYS_DIR_BIN]           = MOLKO_ABS_BINDIR,
	[SYS_DIR_DATA]          = MOLKO_ABS_DATADIR,
	[SYS_DIR_LOCALE]        = MOLKO_ABS_LOCALEDIR
};

#if defined(_WIN32)

static inline int
is_absolute(const char *path)
{
	return !PathIsRelativeA(path);
}

#else

static inline int
is_absolute(const char *path)
{
	return path[0] == '/';
}

#endif

static inline char *
normalize(char *str)
{
	for (char *p = str; *p; ++p)
		if (*p == '\\')
			*p = '/';

	return str;
}

static inline const char *
absolute(const char *which)
{
	static char path[PATH_MAX];

	strlcpy(path, which, sizeof (path));

	return normalize(path);
}

static const char *
system_directory(enum sys_dir kind)
{
	static char path[PATH_MAX];
	static char ret[PATH_MAX];
	char *base, *binsect;

	/* 1. Get current binary directory. */
	base = SDL_GetBasePath();

	/*
	 * 2. Decompose the path to the given special directory by computing
	 *    relative directory to it from where the binary is located.
	 *
	 * Example:
	 *   PREFIX/bin/mlk
	 *   PREFIX/share/molko
	 *
	 * The path to the data is ../share/molko starting from the binary.
	 *
	 * If path to binary is absolute we can't determine relative paths to
	 * any other directory and use the absolute one instead.
	 *
	 * Also, on some platforms SDL_GetBasePath isn't implemented and returns
	 * NULL, in that case return the fallback to the installation prefix.
	 */
	if (is_absolute(paths[SYS_DIR_BIN]) || is_absolute(paths[kind]) || !base)
		return absolute(abspaths[kind]);

	/*
	 * 3. Put the base path into the path and remove the value of
	 *    MOLKO_BINDIR.
	 *
	 * Example:
	 *   from: /usr/local/bin
	 *   to:   /usr/local
	 */
	strlcpy(path, base, sizeof (path));
	SDL_free(base);

	if ((binsect = strstr(path, paths[SYS_DIR_BIN])))
		*binsect = '\0';

	snprintf(ret, sizeof (ret), "%s%s", path, paths[kind]);

	return normalize(ret);
}

static const char *
user_directory(enum sys_dir kind)
{
	/* Kept for future use. */
	(void)kind;

	static char path[PATH_MAX];
	char *pref;

	if ((pref = SDL_GetPrefPath(info.organization, info.name))) {
		strlcpy(path, pref, sizeof (path));
		SDL_free(pref);
	} else
		strlcpy(path, "./", sizeof (path));

	return path;
}

static inline int
mkpath(const char *path)
{
#ifdef _WIN32
	/* TODO: add error using the convenient FormatMessage function. */
	if (!CreateDirectoryA(path, NULL) && GetLastError() != ERROR_ALREADY_EXISTS)
		return errorf("unable to create directory: %s", path);
#else
	if (mkdir(path, 0755) < 0 && errno != EEXIST)
		return errorf("%s", strerror(errno));
#endif

	return 0;
}

int
sys_init(const char *organization, const char *name)
{
#if defined(__MINGW64__)
	/* On MinGW buffering leads to painful debugging. */
	setbuf(stderr, NULL);
	setbuf(stdout, NULL);
#endif

	strlcpy(info.organization, organization, sizeof (info.organization));
	strlcpy(info.name, name, sizeof (info.name));

	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0)
		return errorf("%s", SDL_GetError());
	if (IMG_Init(IMG_INIT_PNG) != IMG_INIT_PNG)
		return errorf("%s", SDL_GetError());
	if (TTF_Init() < 0)
		return errorf("%s", SDL_GetError());
	if (Mix_Init(MIX_INIT_OGG) != MIX_INIT_OGG)
		return errorf("%s", SDL_GetError());
	if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 4096) < 0)
		return errorf("%s", SDL_GetError());

	Mix_AllocateChannels(SOUND_CHANNELS_MAX);

	return 0;
}

const char *
sys_dir(enum sys_dir kind)
{
	switch (kind) {
	case SYS_DIR_BIN:
	case SYS_DIR_DATA:
	case SYS_DIR_LOCALE:
		return system_directory(kind);
	default:
		return user_directory(kind);
	}
}

int
sys_mkdir(const char *directory)
{
	char path[PATH_MAX], *p;

	/* Copy the directory to normalize and iterate over '/'. */
	strlcpy(path, directory, sizeof (path));
	normalize(path);

#if defined(_WIN32)
	/* Remove drive letter that we don't need. */
	if ((p = strchr(path, ':')))
		++p;
	else
		p = path;
#else
	p = path;
#endif

	for (p = p + 1; *p; ++p) {
		if (*p == '/') {
			*p = 0;

			if (mkpath(path) < 0)
				return -1;

			*p = '/';
		}
	}

	return mkpath(path);
}

void
sys_finish(void)
{
	Mix_Quit();
	TTF_Quit();
	IMG_Quit();
	SDL_Quit();
}