/* TODO: * forward http errors to client * don't save thumbnail to file */ #include "mozilla-config.h" #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Components.h" #include "Embed.h" #include "Listener.h" #include "Prefs.h" #include "Writer.h" #define DEFAULT_WIDTH 1024 #define HEIGHT 64 #define DEFAULT_THUMBNAIL_SIZE 256 #if 0 #define LOG g_print #else #define LOG // #endif // #define DEBUG_SHOW_WINDOW typedef enum { STATE_CLEAN, STATE_END, STATE_ERROR, STATE_NEXT, STATE_WAIT, STATE_WORK, } StateType; static const char STATES[][6] = { "CLEAN", "END", "ERROR", "NEXT", "WAIT", "WORK" }; typedef struct { char *url; int width; int thumb_size; SoupMessage *msg; } RequestData; static StateType state; static void state_change (StateType); static Embed *gEmbed; static GtkWidget *window; /* --- */ static guint timeout_id = 0; static GQueue *queue = NULL; static RequestData *current_request = NULL; static void request_free (RequestData *data) { g_object_unref (data->msg); g_free (data->url); g_free (data); } static void take_picture (Embed *embed) { GtkMozEmbed *mozembed = GTK_MOZ_EMBED (embed); gboolean success = FALSE; Writer *writer = nsnull; LOG ("Writing thumbnail\n"); g_assert (current_request); g_assert (current_request->msg); /* TODO: add support for passing fd, or writing to memory */ writer = new ThumbnailWriter (mozembed, "TODO.png", current_request->thumb_size); if (writer) { success = writer->Write(); if (!success) { g_warning ("Failed to write"); soup_message_set_status (current_request->msg, SOUP_STATUS_INTERNAL_SERVER_ERROR); soup_message_io_unpause (current_request->msg); } delete writer; soup_message_add_header (current_request->msg->response_headers, "Content-Type", "image/png"); g_file_get_contents ("TODO.png", ¤t_request->msg->response.body, ¤t_request->msg->response.length, NULL); current_request->msg->response.owner = SOUP_BUFFER_SYSTEM_OWNED; soup_message_set_status (current_request->msg, SOUP_STATUS_OK); soup_message_io_unpause (current_request->msg); state_change (STATE_CLEAN); } else { soup_message_set_status (current_request->msg, SOUP_STATUS_INTERNAL_SERVER_ERROR); soup_message_io_unpause (current_request->msg); state_change (STATE_CLEAN); } } static void embed_ready_cb (Embed *embed) { LOG ("ready\n"); if (timeout_id) { g_source_remove (timeout_id); timeout_id = 0; } if (state == STATE_CLEAN) { state_change (STATE_NEXT); } else { state_change (STATE_WORK); } } static gboolean timeout_cb (void) { LOG ("Timed out\n"); timeout_id = 0; #if 0 /* TODO */ if (force) { g_print ("Load timed out; consider increasing the timeout (use 0 for no timeout)\n"); state_change (STATE_WORK); } else { g_print ("Load timed out; consider using --force or increasing the timeout (use 0 for no timeout)\n"); state_change (STATE_CLEAN); } #endif soup_message_set_status (current_request->msg, SOUP_STATUS_REQUEST_TIMEOUT); soup_message_io_unpause (current_request->msg); state_change (STATE_CLEAN); return FALSE; } static gboolean state_dispatch (void) { LOG ("Dispatch state %s\n", STATES[state]); switch (state) { case STATE_NEXT: if (current_request) { /* We are already processing, we're done */ break; } current_request = (RequestData*) g_queue_pop_head (queue); if (current_request) { GtkAllocation alloc = { 0, 0, current_request->width, HEIGHT }; LOG ("Now processing %s\n", current_request->url); state = STATE_WAIT; g_assert (timeout_id == 0); timeout_id = g_timeout_add (10 * 1000, (GSourceFunc) timeout_cb, NULL); gtk_widget_size_allocate (GTK_WIDGET (gEmbed), &alloc); embed_load (gEmbed, current_request->url); } else { state_change (STATE_WAIT); } break; case STATE_WORK: take_picture (gEmbed); break; case STATE_CLEAN: if (current_request) { request_free (current_request); current_request = NULL; } embed_load (gEmbed, "about:blank"); break; case STATE_WAIT: break; case STATE_END: case STATE_ERROR: gtk_main_quit (); break; default: g_assert_not_reached (); } /* don't run again */ return FALSE; } static void state_change (StateType new_state) { LOG ("state_change old-state %s new-state %s\n", STATES[state], STATES[new_state]); state = new_state; g_idle_add ((GSourceFunc) state_dispatch, NULL); } static nsresult gecko_startup (void) { /* BUG ALERT! If we don't have a profile, Gecko will crash on https sites and * when trying to open the password manager. The prefs will be set up so that * no cookies or passwords etc. will be persisted. */ gtk_moz_embed_set_profile_path (g_get_user_config_dir (), "gnome-web-photo"); #ifdef HAVE_GECKO_1_9 gtk_moz_embed_set_path (GECKO_HOME); #else gtk_moz_embed_set_comp_path (GECKO_HOME); #endif /* Fire up the beast! */ gtk_moz_embed_push_startup (); if (!RegisterComponents ()) { g_warning ("Cannot register components"); return NS_ERROR_FAILURE; } if (!InitPrefs ()) { g_warning ("Cannot initialise preferences"); return NS_ERROR_FAILURE; } nsresult rv = NS_OK; /* This prevents us from printing all pages, so only do it for photo/thumbnail */ nsCOMPtr sheetService (do_GetService ("@mozilla.org/content/style-sheet-service;1", &rv)); NS_ENSURE_SUCCESS (rv, rv); nsCOMPtr styleFile; rv = NS_NewNativeLocalFile(nsDependentCString(SHARE_DIR "/style.css"), PR_TRUE, getter_AddRefs(styleFile)); NS_ENSURE_SUCCESS (rv, rv); nsCOMPtr file (do_QueryInterface (styleFile, &rv)); NS_ENSURE_SUCCESS (rv, rv); nsCOMPtr styleURI; rv = NS_NewFileURI (getter_AddRefs (styleURI), styleFile); NS_ENSURE_SUCCESS (rv, rv); rv = sheetService->LoadAndRegisterSheet (styleURI, nsIStyleSheetService::AGENT_SHEET); NS_ENSURE_SUCCESS (rv, rv); return rv; } static void gecko_shutdown (void) { gtk_moz_embed_pop_startup (); } /* --- */ static GHashTable * query_to_hash (const SoupUri *uri) { char **fragments, **i; GHashTable *hash; g_assert (uri); /* Freeing the key will also free the value, because they are the same chunk really */ hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); if (uri->query == NULL) return hash; fragments = g_strsplit (uri->query, "&", 0); for (i = fragments; *i; i++) { char *s; s = strchr (*i, '='); if (s) { *s++ = '\0'; /* Now *i points to the name, and s points to the argument */ soup_uri_decode (*i); soup_uri_decode (s); g_hash_table_insert (hash, *i, s); } else { g_free (*i); } } g_free (fragments); return hash; } static int get_integer (GHashTable *hash, const char *key, const int def) { const char *value; value = (const char*)g_hash_table_lookup (hash, key); return value ? atoi (value) : def; } static void server_callback (SoupServerContext *context, SoupMessage *msg, gpointer user_data) { GHashTable *hash; RequestData *data; LOG ("server_callback %s %s\n", msg->method, soup_uri_to_string (soup_message_get_uri (msg), FALSE)); if (soup_method_get_id (msg->method) != SOUP_METHOD_ID_GET) { soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED); return; } hash = query_to_hash (soup_message_get_uri (msg)); /* Sanity check arguments */ if (!g_hash_table_lookup (hash, "url")) { static char error[] = "No 'url' argument specified"; g_hash_table_destroy (hash); soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST); soup_message_set_response (msg, "text/plain", SOUP_BUFFER_STATIC, error, strlen (error)); return; } soup_message_io_pause (msg); data = g_new0 (RequestData, 1); data->msg = (SoupMessage*)g_object_ref (msg); /* TODO: very lax */ data->url = g_strdup ((char*)g_hash_table_lookup (hash, "url")); data->width = get_integer (hash, "width", DEFAULT_WIDTH); data->thumb_size = get_integer (hash, "thumb_size", DEFAULT_THUMBNAIL_SIZE); g_hash_table_destroy (hash); g_queue_push_tail (queue, data); if (state == STATE_WAIT) state_change (STATE_NEXT); } /* --- */ int main (int argc, char **argv) { GdkScreen *screen; GOptionContext *context; SoupServer *server; GError *error = NULL; int i, len; nsresult rv; #ifdef ENABLE_NLS /* Initialize the i18n stuff */ bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); textdomain (GETTEXT_PACKAGE); #endif /* Have to initialise threads before calling any glib function */ g_thread_init (NULL); /* Now parse the arguments */ context = g_option_context_new (NULL); #if GLIB_CHECK_VERSION (2, 12, 0) g_option_context_set_translation_domain (context, GETTEXT_PACKAGE); #endif //g_option_context_add_main_entries (context, main_options, GETTEXT_PACKAGE); g_option_context_add_group (context, gtk_get_option_group (TRUE)); if (!g_option_context_parse (context, &argc, &argv, &error)) { g_print ("%s\n", error->message); g_error_free (error); g_option_context_free (context); return 1; } g_option_context_free (context); /* Initialised gecko */ rv = gecko_startup (); if (NS_FAILED (rv)) { g_print ("Failed to initialise gecko (rv = %x)!\n", rv); return 1; }; queue = g_queue_new (); server = soup_server_new (SOUP_SERVER_PORT, 8080, NULL); if (!server) g_error ("Cannot start server"); soup_server_add_handler (server, NULL, NULL, server_callback, NULL, NULL); soup_server_run_async (server); /* Create window */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_role (GTK_WINDOW (window), "gnome-web-photo-hidden-window"); gtk_window_set_skip_taskbar_hint (GTK_WINDOW (window), TRUE); gtk_window_set_skip_pager_hint (GTK_WINDOW (window), TRUE); gtk_window_set_focus_on_map (GTK_WINDOW (window), FALSE); gEmbed = EMBED (g_object_new (TYPE_EMBED, NULL)); g_signal_connect (gEmbed, "ready", G_CALLBACK (embed_ready_cb), NULL); gtk_widget_set_size_request (GTK_WIDGET (gEmbed), DEFAULT_WIDTH, HEIGHT); gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (gEmbed)); gtk_widget_show_all (window); state_change (STATE_WAIT); gtk_main (); g_assert (state == STATE_END || state == STATE_ERROR); gtk_widget_destroy (window); gecko_shutdown (); return state == STATE_ERROR ? 1 : 0; }