1 // Copyright 2005-8 Ben Hutchings <ben@decadent.org.uk>.
2 // See the file "COPYING" for licence details.
18 #include <gdk/gdkkeysyms.h>
19 #include <gdkmm/pixbuf.h>
20 #include <glibmm/convert.h>
21 #include <glibmm/spawn.h>
22 #include <gtkmm/main.h>
23 #include <gtkmm/window.h>
25 #include <ImageErrors.h>
26 #if MOZ_VERSION_MAJOR == 1 && MOZ_VERSION_MINOR == 9
27 #include <nsWeakPtr.h>
28 /* For some reason <nsWeakPtr.h> no longer defines this */
29 typedef nsCOMPtr<nsIWeakReference> nsWeakPtr;
31 #include <nsGUIEvent.h>
32 #include <nsIBoxObject.h>
33 #include <nsIContent.h>
34 #include <nsIDocShell.h>
35 #include <nsIDOMAbstractView.h>
36 #include <nsIDOMBarProp.h>
37 #include <nsIDOMDocumentEvent.h>
38 #include <nsIDOMDocumentView.h>
39 #include <nsIDOMElement.h>
40 #include <nsIDOMEventTarget.h>
41 #include <nsIDOMHTMLDocument.h>
42 #include <nsIDOMMouseEvent.h>
43 #include <nsIDOMNSDocument.h>
44 #include <nsIDOMWindow.h>
45 #include <nsIEventStateManager.h>
46 #include <nsIInterfaceRequestorUtils.h>
47 #include <nsIURI.h> // required before nsILink.h
49 #include <nsIPrefBranch.h>
50 #include <nsIPrefService.h>
51 #include <nsIPresShell.h>
52 #include <nsServiceManagerUtils.h>
53 #include <nsIWebBrowser.h>
54 #ifdef MOZILLA_INTERNAL_API
57 #include <nsStringAPI.h>
60 #include "browser_widget.hpp"
61 #include "child_iterator.hpp"
63 #include "event_state_manager.hpp"
64 #include "generate_dvd.hpp"
65 #include "geometry.hpp"
66 #include "link_iterator.hpp"
67 #include "null_prompt_service.hpp"
68 #include "pixbufs.hpp"
69 #include "style_sheets.hpp"
70 #include "temp_file.hpp"
72 #include "warp_pointer.hpp"
73 #include "x_frame_buffer.hpp"
74 #include "xml_utils.hpp"
75 #include "xpcom_support.hpp"
77 using xpcom_support::check;
81 rectangle get_elem_rect(nsIDOMNSDocument * ns_doc,
86 // Start with this element's bounding box
87 nsCOMPtr<nsIBoxObject> box;
88 check(ns_doc->GetBoxObjectFor(elem, getter_AddRefs(box)));
90 check(box->GetScreenX(&result.left));
91 check(box->GetScreenY(&result.top));
92 check(box->GetWidth(&width));
93 check(box->GetHeight(&height));
94 result.right = result.left + width;
95 result.bottom = result.top + height;
97 // Merge bounding boxes of all child elements
98 for (child_iterator it = child_iterator(elem), end; it != end; ++it)
100 nsCOMPtr<nsIDOMNode> child_node(*it);
102 if (check(child_node->GetNodeType(&child_type)),
103 child_type == nsIDOMNode::ELEMENT_NODE)
105 nsCOMPtr<nsIDOMElement> child_elem(
106 do_QueryInterface(child_node));
107 result |= get_elem_rect(ns_doc, child_elem);
118 video_format_mpeg2_ps,
119 video_format_vob_list
122 video_format video_format_from_uri(const std::string & uri)
124 // FIXME: This is a bit of a hack. Perhaps we could decide
125 // later based on the MIME type determined by Mozilla?
127 const char * extension;
129 } const mapping[] = {
130 {".vob", video_format_mpeg2_ps},
131 {".mpeg", video_format_mpeg2_ps},
132 {".mpeg2", video_format_mpeg2_ps},
133 {".mpg", video_format_mpeg2_ps},
134 {".voblist", video_format_vob_list}
136 for (std::size_t i = 0;
137 i != sizeof(mapping) / sizeof(mapping[0]);
140 std::size_t ext_len = std::strlen(mapping[i].extension);
141 if (uri.size() > ext_len
142 && uri.compare(uri.size() - ext_len, ext_len,
143 mapping[i].extension) == 0)
144 return mapping[i].format;
146 return video_format_none;
150 class base_window : public Gtk::Window
153 base_window(const video::frame_params & frame_params);
156 video::frame_params frame_params_;
157 browser_widget browser_widget_;
160 base_window::base_window(const video::frame_params & frame_params)
161 : frame_params_(frame_params)
163 set_size_request(frame_params_.width, frame_params_.height);
164 set_resizable(false);
166 add(browser_widget_);
167 browser_widget_.show();
170 class preview_window : public base_window
173 preview_window(const video::frame_params & frame_params,
174 const std::string & main_page_uri);
178 bool on_key_press(GdkEventKey *);
180 std::string main_page_uri_;
183 preview_window::preview_window(const video::frame_params & frame_params,
184 const std::string & main_page_uri)
185 : base_window(frame_params),
186 main_page_uri_(main_page_uri)
188 Glib::signal_idle().connect(
189 sigc::mem_fun(this, &preview_window::on_idle));
190 signal_key_press_event().connect(
191 sigc::mem_fun(this, &preview_window::on_key_press));
194 bool preview_window::on_idle()
196 browser_widget_.load_uri(main_page_uri_);
197 return false; // don't call again
200 bool preview_window::on_key_press(GdkEventKey * event)
202 switch (event->keyval)
204 case GDK_t: // = top menu
205 browser_widget_.load_uri(main_page_uri_);
207 case GDK_q: // = quit
215 class conversion_window : public base_window
218 conversion_window(const video::frame_params & frame_params,
219 const std::string & main_page_uri,
220 const std::string & output_dir,
221 dvd_generator::mpeg_encoder encoder);
223 bool is_finished() const;
228 dvd_generator::pgc_ref add_menu(const std::string & uri);
229 dvd_generator::pgc_ref add_title(const std::string & uri,
230 video_format format);
231 void load_next_page();
233 void on_net_state_change(const char * uri, gint flags, guint status);
234 bool browser_is_busy() const
236 return pending_window_update_ || pending_req_count_;
238 // Do as much processing as possible. Return a flag indicating
239 // whether to call again once the browser is idle.
241 // Return a Pixbuf containing a copy of the window contents.
242 Glib::RefPtr<Gdk::Pixbuf> get_screenshot();
243 // Do as much processing as possible on the page links. Return
244 // a flag indicating whether to call again once the browser is
248 nsIDOMDocument * basic_doc,
249 nsIDocShell * doc_shell,
250 nsIDOMWindow * dom_window);
252 std::string output_dir_;
260 dvd_generator generator_;
261 typedef std::map<std::string, dvd_generator::pgc_ref>
263 resource_map_type resource_map_;
265 std::queue<std::string> page_queue_;
266 bool pending_window_update_;
267 int pending_req_count_;
268 std::auto_ptr<page_state> page_state_;
271 conversion_window::conversion_window(
272 const video::frame_params & frame_params,
273 const std::string & main_page_uri,
274 const std::string & output_dir,
275 dvd_generator::mpeg_encoder encoder)
276 : base_window(frame_params),
277 output_dir_(output_dir),
278 state_(state_initial),
279 generator_(frame_params, encoder),
280 pending_window_update_(false),
281 pending_req_count_(0)
283 Glib::signal_idle().connect(
284 sigc::mem_fun(this, &conversion_window::on_idle));
285 browser_widget_.signal_net_state().connect(
286 sigc::mem_fun(this, &conversion_window::on_net_state_change));
288 add_menu(main_page_uri);
291 bool conversion_window::is_finished() const
293 return state_ == state_finished;
296 dvd_generator::pgc_ref conversion_window::add_menu(const std::string & uri)
298 dvd_generator::pgc_ref & pgc_ref = resource_map_[uri];
299 if (pgc_ref.type == dvd_generator::unknown_pgc)
301 pgc_ref = generator_.add_menu();
302 page_queue_.push(uri);
307 dvd_generator::pgc_ref conversion_window::add_title(const std::string & uri,
310 dvd_generator::pgc_ref & pgc_ref = resource_map_[uri];
312 if (pgc_ref.type == dvd_generator::unknown_pgc)
314 Glib::ustring hostname;
315 std::string path(Glib::filename_from_uri(uri, hostname));
316 // FIXME: Should check the hostname
320 // Store a reference to a linked VOB file, or the contents
321 // of a linked VOB list file.
322 if (format == video_format_mpeg2_ps)
324 if (!Glib::file_test(path, Glib::FILE_TEST_IS_REGULAR))
325 throw std::runtime_error(
326 path + " is missing or not a regular file");
331 else if (format == video_format_vob_list)
333 read_vob_list(path).swap(list);
337 assert(!"unrecognised format in add_title");
340 pgc_ref = generator_.add_title(list);
346 void conversion_window::load_next_page()
348 assert(!page_queue_.empty());
349 const std::string & uri = page_queue_.front();
350 std::cout << "INFO: Loading <" << uri << ">" << std::endl;
352 browser_widget_.load_uri(uri);
355 void conversion_window::on_net_state_change(const char * uri,
356 gint flags, guint status)
358 # ifdef DEBUG_ON_NET_STATE_CHANGE
359 std::cout << "conversion_window::on_net_state_change(";
361 std::cout << '"' << uri << '"';
366 gint flags_left = flags;
367 static const struct {
371 { GTK_MOZ_EMBED_FLAG_START, "STATE_START" },
372 { GTK_MOZ_EMBED_FLAG_REDIRECTING, "STATE_REDIRECTING" },
373 { GTK_MOZ_EMBED_FLAG_TRANSFERRING, "STATE_TRANSFERRING" },
374 { GTK_MOZ_EMBED_FLAG_NEGOTIATING, "STATE_NEGOTIATING" },
375 { GTK_MOZ_EMBED_FLAG_STOP, "STATE_STOP" },
376 { GTK_MOZ_EMBED_FLAG_IS_REQUEST, "STATE_IS_REQUEST" },
377 { GTK_MOZ_EMBED_FLAG_IS_DOCUMENT, "STATE_IS_DOCUMENT" },
378 { GTK_MOZ_EMBED_FLAG_IS_NETWORK, "STATE_IS_NETWORK" },
379 { GTK_MOZ_EMBED_FLAG_IS_WINDOW, "STATE_IS_WINDOW" }
381 for (int i = 0; i != sizeof(flag_names)/sizeof(flag_names[0]); ++i)
383 if (flags & flag_names[i].value)
385 std::cout << flag_names[i].name;
386 flags_left -= flag_names[i].value;
392 std::cout << "0x" << std::setbase(16) << flags_left;
394 std::cout << ", " << "0x" << std::setbase(16) << status << ")\n";
395 # endif // DEBUG_ON_NET_STATE_CHANGE
397 if (flags & GTK_MOZ_EMBED_FLAG_IS_REQUEST)
399 if (flags & GTK_MOZ_EMBED_FLAG_START)
400 ++pending_req_count_;
402 if (flags & GTK_MOZ_EMBED_FLAG_STOP)
404 assert(pending_req_count_ != 0);
405 --pending_req_count_;
409 if (flags & GTK_MOZ_EMBED_FLAG_IS_DOCUMENT
410 && flags & GTK_MOZ_EMBED_FLAG_START)
412 pending_window_update_ = true;
415 if (flags & GTK_MOZ_EMBED_FLAG_IS_WINDOW
416 && flags & GTK_MOZ_EMBED_FLAG_STOP)
418 // Check whether the load was successful, ignoring this
420 if (status != NS_IMAGELIB_ERROR_LOAD_ABORTED)
423 pending_window_update_ = false;
427 struct conversion_window::page_state
429 page_state(Glib::RefPtr<Gdk::Pixbuf> norm_pixbuf,
430 nsIDOMDocument * doc, int width, int height)
431 : norm_pixbuf(norm_pixbuf),
432 diff_pixbuf(Gdk::Pixbuf::create(
434 true, 8, // has_alpha, bits_per_sample
441 Glib::RefPtr<Gdk::Pixbuf> norm_pixbuf;
442 Glib::RefPtr<Gdk::Pixbuf> diff_pixbuf;
444 link_iterator links_it, links_end;
447 std::string link_target;
451 bool conversion_window::on_idle()
453 if (state_ == state_initial)
455 // Put pointer in the top-left so that no links appear in
456 // the hover state when we take a screenshot.
457 warp_pointer(get_window(),
458 -frame_params_.width, -frame_params_.height);
462 state_ = state_processing;
464 else if (state_ == state_processing && !browser_is_busy())
470 state_ = state_finished;
476 // Print context of exception.
477 if (!page_queue_.empty())
479 std::cerr << "ERROR: While processing page <"
480 << page_queue_.front() << ">:\n";
481 if (page_state_.get() && !page_state_->link_target.empty())
482 std::cerr << "ERROR: While processing link to <"
483 << page_state_->link_target << ">:\n";
486 // Print exception message.
491 catch (std::exception & e)
493 std::cerr << "ERROR: " << e.what() << "\n";
495 catch (Glib::Exception & e)
497 std::cerr << "ERROR: " << e.what() << "\n";
501 std::cerr << "ERROR: Unknown exception\n";
508 // Call again if we're not done.
509 return state_ != state_finished;
512 bool conversion_window::process()
514 assert(!page_queue_.empty());
516 nsCOMPtr<nsIWebBrowser> browser(browser_widget_.get_browser());
517 nsCOMPtr<nsIDocShell> doc_shell(do_GetInterface(browser));
519 nsCOMPtr<nsIDOMWindow> dom_window;
520 check(browser->GetContentDOMWindow(getter_AddRefs(dom_window)));
522 nsCOMPtr<nsIDOMDocument> basic_doc;
523 check(dom_window->GetDocument(getter_AddRefs(basic_doc)));
525 // Start or continue processing links.
526 if (!page_state_.get())
530 basic_doc, frame_params_.width, frame_params_.height));
531 if (!process_links(page_state_.get(), basic_doc, doc_shell, dom_window))
533 // We've finished work on the links so generate the
535 quantise_rgba_pixbuf(page_state_->diff_pixbuf,
536 dvd::button_n_colours);
537 generator_.generate_menu_vob(
538 resource_map_[page_queue_.front()].index,
539 page_state_->norm_pixbuf, page_state_->diff_pixbuf);
541 // Move on to the next page, if any, or else generate
542 // the DVD filesystem.
545 if (!page_queue_.empty())
551 generator_.generate(output_dir_);
559 Glib::RefPtr<Gdk::Pixbuf> conversion_window::get_screenshot()
561 Glib::RefPtr<Gdk::Window> window(get_window());
563 window->process_updates(true);
565 return Gdk::Pixbuf::create(Glib::RefPtr<Gdk::Drawable>(window),
566 window->get_colormap(),
568 frame_params_.width, frame_params_.height);
571 bool conversion_window::process_links(
573 nsIDOMDocument * basic_doc,
574 nsIDocShell * doc_shell,
575 nsIDOMWindow * dom_window)
577 Glib::RefPtr<Gdk::Window> window(get_window());
580 nsCOMPtr<nsIDOMNSDocument> ns_doc(do_QueryInterface(basic_doc));
582 nsCOMPtr<nsIPresShell> pres_shell;
583 check(doc_shell->GetPresShell(getter_AddRefs(pres_shell)));
584 nsCOMPtr<nsIEventStateManager> event_state_man(
585 get_event_state_manager(doc_shell));
586 assert(event_state_man);
587 nsCOMPtr<nsIDOMDocumentEvent> event_factory(
588 do_QueryInterface(basic_doc));
589 assert(event_factory);
590 nsCOMPtr<nsIDOMDocumentView> doc_view(do_QueryInterface(basic_doc));
592 nsCOMPtr<nsIDOMAbstractView> view;
593 check(doc_view->GetDefaultView(getter_AddRefs(view)));
595 rectangle window_rect = {
596 0, 0, frame_params_.width, frame_params_.height
599 unsigned menu_index = resource_map_[page_queue_.front()].index;
601 for (/* no initialisation */;
602 state->links_it != state->links_end;
605 nsCOMPtr<nsIDOMNode> node(*state->links_it);
607 // Find the link URI and separate any fragment from it.
608 nsCOMPtr<nsILink> link(do_QueryInterface(node));
610 nsCOMPtr<nsIURI> uri_iface;
611 check(link->GetHrefURI(getter_AddRefs(uri_iface)));
612 std::string uri, fragment;
614 nsCString link_target_ns;
615 check(uri_iface->GetSpec(link_target_ns));
617 PRUint32 len = NS_CStringGetData(link_target_ns, &str);
618 state->link_target.assign(str, len);
620 std::size_t hash_pos = state->link_target.find('#');
621 uri.assign(state->link_target, 0, hash_pos);
622 if (hash_pos != std::string::npos)
623 fragment.assign(state->link_target,
624 hash_pos + 1, std::string::npos);
627 // Is this a new link?
628 if (!state->link_changing)
630 // Find a rectangle enclosing the link and clip it to the
632 nsCOMPtr<nsIDOMElement> elem(do_QueryInterface(node));
634 state->link_rect = get_elem_rect(ns_doc, elem);
635 state->link_rect &= window_rect;
637 if (state->link_rect.empty())
639 std::cerr << "WARN: Ignoring invisible link to <"
640 << state->link_target << ">\n";
644 // Check whether this is a link to a video or a page then
645 // add it to the known resources if not already seen; then
646 // add it to the menu entries.
647 dvd_generator::pgc_ref target;
648 video_format format = video_format_from_uri(uri);
649 if (format != video_format_none)
652 check(uri_iface->SchemeIs("file", &is_file));
654 throw std::runtime_error(
655 "Link to video does not use file: scheme");
656 target = add_title(uri, format);
658 std::strtoul(fragment.c_str(), NULL, 10);
660 else // video_format == video_format_none
662 target = add_menu(uri);
663 // TODO: If there's a fragment, work out which button
664 // is closest and set target.sub_index.
667 generator_.add_menu_entry(menu_index,
668 state->link_rect, target);
670 nsCOMPtr<nsIContent> content(do_QueryInterface(node));
672 nsCOMPtr<nsIDOMEventTarget> event_target(
673 do_QueryInterface(node));
674 assert(event_target);
676 nsCOMPtr<nsIDOMEvent> event;
677 check(event_factory->CreateEvent(
678 NS_ConvertASCIItoUTF16("MouseEvents"),
679 getter_AddRefs(event)));
680 nsCOMPtr<nsIDOMMouseEvent> mouse_event(
681 do_QueryInterface(event));
683 check(mouse_event->InitMouseEvent(
684 NS_ConvertASCIItoUTF16("mouseover"),
688 0, // detail: mouse click count
689 state->link_rect.left, // screenX
690 state->link_rect.top, // screenY
691 state->link_rect.left, // clientX
692 state->link_rect.top, // clientY
693 false, false, false, false, // qualifiers
694 0, // button: left (or primary)
695 0)); // related target
697 check(event_target->DispatchEvent(mouse_event,
699 check(event_state_man->SetContentState(content,
700 NS_EVENT_STATE_HOVER));
702 pres_shell->FlushPendingNotifications(Flush_Display);
704 // We may have to exit and wait for image loading
705 // to complete, at which point we will be called
707 if (browser_is_busy())
709 state->link_changing = true;
714 window->process_updates(true);
716 Glib::RefPtr<Gdk::Pixbuf> changed_pixbuf(
718 Glib::RefPtr<Gdk::Drawable>(window),
719 window->get_colormap(),
720 state->link_rect.left,
721 state->link_rect.top,
724 state->link_rect.right - state->link_rect.left,
725 state->link_rect.bottom - state->link_rect.top));
730 state->link_rect.left,
731 state->link_rect.top,
732 state->link_rect.right - state->link_rect.left,
733 state->link_rect.bottom - state->link_rect.top);
739 const video::frame_params & lookup_frame_params(const char * str)
742 static const char * const known_strings[] = {
748 for (std::size_t i = 0;
749 i != sizeof(known_strings)/sizeof(known_strings[0]);
751 if (std::strcmp(str, known_strings[i]) == 0)
753 ? video::frame_params_625
754 : video::frame_params_525;
755 throw std::runtime_error(
756 std::string("Invalid video standard: ").append(str));
759 void print_usage(std::ostream & stream, const char * command_name)
762 "Usage: " << command_name << " [gtk-options] [--preview]\n"
763 " [--video-std {525|525/60|NTSC|ntsc"
764 " | 625|625/50|PAL|pal}]\n"
765 " [--encoder {ffmpeg|mjpegtools}]\n"
766 " menu-url [output-dir]\n";
769 void set_browser_preferences()
771 nsCOMPtr<nsIPrefService> pref_service;
772 static const nsCID pref_service_cid = NS_PREFSERVICE_CID;
773 check(CallGetService<nsIPrefService>(pref_service_cid,
774 getter_AddRefs(pref_service)));
775 nsCOMPtr<nsIPrefBranch> pref_branch;
776 check(pref_service->GetBranch("", getter_AddRefs(pref_branch)));
778 // Disable IE-compatibility kluge that causes backgrounds to
779 // sometimes/usually be missing from snapshots. This is only
780 // effective from Mozilla 1.8 onward.
781 check(pref_branch->SetBoolPref(
782 "layout.fire_onload_after_image_background_loads",
785 // Set display resolution. With standard-definition video we
786 // will be fitting ~600 pixels across a screen typically
787 // ranging from 10 to 25 inches wide, for a resolution of
788 // 24-60 dpi. I therefore declare the average horizontal
789 // resolution to be 40 dpi. The vertical resolution will be
790 // slightly different but unfortunately Mozilla doesn't
791 // support non-square pixels (and neither do fontconfig or Xft
794 // The browser.display.screen_resolution preference sets the
795 // the nominal resolution for dimensions expressed in pixels.
796 // (They may be scaled!) In Mozilla 1.7 it also sets the
797 // assumed resolution of the display - hence pixel sizes are
798 // respected on-screen - but this is no longer the case in
799 // 1.8. Therefore it was renamed to layout.css.dpi in 1.8.1.
800 // In 1.8 we need to set the assumed screen resolution
801 // separately, but don't know how yet. Setting one to 40
802 // but not the other is *bad*, so currently we set neither.
805 check(pref_branch->SetIntPref("browser.display.screen_resolution",
812 void fatal_error(const std::string & message)
814 std::cerr << "ERROR: " << message << "\n";
818 int main(int argc, char ** argv)
822 video::frame_params frame_params = video::frame_params_625;
823 bool preview_mode = false;
824 std::string menu_url;
825 std::string output_dir;
826 dvd_generator::mpeg_encoder encoder =
827 dvd_generator::mpeg_encoder_ffmpeg;
829 // Do initial option parsing. We have to do this before
830 // letting Gtk parse the arguments since we may need to spawn
835 if (std::strcmp(argv[argi], "--") == 0)
839 else if (std::strcmp(argv[argi], "--help") == 0)
841 print_usage(std::cout, argv[0]);
844 else if (std::strcmp(argv[argi], "--preview") == 0)
849 else if (std::strcmp(argv[argi], "--video-std") == 0)
851 if (argi + 1 == argc)
853 std::cerr << "Missing argument to --video-std\n";
854 print_usage(std::cerr, argv[0]);
857 frame_params = lookup_frame_params(argv[argi + 1]);
866 std::auto_ptr<x_frame_buffer> fb;
869 // Spawn Xvfb and set env variables so that Xlib will use it
870 // Use 8 bits each for RGB components, which should translate into
871 // "enough" bits for YUV components.
872 fb.reset(new x_frame_buffer(frame_params.width,
875 setenv("XAUTHORITY", fb->get_authority().c_str(), true);
876 setenv("DISPLAY", fb->get_display().c_str(), true);
880 Gtk::Main kit(argc, argv);
882 // Complete option parsing with Gtk's options out of the way.
886 if (std::strcmp(argv[argi], "--") == 0)
891 else if (std::strcmp(argv[argi], "--preview") == 0)
895 else if (std::strcmp(argv[argi], "--video-std") == 0)
899 else if (std::strcmp(argv[argi], "--save-temps") == 0)
901 temp_file::keep_all(true);
904 else if (std::strcmp(argv[argi], "--encoder") == 0)
906 if (argi + 1 == argc)
908 std::cerr << "Missing argument to --encoder\n";
909 print_usage(std::cerr, argv[0]);
912 if (std::strcmp(argv[argi + 1], "ffmpeg") == 0)
914 encoder = dvd_generator::mpeg_encoder_ffmpeg;
916 else if (std::strcmp(argv[argi + 1], "mjpegtools") == 0)
918 encoder = dvd_generator::mpeg_encoder_mjpegtools;
922 std::cerr << "Invalid argument to --encoder\n";
923 print_usage(std::cerr, argv[0]);
928 else if (argv[argi][0] == '-')
930 std::cerr << "Invalid option: " << argv[argi] << "\n";
931 print_usage(std::cerr, argv[0]);
940 // Look for a starting URL or filename and (except in preview
941 // mode) an output directory after the options.
942 if (argc - argi != (preview_mode ? 1 : 2))
944 print_usage(std::cerr, argv[0]);
947 if (std::strstr(argv[argi], "://"))
949 // It appears to be an absolute URL, so use it as-is.
950 menu_url = argv[argi];
954 // Assume it's a filename. Resolve it to an absolute URL.
955 std::string path(argv[argi]);
956 if (!Glib::path_is_absolute(path))
957 path = Glib::build_filename(Glib::get_current_dir(), path);
958 menu_url = Glib::filename_to_uri(path);
961 output_dir = argv[argi + 1];
963 // Initialise Mozilla
964 browser_widget::initialiser browser_init;
965 set_browser_preferences();
966 init_agent_style_sheet("file://" VIDEOLINK_SHARE_DIR "/videolink.css");
967 init_agent_style_sheet(std::string("file://" VIDEOLINK_SHARE_DIR "/")
968 .append(frame_params.common_name).append(".css")
971 null_prompt_service::install();
973 // Run the browser/converter
976 preview_window window(frame_params, menu_url);
978 window.signal_hide().connect(sigc::ptr_fun(Gtk::Main::quit));
984 conversion_window window(frame_params, menu_url, output_dir, encoder);
986 window.signal_hide().connect(sigc::ptr_fun(Gtk::Main::quit));
988 return window.is_finished() ? EXIT_SUCCESS : EXIT_FAILURE;
991 catch (std::exception & e)
993 std::cerr << "ERROR: " << e.what() << "\n";