Compare commits
3 Commits
8fb2ce29cf
...
c0a094e473
Author | SHA1 | Date | |
---|---|---|---|
c0a094e473 | |||
f147b54393 | |||
10c05a2422 |
115
src/sdgui.c
115
src/sdgui.c
@ -26,16 +26,6 @@
|
||||
#include "utils.h"
|
||||
#include "stardict-view.h"
|
||||
|
||||
typedef struct dictionary Dictionary;
|
||||
|
||||
struct dictionary
|
||||
{
|
||||
const gchar *filename; ///< Filename
|
||||
StardictDict *dict; ///< Stardict dictionary data
|
||||
gchar *name; ///< Name to show
|
||||
guint position; ///< Current position
|
||||
};
|
||||
|
||||
static struct
|
||||
{
|
||||
GtkWidget *window; ///< Top-level window
|
||||
@ -44,8 +34,7 @@ static struct
|
||||
GtkWidget *view; ///< Entries view
|
||||
|
||||
gint dictionary; ///< Index of the current dictionary
|
||||
Dictionary *dictionaries; ///< All open dictionaries
|
||||
gsize dictionaries_len; ///< Total number of dictionaries
|
||||
GPtrArray *dictionaries; ///< All open dictionaries
|
||||
|
||||
gboolean watch_selection; ///< Following X11 PRIMARY?
|
||||
}
|
||||
@ -53,37 +42,62 @@ g;
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static gboolean
|
||||
dictionary_load (Dictionary *self, gchar *filename, GError **e)
|
||||
static void
|
||||
init (gchar **filenames)
|
||||
{
|
||||
self->filename = filename;
|
||||
if (!(self->dict = stardict_dict_new (self->filename, e)))
|
||||
for (gsize i = 0; filenames[i]; i++)
|
||||
{
|
||||
Dictionary *dict = g_malloc0 (sizeof *dict);
|
||||
dict->filename = g_strdup (filenames[i]);
|
||||
g_ptr_array_add (g.dictionaries, dict);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: try to deduplicate, similar to app_load_config_values()
|
||||
static gboolean
|
||||
init_from_key_file (GKeyFile *kf, GError **error)
|
||||
{
|
||||
const gchar *dictionaries = "Dictionaries";
|
||||
gchar **names = g_key_file_get_keys (kf, dictionaries, NULL, NULL);
|
||||
if (!names)
|
||||
return TRUE;
|
||||
|
||||
for (gsize i = 0; names[i]; i++)
|
||||
{
|
||||
Dictionary *dict = g_malloc0 (sizeof *dict);
|
||||
dict->name = names[i];
|
||||
g_ptr_array_add (g.dictionaries, dict);
|
||||
}
|
||||
g_free (names);
|
||||
|
||||
for (gsize i = 0; i < g.dictionaries->len; i++)
|
||||
{
|
||||
Dictionary *dict = g_ptr_array_index (g.dictionaries, i);
|
||||
gchar *path =
|
||||
g_key_file_get_string (kf, dictionaries, dict->name, error);
|
||||
if (!path)
|
||||
return FALSE;
|
||||
|
||||
if (!self->name)
|
||||
{
|
||||
self->name = g_strdup (stardict_info_get_book_name
|
||||
(stardict_dict_get_info (self->dict)));
|
||||
// Try to resolve relative paths and expand tildes
|
||||
if (!(dict->filename =
|
||||
resolve_filename (path, resolve_relative_config_filename)))
|
||||
dict->filename = path;
|
||||
else
|
||||
g_free (path);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static gboolean
|
||||
init (gchar **filenames, GError **e)
|
||||
init_from_config (GError **error)
|
||||
{
|
||||
while (filenames[g.dictionaries_len])
|
||||
g.dictionaries_len++;
|
||||
|
||||
g.dictionaries = g_malloc0_n (sizeof *g.dictionaries, g.dictionaries_len);
|
||||
for (gsize i = 0; i < g.dictionaries_len; i++)
|
||||
{
|
||||
Dictionary *dict = &g.dictionaries[i];
|
||||
if (!dictionary_load (dict, filenames[i], e))
|
||||
GKeyFile *key_file = load_project_config_file (error);
|
||||
if (!key_file)
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
|
||||
gboolean result = init_from_key_file (key_file, error);
|
||||
g_key_file_free (key_file);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
@ -94,18 +108,17 @@ search (Dictionary *dict)
|
||||
|
||||
StardictIterator *iterator =
|
||||
stardict_dict_search (dict->dict, input_utf8, NULL);
|
||||
dict->position = stardict_iterator_get_offset (iterator);
|
||||
stardict_view_set_position (STARDICT_VIEW (g.view),
|
||||
dict->dict, stardict_iterator_get_offset (iterator));
|
||||
g_object_unref (iterator);
|
||||
|
||||
stardict_view_set_position (STARDICT_VIEW (g.view),
|
||||
dict->dict, dict->position);
|
||||
stardict_view_set_matched (STARDICT_VIEW (g.view), input_utf8);
|
||||
}
|
||||
|
||||
static void
|
||||
on_changed (G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED gpointer data)
|
||||
{
|
||||
search (&g.dictionaries[g.dictionary]);
|
||||
search (g_ptr_array_index (g.dictionaries, g.dictionary));
|
||||
}
|
||||
|
||||
static void
|
||||
@ -141,7 +154,7 @@ on_switch_page (G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED GtkWidget *page,
|
||||
guint page_num, G_GNUC_UNUSED gpointer data)
|
||||
{
|
||||
g.dictionary = page_num;
|
||||
search (&g.dictionaries[g.dictionary]);
|
||||
search (g_ptr_array_index (g.dictionaries, g.dictionary));
|
||||
}
|
||||
|
||||
static gboolean
|
||||
@ -205,7 +218,7 @@ main (int argc, char *argv[])
|
||||
GOptionEntry option_entries[] =
|
||||
{
|
||||
{G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames,
|
||||
NULL, N_("FILE...")},
|
||||
NULL, N_("[FILE]...")},
|
||||
{},
|
||||
};
|
||||
|
||||
@ -219,12 +232,18 @@ main (int argc, char *argv[])
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!filenames)
|
||||
{
|
||||
// TODO: eventually just load all dictionaries from configuration
|
||||
die_with_dialog ("No arguments have been passed.");
|
||||
}
|
||||
if (!init (filenames, &error))
|
||||
g.dictionaries =
|
||||
g_ptr_array_new_with_free_func ((GDestroyNotify) dictionary_destroy);
|
||||
if (filenames)
|
||||
init (filenames);
|
||||
else if (!init_from_config (&error) && error)
|
||||
die_with_dialog (error->message);
|
||||
g_strfreev (filenames);
|
||||
|
||||
if (!g.dictionaries->len)
|
||||
die_with_dialog (_("No dictionaries found either in "
|
||||
"the configuration or on the command line"));
|
||||
if (!load_dictionaries (g.dictionaries, &error))
|
||||
die_with_dialog (error->message);
|
||||
|
||||
// Some Adwaita stupidity
|
||||
@ -290,9 +309,9 @@ main (int argc, char *argv[])
|
||||
g.view = stardict_view_new ();
|
||||
gtk_box_pack_end (GTK_BOX (superbox), g.view, TRUE, TRUE, 0);
|
||||
|
||||
for (gsize i = 0; i < g.dictionaries_len; i++)
|
||||
for (gsize i = 0; i < g.dictionaries->len; i++)
|
||||
{
|
||||
Dictionary *dict = &g.dictionaries[i];
|
||||
Dictionary *dict = g_ptr_array_index (g.dictionaries, i);
|
||||
GtkWidget *dummy = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
|
||||
GtkWidget *label = gtk_label_new (dict->name);
|
||||
gtk_notebook_append_page (GTK_NOTEBOOK (g.notebook), dummy, label);
|
||||
@ -305,7 +324,5 @@ main (int argc, char *argv[])
|
||||
gtk_widget_grab_focus (g.entry);
|
||||
gtk_widget_show_all (g.window);
|
||||
gtk_main ();
|
||||
|
||||
g_strfreev (filenames);
|
||||
return 0;
|
||||
}
|
||||
|
273
src/sdtui.c
273
src/sdtui.c
@ -28,13 +28,11 @@
|
||||
#include <gio/gio.h>
|
||||
#include <pango/pango.h>
|
||||
#include <glib/gi18n.h>
|
||||
#include <glib/gstdio.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <poll.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <pwd.h>
|
||||
|
||||
#include <termo.h> // input
|
||||
#include <ncurses.h> // output
|
||||
@ -96,94 +94,6 @@ add_read_watch (int fd, GIOFunc func, gpointer user_data)
|
||||
return res;
|
||||
}
|
||||
|
||||
// At times, GLib even with its sheer size is surprisingly useless,
|
||||
// and I need to port some code over from "liberty".
|
||||
|
||||
static gchar **
|
||||
get_xdg_config_dirs (void)
|
||||
{
|
||||
GPtrArray *paths = g_ptr_array_new ();
|
||||
g_ptr_array_add (paths, (gpointer) g_get_user_config_dir ());
|
||||
for (const gchar *const *system = g_get_system_config_dirs ();
|
||||
*system; system++)
|
||||
g_ptr_array_add (paths, (gpointer) *system);
|
||||
g_ptr_array_add (paths, NULL);
|
||||
return (gchar **) g_ptr_array_free (paths, FALSE);
|
||||
}
|
||||
|
||||
static gchar *
|
||||
resolve_relative_filename_generic
|
||||
(gchar **paths, const gchar *tail, const gchar *filename)
|
||||
{
|
||||
for (; *paths; paths++)
|
||||
{
|
||||
// As per XDG spec, relative paths are ignored
|
||||
if (**paths != '/')
|
||||
continue;
|
||||
|
||||
gchar *file = g_build_filename (*paths, tail, filename, NULL);
|
||||
GStatBuf st;
|
||||
if (!g_stat (file, &st))
|
||||
return file;
|
||||
g_free (file);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static gchar *
|
||||
resolve_relative_config_filename (const gchar *filename)
|
||||
{
|
||||
gchar **paths = get_xdg_config_dirs ();
|
||||
gchar *result = resolve_relative_filename_generic
|
||||
(paths, PROJECT_NAME, filename);
|
||||
g_strfreev (paths);
|
||||
return result;
|
||||
}
|
||||
|
||||
static gchar *
|
||||
try_expand_tilde (const gchar *filename)
|
||||
{
|
||||
size_t until_slash = strcspn (filename, "/");
|
||||
if (!until_slash)
|
||||
return g_build_filename (g_get_home_dir () ?: "", filename, NULL);
|
||||
|
||||
long buf_len = sysconf (_SC_GETPW_R_SIZE_MAX);
|
||||
if (buf_len < 0)
|
||||
buf_len = 1024;
|
||||
struct passwd pwd, *success = NULL;
|
||||
|
||||
gchar *user = g_strndup (filename, until_slash);
|
||||
gchar *buf = g_malloc (buf_len);
|
||||
while (getpwnam_r (user, &pwd, buf, buf_len, &success) == ERANGE)
|
||||
buf = g_realloc (buf, buf_len <<= 1);
|
||||
g_free (user);
|
||||
|
||||
gchar *result = NULL;
|
||||
if (success)
|
||||
result = g_strdup_printf ("%s%s", pwd.pw_dir, filename + until_slash);
|
||||
g_free (buf);
|
||||
return result;
|
||||
}
|
||||
|
||||
static gchar *
|
||||
resolve_filename (const gchar *filename, gchar *(*relative_cb) (const char *))
|
||||
{
|
||||
// Absolute path is absolute
|
||||
if (*filename == '/')
|
||||
return g_strdup (filename);
|
||||
|
||||
// We don't want to use wordexp() for this as it may execute /bin/sh
|
||||
if (*filename == '~')
|
||||
{
|
||||
// Paths to home directories ought to be absolute
|
||||
char *expanded = try_expand_tilde (filename + 1);
|
||||
if (expanded)
|
||||
return expanded;
|
||||
g_debug ("failed to expand the home directory in `%s'", filename);
|
||||
}
|
||||
return relative_cb (filename);
|
||||
}
|
||||
|
||||
// --- Application -------------------------------------------------------------
|
||||
|
||||
#define ATTRIBUTE_TABLE(XX) \
|
||||
@ -215,7 +125,7 @@ struct attrs
|
||||
/// Data relating to one entry within the dictionary.
|
||||
typedef struct view_entry ViewEntry;
|
||||
/// Data relating to a dictionary file.
|
||||
typedef struct dictionary Dictionary;
|
||||
typedef struct app_dictionary AppDictionary;
|
||||
/// Encloses application data.
|
||||
typedef struct application Application;
|
||||
|
||||
@ -226,12 +136,10 @@ struct view_entry
|
||||
GPtrArray * formatting; ///< chtype * or NULL per definition
|
||||
};
|
||||
|
||||
struct dictionary
|
||||
struct app_dictionary
|
||||
{
|
||||
gchar * name; ///< Visible identifier
|
||||
Dictionary super; ///< Superclass
|
||||
gsize name_width; ///< Visible width of the name
|
||||
gchar * filename; ///< Path to the dictionary
|
||||
StardictDict * dict; ///< Dictionary
|
||||
};
|
||||
|
||||
struct application
|
||||
@ -243,7 +151,7 @@ struct application
|
||||
gboolean locale_is_utf8; ///< The locale is Unicode
|
||||
gboolean focused; ///< Whether the terminal has focus
|
||||
|
||||
GArray * dictionaries; ///< All loaded dictionaries
|
||||
GPtrArray * dictionaries; ///< All loaded AppDictionaries
|
||||
|
||||
StardictDict * dict; ///< The current dictionary
|
||||
guint show_help : 1; ///< Whether help can be shown
|
||||
@ -442,42 +350,6 @@ view_entry_free (ViewEntry *ve)
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static gboolean
|
||||
dictionary_load (Dictionary *self, Application *app, GError **e)
|
||||
{
|
||||
if (!(self->dict = stardict_dict_new (self->filename, e)))
|
||||
return FALSE;
|
||||
|
||||
if (!self->name)
|
||||
{
|
||||
self->name = g_strdup (stardict_info_get_book_name
|
||||
(stardict_dict_get_info (self->dict)));
|
||||
}
|
||||
|
||||
// Add some padding for decorative purposes
|
||||
gchar *tmp = g_strdup_printf (" %s ", self->name);
|
||||
g_free (self->name);
|
||||
self->name = tmp;
|
||||
|
||||
gunichar *ucs4 = g_utf8_to_ucs4_fast (self->name, -1, NULL);
|
||||
for (gunichar *it = ucs4; *it; it++)
|
||||
self->name_width += app_char_width (app, *it);
|
||||
g_free (ucs4);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
dictionary_free (Dictionary *self)
|
||||
{
|
||||
g_free (self->name);
|
||||
g_free (self->filename);
|
||||
|
||||
if (self->dict)
|
||||
g_object_unref (self->dict);
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
/// Reload view items.
|
||||
static void
|
||||
app_reload_view (Application *self)
|
||||
@ -574,44 +446,32 @@ app_load_config_values (Application *self, GKeyFile *kf)
|
||||
continue;
|
||||
|
||||
// Try to resolve relative paths and expand tildes
|
||||
gchar *resolved = resolve_filename
|
||||
(path, resolve_relative_config_filename);
|
||||
gchar *resolved =
|
||||
resolve_filename (path, resolve_relative_config_filename);
|
||||
if (resolved)
|
||||
g_free (path);
|
||||
else
|
||||
resolved = path;
|
||||
|
||||
Dictionary dict = { .name = g_strdup (*it), .filename = resolved };
|
||||
g_array_append_val (self->dictionaries, dict);
|
||||
AppDictionary *dict = g_malloc0 (sizeof *dict);
|
||||
dict->super.name = g_strdup (*it);
|
||||
dict->super.filename = resolved;
|
||||
g_ptr_array_add (self->dictionaries, dict);
|
||||
}
|
||||
g_strfreev (names);
|
||||
}
|
||||
|
||||
static void
|
||||
app_load_config (Application *self, GError **e)
|
||||
app_load_config (Application *self, GError **error)
|
||||
{
|
||||
GKeyFile *kf = g_key_file_new ();
|
||||
gchar **paths = get_xdg_config_dirs ();
|
||||
|
||||
// XXX: if there are dashes in the final path component,
|
||||
// the function tries to replace them with directory separators,
|
||||
// which is completely undocumented
|
||||
GError *error = NULL;
|
||||
g_key_file_load_from_dirs (kf,
|
||||
PROJECT_NAME G_DIR_SEPARATOR_S PROJECT_NAME ".conf",
|
||||
(const gchar **) paths, NULL, 0, &error);
|
||||
g_strfreev (paths);
|
||||
|
||||
// TODO: proper error handling showing all relevant information;
|
||||
// we can afford that here since the terminal hasn't been initialized yet
|
||||
if (!error)
|
||||
app_load_config_values (self, kf);
|
||||
else if (error->code == G_KEY_FILE_ERROR_NOT_FOUND)
|
||||
g_error_free (error);
|
||||
else
|
||||
g_propagate_error (e, error);
|
||||
|
||||
g_key_file_free (kf);
|
||||
GKeyFile *key_file = load_project_config_file (error);
|
||||
if (key_file)
|
||||
{
|
||||
app_load_config_values (self, key_file);
|
||||
g_key_file_free (key_file);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
@ -625,67 +485,29 @@ app_init_attrs (Application *self)
|
||||
#undef XX
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static gboolean
|
||||
app_load_dictionaries (Application *self, GError **e)
|
||||
{
|
||||
for (guint i = 0; i < self->dictionaries->len; i++)
|
||||
if (!dictionary_load (&g_array_index (self->dictionaries,
|
||||
Dictionary, i), self, e))
|
||||
if (!load_dictionaries (self->dictionaries, e))
|
||||
return FALSE;
|
||||
|
||||
for (gsize i = 0; i < self->dictionaries->len; i++)
|
||||
{
|
||||
AppDictionary *dict = g_ptr_array_index (self->dictionaries, i);
|
||||
|
||||
// Add some padding for decorative purposes
|
||||
gchar *tmp = g_strdup_printf (" %s ", dict->super.name);
|
||||
g_free (dict->super.name);
|
||||
dict->super.name = tmp;
|
||||
|
||||
gunichar *ucs4 = g_utf8_to_ucs4_fast (dict->super.name, -1, NULL);
|
||||
for (gunichar *it = ucs4; *it; it++)
|
||||
dict->name_width += app_char_width (self, *it);
|
||||
g_free (ucs4);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Parallelize dictionary loading if possible, because of collation reindexing
|
||||
#if GLIB_CHECK_VERSION (2, 36, 0)
|
||||
struct load_ctx
|
||||
{
|
||||
Application *self; ///< Application context
|
||||
GAsyncQueue *error_queue; ///< Errors
|
||||
};
|
||||
|
||||
static void
|
||||
app_load_worker (gpointer data, gpointer user_data)
|
||||
{
|
||||
struct load_ctx *ctx = user_data;
|
||||
GError *e = NULL;
|
||||
dictionary_load (&g_array_index (ctx->self->dictionaries, Dictionary,
|
||||
GPOINTER_TO_UINT (data) - 1), ctx->self, &e);
|
||||
if (e)
|
||||
g_async_queue_push (ctx->error_queue, e);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
app_load_dictionaries_parallel (Application *self, GError **e)
|
||||
{
|
||||
struct load_ctx ctx;
|
||||
GThreadPool *pool = g_thread_pool_new (app_load_worker, &ctx,
|
||||
g_get_num_processors (), TRUE, NULL);
|
||||
if G_UNLIKELY (!g_thread_pool_get_num_threads (pool))
|
||||
{
|
||||
g_thread_pool_free (pool, TRUE, TRUE);
|
||||
return app_load_dictionaries (self, e);
|
||||
}
|
||||
|
||||
ctx.self = self;
|
||||
ctx.error_queue = g_async_queue_new_full ((GDestroyNotify) g_error_free);
|
||||
for (guint i = 0; i < self->dictionaries->len; i++)
|
||||
g_thread_pool_push (pool, GUINT_TO_POINTER (i + 1), NULL);
|
||||
|
||||
g_thread_pool_free (pool, FALSE, TRUE);
|
||||
|
||||
gboolean result = TRUE;
|
||||
if ((*e = g_async_queue_try_pop (ctx.error_queue)))
|
||||
result = FALSE;
|
||||
|
||||
g_async_queue_unref (ctx.error_queue);
|
||||
return result;
|
||||
}
|
||||
|
||||
#define app_load_dictionaries app_load_dictionaries_parallel
|
||||
#endif // GLib >= 2.36
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
/// Initialize the application core.
|
||||
@ -727,9 +549,8 @@ app_init (Application *self, char **filenames)
|
||||
self->focused = TRUE;
|
||||
|
||||
app_init_attrs (self);
|
||||
self->dictionaries = g_array_new (FALSE, TRUE, sizeof (Dictionary));
|
||||
g_array_set_clear_func
|
||||
(self->dictionaries, (GDestroyNotify) dictionary_free);
|
||||
self->dictionaries =
|
||||
g_ptr_array_new_with_free_func ((GDestroyNotify) dictionary_destroy);
|
||||
|
||||
GError *error = NULL;
|
||||
app_load_config (self, &error);
|
||||
@ -744,11 +565,12 @@ app_init (Application *self, char **filenames)
|
||||
// Dictionaries given on the command line override the configuration
|
||||
if (*filenames)
|
||||
{
|
||||
g_array_set_size (self->dictionaries, 0);
|
||||
g_ptr_array_set_size (self->dictionaries, 0);
|
||||
while (*filenames)
|
||||
{
|
||||
Dictionary dict = { .filename = g_strdup (*filenames++) };
|
||||
g_array_append_val (self->dictionaries, dict);
|
||||
AppDictionary *dict = g_malloc0 (sizeof *dict);
|
||||
dict->super.filename = g_strdup (*filenames++);
|
||||
g_ptr_array_add (self->dictionaries, dict);
|
||||
}
|
||||
}
|
||||
|
||||
@ -763,7 +585,8 @@ app_init (Application *self, char **filenames)
|
||||
"the configuration or on the command line"));
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
self->dict = g_array_index (self->dictionaries, Dictionary, 0).dict;
|
||||
self->dict = ((AppDictionary *)
|
||||
g_ptr_array_index (self->dictionaries, 0))->super.dict;
|
||||
app_reload_view (self);
|
||||
}
|
||||
|
||||
@ -819,7 +642,7 @@ app_destroy (Application *self)
|
||||
g_ptr_array_free (self->entries, TRUE);
|
||||
g_free (self->search_label);
|
||||
g_array_free (self->input, TRUE);
|
||||
g_array_free (self->dictionaries, TRUE);
|
||||
g_ptr_array_free (self->dictionaries, TRUE);
|
||||
|
||||
g_iconv_close (self->ucs4_to_locale);
|
||||
}
|
||||
@ -1039,7 +862,7 @@ app_redraw_top (Application *self)
|
||||
|
||||
for (guint i = 0; i < self->dictionaries->len; i++)
|
||||
{
|
||||
Dictionary *dict = &g_array_index (self->dictionaries, Dictionary, i);
|
||||
Dictionary *dict = g_ptr_array_index (self->dictionaries, i);
|
||||
row_buffer_append (&buf, dict->name,
|
||||
APP_ATTR_IF (self->dictionaries->len > 1
|
||||
&& self->dict == dict->dict, ACTIVE, HEADER));
|
||||
@ -1514,7 +1337,7 @@ app_goto_dictionary (Application *self, guint n)
|
||||
if (n >= self->dictionaries->len)
|
||||
return FALSE;
|
||||
|
||||
Dictionary *dict = &g_array_index (self->dictionaries, Dictionary, n);
|
||||
Dictionary *dict = g_ptr_array_index (self->dictionaries, n);
|
||||
self->dict = dict->dict;
|
||||
app_search_for_entry (self);
|
||||
app_redraw_top (self);
|
||||
@ -1525,13 +1348,13 @@ app_goto_dictionary (Application *self, guint n)
|
||||
static gboolean
|
||||
app_goto_dictionary_delta (Application *self, gint n)
|
||||
{
|
||||
GArray *dicts = self->dictionaries;
|
||||
GPtrArray *dicts = self->dictionaries;
|
||||
if (dicts->len <= 1)
|
||||
return FALSE;
|
||||
|
||||
guint i = 0;
|
||||
while (i < dicts->len &&
|
||||
g_array_index (dicts, Dictionary, i).dict != self->dict)
|
||||
((Dictionary *) g_ptr_array_index (dicts, i))->dict != self->dict)
|
||||
i++;
|
||||
|
||||
return app_goto_dictionary (self, (i + dicts->len + n) % dicts->len);
|
||||
@ -1962,10 +1785,10 @@ app_process_left_mouse_click (Application *self, int line, int column)
|
||||
if (column < indent)
|
||||
return;
|
||||
|
||||
Dictionary *dicts = (Dictionary *) self->dictionaries->data;
|
||||
for (guint i = 0; i < self->dictionaries->len; i++)
|
||||
{
|
||||
if (column < (indent += dicts[i].name_width))
|
||||
AppDictionary *dict = g_ptr_array_index (self->dictionaries, i);
|
||||
if (column < (indent += dict->name_width))
|
||||
{
|
||||
app_goto_dictionary (self, i);
|
||||
return;
|
||||
|
199
src/utils.c
199
src/utils.c
@ -19,10 +19,14 @@
|
||||
#include <glib.h>
|
||||
#include <glib/gprintf.h>
|
||||
#include <gio/gio.h>
|
||||
#include <glib/gstdio.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include <pwd.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "utils.h"
|
||||
|
||||
@ -105,3 +109,198 @@ fatal (const gchar *format, ...)
|
||||
exit (EXIT_FAILURE);
|
||||
va_end (ap);
|
||||
}
|
||||
|
||||
// At times, GLib even with its sheer size is surprisingly useless,
|
||||
// and I need to port some code over from "liberty".
|
||||
|
||||
static const gchar **
|
||||
get_xdg_config_dirs (void)
|
||||
{
|
||||
GPtrArray *paths = g_ptr_array_new ();
|
||||
g_ptr_array_add (paths, (gpointer) g_get_user_config_dir ());
|
||||
for (const gchar *const *system = g_get_system_config_dirs ();
|
||||
*system; system++)
|
||||
g_ptr_array_add (paths, (gpointer) *system);
|
||||
g_ptr_array_add (paths, NULL);
|
||||
return (const gchar **) g_ptr_array_free (paths, FALSE);
|
||||
}
|
||||
|
||||
gchar *
|
||||
resolve_relative_filename_generic
|
||||
(const gchar **paths, const gchar *tail, const gchar *filename)
|
||||
{
|
||||
for (; *paths; paths++)
|
||||
{
|
||||
// As per XDG spec, relative paths are ignored
|
||||
if (**paths != '/')
|
||||
continue;
|
||||
|
||||
gchar *file = g_build_filename (*paths, tail, filename, NULL);
|
||||
GStatBuf st;
|
||||
if (!g_stat (file, &st))
|
||||
return file;
|
||||
g_free (file);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
gchar *
|
||||
resolve_relative_config_filename (const gchar *filename)
|
||||
{
|
||||
const gchar **paths = get_xdg_config_dirs ();
|
||||
gchar *result =
|
||||
resolve_relative_filename_generic (paths, PROJECT_NAME, filename);
|
||||
g_free (paths);
|
||||
return result;
|
||||
}
|
||||
|
||||
static gchar *
|
||||
try_expand_tilde (const gchar *filename)
|
||||
{
|
||||
size_t until_slash = strcspn (filename, "/");
|
||||
if (!until_slash)
|
||||
return g_build_filename (g_get_home_dir () ?: "", filename, NULL);
|
||||
|
||||
long buf_len = sysconf (_SC_GETPW_R_SIZE_MAX);
|
||||
if (buf_len < 0)
|
||||
buf_len = 1024;
|
||||
struct passwd pwd, *success = NULL;
|
||||
|
||||
gchar *user = g_strndup (filename, until_slash);
|
||||
gchar *buf = g_malloc (buf_len);
|
||||
while (getpwnam_r (user, &pwd, buf, buf_len, &success) == ERANGE)
|
||||
buf = g_realloc (buf, buf_len <<= 1);
|
||||
g_free (user);
|
||||
|
||||
gchar *result = NULL;
|
||||
if (success)
|
||||
result = g_strdup_printf ("%s%s", pwd.pw_dir, filename + until_slash);
|
||||
g_free (buf);
|
||||
return result;
|
||||
}
|
||||
|
||||
gchar *
|
||||
resolve_filename (const gchar *filename, gchar *(*relative_cb) (const char *))
|
||||
{
|
||||
// Absolute path is absolute
|
||||
if (*filename == '/')
|
||||
return g_strdup (filename);
|
||||
|
||||
// We don't want to use wordexp() for this as it may execute /bin/sh
|
||||
if (*filename == '~')
|
||||
{
|
||||
// Paths to home directories ought to be absolute
|
||||
char *expanded = try_expand_tilde (filename + 1);
|
||||
if (expanded)
|
||||
return expanded;
|
||||
g_debug ("failed to expand the home directory in `%s'", filename);
|
||||
}
|
||||
return relative_cb (filename);
|
||||
}
|
||||
|
||||
GKeyFile *
|
||||
load_project_config_file (GError **error)
|
||||
{
|
||||
GKeyFile *key_file = g_key_file_new ();
|
||||
const gchar **paths = get_xdg_config_dirs ();
|
||||
GError *e = NULL;
|
||||
|
||||
// XXX: if there are dashes in the final path component,
|
||||
// the function tries to replace them with directory separators,
|
||||
// which is completely undocumented
|
||||
g_key_file_load_from_dirs (key_file,
|
||||
PROJECT_NAME G_DIR_SEPARATOR_S PROJECT_NAME ".conf",
|
||||
paths, NULL, 0, &e);
|
||||
g_free (paths);
|
||||
if (!e)
|
||||
return key_file;
|
||||
|
||||
if (e->code == G_KEY_FILE_ERROR_NOT_FOUND)
|
||||
g_error_free (e);
|
||||
else
|
||||
g_propagate_error (error, e);
|
||||
|
||||
g_key_file_free (key_file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// --- Loading -----------------------------------------------------------------
|
||||
|
||||
void
|
||||
dictionary_destroy (Dictionary *self)
|
||||
{
|
||||
g_free (self->name);
|
||||
g_free (self->filename);
|
||||
|
||||
if (self->dict)
|
||||
g_object_unref (self->dict);
|
||||
|
||||
g_free (self);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
dictionary_load (Dictionary *self, GError **e)
|
||||
{
|
||||
if (!(self->dict = stardict_dict_new (self->filename, e)))
|
||||
return FALSE;
|
||||
|
||||
if (!self->name)
|
||||
{
|
||||
self->name = g_strdup (stardict_info_get_book_name
|
||||
(stardict_dict_get_info (self->dict)));
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
load_dictionaries_sequentially (GPtrArray *dictionaries, GError **e)
|
||||
{
|
||||
for (guint i = 0; i < dictionaries->len; i++)
|
||||
if (!dictionary_load (g_ptr_array_index (dictionaries, i), e))
|
||||
return FALSE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Parallelize dictionary loading if possible, because of collation reindexing
|
||||
#if GLIB_CHECK_VERSION (2, 36, 0)
|
||||
static void
|
||||
load_worker (gpointer data, gpointer user_data)
|
||||
{
|
||||
GError *e = NULL;
|
||||
dictionary_load (data, &e);
|
||||
if (e)
|
||||
g_async_queue_push (user_data, e);
|
||||
}
|
||||
|
||||
gboolean
|
||||
load_dictionaries (GPtrArray *dictionaries, GError **e)
|
||||
{
|
||||
GAsyncQueue *error_queue =
|
||||
g_async_queue_new_full ((GDestroyNotify) g_error_free);
|
||||
GThreadPool *pool = g_thread_pool_new (load_worker, error_queue,
|
||||
g_get_num_processors (), TRUE, NULL);
|
||||
if G_UNLIKELY (!g_thread_pool_get_num_threads (pool))
|
||||
{
|
||||
g_thread_pool_free (pool, TRUE, TRUE);
|
||||
g_async_queue_unref (error_queue);
|
||||
return load_dictionaries_sequentially (dictionaries, e);
|
||||
}
|
||||
|
||||
for (guint i = 0; i < dictionaries->len; i++)
|
||||
g_thread_pool_push (pool, g_ptr_array_index (dictionaries, i), NULL);
|
||||
g_thread_pool_free (pool, FALSE, TRUE);
|
||||
|
||||
gboolean result = TRUE;
|
||||
if ((*e = g_async_queue_try_pop (error_queue)))
|
||||
result = FALSE;
|
||||
|
||||
g_async_queue_unref (error_queue);
|
||||
return result;
|
||||
}
|
||||
#else // GLib < 2.36
|
||||
gboolean
|
||||
load_dictionaries (GPtrArray *dictionaries, GError **e)
|
||||
{
|
||||
return load_dictionaries_sequentially (dictionaries, e);
|
||||
}
|
||||
#endif // GLib < 2.36
|
||||
|
28
src/utils.h
28
src/utils.h
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* utils.h: miscellaneous utilities
|
||||
*
|
||||
* Copyright (c) 2013 - 2020, Přemysl Eric Janouch <p@janouch.name>
|
||||
* Copyright (c) 2013 - 2021, Přemysl Eric Janouch <p@janouch.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted.
|
||||
@ -19,6 +19,11 @@
|
||||
#ifndef UTILS_H
|
||||
#define UTILS_H
|
||||
|
||||
#include <glib.h>
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include "stardict.h"
|
||||
|
||||
/// After this statement, the element has been found and its index is stored
|
||||
/// in the variable "imid".
|
||||
#define BINARY_SEARCH_BEGIN(max, compare) \
|
||||
@ -43,4 +48,25 @@ gchar *stream_read_string (GDataInputStream *dis, GError **error);
|
||||
gboolean xstrtoul (unsigned long *out, const char *s, int base);
|
||||
void fatal (const gchar *format, ...) G_GNUC_PRINTF (1, 2) G_GNUC_NORETURN;
|
||||
|
||||
gchar *resolve_relative_filename_generic
|
||||
(const gchar **paths, const gchar *tail, const gchar *filename);
|
||||
gchar *resolve_relative_config_filename (const gchar *filename);
|
||||
gchar *resolve_filename
|
||||
(const gchar *filename, gchar *(*relative_cb) (const char *));
|
||||
GKeyFile *load_project_config_file (GError **error);
|
||||
|
||||
// --- Loading -----------------------------------------------------------------
|
||||
|
||||
typedef struct dictionary Dictionary;
|
||||
|
||||
struct dictionary
|
||||
{
|
||||
gchar *filename; ///< Path to the dictionary
|
||||
StardictDict *dict; ///< StarDict dictionary data
|
||||
gchar *name; ///< Name to show
|
||||
};
|
||||
|
||||
void dictionary_destroy (Dictionary *self);
|
||||
gboolean load_dictionaries (GPtrArray *dictionaries, GError **e);
|
||||
|
||||
#endif // ! UTILS_H
|
||||
|
Loading…
x
Reference in New Issue
Block a user