Blob Blame History Raw
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* 
 * ezfc-font-config.c
 * Copyright (C) 2011-2018 Akira TAGOH
 * 
 * Authors:
 *   Akira TAGOH  <akira@tagoh.org>
 * 
 * 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, either
 * version 3 of the License, or (at your option) any later version.
 * 
 * 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <glib/gstdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include "ezfc-font-config-private.h"

/**
 * SECTION: ezfc-font-config
 * @Short_Description: A class to hold font configuration information
 * @Title: ezfc_font_config_t
 *
 * This class provides an access to the font configuration information
 * from fontconfig.
 *
 * Since: 0.14
 */

/*< private >*/

/*< protected >*/
gboolean
ezfc_font_config_set_name(ezfc_font_config_t *info,
			  const gchar        *name)
{
	ezfc_font_config_private_t *priv = (ezfc_font_config_private_t *)info;

	g_return_val_if_fail (info != NULL, FALSE);
	g_return_val_if_fail (name != NULL, FALSE);

	if (priv->name)
		ezfc_mem_remove_ref(&priv->parent, priv->name);
	priv->name = g_strdup(name);
	ezfc_mem_add_ref(&priv->parent, priv->name, g_free);

	return TRUE;
}

gboolean
ezfc_font_config_set_description(ezfc_font_config_t *info,
				 const gchar        *desc)
{
	ezfc_font_config_private_t *priv = (ezfc_font_config_private_t *)info;

	g_return_val_if_fail (info != NULL, FALSE);
	g_return_val_if_fail (desc != NULL, FALSE);

	if (priv->desc)
		ezfc_mem_remove_ref(&priv->parent, priv->desc);
	priv->desc = g_strdup(desc);
	ezfc_mem_add_ref(&priv->parent, priv->desc, g_free);

	return TRUE;
}

gboolean
ezfc_font_config_set_enabled(ezfc_font_config_t *info,
			     gboolean            is_enabled)
{
	ezfc_font_config_private_t *priv = (ezfc_font_config_private_t *)info;

	g_return_val_if_fail (info != NULL, FALSE);

	priv->is_enabled = is_enabled;

	return TRUE;
}

void
ezfc_font_config_set_config_type(ezfc_font_config_t *info,
				 gint                type)
{
	ezfc_font_config_private_t *priv = (ezfc_font_config_private_t *)info;

	g_return_if_fail (info != NULL);

	priv->type = type;
}

/*< public >*/

/**
 * ezfc_font_config_new:
 *
 * Create an instance of #ezfc_font_config_t.
 *
 * Returns: a #ezfc_font_config_t.
 */
ezfc_font_config_t *
ezfc_font_config_new(void)
{
	ezfc_font_config_private_t *retval = ezfc_mem_alloc_object(sizeof (ezfc_font_config_private_t));

	return (ezfc_font_config_t *)retval;
}

/**
 * ezfc_font_config_ref:
 * @info: a #ezfc_font_config_t.
 *
 * Increases the reference count of @info.
 *
 * Returns: (transfer none): the same @info object.
 */
ezfc_font_config_t *
ezfc_font_config_ref(ezfc_font_config_t *info)
{
	ezfc_font_config_private_t *priv = (ezfc_font_config_private_t *)info;

	return ezfc_mem_ref(&priv->parent);
}

/**
 * ezfc_font_config_unref:
 * @info: a #ezfc_font_config_t.
 *
 * Decreases the reference count of @info. when its reference count
 * drops to 0, the object is finalized (i.e. its memory is freed).
 */
void
ezfc_font_config_unref(ezfc_font_config_t *info)
{
	ezfc_font_config_private_t *priv = (ezfc_font_config_private_t *)info;

	if (priv)
		ezfc_mem_unref(&priv->parent);
}

/**
 * ezfc_font_config_get_name:
 * @info: a #ezfc_font_config_t.
 *
 * Obtain the configuration name being stored in @info object.
 *
 * Returns: the configuration filename
 */
const gchar *
ezfc_font_config_get_name(ezfc_font_config_t *info)
{
	ezfc_font_config_private_t *priv = (ezfc_font_config_private_t *)info;

	g_return_val_if_fail (info != NULL, NULL);

	return priv->name;
}

/**
 * ezfc_font_config_get_description:
 * @info: a #ezfc_font_config_t.
 *
 * Obtain the description for the configuration
 *
 * Returns: the description string
 */
const gchar *
ezfc_font_config_get_description(ezfc_font_config_t *info)
{
	ezfc_font_config_private_t *priv = (ezfc_font_config_private_t *)info;

	g_return_val_if_fail (info != NULL, NULL);

	return priv->desc;
}

