#include #include #include #include #include #include #include #include #include "siv.h" #define _(a) a struct App { int n_windows; GtkWidget * chooser; GPtrArray * windows; }; void app_show_warning (GtkWidget *parent_window, const gchar *secondary, const gchar *format, ...) { va_list args; char *message; GtkWidget *dialog; va_start (args, format); g_vasprintf (&message, format, args); va_end (args); dialog = gtk_message_dialog_new_with_markup ( parent_window ? GTK_WINDOW (parent_window) : NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, message); if (secondary) { gtk_message_dialog_format_secondary_markup ( GTK_MESSAGE_DIALOG (dialog), "%s", secondary); } g_free (message); gtk_window_set_title (GTK_WINDOW (dialog), APPLICATION_NAME " Warning"); gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); } void app_show_could_not_open (GtkWidget *parent_window, int n_files, gchar **files) { const char *header = NULL; GString *text; int i; if (n_files == 0) { return; } else if (n_files == 1) { header = "Could not open this file:"; } else { header = "Could not open these files:"; } text = g_string_new (header); for (i = 0; i < n_files; ++i) g_string_append_printf (text, "\n%s", files[i]); app_show_warning (NULL, NULL, "%s", text->str); g_string_free (text, TRUE); } void app_register_window (App *app, SivWindow *window) { ++app->n_windows; g_ptr_array_add (app->windows, window); } void app_unregister_window (App *app, SivWindow *window) { if (--app->n_windows == 0) gtk_main_quit (); g_ptr_array_remove (app->windows, window); } GtkWidget * app_get_open_chooser (App *app) { if (!app->chooser) { app->chooser = gtk_file_chooser_dialog_new ("Open", NULL, GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); } return app->chooser; } static gchar * encode (const char *filename) { GString *result = g_string_new (NULL); int i; for (i = 0; filename[i] != '\0'; ++i) { char c = filename[i]; if (!g_ascii_isprint (c) || c == '@' || c == '[' || c == ']') g_string_append_printf (result, "@%x", c); else g_string_append_c (result, c); } return g_string_free (result, FALSE); } static gboolean get_int (GKeyFile *keyfile, const char *group, const char *key, int *result) { GError *err = NULL; int d; d = g_key_file_get_integer (keyfile, group, key, &err); if (err) { g_error_free (err); return FALSE; } if (result) *result = d; return TRUE; } static gchar * make_filename (void) { return g_build_filename ( g_get_home_dir(), ".gnome2", "siv.metadata", NULL); } static GHashTable * load_meta_data (void) { GKeyFile *keyfile; char *filename; GHashTable *result; keyfile = g_key_file_new (); filename = make_filename(); result = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); if (g_key_file_load_from_file (keyfile, filename, G_KEY_FILE_NONE, NULL)) { gsize n_groups; char **groups = g_key_file_get_groups (keyfile, &n_groups); int i; for (i = 0; i < n_groups; ++i) { MetaData *data = g_new0 (MetaData, 1); char *group = groups[i]; int b; if (!get_int (keyfile, group, "window_x", &(data->window_x))) data->window_x = 0; if (!get_int (keyfile, group, "window_y", &(data->window_y))) data->window_y = 0; if (!get_int (keyfile, group, "window_width", &(data->window_width))) data->window_width = 0; if (!get_int (keyfile, group, "window_height", &(data->window_height))) data->window_height = 0; if (!get_int (keyfile, group, "background", &b)) data->background = BG_NONE; else data->background = b; if (data->background >= BG_LAST || data->background < BG_FIRST) data->background = BG_NONE; if (!get_int (keyfile, group, "smooth_image", &(data->smooth_image))) data->smooth_image = TRUE; if (!get_int (keyfile, group, "zoom_level", &(data->zoom_level))) data->zoom_level = 0; if (!get_int (keyfile, group, "vadj", &(data->vadj))) data->vadj = 0; if (!get_int (keyfile, group, "hadj", &(data->hadj))) data->hadj = 0; g_hash_table_insert (result, g_strdup (group), data); } g_strfreev (groups); } g_free (filename); g_key_file_free (keyfile); return result; } gboolean app_get_meta_data (App *app, const char *file, MetaData *data) { char *encoded = encode (file); GHashTable *table = load_meta_data (); MetaData *d = g_hash_table_lookup (table, encoded); gboolean result = FALSE; if (d) { if (data) *data = *d; result = TRUE; } g_free (encoded); g_hash_table_destroy (table); return result; } static void foreach (gpointer key, gpointer value, gpointer user_data) { gchar *filename = key; MetaData *meta = value; GKeyFile *key_file = user_data; g_key_file_set_integer (key_file, filename, "window_x", meta->window_x); g_key_file_set_integer (key_file, filename, "window_y", meta->window_y); g_key_file_set_integer (key_file, filename, "window_width", meta->window_width); g_key_file_set_integer (key_file, filename, "window_height", meta->window_height); g_key_file_set_integer (key_file, filename, "background", meta->background); g_key_file_set_integer (key_file, filename, "smooth_image", meta->smooth_image); g_key_file_set_integer (key_file, filename, "zoom_level", meta->zoom_level); g_key_file_set_integer (key_file, filename, "hadj", meta->hadj); g_key_file_set_integer (key_file, filename, "vadj", meta->vadj); } void app_set_meta_data (App *app, const char *filename, int window_x, int window_y, int window_width, int window_height, gboolean smooth_image, BackgroundType background, int zoom_level, int vadj, int hadj) { GKeyFile *keyfile = g_key_file_new (); char *encoded = encode (filename); GHashTable *table = load_meta_data (); MetaData *data = g_hash_table_lookup (table, encoded); char *key_filename = make_filename(); char *output; if (!data) { data = g_new0 (MetaData, 1); g_hash_table_insert (table, g_strdup (encoded), data); } data->window_x = window_x; data->window_y = window_y; data->window_width = window_width; data->window_height = window_height; data->background = background; data->smooth_image = smooth_image; data->zoom_level = zoom_level; data->hadj = hadj; data->vadj = vadj; g_hash_table_foreach (table, foreach, keyfile); output = g_key_file_to_data (keyfile, NULL, NULL); if (output) { g_file_set_contents (key_filename, output, -1, NULL); g_free (output); } g_key_file_free (keyfile); g_free (key_filename); g_free (encoded); g_hash_table_destroy (table); } static char ** process_options (int argc, char **argv) { int i; GPtrArray *result = g_ptr_array_new (); for (i = 1; i < argc; ++i) { char *option = argv[i]; char *name; if (strcmp (option, "--version") == 0 || strcmp (option, "-v") == 0) { g_print ("%s %s\n", APPLICATION_NAME, PACKAGE_VERSION); exit (1); return NULL; } /* Don't open more than 32 windows */ if (i < 32) { if (g_path_is_absolute (option)) name = g_strdup (option); else name = g_build_filename (g_get_current_dir(), option, NULL); g_ptr_array_add (result, name); } } g_ptr_array_add (result, NULL); return (char **)g_ptr_array_free (result, FALSE); } static gboolean open_file (App *app, const char *filename, GPtrArray *err_files) { SivWindow *window = NULL; GError *err = NULL; gboolean new_window = FALSE; int i; for (i = 0; i < app->windows->len; ++i) { const char *f; SivWindow *w; w = app->windows->pdata[i]; f = window_get_filename (w); if (!f) { /* empty window */ window = w; break; } if (strcmp (f, filename) == 0) { window_show (window, GDK_CURRENT_TIME); return TRUE; } } if (!window) { window = window_new (app); new_window = TRUE; } if (!window_load_file (window, filename, &err)) { if (err_files) g_ptr_array_add (err_files, g_strdup (filename)); if (new_window) window_free (window); return FALSE; } window_show (window, GDK_CURRENT_TIME); return TRUE; } static void app_begin_load (App *app) { /* When a window is created, it increases the app->n_windows counter; * when it is destroyed it decreases it. The problem is if the first * window fails to load the file, it will cause the counter to reach * zero, which will cause the application to exit. * * So set it to 1 here and decrement it after loading all the files to * prevent that from happening. */ app->n_windows++; } static void app_end_load (App *app) { app->n_windows--; } static void on_open (App *app, const char *filename) { app_begin_load (app); if (!open_file (app, filename, NULL)) { /* FIXME: show error * * However, it would be better if we could show * all the errors at once; so we need to support * asking to load many files at once */ app_show_could_not_open (NULL, 1, (char **)&filename); } app_end_load (app); } static App * app_new (int argc, char **argv) { GPtrArray *err_files = g_ptr_array_new (); char **filenames; App *app; int i; filenames = process_options (argc, argv); app = g_new0 (App, 1); app->windows = g_ptr_array_new (); app_begin_load (app); if (filenames[0]) { for (i = 0; filenames[i] != NULL; ++i) open_file (app, filenames[i], err_files); } else { window_show (window_new (app), GDK_CURRENT_TIME); } g_strfreev (filenames); app_show_could_not_open (NULL, err_files->len, (char **)err_files->pdata); g_ptr_array_foreach (err_files, (GFunc)g_free, NULL); g_ptr_array_free (err_files, TRUE); app_end_load (app); if (app->n_windows == 0) exit (1); return app; } static nul_dbus_object_t * make_object (gpointer data) { return nul_dbus_object ( "/app", data, nul_dbus_interface ( "dk.au.daimi.sandmann.siv", nul_dbus_method ( "open", (nul_dbus_function_t)on_open, /* on_open,*/ nul_dbus_parameter_in ("filename", nul_dbus_type_string()), nul_dbus_parameter_out ("result", nul_dbus_type_int32()), NULL), NULL), NULL); } static void on_sigint (int signo, gpointer data) { App *app = data; while (app->windows->len) { SivWindow *window = app->windows->pdata[0]; window_free (window); } gtk_main_quit (); } int main (int argc, char **argv) { nul_dbus_service_t *service; /* Disable gslice, since it * * - confuses valgrind * - caches too much memory * - hides memory access bugs * - is not faster than malloc * * Note that g_slice_set_config() is broken in some versions of * GLib (and 'declared internal' according to Tim), so we use the * environment variable instead. */ if (!getenv ("G_SLICE")) putenv ("G_SLICE=always_malloc"); service = nul_dbus_session_service ("dk.au.daimi.sandmann.siv", NULL); if (nul_dbus_service_start (service) /* FIXME: or if it failed due to lack of dbus daemon */) { App *app; gtk_init (&argc, &argv); if (!g_file_test (GLADE_FILE, G_FILE_TEST_EXISTS)) { app_show_warning ( NULL, "Running \"make install\" may solve this problem.", ""APPLICATION_NAME " was not compiled or installed correctly."); return FALSE; } app = app_new (argc, argv); nul_dbus_service_add_object (service, make_object (app)); nul_signal_set_handler (SIGINT, on_sigint, app, NULL); gtk_main (); } else { char **files; int i; g_print ("existing process\n"); files = process_options (argc, argv); nul_dbus_service_add_object (service, make_object (NULL)); for (i = 0; files[i] != NULL; ++i) { nul_dbus_invoke (service, "/app/dk.au.daimi.sandmann.siv.open", NULL/* callback */, NULL/* data */, files[i]); } } return 0; }