1 // plugin.cpp:  Windows "win32" flash player Mozilla plugin, for Gnash.
2 //
3 //   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
4 //   Free Software 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.
15 //
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
19 //
20 
21 #ifdef HAVE_CONFIG_H
22 #include "gnashconfig.h"
23 #endif
24 
25 #include <proto/intuition.h>
26 #include <proto/Picasso96API.h>
27 #include <proto/exec.h>
28 #include <cstdlib>
29 
30 #define FLASH_MAJOR_VERSION "9"
31 #define FLASH_MINOR_VERSION "0"
32 #define FLASH_REV_NUMBER "82"
33 
34 #define MIME_TYPES_HANDLED "application/x-shockwave-flash"
35 // The name must be this value to get flash movies that check the
36 // plugin version to load.
37 #define PLUGIN_NAME "Shockwave Flash"
38 #define MIME_TYPES_DESCRIPTION MIME_TYPES_HANDLED":swf:"PLUGIN_NAME
39 
40 // Some javascript plugin detectors use the description
41 // to decide the flash version to display. They expect the
42 // form (major version).(minor version) r(revision).
43 // e.g. "8.0 r99."
44 #ifndef FLASH_MAJOR_VERSION
45 #define FLASH_MAJOR_VERSION DEFAULT_FLASH_MAJOR_VERSION
46 #endif
47 #ifndef FLASH_MINOR_VERSION
48 #define FLASH_MINOR_VERSION DEFAULT_FLASH_MINOR_VERSION
49 #endif
50 #ifndef FLASH_REV_NUMBER
51 #define FLASH_REV_NUMBER DEFAULT_FLASH_REV_NUMBER
52 #endif
53 #define FLASH_VERSION FLASH_MAJOR_VERSION"."\
54     FLASH_MINOR_VERSION" r"FLASH_REV_NUMBER"."
55 
56 #define PLUGIN_DESCRIPTION \
57   "Shockwave Flash "FLASH_VERSION" Gnash "VERSION", the GNU SWF Player. \
58   Copyright &copy; 2006, 2007, 2008 \
59   <a href=\"http://www.fsf.org\">Free Software Foundation</a>, Inc.<br> \
60   Gnash comes with NO WARRANTY, to the extent permitted by law. \
61   You may redistribute copies of Gnash under the terms of the \
62   <a href=\"http://www.gnu.org/licenses/gpl.html\">GNU General Public \
63   License</a>. For more information about Gnash, see <a \
64   href=\"http://www.gnu.org/software/gnash/\"> \
65   http://www.gnu.org/software/gnash</a>. \
66   Compatible Shockwave Flash "FLASH_VERSION
67 
68 #include <cstdarg>
69 #include <cstdint>
70 #include <fstream>
71 
72 #include "plugin.h"
73 
74 static int module_initialized = FALSE;
75 static PRLock* playerLock = NULL;
76 static int instances = 0;
77 
78 #ifdef NPAPI_CONST
79 const
80 #endif
81 char* NPP_GetMIMEDescription(void);
82 static void playerThread(void *arg);
83 //static LRESULT CALLBACK PluginWinProc(HWND, UINT, WPARAM, LPARAM);
84 
85 #define DBG(x, ...) __DBG(x, ## __VA_ARGS__)
86 inline void
__DBG(const char * fmt,...)87 __DBG(const char *fmt, ...)
88 {
89     char buf[1024];
90     va_list ap;
91 
92     va_start(ap, fmt);
93     vsnprintf(buf, sizeof(buf) - 1, fmt, ap);
94     va_end(ap);
95     printf(buf);
96 }
97 
98 // general initialization and shutdown
99 
100 NPError
NS_PluginInitialize(void)101 NS_PluginInitialize(void)
102 {
103     DBG("NS_PluginInitialize\n");
104     if (!playerLock) {
105         playerLock = PR_NewLock();
106     }
107     module_initialized = TRUE;
108     return NPERR_NO_ERROR;
109 }
110 
111 void
NS_PluginShutdown(void)112 NS_PluginShutdown(void)
113 {
114     DBG("NS_PluginShutdown\n");
115     if (!module_initialized) return;
116     if (playerLock) {
117 		PR_DestroyLock(playerLock);
118 		playerLock = NULL;
119     }
120 }
121 
122 /// \brief Return the MIME Type description for this plugin.
123 #ifdef NPAPI_CONST
124 const
125 #endif
126 char*
NPP_GetMIMEDescription(void)127 NPP_GetMIMEDescription(void)
128 {
129     if (!module_initialized) return NULL;
130     return MIME_TYPES_HANDLED;
131 }
132 
133 #if 0
134 /// \brief Retrieve values from the plugin for the Browser
135 ///
136 /// This C++ function is called by the browser to get certain
137 /// information is needs from the plugin. This information is the
138 /// plugin name, a description, etc...
139 ///
140 /// This is actually not used on Win32 (XP_WIN), only on Unix (XP_UNIX).
141 NPError
142 NS_PluginGetValue(NPPVariable aVariable, void *aValue)
143 {
144     NPError err = NPERR_NO_ERROR;
145 
146     if (!module_initialized) return NPERR_NO_ERROR;
147     DBG("aVariable = %d\n", aVariable);
148     switch (aVariable) {
149         case NPPVpluginNameString:
150             *static_cast<char **> (aValue) = PLUGIN_NAME;
151             break;
152 
153         // This becomes the description field you see below the opening
154         // text when you type about:plugins and in
155         // navigator.plugins["Shockwave Flash"].description, used in
156         // many flash version detection scripts.
157         case NPPVpluginDescriptionString:
158             *static_cast<const char **>(aValue) = PLUGIN_DESCRIPTION;
159             break;
160 
161         case NPPVpluginNeedsXEmbed:
162 #ifdef HAVE_GTK2
163             *static_cast<PRBool *>(aValue) = PR_TRUE;
164 #else
165             *static_cast<PRBool *>(aValue) = PR_FALSE;
166 #endif
167             break;
168 
169         case NPPVpluginTimerInterval:
170 
171         case NPPVpluginKeepLibraryInMemory:
172 
173         default:
174             err = NPERR_INVALID_PARAM;
175             break;
176     }
177     return err;
178 }
179 #endif
180 
181 // construction and destruction of our plugin instance object
182 
183 nsPluginInstanceBase*
NS_NewPluginInstance(nsPluginCreateData * aCreateDataStruct)184 NS_NewPluginInstance(nsPluginCreateData* aCreateDataStruct)
185 {
186     DBG("NS_NewPluginInstance\n");
187     if (!module_initialized) return NULL;
188     if (instances > 0) {
189         return NULL;
190     }
191     instances++; // N.B. This is a threading race condition. FIXME.
192     if (!playerLock) {
193         playerLock = PR_NewLock();
194     }
195     if (!aCreateDataStruct) {
196         return NULL;
197     }
198     return new nsPluginInstance(aCreateDataStruct);
199 }
200 
201 void
NS_DestroyPluginInstance(nsPluginInstanceBase * aPlugin)202 NS_DestroyPluginInstance(nsPluginInstanceBase* aPlugin)
203 {
204     DBG("NS_DestroyPluginInstance\n");
205     if (!module_initialized) return;
206     if (aPlugin) {
207         delete (nsPluginInstance *) aPlugin;
208     }
209     if (playerLock) {
210 		PR_DestroyLock(playerLock);
211 		playerLock = NULL;
212     }
213     instances--;
214 }
215 
216 // nsPluginInstance class implementation
217 
218 /// \brief Constructor
nsPluginInstance(nsPluginCreateData * data)219 nsPluginInstance::nsPluginInstance(nsPluginCreateData* data) :
220     nsPluginInstanceBase(),
221     _instance(data->instance),
222     _window(NULL),
223     _initialized(FALSE),
224     _shutdown(FALSE),
225     _stream(NULL),
226     _url(""),
227     _thread(NULL),
228     _width(0),
229     _height(0),
230     _rowstride(0),
231     _hMemDC(NULL),
232     _bmp(NULL),
233     _memaddr(NULL),
234     mouse_x(0),
235     mouse_y(0),
236     mouse_buttons(0),
237     _oldWndProc(NULL)
238 {
239     DBG("nsPluginInstance::nsPluginInstance\n");
240 }
241 
242 /// \brief Destructor
~nsPluginInstance()243 nsPluginInstance::~nsPluginInstance()
244 {
245     DBG("nsPluginInstance::~nsPluginInstance\n");
246     if (_memaddr) {
247         // Deleting _bmp should free this memory.
248         _memaddr = NULL;
249     }
250     if (_hMemDC) {
251         //DeleteObject(_hMemDC);
252         IExec->FreeVec(_hMemDC);
253         _hMemDC = NULL;
254     }
255     if (_bmp) {
256         //DeleteObject(_bmp);
257         IP96->p96FreeBitMap(_bmp);
258         _bmp = NULL;
259     }
260 }
261 
262 NPBool
init(NPWindow * aWindow)263 nsPluginInstance::init(NPWindow* aWindow)
264 {
265     DBG("nsPluginInstance::init\n");
266 
267     if (!aWindow) {
268         DBG("aWindow == NULL\n");
269         return FALSE;
270     }
271 
272     _x = aWindow->x;
273     _y = aWindow->y;
274     _width = aWindow->width;
275     _height = aWindow->height;
276     _window = (struct Window *) aWindow->window;
277     // Windows DIB row stride is always a multiple of 4 bytes.
278     _rowstride = /* 24 bits */ 3 * _width;
279     _rowstride += _rowstride % 4;
280 
281 	/*
282     memset(&_bmpInfo, 0, sizeof(BITMAPINFOHEADER));
283     _bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
284     _bmpInfo.bmiHeader.biWidth = _width;
285     // Negative height means first row comes first in memory.
286     _bmpInfo.bmiHeader.biHeight = -1 * _height;
287     _bmpInfo.bmiHeader.biPlanes = 1;
288     _bmpInfo.bmiHeader.biBitCount = 24;
289     _bmpInfo.bmiHeader.biCompression = BI_RGB;
290     _bmpInfo.bmiHeader.biSizeImage = 0;
291     _bmpInfo.bmiHeader.biXPelsPerMeter = 0;
292     _bmpInfo.bmiHeader.biYPelsPerMeter = 0;
293     _bmpInfo.bmiHeader.biClrUsed = 0;
294     _bmpInfo.bmiHeader.biClrImportant = 0;
295 
296     HDC hDC = GetDC(_window);
297     _hMemDC = CreateCompatibleDC(hDC);
298     _bmp = CreateDIBSection(_hMemDC, &_bmpInfo,
299             DIB_RGB_COLORS, (void **) &_memaddr, 0, 0);
300     SelectObject(_hMemDC, _bmp);
301     */
302 
303 	_bmp = IP96->p96AllocBitMap(_width, _height, 24, BMF_MINPLANES | BMF_DISPLAYABLE,((struct Window *)aWindow)->RPort->BitMap, (RGBFTYPE)0);
304     DBG("aWindow->type: %s (%u)\n",
305             (aWindow->type == NPWindowTypeWindow) ? "NPWindowTypeWindow" :
306             (aWindow->type == NPWindowTypeDrawable) ? "NPWindowTypeDrawable" :
307             "unknown",
308             aWindow->type);
309 
310     // subclass window so we can intercept window messages and
311     // do our drawing to it
312     //_oldWndProc = SubclassWindow(_window, (WNDPROC) PluginWinProc);
313 
314     // associate window with our nsPluginInstance object so we can access
315     // it in the window procedure
316     //SetWindowLong(_window, GWL_USERDATA, (LONG) this);
317 
318     _initialized = TRUE;
319     return TRUE;
320 }
321 
322 void
shut(void)323 nsPluginInstance::shut(void)
324 {
325     DBG("nsPluginInstance::shut\n");
326 
327     DBG("Acquiring playerLock mutex for shutdown.\n");
328     PR_Lock(playerLock);
329     _shutdown = TRUE;
330     DBG("Releasing playerLock mutex for shutdown.\n");
331     PR_Unlock(playerLock);
332 
333     if (_thread) {
334         DBG("Waiting for thread to terminate.\n");
335         PR_JoinThread(_thread);
336         _thread = NULL;
337     }
338 
339     // subclass it back
340     //SubclassWindow(_window, _oldWndProc);
341 
342     _initialized = FALSE;
343 }
344 
345 NPError
NewStream(NPMIMEType type,NPStream * stream,NPBool seekable,std::uint16_t * stype)346 nsPluginInstance::NewStream(NPMIMEType type, NPStream *stream,
347         NPBool seekable, std::uint16_t *stype)
348 {
349     DBG("nsPluginInstance::NewStream\n");
350     DBG("stream->url: %s\n", stream->url);
351 
352     if (!_stream) {
353         _stream = stream;
354         _url = stream->url;
355 #if 0
356         if (seekable) {
357             *stype = NP_SEEK;
358         }
359 #endif
360     }
361 
362     return NPERR_NO_ERROR;
363 }
364 
365 NPError
DestroyStream(NPStream * stream,NPError reason)366 nsPluginInstance::DestroyStream(NPStream *stream, NPError reason)
367 {
368     DBG("nsPluginInstance::DestroyStream\n");
369     DBG("stream->url: %s\n", stream->url);
370 
371     // N.B. We can only support one Gnash VM/thread right now.
372     if (!_thread) {
373         _thread = PR_CreateThread(PR_USER_THREAD, playerThread, this,
374                 PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0);
375     }
376 
377     return NPERR_NO_ERROR;
378 }
379 
380 int32
Write(NPStream * stream,int32 offset,int32 len,void * buffer)381 nsPluginInstance::Write(NPStream *stream, int32 offset, int32 len,
382         void *buffer)
383 {
384     DBG("nsPluginInstance::Write\n");
385     DBG("stream->url: %s, offset: %ld, len: %ld\n",
386             stream->url, offset, len);
387 }
388 
389 static void
playerThread(void * arg)390 playerThread(void *arg)
391 {
392     nsPluginInstance *plugin = (nsPluginInstance *) arg;
393 
394     plugin->threadMain();
395 }
396 
397 void
threadMain(void)398 nsPluginInstance::threadMain(void)
399 {
400     DBG("nsPluginInstance::threadMain started\n");
401     DBG("URL: %s\n", _url.c_str());
402 
403 	PR_Lock(playerLock);
404 
405     // Initialize Gnash core library.
406     DBG("Gnash core initialized.\n");
407 
408     // Init logfile.
409     gnash::RcInitFile& rcinit = gnash::RcInitFile::getDefaultInstance();
410     std::string logfilename = std::string("T:npgnash.log");
411     rcinit.setDebugLog(logfilename);
412     gnash::LogFile& dbglogfile = gnash::LogFile::getDefaultInstance();
413     dbglogfile.setWriteDisk(true);
414     dbglogfile.setVerbosity(GNASH_DEBUG_LEVEL);
415     DBG("Gnash logging initialized: %s\n", logfilename.c_str());
416 
417     // Init sound.
418     //_sound_handler.reset(gnash::sound::create_sound_handler_sdl());
419     //gnash::set_sound_handler(_sound_handler.get());
420     DBG("Gnash sound initialized.\n");
421 
422     // Init GUI.
423     int old_mouse_x = 0, old_mouse_y = 0, old_mouse_buttons = 0;
424     _render_handler =
425         (gnash::render_handler *) gnash::create_render_handler_agg("BGR24");
426     // _memaddr = (unsigned char *) malloc(getMemSize());
427     static_cast<gnash::render_handler_agg_base *>(_render_handler)->init_buffer(
428             getMemAddr(), getMemSize(), _width, _height, _rowstride);
429     gnash::set_render_handler(_render_handler);
430     DBG("Gnash GUI initialized: %ux%u\n", _width, _height);
431 
432     gnash::URL url(_url);
433 
434     VariableMap vars;
435     gnash::URL::parse_querystring(url.querystring(), vars);
436     for (VariableMap::iterator i = vars.begin(), ie = vars.end(); i != ie; ++i) {
437         _flashVars[i->first] = i->second;
438     }
439 
440     gnash::set_base_url(url);
441 
442     gnash::movie_definition* md = NULL;
443     try {
444         md = gnash::createMovie(url, _url.c_str(), false);
445     } catch (const gnash::GnashException& err) {
446         md = NULL;
447     }
448     if (!md) {
449         /*
450          * N.B. Can't use the goto here, as C++ complains about "jump to
451          * label 'done' from here crosses initialization of ..." a bunch
452          * of things.  Sigh.  So, instead, I duplicate the cleanup code
453          * here.  TODO: Remove this duplication.
454          */
455         // goto done;
456 
457         PR_Unlock(playerLock);
458 
459         DBG("Clean up Gnash.\n");
460         //gnash::clear();
461 
462         DBG("nsPluginInstance::threadMain exiting\n");
463         return;
464     }
465     DBG("Movie created: %s\n", _url.c_str());
466 
467     int movie_width = static_cast<int>(md->get_width_pixels());
468     int movie_height = static_cast<int>(md->get_height_pixels());
469     float movie_fps = md->get_frame_rate();
470     DBG("Movie dimensions: %ux%u (%.2f fps)\n",
471             movie_width, movie_height, movie_fps);
472 
473     gnash::SystemClock clock; // use system clock here...
474     gnash::movie_root& root = gnash::VM::init(*md, clock).getRoot();
475     DBG("Gnash VM initialized.\n");
476 
477     // Register this plugin as listener for FsCommands from the core
478     // (movie_root)
479 #if 0
480     /* Commenting out for now as registerFSCommandCallback() has changed. */
481     root.registerFSCommandCallback(FSCommand_callback);
482 #endif
483 
484     // Register a static function to handle ActionScript events such
485     // as Mouse.hide, Stage.align etc.
486     // root.registerEventCallback(&staticEventHandlingFunction);
487 
488     md->completeLoad();
489     DBG("Movie loaded.\n");
490 
491     std::unique_ptr<gnash::Movie> mr(md->createMovie());
492     mr->setVariables(_flashVars);
493     root.setRootMovie(mr.release());
494     //root.set_display_viewport(0, 0, _width, _height);
495     root.set_background_alpha(1.0f);
496     gnash::Movie* mi = root.getRootMovie();
497     DBG("Movie instance created.\n");
498 
499     //ShowWindow(_window, SW_SHOW);
500 	IIntuition->ShowWindow(_window,NULL);
501 
502     for (;;) {
503         // DBG("Inside main thread loop.\n");
504 
505         if (_shutdown) {
506             DBG("Main thread shutting down.\n");
507             break;
508         }
509 
510         size_t cur_frame = mi->get_current_frame();
511         // DBG("Got current frame number: %d.\n", cur_frame);
512         size_t tot_frames = mi->get_frame_count();
513         // DBG("Got total frame count: %d.\n", tot_frames);
514 
515         // DBG("Advancing one frame.\n");
516         root.advance();
517         // DBG("Going to next frame.\n");
518         root.goto_frame(cur_frame + 1);
519         // DBG("Ensuring frame is loaded.\n");
520         //root.get_movie_definition()->ensure_frame_loaded(tot_frames);
521         // DBG("Setting play state to PLAY.\n");
522         root.set_play_state(gnash::MovieClip::PLAYSTATE_PLAY);
523 
524         if (old_mouse_x != mouse_x || old_mouse_y != mouse_y) {
525             old_mouse_x = mouse_x;
526             old_mouse_y = mouse_y;
527             //root.notify_mouse_moved(mouse_x, mouse_y);
528         }
529         if (old_mouse_buttons != mouse_buttons) {
530             old_mouse_buttons = mouse_buttons;
531             int mask = 1;
532             //root.notify_mouse_clicked(mouse_buttons > 0, mask);
533         }
534 
535         root.display();
536 
537 #if 0
538         RECT rt;
539         GetClientRect(_window, &rt);
540         InvalidateRect(_window, &rt, FALSE);
541 
542         InvalidatedRanges ranges;
543         ranges.setSnapFactor(1.3f);
544         ranges.setSingleMode(false);
545         root.add_invalidated_bounds(ranges, false);
546         ranges.growBy(40.0f);
547         ranges.combine_ranges();
548 
549         if (!ranges.isNull()) {
550             InvalidateRect(_window, &rt, FALSE);
551         }
552 
553         root.display();
554 #endif
555 
556         // DBG("Unlocking playerLock mutex.\n");
557         PR_Unlock(playerLock);
558         // DBG("Sleeping.\n");
559         PR_Sleep(PR_INTERVAL_MIN);
560         // DBG("Acquiring playerLock mutex.\n");
561         PR_Lock(playerLock);
562     }
563 
564 done:
565 	PR_Unlock(playerLock);
566 
567     DBG("Clean up Gnash.\n");
568 
569     /*
570      * N.B.  As per server/impl.cpp:clear(), all of Gnash's threads aren't
571      * guaranteed to be terminated by this, yet.  Therefore, when Firefox
572      * unloads npgnash.dll after calling NS_PluginShutdown(), and there are
573      * still Gnash threads running, they will try and access memory that was
574      * freed as part of the unloading of npgnash.dll, resulting in a process
575      * abend.
576      */
577 
578     //gnash::clear();
579 
580     DBG("nsPluginInstance::threadMain exiting\n");
581 }
582 
583 const char*
getVersion()584 nsPluginInstance::getVersion()
585 {
586     return NPN_UserAgent(_instance);
587 }
588 
589 void
FSCommand_callback(gnash::MovieClip * movie,const std::string & command,const std::string & args)590 nsPluginInstance::FSCommand_callback(gnash::MovieClip* movie, const std::string& command, const std::string& args)
591 // For handling notification callbacks from ActionScript.
592 {
593     gnash::log_debug(_("FSCommand_callback(%p): %s %s"), (void*) movie, command, args);
594 }
595 
596 /*
597 static LRESULT CALLBACK
598 PluginWinProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
599 {
600     // get our plugin instance object
601     nsPluginInstance *plugin =
602         (nsPluginInstance *) GetWindowLong(hWnd, GWL_USERDATA);
603 
604     if (plugin) {
605         switch (msg) {
606             case WM_PAINT:
607             {
608                 if (plugin->getMemAddr() == NULL) {
609                     break;
610                 }
611 
612                 PAINTSTRUCT ps;
613                 HDC hDC = BeginPaint(hWnd, &ps);
614 
615                 RECT rt;
616                 GetClientRect(hWnd, &rt);
617                 int w = rt.right - rt.left;
618                 int h = rt.bottom - rt.top;
619 
620                 BitBlt(hDC, rt.left, rt.top, w, h,
621                         plugin->getMemDC(), 0, 0, SRCCOPY);
622 
623                 EndPaint(hWnd, &ps);
624                 return 0L;
625             }
626             case WM_MOUSEMOVE:
627             {
628                 int x = GET_X_LPARAM(lParam);
629                 int y = GET_Y_LPARAM(lParam);
630 
631                 plugin->notify_mouse_state(x, y, -1);
632                 break;
633             }
634             case WM_LBUTTONDOWN:
635             case WM_LBUTTONUP:
636             {
637                 int x = GET_X_LPARAM(lParam);
638                 int y = GET_Y_LPARAM(lParam);
639                 int buttons = (msg == WM_LBUTTONDOWN) ? 1 : 0;
640 
641                 plugin->notify_mouse_state(x, y, buttons);
642                 break;
643             }
644             default:
645 //                dbglogfile << "msg " << msg << endl;
646                 break;
647         }
648     }
649     return DefWindowProc(hWnd, msg, wParam, lParam);
650 }
651 */
652