/**
 * ezfc_font_config_is_enabled:
 * @info: a #ezfc_font_config_t
 *
 * Returns %TRUE if the configuration is enabled in fontconfig, otherwise %FALSE.
 *
 * Returns: a boolean value
 */
gboolean
ezfc_font_config_is_enabled(ezfc_font_config_t *info)
{
	ezfc_font_config_private_t *priv = (ezfc_font_config_private_t *)info;

	g_return_val_if_fail (info != NULL, FALSE);

	return priv->is_enabled;
}

/**
 * ezfc_font_config_set_enable:
 * @info: a #ezfc_font_config_t
 * @flag: a boolean value
 * @error: (allow-none): a #GError to store an error if any, or %NULL.
 *
 * Enable/Disable a configuration.
 *
 * Returns: %TRUE if successfully enabled or disabled, otherwise %FALSE.
 */
gboolean
ezfc_font_config_set_enable(ezfc_font_config_t  *info,
			    gboolean             flag,
			    GError             **error)
{
	ezfc_font_config_private_t *priv = (ezfc_font_config_private_t *)info;
	gboolean retval = FALSE;

	g_return_val_if_fail (info != NULL, FALSE);
	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

	if (priv->is_enabled != flag) {
		gint save_errno = 0;

		if (flag) {
			GList *ll, *l = ezfc_font_config_get_config_dirs();

			for (ll = l; ll; ll = g_list_next(ll)) {
				const gchar *d = ll->data;

				if (g_access(d, W_OK) == 0) {
					gchar *fn = g_path_get_basename(priv->name);
					gchar *lfn = g_build_filename(d, fn, NULL);

					if (symlink(priv->name, lfn) == -1) {
						if (error) {
							save_errno = errno;
							g_set_error(error, G_FILE_ERROR,
								    g_file_error_from_errno(save_errno),
								    "%s", g_strerror(save_errno));
						}
					} else {
						g_free(priv->name);
						priv->name = g_strdup(lfn);
						priv->is_enabled = TRUE;
						retval = TRUE;
					}
					g_free(fn);
					g_free(lfn);
					break;
				}
			}
			g_list_free(l);
		} else {
			if (g_file_test(priv->name, G_FILE_TEST_IS_SYMLINK)) {
				gchar *lfn = g_file_read_link(priv->name, error);

				if (lfn) {
					if (g_unlink(priv->name) == -1) {
						if (error) {
							save_errno = errno;
							g_set_error(error, G_FILE_ERROR,
								    g_file_error_from_errno(save_errno),
								    "%s", g_strerror(save_errno));
						}
					} else {
						g_free(priv->name);
						priv->name = g_strdup(lfn);
						priv->is_enabled = FALSE;
						retval = TRUE;
					}
					g_free(lfn);
				}
			}
		}
	}
	return retval;
}

/**
 * ezfc_font_config_is_user_conf:
 * @info: a #ezfc_font_config_t
 *
 * Returns %TRUE if the configuration is an user config otherwise %FALSE.
 *
 * Returns: a boolean value
 */
gboolean
ezfc_font_config_is_user_conf(ezfc_font_config_t *info)
{
	ezfc_font_config_private_t *priv = (ezfc_font_config_private_t *)info;

	g_return_val_if_fail (info != NULL, FALSE);

	return (priv->type & EZFC_FONT_CONFIG_TYPE_USER) != 0;
}

/**
 * ezfc_font_config_is_system_conf:
 * @info: a #ezfc_font_config_t
 *
 * Returns %TRUE if the configuration is a system config otherwise %FALSE.
 *
 * Returns: a boolean value
 */
gboolean
ezfc_font_config_is_system_conf(ezfc_font_config_t *info)
{
	ezfc_font_config_private_t *priv = (ezfc_font_config_private_t *)info;

	g_return_val_if_fail (info != NULL, FALSE);

	return (priv->type & EZFC_FONT_CONFIG_TYPE_SYSTEM) != 0;
}

/**
 * ezfc_font_config_is_writable:
 * @info:a #ezfc_font_config_t
 *
 * Returns %TRUE if the configuration is writable to be disabled/enabled otherwise %FALSE
 *
 * Returns: a boolean value
 */
gboolean
ezfc_font_config_is_writable(ezfc_font_config_t *info)
{
	ezfc_font_config_private_t *priv = (ezfc_font_config_private_t *)info;

	g_return_val_if_fail (info != NULL, FALSE);

	return access(priv->name, W_OK) == 0 && g_file_test(priv->name, G_FILE_TEST_IS_SYMLINK);
}

