1 // Player.cpp: Top level SWF player, for gnash.
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Free Software
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.
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
22 #include "gnashconfig.h"
26 # define DEFAULT_GUI "NULL"
32 #include "MovieFactory.h"
33 #include "movie_definition.h"
34 #include "sound_handler.h" // for set_sound_handler and create_sound_handler_*
35 #include "MovieClip.h" // for setting FlashVars
36 #include "movie_root.h"
38 #include "StreamProvider.h"
40 #include "swf/TagLoadersTable.h"
41 #include "swf/DefaultTagLoaders.h"
42 #include "NamingPolicy.h"
43 #include "StringPredicates.h"
46 #include "GnashException.h"
47 #include "noseek_fd_adapter.h"
49 #include "SystemClock.h"
50 #include "ExternalInterface.h"
52 #include "GnashSystemIOHeaders.h" // for write()
57 #include <boost/lexical_cast.hpp>
59 using namespace gnash
;
62 gnash::LogFile
& dbglogfile
= gnash::LogFile::getDefaultInstance();
66 Player::setFlashVars(const std::string
& varstr
)
68 typedef Gui::VariableMap maptype
;
71 URL::parse_querystring(varstr
, vars
);
73 _gui
->addFlashVars(vars
);
77 Player::setScriptableVar(const std::string
&name
, const std::string
&value
)
79 _gui
->addScriptableVar(name
, value
);
84 #if defined(RENDERER_CAIRO)
102 #ifdef GNASH_FPS_DEBUG
107 _startFullscreen(false),
114 Player::setScale(float newscale
)
116 float oldscale
= _scale
;
124 /// Initialize gnash core library
130 Player::init_logfile()
132 dbglogfile
.setWriteDisk(false);
134 RcInitFile
& rcfile
= RcInitFile::getDefaultInstance();
135 if (rcfile
.useWriteLog()) {
136 dbglogfile
.setWriteDisk(true);
139 dbglogfile
.setLogFilename(rcfile
.getDebugLog());
141 if (rcfile
.verbosityLevel() > 0) {
142 dbglogfile
.setVerbosity(rcfile
.verbosityLevel());
145 if (rcfile
.useActionDump()) {
146 dbglogfile
.setActionDump(true);
147 dbglogfile
.setVerbosity();
150 if (rcfile
.useParserDump()) {
151 dbglogfile
.setParserDump(true);
152 dbglogfile
.setVerbosity();
155 // If a delay was not specified yet use
156 // any eventual setting for it found in
159 // TODO: we should remove all uses of the rcfile
160 // from Player class..
162 if (!_delay
&& rcfile
.getTimerDelay() > 0) {
163 _delay
= rcfile
.getTimerDelay();
164 log_debug (_("Timer delay set to %d milliseconds"), _delay
);
170 Player::silentStream(void* /*udata*/, boost::int16_t* stream
, unsigned int len
, bool& atEOF
)
172 std::fill(stream
, stream
+len
, 0);
184 _soundHandler
.reset(sound::create_sound_handler_sdl(
185 _mediaHandler
.get(), _audioDump
));
186 #elif defined(SOUND_AHI)
187 _soundHandler
.reset(sound::create_sound_handler_aos4(
188 _mediaHandler
.get(), _audioDump
));
189 #elif defined(SOUND_MKIT)
190 _soundHandler
.reset(sound::create_sound_handler_mkit(
191 _mediaHandler
.get(), _audioDump
));
193 log_error(_("Sound requested but no sound support compiled in"));
196 } catch (SoundException
& ex
) {
197 log_error(_("Could not create sound handler: %s."
198 " Will continue w/out sound."), ex
.what());
200 if (! _audioDump
.empty()) {
201 // add a silent stream to the audio pool so that our output file
202 // is homogenous; we actually want silent wave data when no sounds
203 // are playing on the stage
204 _soundHandler
->attach_aux_streamer(silentStream
, (void*) this);
215 _gui
.reset(new NullGui(_doLoop
, *_runResources
));
218 _gui
->setMaxAdvances(_maxAdvances
);
220 #ifdef GNASH_FPS_DEBUG
222 log_debug(_("Activating FPS debugging every %g seconds"),
224 _gui
->setFpsTimerInterval(_fpsDebugTime
);
229 boost::intrusive_ptr
<movie_definition
>
232 /// The RunResources must be initialized by this point to provide resources
234 assert(_runResources
.get());
236 boost::intrusive_ptr
<gnash::movie_definition
> md
;
238 RcInitFile
& rcfile
= RcInitFile::getDefaultInstance();
241 if (vurl
.protocol() == "file") {
242 const std::string
& path
= vurl
.path();
243 size_t lastSlash
= path
.find_last_of('/');
244 std::string dir
= path
.substr(0, lastSlash
+1);
245 rcfile
.addLocalSandboxPath(dir
);
246 log_debug(_("%s appended to local sandboxes"), dir
.c_str());
250 if ( _infile
== "-" ) {
251 std::auto_ptr
<IOChannel
> in (
252 noseek_fd_adapter::make_stream(fileno(stdin
)));
253 md
= MovieFactory::makeMovie(in
, _url
, *_runResources
, false);
256 if ( url
.protocol() == "file" ) {
257 std::string path
= url
.path();
258 // We'll need to allow load of the file, no matter virtual url
260 // This is kind of hackish, cleaner would be adding an argument
261 // to createMovie to skip the security checking phase.
262 // NOTE that if we fail to allow this load, the konqueror plugin
263 // would not be able to load anything
265 rcfile
.addLocalSandboxPath(path
);
266 log_debug(_("%s appended to local sandboxes"), path
.c_str());
269 // _url should be always set at this point...
270 md
= MovieFactory::makeMovie(url
, *_runResources
, _url
.c_str(),
273 } catch (const GnashException
& er
) {
274 std::cerr
<< er
.what() << std::endl
;
279 fprintf(stderr
, "Could not load movie '%s'\n", _infile
.c_str());
286 /// \brief Run, used to open a new flash file. Using previous initialization
288 Player::run(int argc
, char* argv
[], const std::string
& infile
,
289 const std::string
& url
)
291 // Call this at run() time, so the caller has
292 // a cache of setting some parameter before calling us...
293 // (example: setDoSound(), setWindowId() etc.. )
296 // gnash.cpp should check that a filename is supplied.
297 assert (!infile
.empty());
302 if (_baseurl
.empty()) {
303 if (!url
.empty()) _baseurl
= url
;
304 else if (infile
== "-") _baseurl
= URL("./").str();
305 else _baseurl
= infile
;
308 // Set _root._url (either explicit of from infile)
315 // Parse player parameters. These are not passed to the SWF, but rather
316 // control stage properties etc.
317 Params::const_iterator it
= _params
.find("base");
318 const URL baseURL
= (it
== _params
.end()) ? _baseurl
:
319 URL(it
->second
, _baseurl
);
320 /// The RunResources should be populated before parsing.
321 _runResources
.reset(new RunResources(baseURL
.str()));
323 boost::shared_ptr
<SWF::TagLoadersTable
> loaders(new SWF::TagLoadersTable());
324 addDefaultLoaders(*loaders
);
325 _runResources
->setTagLoaders(loaders
);
327 std::auto_ptr
<NamingPolicy
> np(new IncrementalRename(_baseurl
));
328 boost::shared_ptr
<StreamProvider
> sp(new StreamProvider(np
));
330 _runResources
->setStreamProvider(sp
);
332 // Set the Hardware video decoding resources. none, vaapi, xv, omap
333 _runResources
->setHWAccelBackend(_hwaccel
);
334 // Set the Renderer resource, opengl, agg, or cairo
335 _runResources
->setRenderBackend(_renderer
);
337 _mediaHandler
.reset(media::MediaFactory::instance().get(_media
));
339 if (!_mediaHandler
.get()) {
341 boost::format(_("Non-existent media handler %1% specified"))
343 throw GnashException(fmt
.str());
346 _runResources
->setMediaHandler(_mediaHandler
);
349 _runResources
->setSoundHandler(_soundHandler
);
353 // Initialize gui (we need argc/argv for this)
354 // note that this will also initialize the renderer
355 // which is *required* during movie loading
356 if (!_gui
->init(argc
, &argv
)) {
357 throw GnashException("Could not initialize GUI");
360 // Parse querystring (before FlashVars, see
361 // testsuite/misc-ming.all/FlashVarsTest*)
362 setFlashVars(URL(_url
).querystring());
365 Params::const_iterator fv
= _params
.find("flashvars");
366 if (fv
!= _params
.end()) {
367 setFlashVars(fv
->second
);
370 // Load the actual movie.
371 _movieDef
= load_movie();
373 throw GnashException("Could not load movie!");
376 // Get info about the width & height of the movie.
377 const size_t movie_width
= _movieDef
->get_width_pixels();
378 const size_t movie_height
= _movieDef
->get_height_pixels();
381 _width
= static_cast<size_t>(movie_width
* _scale
);
384 _height
= static_cast<size_t>(movie_height
* _scale
);
387 if (!_width
|| !_height
) {
388 log_debug(_("Input movie has collapsed dimensions "
389 "%d/%d. Setting to 1/1 and going on."),
391 if (!_width
) _width
= 1;
392 if (!_height
) _height
= 1;
395 // Register movie definition before creating the window
396 _gui
->setMovieDefinition(_movieDef
.get());
398 // Now that we know about movie size, create gui window.
399 _gui
->createWindow(_url
.c_str(), _width
, _height
, _xPosition
, _yPosition
);
401 movie_root
root(*_movieDef
, _gui
->getClock(), *_runResources
);
403 _callbacksHandler
.reset(new CallbacksHandler(*_gui
, *this));
405 // Register Player to receive events from the core (Mouse, Stage,
407 root
.registerEventCallback(_callbacksHandler
.get());
409 // Register Player to receive FsCommand events from the core.
410 root
.registerFSCommandCallback(_callbacksHandler
.get());
412 log_debug("Player Host FD #%d, Player Control FD #%d",
413 _hostfd
, _controlfd
);
415 // Set host requests fd (if any)
416 if ( _hostfd
!= -1 ) {
417 root
.setHostFD(_hostfd
);
420 if (_controlfd
!= -1) {
421 root
.setControlFD(_controlfd
);
424 _gui
->setStage(&root
);
426 // When startStopped is true, stop here after the stage has been
427 // registered, but before the movie has started. Initial loading
428 // and VM initialization have been done by this stage, but not
429 // the complete parsing of the SWF. This is important because
430 // the Gui accesses movie_root to get the sound_handler, but also
431 // because the gui window should be properly set up by this point.
432 RcInitFile
& rcfile
= RcInitFile::getDefaultInstance();
434 if (rcfile
.startStopped()) {
438 // Start loader thread
439 // NOTE: the loader thread might (in IMPORT tag parsing)
440 // create new movies and register them to the MovieLibrary.
441 // If MovieLibrary size exceeded, _movieDef might be
442 // destroyed prematurely. movie_root might actually be
443 // keeping it alive, as Gui might as well, but why relying
444 // on luck ? So we made sure to keep _movieDef by
446 _movieDef
->completeLoad();
449 // 10ms per heart beat
452 _gui
->setInterval(_delay
);
455 _gui
->setTimeout(static_cast<unsigned int>(_exitTimeout
* 1000));
458 if (!_windowID
&& _startFullscreen
) {
459 _gui
->setFullscreen();
462 if (!_windowID
&& _hideMenu
) {
466 // Now handle stage alignment and scale mode. This should be done after
467 // the GUI is created, after its stage member is set, and after the
468 // interface callbacks are registered.
469 it
= _params
.find("salign");
470 if (it
!= _params
.end()) {
471 log_debug("Setting align");
472 const short align
= stringToStageAlign(it
->second
);
473 root
.setStageAlignment(align
);
476 it
= _params
.find("allowscriptaccess");
477 if (it
!= _params
.end()) {
478 std::string access
= it
->second
;
479 StringNoCaseEqual noCaseCompare
;
480 const std::string
& str
= it
->second
;
482 movie_root::AllowScriptAccessMode mode
=
483 movie_root::SCRIPT_ACCESS_SAME_DOMAIN
;
485 if (noCaseCompare(str
, "never")) {
486 mode
= movie_root::SCRIPT_ACCESS_NEVER
;
488 else if (noCaseCompare(str
, "sameDomain")) {
489 mode
= movie_root::SCRIPT_ACCESS_SAME_DOMAIN
;
491 else if (noCaseCompare(str
, "always")) {
492 mode
= movie_root::SCRIPT_ACCESS_ALWAYS
;
494 log_debug("Setting allowscriptaccess to %s", mode
);
495 root
.setAllowScriptAccess(mode
);
498 it
= _params
.find("scale");
499 if (it
!= _params
.end()) {
500 StringNoCaseEqual noCaseCompare
;
501 const std::string
& str
= it
->second
;
502 movie_root::ScaleMode mode
= movie_root::SCALEMODE_SHOWALL
;
504 if (noCaseCompare(str
, "noScale")) {
505 mode
= movie_root::SCALEMODE_NOSCALE
;
507 else if (noCaseCompare(str
, "exactFit")) {
508 mode
= movie_root::SCALEMODE_EXACTFIT
;
510 else if (noCaseCompare(str
, "noBorder")) {
511 mode
= movie_root::SCALEMODE_NOBORDER
;
514 log_debug("Setting scale mode");
515 root
.setStageScaleMode(mode
);
518 // Set up screenshots.
519 if (!_screenshots
.empty()) {
520 std::istringstream
is(_screenshots
);
523 ScreenShotter::FrameList v
;
525 while (std::getline(is
, arg
, ',')) {
526 if (arg
== "last") last
= true;
528 const size_t frame
= boost::lexical_cast
<size_t>(arg
);
531 catch (const boost::bad_lexical_cast
&) {}
534 // Use default if filename is empty.
535 if (_screenshotFile
.empty()) {
536 URL
url(_runResources
->baseURL());
537 std::string::size_type p
= url
.path().rfind('/');
538 const std::string
& name
= (p
== std::string::npos
) ? url
.path() :
539 url
.path().substr(p
+ 1);
540 _screenshotFile
= "screenshot-" + name
+ "-%f";
543 _gui
->requestScreenShots(v
, last
, _screenshotFile
);
548 log_debug("Main loop ended, cleaning up");
550 // Clean up as much as possible, so valgrind will help find actual leaks.
556 Player::CallbacksHandler::exit()
562 Player::CallbacksHandler::error(const std::string
& msg
)
568 Player::CallbacksHandler::yesNo(const std::string
& query
)
570 return _gui
.yesno(query
);
574 Player::CallbacksHandler::call(const std::string
& event
, const std::string
& arg
)
576 StringNoCaseEqual noCaseCompare
;
578 if (event
== "Mouse.hide") {
579 return _gui
.showMouse(false) ? "true" : "false";
582 if (event
== "Mouse.show") {
583 return _gui
.showMouse(true) ? "true" : "false";
586 if (event
== "Stage.displayState") {
587 if (arg
== "fullScreen") _gui
.setFullscreen();
588 else if (arg
== "normal") _gui
.unsetFullscreen();
592 if (event
== "Stage.scaleMode" || event
== "Stage.align" ) {
593 _gui
.updateStageMatrix();
597 if (event
== "Stage.showMenu") {
598 if (noCaseCompare(arg
, "true")) _gui
.showMenu(true);
599 else if (noCaseCompare(arg
, "false")) _gui
.showMenu(false);
603 if (event
== "Stage.resize") {
604 if ( _gui
.isPlugin() ) {
605 log_debug("Player doing nothing on Stage.resize as we're a plugin");
609 // arg contains WIDTHxHEIGHT
610 log_debug("Player got Stage.resize(%s) message", arg
);
612 sscanf(arg
.c_str(), "%dx%d", &width
, &height
);
613 _gui
.resizeWindow(width
, height
);
618 if (event
== "ExternalInterface.Play") {
623 if (event
== "ExternalInterface.StopPlay") {
628 if (event
== "ExternalInterface.Rewind") {
633 if (event
== "ExternalInterface.Pan") {
634 // FIXME: the 3 args are encoded as 1:2:0
635 log_unimpl("ExternalInterface.Pan");
639 if (event
== "ExternalInterface.IsPlaying") {
640 return (_gui
.isStopped()) ? "false" : "true";
643 if (event
== "ExternalInterface.SetZoomRect") {
644 // FIXME: the 4 arguments are encoded as 1:2:0:1
645 log_unimpl("ExternalInterface.SetZoomRect");
649 if (event
== "ExternalInterface.Zoom") {
650 // The 1 argument is a percentage to zoom
651 int percent
= strtol(arg
.c_str(), NULL
, 0);
652 log_unimpl("ExternalInterface.Zoom(%d)", percent
);
656 if (event
== "System.capabilities.screenResolutionX") {
657 std::ostringstream ss
;
658 ss
<< _gui
.getScreenResX();
662 if (event
== "System.capabilities.screenResolutionY") {
663 std::ostringstream ss
;
664 ss
<< _gui
.getScreenResY();
668 if (event
== "System.capabilities.pixelAspectRatio") {
669 std::ostringstream ss
;
670 // Whether the pp actively limits the precision or simply
671 // gets a slightly different result isn't clear.
672 ss
<< std::setprecision(7) << _gui
.getPixelAspectRatio();
676 if (event
== "System.capabilities.screenDPI") {
677 std::ostringstream ss
;
678 ss
<< _gui
.getScreenDPI();
682 if (event
== "System.capabilities.screenColor") {
683 return _gui
.getScreenColor();
686 if (event
== "System.capabilities.playerType") {
687 return _gui
.isPlugin() ? "PlugIn" : "StandAlone";
690 log_error(_("Unhandled callback %s with arguments %s"), event
, arg
);
695 Player::CallbacksHandler::notify(const std::string
& command
,
696 const std::string
& args
)
698 //log_debug(_("fs_callback(%p): %s %s"), (void*)movie, command, args);
700 gnash::RcInitFile
& rcfile
= gnash::RcInitFile::getDefaultInstance();
702 // it's _hostfd, but we're a static method...
703 const int hostfd
= _player
.getHostFD();
705 //log_debug("user-provided host requests fd is %d", hostfd);
706 std::stringstream request
;
707 std::vector
<as_value
> fnargs
;
708 fnargs
.push_back(as_value(command
));
709 fnargs
.push_back(as_value(args
));
710 request
<< ExternalInterface::makeInvoke("fsCommand", fnargs
);
712 std::string requestString
= request
.str();
713 const char* cmd
= requestString
.c_str();
714 size_t len
= requestString
.length();
715 // TODO: should mutex-protect this ?
716 // NOTE: we assuming the hostfd is set in blocking mode here..
717 //log_debug("Attempt to write INVOKE requests fd %d", hostfd);
718 int ret
= write(hostfd
, cmd
, len
);
720 log_error("Could not write to user-provided host "
721 "requests fd %d: %s", hostfd
, strerror(errno
));
723 if ( static_cast<size_t>(ret
) < len
) {
724 log_error("Could only write %d bytes over %d required to "
725 "user-provided host requests fd %d",
729 // Remove the newline for logging
730 requestString
.resize(requestString
.size() - 1);
731 log_debug(_("Sent FsCommand '%s' to host fd %d"),
732 requestString
, hostfd
);
735 /// Fscommands can be ignored using an rcfile setting. As a
736 /// plugin they are always ignored.
737 if (_gui
.isPlugin()) {
738 // We log the request to the fd above
739 log_debug(_("Running as plugin: skipping internal "
740 "handling of FsCommand %s%s."));
744 // This only disables fscommands for the standalone player. In the
745 // plugin or a hosting application, the fscommands are always passed
746 // on; the hosting application should decide what to do with them.
747 // (Or do we want to allow disabling all external communication?)
748 if (rcfile
.ignoreFSCommand()) return;
750 StringNoCaseEqual noCaseCompare
;
752 // There are six defined FsCommands handled by the standalone player:
753 // quit, fullscreen, showmenu, exec, allowscale, and trapallkeys.
756 if (noCaseCompare(command
, "quit")) {
761 // FSCommand fullscreen
762 if (noCaseCompare(command
, "fullscreen")) {
763 if (noCaseCompare(args
, "true")) _gui
.setFullscreen();
764 else if (noCaseCompare(args
, "false")) _gui
.unsetFullscreen();
768 // FSCommand showmenu
769 if (noCaseCompare(command
, "showmenu")) {
770 if (noCaseCompare(args
, "true")) _gui
.showMenu(true);
771 else if (noCaseCompare(args
, "false")) _gui
.showMenu(false);
776 // Note: the pp insists that the file to execute should be in
777 // a subdirectory 'fscommand' of the 'projector' executable's
778 // location. In SWF5 there were no restrictions.
779 if (noCaseCompare(command
, "exec")) {
780 log_unimpl(_("FsCommand exec called with argument %s"), args
);
784 // FSCommand allowscale
785 if (noCaseCompare(command
, "allowscale")) {
786 //log_debug("allowscale: %s", args);
787 if (noCaseCompare(args
, "true")) _gui
.allowScale(true);
789 if (strtol(args
.c_str(), NULL
, 0)) _gui
.allowScale(true);
790 else _gui
.allowScale(false);
795 // FSCommand trapallkeys
796 if (noCaseCompare(command
, "trapallkeys")) {
797 log_unimpl(_("FsCommand trapallkeys called with argument %s"), args
);
801 // The plugin never reaches this point; anything sent to the fd has
802 // been logged already.
803 log_debug(_("FsCommand '%s(%s)' not handled internally"),
814 return createGTKGui(_windowID
, _scale
, _doLoop
, *_runResources
);
818 return createKDEGui(_windowID
, _scale
, _doLoop
, *_runResources
);
822 return createKDE4Gui(_windowID
, _scale
, _doLoop
, *_runResources
);
826 return createSDLGui(_windowID
, _scale
, _doLoop
, *_runResources
);
830 return createAQUAGui(_windowID
, _scale
, _doLoop
, *_runResources
);
834 return createRISCOSGui(_windowID
, _scale
, _doLoop
, *_runResources
);
838 return createFLTKGui(_windowID
, _scale
, _doLoop
, *_runResources
);
842 return createFBGui(_windowID
, _scale
, _doLoop
, *_runResources
);
846 return createAOS4Gui(_windowID
, _scale
, _doLoop
, *_runResources
);
850 return createHaikuGui(_windowID
, _scale
, _doLoop
, *_runResources
);
854 return createDumpGui(_windowID
, _scale
, _doLoop
, *_runResources
);
857 return std::auto_ptr
<Gui
>(new NullGui(_doLoop
, *_runResources
));
862 if (_movieDef
.get()) {
863 log_debug("~Player - _movieDef refcount: %d (1 will be dropped "
864 "now)", _movieDef
->get_ref_count());