/*
* This file is part of GtkHotkey.
* Copyright Mikkel Kamstrup Erlandsen, March, 2008
*
* GtkHotkey 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.
*
* GtkHotkey 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with GtkHotkey. If not, see .
*/
#include "config.h"
#include "utils.h"
#include "gtk-hotkey-key-file-registry.h"
#include "gtk-hotkey-utils.h"
#include
#include
enum {
GTK_HOTKEY_KEY_FILE_REGISTRY_DUMMY_PROPERTY
};
/* Beware - the lengths of these strings are hardcoded throughout
* this file (sorry) */
#define HOTKEY_HOME "~/.config/hotkeys"
#define HOTKEY_FILE_EXT ".hotkeys"
#define HOTKEY_GROUP "hotkey:"
static GtkHotkeyInfo* gtk_hotkey_key_file_registry_real_get_hotkey (GtkHotkeyRegistry *base,
const char *app_id,
const char *key_id,
GError **error);
static GList* gtk_hotkey_key_file_registry_real_get_application_hotkeys
(GtkHotkeyRegistry *base,
const char *app_id,
GError **error);
static GList* gtk_hotkey_key_file_registry_real_get_all_hotkeys
(GtkHotkeyRegistry *base);
static gboolean gtk_hotkey_key_file_registry_real_store_hotkey(GtkHotkeyRegistry *base,
GtkHotkeyInfo *info,
GError **error);
static gboolean gtk_hotkey_key_file_registry_real_delete_hotkey
(GtkHotkeyRegistry *base,
const gchar *app_id,
const gchar *key_id,
GError **error);
static gboolean gtk_hotkey_key_file_registry_real_has_hotkey (GtkHotkeyRegistry *base,
const gchar *app_id,
const gchar *key_id);
static GFile* get_hotkey_home (void);
static GFile* get_hotkey_file (const gchar *app_id);
static GKeyFile* get_hotkey_key_file (const gchar *app_id,
GError **error);
static GtkHotkeyInfo* get_hotkey_info_from_key_file (GKeyFile *keyfile,
const gchar *app_id,
const gchar *key_id,
GError **error);
static GList* get_all_hotkey_infos_from_key_file (GKeyFile *keyfile,
const gchar *app_id);
static gpointer gtk_hotkey_key_file_registry_parent_class = NULL;
static
GtkHotkeyInfo*
gtk_hotkey_key_file_registry_real_get_hotkey (GtkHotkeyRegistry *base,
const char *app_id,
const char *key_id,
GError **error)
{
GKeyFile *keyfile = NULL;
GtkHotkeyInfo *info = NULL;
g_return_val_if_fail (GTK_HOTKEY_IS_KEY_FILE_REGISTRY(base), NULL);
g_return_val_if_fail (app_id != NULL, NULL);
g_return_val_if_fail (key_id != NULL, NULL);
keyfile = get_hotkey_key_file (app_id, error);
if (keyfile == NULL)
goto clean_up;
info = get_hotkey_info_from_key_file (keyfile, app_id, key_id, error);
clean_up:
if (keyfile) g_key_file_free (keyfile);
return info;
}
static GList*
gtk_hotkey_key_file_registry_real_get_application_hotkeys (GtkHotkeyRegistry *base,
const char *app_id,
GError **error)
{
GKeyFile *keyfile;
g_return_val_if_fail (app_id != NULL, NULL);
keyfile = get_hotkey_key_file (app_id, error);
if (keyfile == NULL)
return NULL; /* error is set by get_hotkey_key_file() */
return get_all_hotkey_infos_from_key_file (keyfile, app_id);
}
static GList*
gtk_hotkey_key_file_registry_real_get_all_hotkeys (GtkHotkeyRegistry *base)
{
GFile *home;
GFileEnumerator *dir;
GFileInfo *file_info;
GError *error;
GList *result = NULL;
home = get_hotkey_home ();
error = NULL;
dir = g_file_enumerate_children (home, G_FILE_ATTRIBUTE_STANDARD_NAME,
0, NULL, &error);
if (error) {
gchar *path = g_file_get_path (home);
g_critical ("Failed to read hotkey home directory '%s': %s",
path, error->message);
g_free (path);
g_error_free (error);
return NULL;
}
error = NULL;
while ((file_info = g_file_enumerator_next_file (dir, NULL, &error)) != NULL) {
const gchar *filename;
GFile *file;
GString *app_id;
GList *app_hotkeys;
filename = g_file_info_get_name(file_info);
if (g_str_has_suffix (filename, HOTKEY_FILE_EXT)) {
file = g_file_get_child (home, filename);
/* Extract app_id from file name */
app_id = g_string_new (filename);
g_string_erase (app_id, app_id->len - 8, 8);
/* Load all hotkeys from the file, and append it to
* the total result */
app_hotkeys = gtk_hotkey_registry_get_application_hotkeys (base,
app_id->str,
&error);
if (error) {
g_warning("failed to read hotkeys for application '%s': %s",
app_id->str, error->message);
g_error_free (error);
error = NULL;
} else {
result = g_list_concat (result, app_hotkeys);
}
g_string_free (app_id, TRUE);
g_object_unref (file);
}
g_object_unref (file_info);
}
if (error) {
gchar *path = g_file_get_path (home);
g_warning("failed to read hotkey home directory '%s': %s",
path, error->message);
g_free (path);
g_error_free (error);
}
g_object_unref (dir);
g_object_unref (home);
return result;
}
static gboolean
gtk_hotkey_key_file_registry_real_store_hotkey (GtkHotkeyRegistry *base,
GtkHotkeyInfo *info,
GError **error)
{
GKeyFile *keyfile;
GFile *file, *home;
GError *tmp_error;
gchar *file_path, *group = NULL;
g_return_val_if_fail (GTK_HOTKEY_IS_INFO (info), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
/* Make sure we have our root dir */
home = get_hotkey_home ();
if (!g_file_query_exists(home, NULL)) {
tmp_error = NULL;
if (!g_file_make_directory (home, NULL, &tmp_error)) {
g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
GTK_HOTKEY_REGISTRY_ERROR_IO,
"Failed to create hotkey configuration dir "
HOTKEY_HOME": %s", tmp_error->message);
g_error_free (tmp_error);
g_object_unref (home);
return FALSE;
}
}
/* Now load any old contents of the keyfile */
file = get_hotkey_file (gtk_hotkey_info_get_application_id (info));
file_path = g_file_get_path (file);
keyfile = g_key_file_new ();
tmp_error = NULL;
if (!g_key_file_load_from_file (keyfile, file_path, 0, &tmp_error)) {
if (tmp_error->code == G_KEY_FILE_ERROR_PARSE) {
g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
GTK_HOTKEY_REGISTRY_ERROR_MALFORMED_MEDIUM,
"The file %s is not in a valid key-file format: %s",
file_path, tmp_error->message);
goto clean_up;
}
/* Ignore other errors */
g_error_free (tmp_error);
}
/* Prepare keyfile data */
group = g_strconcat (HOTKEY_GROUP, gtk_hotkey_info_get_key_id (info), NULL);
g_key_file_set_string (keyfile, group, "Owner",
gtk_hotkey_info_get_application_id (info));
g_key_file_set_string (keyfile, group, "Signature",
gtk_hotkey_info_get_signature (info));
if (gtk_hotkey_info_get_description (info))
g_key_file_set_string (keyfile, group, "Description",
gtk_hotkey_info_get_description (info));
if (gtk_hotkey_info_get_app_info (info)) {
GAppInfo *ai = gtk_hotkey_info_get_app_info (info);
g_key_file_set_string (keyfile, group, "AppInfo",
g_app_info_get_id (ai));
}
gsize size;
gchar *contents;
tmp_error = NULL;
contents = g_key_file_to_data (keyfile, &size, &tmp_error);
if (tmp_error) {
g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
GTK_HOTKEY_REGISTRY_ERROR_UNKNOWN,
"Failed to generate keyfile contents: %s",
tmp_error->message);
goto clean_up;
}
/* Write the actual data */
g_file_set_contents (file_path, contents, size, &tmp_error);
if (tmp_error) {
g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
GTK_HOTKEY_REGISTRY_ERROR_IO,
"Failed to write keyfile '%s': %s",
file_path, tmp_error->message);
goto clean_up;
}
clean_up:
if (tmp_error) g_error_free (tmp_error);
g_free (file_path);
if (group) g_free (group);
g_key_file_free (keyfile);
g_object_unref (file);
g_object_unref (home);
if (*error)
return FALSE;
g_return_val_if_fail (GTK_HOTKEY_IS_INFO (info), FALSE);
gtk_hotkey_registry_hotkey_stored (base, info);
return TRUE;
}
static gboolean
gtk_hotkey_key_file_registry_real_delete_hotkey (GtkHotkeyRegistry *base,
const gchar *app_id,
const gchar *key_id,
GError **error)
{
GtkHotkeyInfo *info = NULL;
GFile *file;
GKeyFile *keyfile;
GError *tmp_error;
gboolean is_error = FALSE;
gchar *path, *group;
g_return_val_if_fail (app_id != NULL, FALSE);
g_return_val_if_fail (key_id != NULL, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
group = NULL;
file = get_hotkey_file (app_id);
g_return_val_if_fail (G_IS_FILE(file), FALSE);
path = g_file_get_path (file);
keyfile = g_key_file_new ();
/* Load the old keyfile */
tmp_error = NULL;
g_key_file_load_from_file (keyfile, path, 0, &tmp_error);
if (tmp_error) {
if ((tmp_error->domain == G_FILE_ERROR &&
tmp_error->code == G_FILE_ERROR_NOENT) ||
(tmp_error->domain == G_KEY_FILE_ERROR &&
tmp_error->code == G_KEY_FILE_ERROR_NOT_FOUND))
g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
GTK_HOTKEY_REGISTRY_ERROR_UNKNOWN_APP,
"No such keyfile '%s'. Application '%s' has not "
"registered any hotkeys",
path, app_id);
else
g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
GTK_HOTKEY_REGISTRY_ERROR_IO,
"Failed to load keyfile '%s': %s",
app_id, tmp_error->message);
is_error = TRUE;
goto clean_up;
}
/* Get a ref to the GtkHotkeyInfo so that we can emit it with the
* hotkey-deleted signal */
tmp_error = NULL;
info = get_hotkey_info_from_key_file (keyfile, app_id, key_id, error);
if (info == NULL) {
is_error = TRUE;
goto clean_up;
}
/* Remove the group for key_id */
group = g_strconcat (HOTKEY_GROUP, key_id, NULL);
tmp_error = NULL;
g_key_file_remove_group (keyfile, group, &tmp_error);
if (tmp_error) {
if (tmp_error->domain == G_KEY_FILE_ERROR &&
tmp_error->code == G_KEY_FILE_ERROR_GROUP_NOT_FOUND)
g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
GTK_HOTKEY_REGISTRY_ERROR_UNKNOWN_APP,
"Application '%s' has not registered a hotkey with"
"id '%s'", app_id, key_id);
else
g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
GTK_HOTKEY_REGISTRY_ERROR_UNKNOWN,
"Failed to delete hotkey '%s' from application %s: %s",
key_id, app_id, tmp_error->message);
is_error = TRUE;
goto clean_up;
}
/* Check if the keyfile is empty. If it is we delete it */
gsize count;
GStrv groups;
groups = g_key_file_get_groups (keyfile, &count);
g_strfreev (groups);
if (count == 0) {
tmp_error = NULL;
g_file_delete (file, NULL, &tmp_error);
if (tmp_error) {
g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
GTK_HOTKEY_REGISTRY_ERROR_IO,
"Failed to delete empty keyfile '%s': %s",
path, tmp_error->message);
is_error = TRUE;
}
/* File deleted, we should just clean up and exit */
goto clean_up;
}
/* Write new keyfile */
gsize size;
gchar *contents;
tmp_error = NULL;
contents = g_key_file_to_data (keyfile, &size, &tmp_error);
if (tmp_error) {
g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
GTK_HOTKEY_REGISTRY_ERROR_UNKNOWN,
"Failed to generate keyfile contents: %s",
tmp_error->message);
is_error = TRUE;
goto clean_up;
}
tmp_error = NULL;
if (g_file_set_contents (path, contents, size, &tmp_error) != TRUE)
FILE_OP_ERROR(path, "g_file_set_contents");
if (tmp_error) {
g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
GTK_HOTKEY_REGISTRY_ERROR_IO,
"Failed to write keyfile '%s': %s",
path, tmp_error->message);
is_error = TRUE;
goto clean_up;
}
clean_up:
if (tmp_error) g_error_free (tmp_error);
g_object_unref (file);
g_free (path);
if (group) g_free (group);
g_key_file_free (keyfile);
if (is_error)
return FALSE;
gtk_hotkey_registry_hotkey_deleted (base, info);
g_object_unref (info);
return TRUE;
}
static gboolean
gtk_hotkey_key_file_registry_real_has_hotkey (GtkHotkeyRegistry *base,
const gchar *app_id,
const gchar *key_id)
{
GFile *file;
gboolean exists;
g_return_val_if_fail (app_id != NULL, FALSE);
g_return_val_if_fail (key_id != NULL, FALSE);
file = get_hotkey_file (app_id);
g_return_val_if_fail (G_IS_FILE(file), FALSE);
if (g_file_query_exists (file, NULL))
exists = TRUE;
else
exists = FALSE;
g_object_unref (file);
return exists;
}
static void
gtk_hotkey_key_file_registry_class_init (GtkHotkeyKeyFileRegistryClass *klass)
{
gtk_hotkey_key_file_registry_parent_class = g_type_class_peek_parent (klass);
GTK_HOTKEY_REGISTRY_CLASS (klass)->get_hotkey = gtk_hotkey_key_file_registry_real_get_hotkey;
GTK_HOTKEY_REGISTRY_CLASS (klass)->get_application_hotkeys = gtk_hotkey_key_file_registry_real_get_application_hotkeys;
GTK_HOTKEY_REGISTRY_CLASS (klass)->get_all_hotkeys = gtk_hotkey_key_file_registry_real_get_all_hotkeys;
GTK_HOTKEY_REGISTRY_CLASS (klass)->store_hotkey = gtk_hotkey_key_file_registry_real_store_hotkey;
GTK_HOTKEY_REGISTRY_CLASS (klass)->delete_hotkey = gtk_hotkey_key_file_registry_real_delete_hotkey;
GTK_HOTKEY_REGISTRY_CLASS (klass)->has_hotkey = gtk_hotkey_key_file_registry_real_has_hotkey;
}
static void
gtk_hotkey_key_file_registry_init (GtkHotkeyKeyFileRegistry *self)
{
}
static void
gtk_hotkey_key_file_registry_finalize (GtkHotkeyKeyFileRegistry *self)
{
}
GType
gtk_hotkey_key_file_registry_get_type (void)
{
static GType gtk_hotkey_key_file_registry_type_id = 0;
if (G_UNLIKELY (gtk_hotkey_key_file_registry_type_id == 0)) {
static const GTypeInfo g_define_type_info = {
sizeof (GtkHotkeyKeyFileRegistryClass),
(GBaseInitFunc) gtk_hotkey_key_file_registry_init,
(GBaseFinalizeFunc) gtk_hotkey_key_file_registry_finalize,
(GClassInitFunc) gtk_hotkey_key_file_registry_class_init,
(GClassFinalizeFunc) NULL,
NULL,
sizeof (GtkHotkeyKeyFileRegistry),
0,
(GInstanceInitFunc) gtk_hotkey_key_file_registry_init,
(const GTypeValueTable *) NULL /* value table */
};
gtk_hotkey_key_file_registry_type_id = g_type_register_static (GTK_HOTKEY_TYPE_STORAGE, "GtkHotkeyKeyFileRegistry", &g_define_type_info, 0);
}
return gtk_hotkey_key_file_registry_type_id;
}
static GFile*
get_hotkey_home (void)
{
GFile *home;
home = g_file_parse_name (HOTKEY_HOME);
if (g_file_query_exists(home, NULL) &&
!gtk_hotkey_g_file_is_directory(home)) {
g_critical (HOTKEY_HOME" exists but is not a directory");
g_object_unref (home);
return NULL;
}
return home;
}
/* It is not guaranteed that the keyfile exists */
static GFile*
get_hotkey_file (const gchar *app_id)
{
GFile *home, *file;
gchar *filename;
g_return_val_if_fail (app_id != NULL, NULL);
home = get_hotkey_home();
g_return_val_if_fail (home != NULL, NULL);
filename = g_strconcat (app_id, HOTKEY_FILE_EXT, NULL);
file = g_file_get_child (home, filename);
g_object_unref (home);
g_free (filename);
return file;
}
static GKeyFile*
get_hotkey_key_file (const gchar *app_id, GError **error)
{
gchar *path;
GFile *file;
GKeyFile *keyfile = NULL;
GError *tmp_error;
g_return_val_if_fail (app_id != NULL, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
file = get_hotkey_file (app_id);
if (!g_file_query_exists (file, NULL)) {
g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
GTK_HOTKEY_REGISTRY_ERROR_UNKNOWN_APP,
"Application '%s' has not registered any hotkeys", app_id);
g_object_unref (file);
return NULL;
}
path = g_file_get_path (file);
keyfile = g_key_file_new ();
tmp_error = NULL;
g_key_file_load_from_file (keyfile, path, 0, &tmp_error);
if (tmp_error) {
g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
GTK_HOTKEY_REGISTRY_ERROR_IO,
"Failed to load keyfile '%s': %s", path, tmp_error->message);
goto clean_up;
}
clean_up:
g_free (path);
g_object_unref (file);
if (tmp_error) g_error_free (tmp_error);
if (*error) {
g_key_file_free (keyfile);
return NULL;
}
return keyfile;
}
static GtkHotkeyInfo*
get_hotkey_info_from_key_file (GKeyFile *keyfile,
const gchar *app_id,
const gchar *key_id,
GError **error)
{
GtkHotkeyInfo *info = NULL;
gchar *group, *description, *app_info_id, *signature;
GAppInfo *app_info = NULL;
g_return_val_if_fail (keyfile != NULL, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
g_return_val_if_fail (app_id != NULL, NULL);
g_return_val_if_fail (key_id != NULL, NULL);
group = g_strconcat (HOTKEY_GROUP, key_id, NULL);
description = g_key_file_get_string (keyfile, group, "Description", NULL);
app_info_id = g_key_file_get_string (keyfile, group, "AppInfo", NULL);
signature = g_key_file_get_string (keyfile, group, "Signature", NULL);
if (!g_key_file_has_group (keyfile, group)) {
g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
GTK_HOTKEY_REGISTRY_ERROR_UNKNOWN_KEY,
"Keyfile has no group "HOTKEY_GROUP"%s", key_id);
goto clean_up;
}
if (!signature) {
g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
GTK_HOTKEY_REGISTRY_ERROR_BAD_SIGNATURE,
"No 'Signature' defined for hotkey '%s' for application '%s'",
key_id, app_id);
goto clean_up;
}
if (app_info_id) {
app_info = G_APP_INFO(g_desktop_app_info_new (app_info_id));
if (!G_IS_APP_INFO(app_info)) {
g_set_error (error, GTK_HOTKEY_REGISTRY_ERROR,
GTK_HOTKEY_REGISTRY_ERROR_MISSING_APP,
"Keyfile referring to 'AppInfo = %s', but no application"
"by that id is registered on the system", app_info_id);
goto clean_up;
}
}
info = gtk_hotkey_info_new (app_id, key_id, signature, app_info);
if (description)
gtk_hotkey_info_set_description (info, description);
clean_up:
g_free (group);
if (signature) g_free (signature);
if (description) g_free (description);
if (app_info_id) g_free (app_info_id);
if (app_info) g_object_unref (app_info);
return info;
}
static GList*
get_all_hotkey_infos_from_key_file (GKeyFile *keyfile,
const gchar *app_id)
{
GList *hotkeys;
GtkHotkeyInfo *hotkey;
GStrv groups;
gsize count;
gchar *group;
GString *key_id;
GError *error;
g_return_val_if_fail (keyfile != NULL, NULL);
g_return_val_if_fail (app_id != NULL, NULL);
hotkeys = NULL;
groups = g_key_file_get_groups (keyfile, &count);
int i;
for (i = 0; i < count; i++) {
group = groups[i];
key_id = g_string_new (group);
/* Ignore non hotkey groups */
if (!g_str_has_prefix (key_id->str, HOTKEY_GROUP)) {
g_warning("hotkey file for %s contains non 'hotkey:' group '%s'",
app_id, group);
g_string_free (key_id, TRUE);
continue;
}
/* Strip 'hotkey:' prefix from key_id */
g_string_erase (key_id, 0, 7);
error = NULL;
hotkey = get_hotkey_info_from_key_file (keyfile, app_id, key_id->str, &error);
if (error) {
g_warning("failed to read hotkey '%s' for application '%s': %s",
key_id->str, app_id, error->message);
g_error_free (error);
g_string_free (key_id, TRUE);
continue;
}
hotkeys = g_list_prepend (hotkeys, hotkey);
g_string_free (key_id, TRUE);
}
g_strfreev (groups);
return hotkeys;
}