/**
 * ezfc_font_config_get_list:
 *
 * Obtains the list of #ezfc_font_config_t to see what kind of configuration
 * files for fontconfig are installed and enabled on the system.
 *
 * Returns: (element-type EzfcFontConfig) (transfer full): a #GList contains #ezfc_font_config_t.
 */
GList *
ezfc_font_config_get_list(void)
{
	FcConfig *config = NULL;

	if (!config)
		config = FcConfigGetCurrent();

	return ezfc_font_config_get_list_from(config);
}

/**
 * ezfc_font_config_get_list_from:
 * @config: a #FcConfig.
 *
 * Obtains the list of #ezfc_font_config_t to see what kind of configuration
 * files for fontconfig are installed and enabled on the system from @config.
 *
 * Returns: (element-type EzfcFontConfig) (transfer full): a #GList contains #ezfc_font_config_t.
 */
GList *
ezfc_font_config_get_list_from(FcConfig *config)
{
	FcConfigFileInfoIter iter;
	GList *retval = NULL;
	FcChar8 *name = NULL, *desc = NULL;
	FcBool enabled;
	ezfc_font_config_t *p;
	const gchar *uconfigdir = g_get_user_config_dir();
	size_t len = strlen(uconfigdir);

	g_return_val_if_fail (config != NULL, NULL);

	FcConfigFileInfoIterInit(config, &iter);
	do {
		gint type = 0;

		if (!FcConfigFileInfoIterGet(config, &iter,
					     &name, &desc, &enabled))
			break;
		p = ezfc_font_config_new();
		if (!p)
			goto bail;
		ezfc_font_config_set_name(p, (const gchar *)name);
		ezfc_font_config_set_description(p, (const gchar *)desc);
		ezfc_font_config_set_enabled(p, enabled);
		if (strncmp((const char *)name, uconfigdir, len) == 0) {
			if (!g_file_test((const gchar *)name, G_FILE_TEST_IS_SYMLINK))
				type |= EZFC_FONT_CONFIG_TYPE_USER;
		} else {
			FcStrList *l = FcConfigGetConfigDirs(NULL);
			FcChar8 *s;
			const gchar *e = g_getenv("FONTCONFIG_FILE");

			while ((s = FcStrListNext(l))) {
				size_t len = strlen((const char *)s);
				if (strncmp((const char *)name, (const char *)s, len) == 0) {
					type |= EZFC_FONT_CONFIG_TYPE_SYSTEM;
					break;
				}
			}
			FcStrListDone(l);
			if (e && strcmp((const char *)name, e) == 0)
				type |= EZFC_FONT_CONFIG_TYPE_SYSTEM;
			else if (strcmp((const char *)name, "fonts.conf") == 0)
				type |= EZFC_FONT_CONFIG_TYPE_SYSTEM;
			else {
				gchar *fn = g_path_get_basename((const gchar *)name);

				if (strcmp(fn, "fonts.conf") == 0)
					type |= EZFC_FONT_CONFIG_TYPE_SYSTEM;
				g_free(fn);
			}
		}
		ezfc_font_config_set_config_type(p, type);
		if (name) {
			FcStrFree(name);
			name = NULL;
		}
		if (desc) {
			FcStrFree(desc);
			desc = NULL;
		}
		retval = g_list_append(retval, p);
	} while (FcConfigFileInfoIterNext (config, &iter));

	return retval;
  bail:
	if (name) {
		FcStrFree(name);
		name = NULL;
	}
	if (desc) {
		FcStrFree(desc);
		desc = NULL;
	}
	if (retval)
		g_list_free_full(retval, (GDestroyNotify)ezfc_font_config_unref);

	return NULL;
}

/**
 * ezfc_font_config_get_config_dirs:
 *
 * Obtains the list of the config directories where fontconfig can recognizes
 * and enables the configuration files.
 *
 * Returns: (element-type utf8) (transfer none): a #GList contains a string
 */
GList *
ezfc_font_config_get_config_dirs(void)
{
	FcConfig *config = NULL;

	if (!config)
		config = FcConfigGetCurrent();

	return ezfc_font_config_get_config_dirs_from(config);
}

/**
 * ezfc_font_config_get_config_dirs_from:
 * @config: a #FcConfig.
 *
 * Obtains the list of the config directories where fontconfig can recognizes
 * and enables the configuration files from @config.
 *
 * Returns: (element-type utf8) (transfer none): a #GList contains a string
 */
GList *
ezfc_font_config_get_config_dirs_from(FcConfig *config)
{
	FcStrList *l = FcConfigGetConfigDirs(config);
	FcChar8 *s;
	GList *ret = NULL;

	while ((s = FcStrListNext (l))) {
		ret = g_list_append(ret, (gpointer)s);
	}
	FcStrListDone(l);

	return ret;
}