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 © 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