2009-12-04 Jeffrey Stedfast <fejj@novell.com>
[moon.git] / src / pipeline-ui.cpp
blob240e02028680c820bbb788f2deb77de9d315977a
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * pipeline-ui.cpp:
5 * Contact:
6 * Moonlight List (moonlight-list@lists.ximian.com)
8 * Copyright 2007 Novell, Inc. (http://www.novell.com)
10 * See the LICENSE file included with the distribution for details.
13 #include <config.h>
15 #include <glib/gstdio.h>
16 #include <fcntl.h>
17 #include <errno.h>
18 #include <stdlib.h>
20 #include <gdk-pixbuf/gdk-pixbuf.h>
22 #define MONO_HEADERS_INCLUDED 1
24 #include <mono/jit/jit.h>
25 #include <mono/metadata/appdomain.h>
26 #include <mono/metadata/assembly.h>
28 #include "codec-version.h"
29 #include "pipeline-ui.h"
30 #include "downloader.h"
31 #include "utils.h"
32 #include "pipeline.h"
33 #include "debug.h"
34 #include "codec-url.h"
36 #define EULA_URL "http://go.microsoft.com/fwlink/?LinkId=149579"
38 bool CodecDownloader::running = false;
40 CodecDownloader::CodecDownloader (Surface *surf, bool is_user_initiated)
42 this->is_user_initiated = is_user_initiated;
43 surface = surf;
44 eula = NULL;
45 state = 0;
46 dl = NULL;
47 dialog = NULL;
48 vbox = NULL;
49 header_label = NULL;
50 message_label = NULL;
51 progress_bar = NULL;
52 eula_scrollwindow = NULL;
53 eula_view = NULL;
54 accept_button = NULL;
55 cancel_button = NULL;
56 icon = NULL;
57 dont_ask = NULL;
60 CodecDownloader::~CodecDownloader ()
62 g_free (eula);
63 if (dl != NULL) {
64 dl->unref ();
66 running = false;
69 void
70 CodecDownloader::ShowUI (Surface *surface, bool is_user_initiated)
72 g_return_if_fail (surface != NULL);
74 if (running) {
75 return;
78 if (!(moonlight_flags & RUNTIME_INIT_ENABLE_MS_CODECS))
79 return;
81 surface->SetCurrentDeployment ();
82 CodecDownloader *cd = new CodecDownloader (surface, is_user_initiated);
83 cd->Show ();
84 cd->unref ();
87 // ----- Event Proxies -----
89 void
90 CodecDownloader::ResponseEventHandler (GtkDialog *dialog, gint response, gpointer data)
92 ((CodecDownloader *) data)->ResponseEvent (dialog, (GtkResponseType)response);
95 void
96 CodecDownloader::DownloadFailedHandler (EventObject *sender, EventArgs *args, gpointer closure)
98 ((CodecDownloader *) closure)->DownloadFailed (sender, args);
101 void
102 CodecDownloader::DownloadCompletedHandler (EventObject *sender, EventArgs *args, gpointer closure)
104 ((CodecDownloader *) closure)->DownloadCompleted (sender, args);
107 void
108 CodecDownloader::DownloadProgressChangedHandler (EventObject *sender, EventArgs *args, gpointer closure)
110 ((CodecDownloader *) closure)->DownloadProgressChanged (sender, args);
113 // ----- Event Handlers -----
115 void
116 CodecDownloader::ResponseEvent (GtkDialog *dialog, GtkResponseType response)
118 LOG_UI ("CodecDownloader::ResponseEvent (%d)\n", response);
119 SetCurrentDeployment ();
121 switch (response) {
122 case GTK_RESPONSE_CANCEL:
123 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dont_ask))) {
124 LOG_UI ("Setting DontInstallMSCodecs\n");
125 configuration.SetBooleanValue ("Codecs", "DontInstallMSCodecs", true);
126 configuration.Save ();
129 state = 5;
130 Close ();
131 return;
132 case GTK_RESPONSE_DELETE_EVENT:
133 Close ();
134 return;
135 case GTK_RESPONSE_OK:
136 AcceptClicked ();
137 return;
138 default:
139 return;
143 void
144 CodecDownloader::DownloadProgressChanged (EventObject *sender, EventArgs *args)
146 g_return_if_fail (dl != NULL);
147 double progress = dl->GetDownloadProgress ();
148 LOG_UI ("CodecDownloader::DownloadProgressChanged (): %.2f\n", progress);
149 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (progress_bar), progress);
152 void
153 CodecDownloader::DownloadFailed (EventObject *sender, EventArgs *args)
155 ErrorEventArgs *eea = (ErrorEventArgs *) args;
156 gchar *msg;
158 LOG_UI ("CodecDownloader::DownloadFailed ()\n");
160 msg = g_strdup_printf ("An error occurred while downloading the %s", state == 1
161 ? "End User License Agreement."
162 : "add-on software.");
164 SetHeader ((const gchar *)msg);
165 SetMessage (eea->GetErrorMessage());
167 ToggleProgress (false);
169 gtk_image_set_from_stock (GTK_IMAGE (icon), GTK_STOCK_DIALOG_ERROR, GTK_ICON_SIZE_DIALOG);
170 gtk_button_set_label (GTK_BUTTON (accept_button), GTK_STOCK_CLOSE);
171 gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, true);
172 gtk_widget_hide (cancel_button);
174 g_free (msg);
176 state = 6;
179 bool
180 CodecDownloader::VerifyDownload (const char *filename)
182 static MonoMethod *moon_codec_integrity = NULL;
184 if (!moon_codec_integrity) {
185 MonoAssembly *sw = mono_assembly_load_with_partial_name ("System.Windows, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e", NULL);
186 if (!sw)
187 return false;
189 MonoImage *image = mono_assembly_get_image (sw);
190 if (!image)
191 return false;
193 MonoClass *klass = mono_class_from_name (image, "Mono", "Helper");
194 if (!klass)
195 return false;
197 moon_codec_integrity = mono_class_get_method_from_name (klass, "CheckFileIntegrity", 1);
198 if (!moon_codec_integrity)
199 return false;
202 void *params [1];
203 params [0] = mono_string_new (mono_domain_get (), filename);
204 MonoObject *exc = NULL;
206 MonoObject *ret = mono_runtime_invoke (moon_codec_integrity, NULL, params, &exc);
207 if (exc)
208 return false;
210 return (bool) (*(MonoBoolean *) mono_object_unbox (ret));
213 void
214 CodecDownloader::DownloadCompleted (EventObject *sender, EventArgs *args)
216 gchar *downloaded_file = NULL;
217 gchar *codec_path = NULL;
218 gchar *codec_dir = NULL;
219 int codec_fd = 0;
220 gint64 size;
222 LOG_UI ("CodecDownloader::DownloadCompleted ()\n");
224 ToggleProgress (false);
226 switch (state) {
227 case 1: // downloading eula, we're now finished downloading the eula
228 eula = dl->GetResponseText (NULL, &size);
230 SetHeader ("End User License Agreement");
231 SetMessage ("Before the required software can be installed, you must first agree "
232 "to the End User License Agreement below.");
233 ToggleEula (true);
235 gtk_button_set_label (GTK_BUTTON (accept_button), "_Accept");
236 gtk_label_set_markup (GTK_LABEL (eula_view), eula);
238 gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, true);
240 state = 2;
241 break;
242 case 3: // downloading codec, we're now finished downloading the codec
243 codec_path = g_build_filename (g_get_home_dir (), ".mozilla", "plugins", "moonlight", CODEC_LIBRARY_NAME, NULL);
244 codec_dir = g_path_get_dirname (codec_path);
246 downloaded_file = dl->GetDownloadedFilename (NULL);
248 errno = 0;
250 if (!VerifyDownload (downloaded_file)) {
251 SetHeader ("An error occurred when installing the software");
252 SetMessage ("We could not verify the downloaded binary. Please try again later.");
253 } else if (g_mkdir_with_parents (codec_dir, 0700) == -1 ||
254 (codec_fd = g_open (codec_path, O_CREAT | O_TRUNC | O_WRONLY, 0700)) == -1 ||
255 CopyFileTo (downloaded_file, codec_fd) == -1) {
256 SetHeader ("An error occurred when installing the software");
257 SetMessage (strerror (errno));
258 } else {
259 SetHeader ("Software successfully downloaded and installed!");
260 SetMessage ("Please refresh the web page you were viewing to allow the new software to take effect.");
262 Media::RegisterMSCodecs ();
265 g_free (codec_path);
266 g_free (codec_dir);
267 g_free (downloaded_file);
269 gtk_widget_hide (cancel_button);
270 gtk_button_set_label (GTK_BUTTON (accept_button), GTK_STOCK_CLOSE);
271 gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, true);
273 state = 4;
274 break;
275 default:
276 printf ("CodecDownloader::DownloadCompleted (): Invalid state: %i\n", state);
277 break;
281 void
282 CodecDownloader::AcceptClicked ()
284 LOG_UI ("CodecDownloader::AcceptClicked\n");
286 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (progress_bar), 0.0);
287 ToggleProgress (true);
289 CreateDownloader ();
291 switch (state) {
292 case 0: // initial, waiting for user input
293 g_return_if_fail (dl != NULL);
294 SetHeader ("Downloading license agreement...");
295 HideMessage ();
296 gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, false);
298 dl->Open ("GET", EULA_URL, NoPolicy);
299 dl->Send ();
301 state = 1;
302 break;
303 case 2: // eula downloaded, waiting for user input
304 g_return_if_fail (dl != NULL);
305 char *env_url;
306 SetHeader ("Downloading the required software...");
307 HideMessage ();
308 ToggleEula (false);
309 gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, false);
311 env_url = getenv ("MOONLIGHT_CODEC_URL");
312 if (env_url != NULL)
313 dl->Open ("GET", env_url, NoPolicy);
314 else
315 dl->Open ("GET", CODEC_URL, NoPolicy);
317 dl->Send ();
319 state = 3;
320 break;
321 case 4:
322 case 6:
323 Close ();
324 break;
325 default:
326 printf ("CodecDownloader::AcceptClicked (): Invalid state: %i\n", state);
327 break;
331 // ----- Utilities -----
333 void
334 CodecDownloader::CreateDownloader ()
336 if (dl == NULL) {
337 dl = surface->CreateDownloader ();
338 // since we put up a UI, this might happen if the user has navigated to another page before dismissing the UI
339 // (the surface would be zombified).
340 g_return_if_fail (dl != NULL);
341 dl->AddHandler (Downloader::DownloadProgressChangedEvent, DownloadProgressChangedHandler, this);
342 dl->AddHandler (Downloader::DownloadFailedEvent, DownloadFailedHandler, this);
343 dl->AddHandler (Downloader::CompletedEvent, DownloadCompletedHandler, this);
347 void
348 CodecDownloader::DestroyDownloader ()
350 if (dl != NULL) {
351 dl->RemoveHandler (Downloader::DownloadProgressChangedEvent, DownloadProgressChangedHandler, this);
352 dl->RemoveHandler (Downloader::DownloadFailedEvent, DownloadFailedHandler, this);
353 dl->RemoveHandler (Downloader::CompletedEvent, DownloadCompletedHandler, this);
354 dl->unref ();
355 dl = NULL;
359 void
360 CodecDownloader::SetHeader (const gchar *message)
362 gchar *message_full = g_strdup_printf ("<big><b>%s</b></big>", message);
363 gtk_label_set_markup (GTK_LABEL (header_label), message_full);
364 g_free (message_full);
367 void
368 CodecDownloader::SetMessage (const gchar *message)
370 gtk_label_set_text (GTK_LABEL (message_label), message);
371 gtk_widget_show (message_label);
374 void
375 CodecDownloader::HideMessage ()
377 gtk_widget_hide (message_label);
380 void
381 CodecDownloader::ToggleEula (bool show)
383 if (show) {
384 gtk_object_set (GTK_OBJECT (dialog), "resizable", true, NULL);
385 gtk_widget_show_all (eula_scrollwindow);
386 } else {
387 gtk_object_set (GTK_OBJECT (dialog), "resizable", false, NULL);
388 gtk_widget_hide (eula_scrollwindow);
392 void
393 CodecDownloader::ToggleProgress (bool show)
395 if (show) {
396 gtk_image_set_from_stock (GTK_IMAGE (icon), GTK_STOCK_SAVE, GTK_ICON_SIZE_DIALOG);
397 gtk_widget_hide (dont_ask);
398 gtk_widget_show_all (progress_bar);
399 } else {
400 gtk_image_set_from_stock (GTK_IMAGE (icon), GTK_STOCK_DIALOG_QUESTION, GTK_ICON_SIZE_DIALOG);
401 gtk_widget_hide (progress_bar);
405 void
406 CodecDownloader::AdaptToParentWindow ()
408 // try to find a parent for our window
409 // there must be a better way of doing this though :|
410 GList *toplevels = gtk_window_list_toplevels ();
411 GList *current = toplevels;
412 GtkWindow *parent = NULL;
414 while (current != NULL) {
415 const char *title = gtk_window_get_title (GTK_WINDOW (current->data));
416 if (title != NULL && strstr (title, "Mozilla Firefox") != NULL) {
417 parent = GTK_WINDOW (current->data);
418 break;
421 current = current->next;
423 g_list_free (toplevels);
425 if (parent != NULL) {
426 gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);
427 gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER_ON_PARENT);
428 } else {
429 // If no parent could be found, just center in the screen
430 gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
434 // ----- Dialog Create/Destroy -----
436 void
437 CodecDownloader::Show ()
439 if (!is_user_initiated && configuration.GetBooleanValue ("Codecs", "DontInstallMSCodecs")) {
440 state = 5;
441 return;
444 if (state != 0) {
445 fprintf (stderr, "CodecDownloader::Show (): Can't call Show more than once.\n");
446 state = 6;
447 return;
450 gint label_width = 400;
451 GdkColor white = {0, 65535, 65535, 65535};
453 // Build HIG Dialog Box
454 dialog = gtk_dialog_new_with_buttons ("Moonlight Codecs Installer", NULL, (GtkDialogFlags)
455 (GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR), NULL);
456 cancel_button = gtk_dialog_add_button (GTK_DIALOG (dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
457 accept_button = gtk_dialog_add_button (GTK_DIALOG (dialog), "_Install Codecs", GTK_RESPONSE_OK);
458 gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
460 AdaptToParentWindow ();
461 gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);
462 gtk_object_set (GTK_OBJECT (dialog), "resizable", false, NULL);
464 // HIG HBox
465 GtkWidget *hbox = gtk_hbox_new (false, 12);
466 gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);
467 gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), hbox, true, true, 0);
469 // Message box icon
470 icon = gtk_image_new_from_stock (GTK_STOCK_DIALOG_QUESTION, GTK_ICON_SIZE_DIALOG);
471 gtk_misc_set_alignment (GTK_MISC (icon), 0.5f, 0.0f);
472 gtk_box_pack_start (GTK_BOX (hbox), icon, false, false, 0);
474 // Contents container
475 vbox = gtk_vbox_new (false, 0);
476 gtk_box_set_spacing (GTK_BOX (vbox), 10);
477 gtk_box_pack_start (GTK_BOX (hbox), vbox, true, true, 0);
479 // Header Label
480 header_label = gtk_label_new (NULL);
481 SetHeader ("Would you like to install the required add-on to play the content on this page?");
482 gtk_label_set_line_wrap (GTK_LABEL (header_label), true);
483 gtk_label_set_justify (GTK_LABEL (header_label), GTK_JUSTIFY_LEFT);
484 gtk_misc_set_alignment (GTK_MISC (header_label), 0.0f, 0.5f);
485 gtk_widget_set_size_request (header_label, label_width, -1);
486 gtk_box_pack_start (GTK_BOX (vbox), header_label, false, false, 0);
488 // Secondary Label
489 message_label = gtk_label_new (NULL);
490 SetMessage ("This page requires the Microsoft Media Pack "
491 "to be installed to play multimedia content.\n\n"
492 "If you choose to install it, the software will be "
493 "automatically downloaded and installed "
494 "from Microsoft's web site.");
495 gtk_label_set_line_wrap (GTK_LABEL (message_label), true);
496 gtk_label_set_justify (GTK_LABEL (message_label), GTK_JUSTIFY_LEFT);
497 gtk_misc_set_alignment (GTK_MISC (message_label), 0.0f, 0.5f);
498 gtk_widget_set_size_request (message_label, label_width, -1);
499 gtk_box_pack_start (GTK_BOX (vbox), message_label, false, false, 0);
501 dont_ask = gtk_check_button_new_with_label ("Do not ask me to install this add-on again");
502 gtk_box_pack_start (GTK_BOX (vbox), dont_ask, false, false, 0);
504 // Other elements
505 progress_bar = gtk_progress_bar_new ();
506 gtk_box_pack_start (GTK_BOX (vbox), progress_bar, false, false, 0);
508 // EULA
509 eula_view = gtk_label_new (NULL);
510 gtk_label_set_selectable (GTK_LABEL (eula_view), TRUE);
511 gtk_label_set_line_wrap (GTK_LABEL (eula_view), TRUE);
513 eula_scrollwindow = gtk_scrolled_window_new (NULL, NULL);
514 eula_evtbox = gtk_event_box_new ();
516 gtk_widget_modify_bg (GTK_WIDGET (eula_evtbox), GTK_STATE_NORMAL, &white);
517 gtk_container_add (GTK_CONTAINER (eula_evtbox), eula_view);
519 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (eula_scrollwindow), GTK_SHADOW_IN);
520 gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (eula_scrollwindow), eula_evtbox);
521 gtk_widget_set_size_request (eula_scrollwindow, -1, 225);
522 gtk_box_pack_end (GTK_BOX (vbox), eula_scrollwindow, true, true, 0);
524 // Connect and go
525 g_signal_connect (G_OBJECT (dialog), "response", G_CALLBACK (ResponseEventHandler), this);
527 gtk_object_set (GTK_OBJECT (accept_button), "has-focus", true, "has-default", true, NULL);
529 gtk_widget_show_all (dialog);
530 ToggleProgress (false);
531 ToggleEula (false);
533 ref (); // We manage our lifetime ourself
534 running = true;
537 void
538 CodecDownloader::Close ()
540 LOG_UI ("CodecDownloader::Close ()\n");
542 if (dl != NULL) {
543 dl->Abort ();
544 DestroyDownloader ();
547 gtk_widget_destroy (dialog);
548 unref ();
549 running = false;