Include program counter on action limit notification log
[gnash.git] / utilities / processor.cpp
blobcf8c9cfd3fa3bd1a9b57c23fce5c694eabd93900
1 // processor.cpp: Flash movie processor (gprocessor command), for Gnash.
2 //
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Free Software
4 // Foundation, Inc
5 //
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 #ifdef HAVE_CONFIG_H
21 #include "gnashconfig.h"
22 #endif
24 #include <ios>
25 #include <iostream>
26 #include <cstdio>
27 #include <cstdlib>
28 #include <ctime>
29 #include <typeinfo>
30 #include <boost/any.hpp>
32 #ifdef ENABLE_NLS
33 # include <clocale>
34 #endif
36 #include "NullSoundHandler.h"
37 #include "MovieFactory.h"
38 #include "swf/TagLoadersTable.h"
39 #include "swf/DefaultTagLoaders.h"
40 #include "ClockTime.h"
41 #include "movie_definition.h"
42 #include "MovieClip.h"
43 #include "movie_root.h"
44 #include "log.h"
45 #include "rc.h"
46 #include "URL.h"
47 #include "GnashException.h"
48 #include "VM.h"
49 #include "noseek_fd_adapter.h"
50 #include "ManualClock.h"
51 #include "StringPredicates.h"
52 #include "smart_ptr.h"
53 #include "IOChannel.h" // for proper dtor call
54 #include "GnashSleep.h" // for usleep comptibility.
55 #include "StreamProvider.h"
56 #include "RunResources.h"
57 #include "HostInterface.h"
59 #ifdef RENDERER_AGG
60 #include "Renderer.h"
61 #include "Renderer_agg.h"
62 #endif
64 extern "C"{
65 #ifdef HAVE_GETOPT_H
66 #include <getopt.h>
67 #endif
68 #ifndef __GNUC__
69 extern char *optarg;
70 extern int optopt;
71 extern int optind, getopt(int, char *const *, const char *);
72 #endif
75 #ifdef BOOST_NO_EXCEPTIONS
77 namespace boost
79 void throw_exception(std::exception const & e)
81 std::abort();
84 #endif
86 // How many seconds to wait for a frame advancement
87 // before kicking the movie (forcing it to next frame)
88 static const double waitforadvance = 5;
90 // How many time do we allow for loop backs
91 // (goto frame < current frame)
92 static const size_t allowloopbacks = 10;
94 // How many times to call 'advance' ?
95 // If 0 number of advance is unlimited
96 // (see other constraints)
97 // TODO: add a command-line switch to control this
98 static size_t limit_advances = 0;
100 // How much time to sleep between advances ?
101 // If set to -1 it will be computed based on FPS.
102 static long int delay = 0;
104 const char *GPROC_VERSION = "1.0";
106 using namespace gnash;
108 static void usage (const char *);
110 namespace {
111 gnash::LogFile& dbglogfile = gnash::LogFile::getDefaultInstance();
112 gnash::RcInitFile& rcfile = gnash::RcInitFile::getDefaultInstance();
115 static bool play_movie(const std::string& filename,
116 const RunResources& runResources);
118 static bool s_stop_on_errors = true;
120 // How many time do we allow to hit the end ?
121 static size_t allowed_end_hits = 1;
123 double lastAdvanceTimer;
125 void
126 resetLastAdvanceTimer()
128 // clocktime::getTicks() returns milliseconds
129 lastAdvanceTimer = static_cast<double>(clocktime::getTicks()) / 1000.0;
132 double
133 secondsSinceLastAdvance()
135 // clocktime::getTicks() returns milliseconds
136 double now = static_cast<double>(clocktime::getTicks()) / 1000.0;
137 return ( now - lastAdvanceTimer);
140 // A flag which will be used to interrupt playback
141 // by effect of a "quit" fscommand
143 static int quitrequested = false;
145 class FsCommandExecutor: public FsCallback {
146 public:
147 void notify(const std::string& command, const std::string& args)
149 log_debug(_("fs_callback(%p): %s %s"), command, args);
151 StringNoCaseEqual ncasecomp;
153 if ( ncasecomp(command, "quit") ) quitrequested = true;
157 class EventCallback: public HostInterface
159 public:
160 boost::any call(const HostInterface::Message& e)
162 if (e.type() != typeid(HostMessage)) return boost::blank();
164 const HostMessage& ev = boost::get<HostMessage>(e);
165 const HostMessage::KnownEvent event = ev.event();
167 log_debug(_("eventCallback: %s %s"), event);
169 static bool mouseShown = true;
170 static std::string clipboard;
172 switch (event) {
173 case HostMessage::QUERY:
174 return true;
175 case HostMessage::SET_CLIPBOARD:
176 clipboard = boost::any_cast<std::string>(ev.arg());
177 return boost::blank();
179 case HostMessage::SHOW_MOUSE:
181 bool state = mouseShown;
182 mouseShown = boost::any_cast<bool>(ev.arg());
183 return state;
186 // Some fake values for consistent test results.
187 case HostMessage::SCREEN_RESOLUTION:
188 return std::make_pair(800, 640);
190 case HostMessage::SCREEN_DPI:
191 return 72.0;
193 case HostMessage::SCREEN_COLOR:
194 return std::string("Color");
196 case HostMessage::PLAYER_TYPE:
197 return std::string("StandAlone");
199 case HostMessage::PIXEL_ASPECT_RATIO:
200 return 0.9978;
202 default:
203 log_debug(_("gprocessor does not handle %1% message"), e);
204 break;
207 return boost::blank();
210 virtual void exit() {
211 std::exit(EXIT_SUCCESS);
215 EventCallback eventCallback;
216 FsCommandExecutor execFsCommand;
219 main(int argc, char *argv[])
221 std::ios::sync_with_stdio(false);
223 // Enable native language support, i.e. internationalization
224 #ifdef ENABLE_NLS
225 std::setlocale (LC_ALL, "");
226 bindtextdomain (PACKAGE, LOCALEDIR);
227 textdomain (PACKAGE);
228 #endif
229 int c;
231 // scan for the two main standard GNU options
232 for (c = 0; c < argc; c++) {
233 if (strcmp("--help", argv[c]) == 0) {
234 usage(argv[0]);
235 return EXIT_SUCCESS;
237 if (strcmp("--version", argv[c]) == 0) {
238 printf (_("Gnash gprocessor version: %s, Gnash version: %s\n"),
239 GPROC_VERSION, VERSION);
240 return EXIT_SUCCESS;
244 std::vector<std::string> infiles;
246 //RcInitFile& rcfile = RcInitFile::getDefaultInstance();
247 //rcfile.loadFiles();
249 if (rcfile.verbosityLevel() > 0) {
250 dbglogfile.setVerbosity(rcfile.verbosityLevel());
253 dbglogfile.setLogFilename(rcfile.getDebugLog());
255 if (rcfile.useWriteLog()) {
256 dbglogfile.setWriteDisk(true);
259 if (rcfile.useActionDump()) {
260 dbglogfile.setActionDump(true);
261 dbglogfile.setVerbosity();
264 if (rcfile.useParserDump()) {
265 dbglogfile.setParserDump(true);
266 dbglogfile.setVerbosity();
269 while ((c = getopt (argc, argv, ":hvapr:gf:d:n")) != -1) {
270 switch (c) {
271 case 'h':
272 usage (argv[0]);
273 dbglogfile.removeLog();
274 return EXIT_SUCCESS;
275 case 'v':
276 dbglogfile.setVerbosity();
277 log_debug (_("Verbose output turned on"));
278 break;
279 case 'n':
280 dbglogfile.setNetwork(true);
281 break;
282 case 'a':
283 #if VERBOSE_ACTION
284 dbglogfile.setActionDump(true);
285 #else
286 log_error (_("Verbose actions disabled at compile time"));
287 #endif
288 break;
289 case 'p':
290 #if VERBOSE_PARSE
291 dbglogfile.setParserDump(true);
292 #else
293 log_error (_("Verbose parsing disabled at compile time"));
294 #endif
295 break;
296 case 'r':
297 allowed_end_hits = strtol(optarg, NULL, 0);
298 break;
299 case 'd':
300 delay = strtol(optarg, NULL, 0)*1000; // delay is in microseconds
301 // this will be recognized as a request to run at FPS speed
302 if ( delay < 0 ) delay = -1;
303 break;
304 case 'f':
305 limit_advances = strtol(optarg, NULL, 0);
306 break;
307 case ':':
308 fprintf(stderr, "Missing argument for switch ``%c''\n", optopt);
309 return EXIT_FAILURE;
310 case '?':
311 default:
312 fprintf(stderr, "Unknown switch ``%c''\n", optopt);
313 return EXIT_FAILURE;
318 // get the file name from the command line
319 while (optind < argc) {
320 infiles.push_back(argv[optind]);
321 optind++;
324 // No file names were supplied
325 if (infiles.empty()) {
326 std::cerr << "no input files" << std::endl;
327 usage(argv[0]);
328 dbglogfile.removeLog();
329 return EXIT_FAILURE;
332 boost::shared_ptr<gnash::media::MediaHandler> mediaHandler;
333 boost::shared_ptr<sound::sound_handler> soundHandler;
335 std::string mh = rcfile.getMediaHandler();
336 mediaHandler.reset(media::MediaFactory::instance().get(mh));
337 soundHandler.reset(new sound::NullSoundHandler(mediaHandler.get()));
339 boost::shared_ptr<SWF::TagLoadersTable> loaders(new SWF::TagLoadersTable());
340 addDefaultLoaders(*loaders);
342 #ifdef RENDERER_AGG
343 boost::shared_ptr<Renderer_agg_base> r(create_Renderer_agg("RGBA32"));
345 // Yes, this leaks. On some systems (e.g. Debian Lenny) the data is
346 // evidently accessed after main() returns. Rather than bothering to
347 // work out why, we let this byte leak, as it's returned to the system on
348 // exit anyway.
349 unsigned char* buf = new unsigned char[8];
350 r->init_buffer(buf, 1, 1, 1, 1);
351 #endif
353 // Play through all the movies.
354 for (std::vector<std::string>::const_iterator i = infiles.begin(),
355 e = infiles.end(); i != e; ++i)
358 RunResources runResources;
359 runResources.setSoundHandler(soundHandler);
360 runResources.setMediaHandler(mediaHandler);
361 runResources.setTagLoaders(loaders);
362 boost::shared_ptr<StreamProvider> sp(new StreamProvider(*i, *i));
363 runResources.setStreamProvider(sp);
365 #ifdef RENDERER_AGG
366 runResources.setRenderer(r);
367 #endif
369 bool success = play_movie(*i, runResources);
370 if (!success) {
371 if (s_stop_on_errors) {
372 // Fail.
373 std::cerr << "error playing through movie " << *i << std::endl;
374 return EXIT_FAILURE;
380 return 0;
383 // Load the named movie, make an instance, and play it, virtually.
384 // I.e. run through and render all the frames, even though we are not
385 // actually doing any output (our output handlers are disabled).
387 bool
388 play_movie(const std::string& filename, const RunResources& runResources)
390 boost::intrusive_ptr<gnash::movie_definition> md;
392 quitrequested = false;
394 URL url(filename);
398 if (filename == "-")
400 std::auto_ptr<IOChannel> in (
401 noseek_fd_adapter::make_stream(fileno(stdin)) );
402 md = MovieFactory::makeMovie(in, filename, runResources, false);
404 else
406 if ( url.protocol() == "file" )
408 const std::string& path = url.path();
409 #if 1 // add the *directory* the movie was loaded from to the local sandbox path
410 size_t lastSlash = path.find_last_of('/');
411 std::string dir = path.substr(0, lastSlash+1);
412 rcfile.addLocalSandboxPath(dir);
413 log_debug(_("%s appended to local sandboxes"), dir.c_str());
414 #else // add the *file* to be loaded to the local sandbox path
415 rcfile.addLocalSandboxPath(path);
416 log_debug(_("%s appended to local sandboxes"), path.c_str());
417 #endif
419 md = MovieFactory::makeMovie(url, runResources, NULL, false);
422 catch (GnashException& ge)
424 md = NULL;
425 fprintf(stderr, "%s\n", ge.what());
427 if (md == NULL) {
428 std::cerr << "error: can't play movie: "<< filename << std::endl;
429 return false;
432 float fps = md->get_frame_rate();
433 long fpsDelay = long(1000000/fps);
434 long clockAdvance = fpsDelay/1000;
435 long localDelay = delay == -1 ? fpsDelay : delay; // microseconds
437 log_debug("Will sleep %ld microseconds between iterations - "
438 "fps is %g, clockAdvance is %lu", localDelay, fps, clockAdvance);
441 // Use a clock advanced at every iteration to match exact FPS speed.
442 ManualClock cl;
444 // Scope to ensure that movie_root is destroyed before the library
445 // is cleared; otherwise movie_root's MovieLoader can continue to
446 // add movie_definitions to MovieLibrary, which then keeps them
447 // and their parsing thread alive until static destruction. The parser
448 // can then continue to access destroyed resources.
450 gnash::movie_root m(*md, cl, runResources);
452 // Register processor to receive ActionScript events (Mouse, Stage
453 // System etc).
454 m.registerEventCallback(&eventCallback);
455 m.registerFSCommandCallback(&execFsCommand);
457 md->completeLoad();
459 MovieClip::MovieVariables v;
460 m.init(md.get(), v);
462 log_debug("iteration, timer: %lu, localDelay: %ld\n",
463 cl.elapsed(), localDelay);
464 gnashSleep(localDelay);
466 resetLastAdvanceTimer();
467 int kick_count = 0;
468 int stop_count=0;
469 size_t loop_back_count=0;
470 size_t latest_frame=0;
471 size_t end_hitcount=0;
472 size_t nadvances=0;
473 // Run through the movie.
474 while (!quitrequested) {
476 size_t last_frame = m.get_current_frame();
477 //printf("advancing clock by %lu\n", clockAdvance);
478 cl.advance(clockAdvance);
479 m.advance();
481 if ( quitrequested )
483 quitrequested = false;
484 break;
487 m.display(); // FIXME: for which reason are we calling display here ??
488 ++nadvances;
489 if ( limit_advances && nadvances >= limit_advances)
491 log_debug("exiting after %d advances", nadvances);
492 break;
495 size_t curr_frame = m.get_current_frame();
497 // We reached the end, done !
498 if (curr_frame >= md->get_frame_count() - 1 )
500 if ( allowed_end_hits && ++end_hitcount >= allowed_end_hits )
502 log_debug("exiting after %d"
503 " times last frame was reached", end_hitcount);
504 break;
508 // We didn't advance
509 if (curr_frame == last_frame)
511 // Max stop counts reached, kick it
512 if ( secondsSinceLastAdvance() > waitforadvance )
514 stop_count=0;
516 // Kick the movie.
517 if ( last_frame + 1 > md->get_frame_count() -1 )
519 fprintf(stderr, "Exiting after %g seconds in STOP mode at last frame\n", waitforadvance);
520 break;
522 fprintf(stderr, "Kicking movie after %g seconds in STOP mode, kick ct = %d\n", waitforadvance, kick_count);
523 fflush(stderr);
524 m.goto_frame(last_frame + 1);
525 m.getRootMovie().setPlayState(gnash::MovieClip::PLAYSTATE_PLAY);
526 kick_count++;
528 if (kick_count > 10) {
529 printf("movie is stalled; giving up on playing it through.\n");
530 break;
533 resetLastAdvanceTimer(); // It's like we advanced
537 // We looped back. Skip ahead...
538 else if (m.get_current_frame() < last_frame)
540 if ( last_frame > latest_frame ) latest_frame = last_frame;
541 if ( ++loop_back_count > allowloopbacks )
543 log_debug("%d loop backs; jumping one-after "
544 "latest frame (%d)",
545 loop_back_count, latest_frame+1);
546 m.goto_frame(latest_frame + 1);
547 loop_back_count = 0;
550 else
552 kick_count = 0;
553 stop_count = 0;
554 resetLastAdvanceTimer();
557 log_debug("iteration, timer: %lu, localDelay: %ld\n",
558 cl.elapsed(), localDelay);
559 gnashSleep(localDelay);
564 log_debug("-- Playback completed");
566 log_debug("-- Dropping ref of movie_definition");
568 // drop reference to movie_definition, to force
569 // destruction when core gnash doesn't need it anymore
570 md = 0;
572 log_debug("-- Cleaning gnash");
574 // Clear resources.
575 // Forces run of GC, which in turn may invoke
576 // destuctors of (say) MovieClip which would try
577 // to access the movie_root to unregister self
579 // This means that movie_root must be available.
580 MovieFactory::clear();
582 return true;
585 static void
586 usage (const char *name)
588 printf(
589 _("gprocessor -- an SWF processor for Gnash.\n"
590 "\n"
591 "usage: %s [options] <file>\n"
592 "\n"
593 "Process the given SWF movie files.\n"
594 "\n"
595 "%s%s%s%s"), name, _(
596 "options:\n"
597 "\n"
598 " --help(-h) Print this info.\n"
599 " --version Print the version numbers.\n"
600 " -v Be verbose; i.e. print log messages to stdout\n"
602 #if VERBOSE_PARSE
603 _(" -vp Be verbose about movie parsing\n"),
604 #else
606 #endif
607 #if VERBOSE_ACTION
608 _(" -va Be verbose about ActionScript\n"),
609 #else
611 #endif
613 " -d [<ms>]\n"
614 " Milliseconds delay between advances (0 by default).\n"
615 " If '-1' the delay will be computed from the FPS.\n"
616 " -r <times> Allow the given number of complete runs.\n"
617 " Keep looping undefinitely if set to 0.\n"
618 " Default is 1 (end as soon as the last frame is reached).\n"
619 " -f <frames> \n"
620 " Allow the given number of frame advancements.\n"
621 " Keep advancing untill any other stop condition\n"
622 " is encountered if set to 0 (default).\n")
627 // Local Variables:
628 // mode: C++
629 // indent-tabs-mode: t
630 // End: