#include #include #include #include #include #include #include #include #include #include "siv.h" #define _(a) a struct App { int n_windows; GtkWidget * chooser; SivWindow **windows; GHashTable *meta_data; }; 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; app->windows = nul_array_append (app->windows, window); } 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); g_key_file_set_integer (key_file, filename, "show_toolbar", meta->show_toolbar); g_key_file_set_integer (key_file, filename, "show_status_bar", meta->show_status_bar); } static gchar * make_filename (void) { return g_build_filename ( g_get_home_dir(), ".gnome2", "siv.metadata", NULL); } static void quit (App *app) { char *key_filename = make_filename(); GKeyFile *keyfile = g_key_file_new (); char *output; g_hash_table_foreach (app->meta_data, 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); gtk_main_quit(); } void app_unregister_window (App *app, SivWindow *window) { if (--app->n_windows == 0) quit (app); app->windows = nul_array_remove_fast (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 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; if (!get_int (keyfile, group, "show_toolbar", &(data->show_toolbar))) data->show_toolbar = TRUE; if (!get_int (keyfile, group, "show_status_bar", &(data->show_status_bar))) data->show_status_bar = FALSE; 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); gboolean result = FALSE; MetaData *d = g_hash_table_lookup (app->meta_data, encoded); if (d) { if (data) *data = *d; result = TRUE; } g_free (encoded); return result; } 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, gboolean show_toolbar, gboolean show_status_bar) { char *encoded = encode (filename); MetaData *data = g_hash_table_lookup (app->meta_data, encoded); if (!data) { data = g_new0 (MetaData, 1); g_hash_table_insert (app->meta_data, 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; data->show_toolbar = show_toolbar; data->show_status_bar = show_status_bar; g_free (encoded); } #define MAX_WINDOWS 1024 static char ** process_options (int argc, char **argv) { int i; char **result = nul_array_new (char *); 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 < MAX_WINDOWS) { if (g_path_is_absolute (option)) name = g_strdup (option); else name = g_build_filename (g_get_current_dir(), option, NULL); result = nul_array_append (result, nul_canonicalize_filename (name)); g_free (name); } } return result; } static gboolean open_file (App *app, const char *startup_id, const char *filename, char ***err_files) { SivWindow *window = NULL; GError *err = NULL; int i; for (i = 0; i < nul_array_len (app->windows); ++i) { SivWindow *w = app->windows[i]; if (window_matches_file (w, filename)) { window_reload (w); window_present (w, startup_id); return TRUE; } } window = window_new (app); if (!window_load_file (window, filename, &err)) { if (err_files) *err_files = nul_array_append (*err_files, g_strdup (filename)); window_free (window); return FALSE; } window_show (window, startup_id); 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 *startup_id, const char *filename) { if (startup_id && *startup_id == 0) startup_id = NULL; app_begin_load (app); if (!open_file (app, startup_id, filename, NULL)) { /* FIXME: 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, which means * support for dbus arrays. */ app_show_could_not_open (NULL, 1, (char **)&filename); } app_end_load (app); } static App * app_new (int argc, char **argv) { char **err_files = nul_array_new (char *); char **filenames; App *app; int i; filenames = process_options (argc, argv); app = g_new0 (App, 1); app->windows = nul_array_new (SivWindow *); app->meta_data = load_meta_data (); app_begin_load (app); if (filenames[0]) { for (i = 0; filenames[i] != NULL; ++i) open_file (app, NULL, filenames[i], &err_files); } else { window_show (window_new (app), NULL); } nul_array_free (filenames); app_show_could_not_open (NULL, nul_array_len (err_files), err_files); for (i = 0; err_files[i] != NULL; ++i) g_free (err_files[i]); nul_array_free (err_files); app_end_load (app); if (app->n_windows == 0) exit (1); return app; } static nul_dbus_service_t * make_service (void) { return nul_dbus_session_service ( "dk.au.daimi.sandmann.siv", nul_dbus_object ( "/app", NULL, nul_dbus_interface ( "dk.au.daimi.sandmann.siv", nul_dbus_method ( "open", (nul_dbus_function_t)on_open, /* on_open,*/ nul_dbus_parameter_in ("startup_id", nul_dbus_type_string()), nul_dbus_parameter_in ("filename", nul_dbus_type_string()), nul_dbus_parameter_out ("result", nul_dbus_type_int32()), NULL), NULL), NULL), NULL); } static void on_sigint (int signo, gpointer data) { App *app = data; while (nul_array_len (app->windows)) { SivWindow *window = *app->windows; window_free (window); } quit (app); } 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 = make_service (); 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_set_object_data (service, "/app", app); nul_signal_set_handler (SIGINT, on_sigint, app, NULL); gtk_main (); } else { const char *startup_id = g_getenv ("DESKTOP_STARTUP_ID"); char **files; int i; if (startup_id) gtk_init (&argc, &argv); files = process_options (argc, argv); for (i = 0; files[i] != NULL; ++i) { nul_dbus_invoke (service, "/app/dk.au.daimi.sandmann.siv.open", NULL/* callback */, NULL/* data */, startup_id? startup_id : "", files[i]); } } return 0; }