// Filename: ppInstance.cxx // Created by: drose (19Jun09) // //////////////////////////////////////////////////////////////////// // // PANDA 3D SOFTWARE // Copyright (c) Carnegie Mellon University. All rights reserved. // // All use of this software is subject to the terms of the revised BSD // license. You should have received a copy of this license along // with this source code in a file named "LICENSE." // //////////////////////////////////////////////////////////////////// #include "ppInstance.h" #include "ppPandaObject.h" #include "ppToplevelObject.h" #include "ppBrowserObject.h" #include "startup.h" #include "p3d_plugin_config.h" #include "mkdir_complete.h" #include "parse_color.h" #include "nppanda3d_common.h" // We can include this header file to get the DTOOL_PLATFORM // definition, even though we don't link with dtool. #include "dtool_platform.h" #include "pandaVersion.h" #include #include #include // strcmp() #include #ifndef _WIN32 #include #include #endif // _WIN32 #ifdef HAVE_X11 #include #include #endif // HAVE_X11 PPInstance::FileDatas PPInstance::_file_datas; //////////////////////////////////////////////////////////////////// // Function: PPInstance::Constructor // Access: Public // Description: Creates a new instance of a Panda3D plugin window. // The create_data structure is supplied from NPAPI, and // defines the initial parameters specified in the HTML // document. //////////////////////////////////////////////////////////////////// PPInstance:: PPInstance(NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc, char *argn[], char *argv[], NPSavedData *saved, P3D_window_handle_type window_handle_type, P3D_event_type event_type) { _p3d_inst = NULL; _npp_instance = instance; _npp_mode = mode; _window_handle_type = window_handle_type; _event_type = event_type; _script_object = NULL; _contents_expiration = 0; _failed = false; _started = false; // Copy the tokens and save them within this object. _tokens.reserve(argc); for (int i = 0; i < argc; ++i) { if (argn[i] != NULL) { const char *v = argv[i]; if (v == NULL) { // Firefox might give us a NULL argv[i] in some cases. v = ""; } // Make the token lowercase, since HTML is case-insensitive but // we're not. string keyword; for (const char *p = argn[i]; *p; ++p) { keyword += tolower(*p); } P3D_token token; token._keyword = strdup(keyword.c_str()); token._value = strdup(v); _tokens.push_back(token); } } _root_dir = global_root_dir; _got_instance_url = false; _p3d_instance_id = 0; // fgcolor and bgcolor are useful to know here (in case we have to // draw a twirling icon). // The default bgcolor is white. _bgcolor_r = _bgcolor_g = _bgcolor_b = 0xff; if (has_token("bgcolor")) { int r, g, b; if (parse_color(r, g, b, lookup_token("bgcolor"))) { _bgcolor_r = r; _bgcolor_g = g; _bgcolor_b = b; } } // The default fgcolor is either black or white, according to the // brightness of the bgcolor. if (_bgcolor_r + _bgcolor_g + _bgcolor_b > 0x80 + 0x80 + 0x80) { _fgcolor_r = _fgcolor_g = _fgcolor_b = 0x00; } else { _fgcolor_r = _fgcolor_g = _fgcolor_b = 0xff; } if (has_token("fgcolor")) { int r, g, b; if (parse_color(r, g, b, lookup_token("fgcolor"))) { _fgcolor_r = r; _fgcolor_g = g; _fgcolor_b = b; } } _use_xembed = false; _got_window = false; _python_window_open = false; #ifdef HAVE_X11 _twirl_subprocess_pid = -1; #ifdef HAVE_GTK _plug = NULL; #endif // HAVE_GTK #endif // HAVE_X11 #ifdef _WIN32 _hwnd = NULL; _bg_brush = NULL; #endif // _WIN32 #ifndef _WIN32 // Save the startup time to improve precision of gettimeofday(). We // also use this to measure elapsed time from the window parameters // having been received. struct timeval tv; gettimeofday(&tv, (struct timezone *)NULL); _init_sec = tv.tv_sec; _init_usec = tv.tv_usec; #endif // !_WIN32 #ifdef __APPLE__ // Get the run loop in the browser thread. (CFRunLoopGetMain() is // only 10.5 or higher. Plus, the browser thread is not necessarily // the "main" thread.) _run_loop_main = CFRunLoopGetCurrent(); CFRetain(_run_loop_main); _request_timer = NULL; INIT_LOCK(_timer_lock); // Also set up a timer to twirl the icon until the instance loads. _twirl_timer = NULL; CFRunLoopTimerContext timer_context; memset(&timer_context, 0, sizeof(timer_context)); timer_context.info = this; _twirl_timer = CFRunLoopTimerCreate (NULL, 0, 0.1, 0, 0, st_twirl_timer_callback, &timer_context); CFRunLoopAddTimer(_run_loop_main, _twirl_timer, kCFRunLoopCommonModes); #endif // __APPLE__ #ifdef MACOSX_HAS_EVENT_MODELS _got_twirl_images = false; #endif // MACOSX_HAS_EVENT_MODELS } //////////////////////////////////////////////////////////////////// // Function: PPInstance::Destructor // Access: Public // Description: //////////////////////////////////////////////////////////////////// PPInstance:: ~PPInstance() { cleanup_window(); #ifdef __APPLE__ if (_twirl_timer != NULL) { CFRunLoopTimerInvalidate(_twirl_timer); CFRelease(_twirl_timer); _twirl_timer = NULL; } if (_request_timer != NULL) { CFRunLoopTimerInvalidate(_request_timer); CFRelease(_request_timer); _request_timer = NULL; } _run_loop_main = CFRunLoopGetCurrent(); CFRelease(_run_loop_main); DESTROY_LOCK(_timer_lock); #endif // __APPLE__ #ifdef MACOSX_HAS_EVENT_MODELS osx_release_twirl_images(); #endif // MACOSX_HAS_EVENT_MODELS if (_p3d_inst != NULL) { P3D_instance_finish_ptr(_p3d_inst); _p3d_inst = NULL; } assert(_streams.empty()); assert(_file_datas.empty()); if (_script_object != NULL) { browser->releaseobject(_script_object); } // Free the tokens we allocated. Tokens::iterator ti; for (ti = _tokens.begin(); ti != _tokens.end(); ++ti) { free((char *)(*ti)._keyword); free((char *)(*ti)._value); } _tokens.clear(); } //////////////////////////////////////////////////////////////////// // Function: PPInstance::begin // Access: Public // Description: Begins the initial download of the core API. This // should be called after constructing the PPInstance. // It is a separate method than the constructor, because // it initiates some callbacks that might rely on the // object having been fully constructed and its pointer // stored. //////////////////////////////////////////////////////////////////// void PPInstance:: begin() { // On Windows and Linux, we must insist on having this call. OSX // doesn't necessarily require it (which is lucky, since it appears // that Safari doesn't necessarily provide it!) #ifndef __APPLE__ if (!has_plugin_thread_async_call) { nout << "Browser version insufficient: we require at least NPAPI version 0.19.\n"; set_failed(); } #else // While Safari 5 on Mac claims to provide this function, it doesn't // appear to work. (!) So we pretend we never have it on Mac. // Fortunately, this hack does us no harm because the _request_timer // hack works fine on OSX. has_plugin_thread_async_call = false; #endif // __APPLE__ string url = PANDA_PACKAGE_HOST_URL; if (!url.empty() && url[url.length() - 1] != '/') { url += '/'; } _download_url_prefix = url; _standard_url_prefix = url; nout << "Plugin is built with " << PANDA_PACKAGE_HOST_URL << "\n"; if (!is_plugin_loaded() && !_failed) { // We need to read the contents.xml file. First, check to see if // the version on disk is already current enough. bool success = false; string contents_filename = _root_dir + "/contents.xml"; if (read_contents_file(contents_filename, false)) { if (time(NULL) < _contents_expiration) { // Got the file, and it's good. get_core_api(); success = true; } } if (!success) { // Go download the latest contents.xml file. _download_url_prefix = _standard_url_prefix; _mirrors.clear(); ostringstream strm; strm << _download_url_prefix << "contents.xml"; // Append a uniquifying query string to the URL to force the // download to go all the way through any caches. We use the time // in seconds; that's unique enough. strm << "?" << time(NULL); url = strm.str(); PPDownloadRequest *req = new PPDownloadRequest(PPDownloadRequest::RT_contents_file); start_download(url, req); } } handle_request_loop(); } //////////////////////////////////////////////////////////////////// // Function: PPInstance::set_window // Access: Public // Description: Stores or updates the window parameters. //////////////////////////////////////////////////////////////////// void PPInstance:: set_window(NPWindow *window) { if (_got_window && window->x == _window.x && window->y == _window.y && window->width == _window.width && window->height == _window.height) { // No changes. return; } if (_got_window) { // We don't expect the browser to change the window's parent // on-the-fly. assert(_window.window == window->window); } if (!_got_window) { #ifdef _WIN32 _orig_window_proc = NULL; if (window->type == NPWindowTypeWindow) { // Save the window handle. _hwnd = (HWND)window->window; // Now that we've got a window handle, we can go get the // twirling icon images. win_get_twirl_bitmaps(); _bg_brush = CreateSolidBrush(RGB(_bgcolor_r, _bgcolor_g, _bgcolor_b)); // Subclass the window to make it call our own window_proc // instead of whatever window_proc it has already. This is // mainly just a dopey trick to allow us to poll events in the // main thread, but we also rely on this to paint the twirling // icon into the browser window. SetWindowLongPtr(_hwnd, GWLP_USERDATA, (LONG_PTR)this); _orig_window_proc = SetWindowLongPtr(_hwnd, GWL_WNDPROC, (LONG_PTR)st_window_proc); // Also set a timer to go off every once in a while, to update // the twirling icon, and also to catch events in case something // slips through. _init_time = GetTickCount(); SetTimer(_hwnd, 1, 100, NULL); } #endif // _WIN32 #ifdef MACOSX_HAS_EVENT_MODELS osx_get_twirl_images(); #endif // MACOSX_HAS_EVENT_MODELS } #if defined(HAVE_GTK) && defined(HAVE_X11) if (_use_xembed) { if (!_got_window || _window.window != window->window) { // The window has changed. Destroy the old GtkPlug. if (_plug != NULL) { gtk_widget_destroy(_plug); _plug = NULL; } // Create a new GtkPlug to bind to the XEmbed socket. _plug = gtk_plug_new((GdkNativeWindow) reinterpret_cast(window->window)); gtk_widget_show(_plug); nout << "original XID is " << window->window << ", created X11 window " << GDK_DRAWABLE_XID(_plug->window) << "\n"; } } #endif // HAVE_GTK && HAVE_X11 _window = *window; _got_window = true; #ifdef HAVE_X11 if (!_failed && !_started) { x11_start_twirl_subprocess(); } #endif // HAVE_X11 if (!_failed) { if (_p3d_inst == NULL) { create_instance(); } else { send_window(); } } } //////////////////////////////////////////////////////////////////// // Function: PPInstance::new_stream // Access: Public // Description: Receives notification of a new stream object, e.g. a // url request. //////////////////////////////////////////////////////////////////// NPError PPInstance:: new_stream(NPMIMEType type, NPStream *stream, bool seekable, uint16_t *stype) { assert(find(_streams.begin(), _streams.end(), stream) == _streams.end()); if (_failed) { return NPERR_GENERIC_ERROR; } if (stream->notifyData == NULL) { // This is an unsolicited stream. Assume the first unsolicited // stream we receive is the instance data; any other unsolicited // stream is an error. if (!_got_instance_url && stream->url != NULL) { _got_instance_url = true; _instance_url = stream->url; stream->notifyData = new PPDownloadRequest(PPDownloadRequest::RT_instance_data); if (_p3d_inst != NULL) { // If we already have an instance by the time we get this // stream, start sending the data to the instance (instead of // having to mess around with a temporary file). _p3d_instance_id = P3D_instance_start_stream_ptr(_p3d_inst, _instance_url.c_str()); nout << "p3d instance to stream " << _p3d_instance_id << "\n"; } *stype = NP_NORMAL; _streams.push_back(stream); return NPERR_NO_ERROR; } // Don't finish downloading the unsolicited stream. return NPERR_GENERIC_ERROR; } PPDownloadRequest *req = (PPDownloadRequest *)(stream->notifyData); switch (req->_rtype) { case PPDownloadRequest::RT_contents_file: // This is the initial contents.xml file. We used to download // this via NP_ASFILEONLY, but that option doesn't work on Windows // within a Unicode user directory. So we use NP_NORMAL instead. *stype = NP_NORMAL; _streams.push_back(stream); return NPERR_NO_ERROR; case PPDownloadRequest::RT_core_dll: // This is the core API DLL (or dylib or whatever). Again, we // have to use NP_NORMAL. *stype = NP_NORMAL; _streams.push_back(stream); return NPERR_NO_ERROR; case PPDownloadRequest::RT_user: // This is a request from the plugin. We'll receive this as a // stream. *stype = NP_NORMAL; _streams.push_back(stream); return NPERR_NO_ERROR; default: // Don't know what this is. nout << "Unexpected request " << (int)req->_rtype << "\n"; } return NPERR_GENERIC_ERROR; } //////////////////////////////////////////////////////////////////// // Function: PPInstance::stop_outstanding_streams // Access: Public // Description: Stops any download streams that are currently active // on the instance. It is necessary to call this // explicitly before destroying the instance, at least // for Safari. //////////////////////////////////////////////////////////////////// void PPInstance:: stop_outstanding_streams() { Streams::iterator si; Streams streams; streams.swap(_streams); for (si = streams.begin(); si != streams.end(); ++si) { NPStream *stream = (*si); nout << "Stopping stream " << (void *)stream << "\n"; browser->destroystream(_npp_instance, stream, NPRES_USER_BREAK); destroy_stream(stream, NPRES_USER_BREAK); } assert(_streams.empty()); // Also stop any currently pending _file_datas; these are // locally-implemented streams. FileDatas::iterator fi; for (fi = _file_datas.begin(); fi != _file_datas.end(); ++fi) { delete (*fi); } _file_datas.clear(); } //////////////////////////////////////////////////////////////////// // Function: PPInstance::write_ready // Access: Public // Description: Called by the browser to ask how much data is ready // to be received for the indicated stream. //////////////////////////////////////////////////////////////////// int32_t PPInstance:: write_ready(NPStream *stream) { // We're supposed to return the maximum amount of data the plugin is // prepared to handle. Gee, I don't know. As much as you can give // me, I guess. return 0x7fffffff; } //////////////////////////////////////////////////////////////////// // Function: PPInstance::write_stream // Access: Public // Description: Called by the browser to feed data read from a URL or // whatever. //////////////////////////////////////////////////////////////////// int PPInstance:: write_stream(NPStream *stream, int offset, int len, void *buffer) { if (stream->notifyData == NULL) { nout << "Unexpected write_stream on " << stream->url << "\n"; browser->destroystream(_npp_instance, stream, NPRES_USER_BREAK); return 0; } if (_failed) { // We're done; stop this. browser->destroystream(_npp_instance, stream, NPRES_USER_BREAK); return 0; } PPDownloadRequest *req = (PPDownloadRequest *)(stream->notifyData); switch (req->_rtype) { case PPDownloadRequest::RT_user: P3D_instance_feed_url_stream_ptr(_p3d_inst, req->_user_id, P3D_RC_in_progress, 0, stream->end, buffer, len); return len; case PPDownloadRequest::RT_instance_data: // There's a special case for the RT_instance_data stream. This // is the first, unsolicited stream that indicates the p3d // instance data. We have to send this stream into the instance, // but we can only do this once the instance itself has been // created. // We used to get away with returning 0 in write_ready until the // instance was ready, but that turns out to fail under Safari // Snow Leopard, which it seems will hold up every other download // until the p3d file has been retrieved. Sigh. So we must start // accepting the data even before the instance has been created, // or we'll never get our contents.xml or any other important bits // of data. // Nowadays we solve this problem by writing the data to a // temporary file until the instance is ready for it. if (_p3d_inst == NULL) { // The instance isn't ready, so stuff it in a temporary file. if (!_p3d_temp_file.feed(stream->end, buffer, len)) { set_failed(); } return len; } else { // The instance has been created. Redirect the stream into the // instance. assert(!_p3d_temp_file._opened); req->_rtype = PPDownloadRequest::RT_user; req->_user_id = _p3d_instance_id; P3D_instance_feed_url_stream_ptr(_p3d_inst, req->_user_id, P3D_RC_in_progress, 0, stream->end, buffer, len); return len; } break; case PPDownloadRequest::RT_contents_file: if (!_contents_temp_file.feed(stream->end, buffer, len)) { set_failed(); } return len; case PPDownloadRequest::RT_core_dll: if (!_core_dll_temp_file.feed(stream->end, buffer, len)) { set_failed(); } return len; default: nout << "Unexpected write_stream on " << stream->url << "\n"; break; } browser->destroystream(_npp_instance, stream, NPRES_USER_BREAK); return 0; } //////////////////////////////////////////////////////////////////// // Function: PPInstance::destroy_stream // Access: Public // Description: Called by the browser to mark the end of a stream; // the file has either been successfully downloaded or // failed. //////////////////////////////////////////////////////////////////// NPError PPInstance:: destroy_stream(NPStream *stream, NPReason reason) { Streams::iterator si = find(_streams.begin(), _streams.end(), stream); if (si != _streams.end()) { _streams.erase(si); } if (stream->notifyData == NULL) { nout << "Unexpected destroy_stream on " << stream->url << "\n"; return NPERR_NO_ERROR; } PPDownloadRequest *req = (PPDownloadRequest *)(stream->notifyData); P3D_result_code result_code = P3D_RC_done; if (reason != NPRES_DONE) { if (reason == NPRES_USER_BREAK) { result_code = P3D_RC_shutdown; } else { result_code = P3D_RC_generic_error; } } switch (req->_rtype) { case PPDownloadRequest::RT_user: if (!req->_notified_done) { P3D_instance_feed_url_stream_ptr(_p3d_inst, req->_user_id, result_code, 0, stream->end, NULL, 0); req->_notified_done = true; } break; case PPDownloadRequest::RT_instance_data: if (!req->_notified_done) { if (_p3d_inst == NULL) { // The instance still isn't ready; just mark the data done. // We'll send the entire file to the instance when it is ready. _p3d_temp_file.finish(); if (result_code != P3D_RC_done) { set_failed(); } } else { // The instance has (only just) been created. Tell it we've // sent it all the data it will get. P3D_instance_feed_url_stream_ptr(_p3d_inst, _p3d_instance_id, result_code, 0, stream->end, NULL, 0); } req->_notified_done = true; } break; case PPDownloadRequest::RT_contents_file: if (!req->_notified_done) { _contents_temp_file.finish(); if (result_code != P3D_RC_done) { set_failed(); } req->_notified_done = true; } break; case PPDownloadRequest::RT_core_dll: if (!req->_notified_done) { _core_dll_temp_file.finish(); if (result_code != P3D_RC_done) { set_failed(); } req->_notified_done = true; } break; default: nout << "Unexpected destroy_stream on " << stream->url << "\n"; break; } return NPERR_NO_ERROR; } //////////////////////////////////////////////////////////////////// // Function: PPInstance::url_notify // Access: Public // Description: Called by the browser to announce the end of a // stream. This normally follows destroy_stream(), // unless the stream was never created in the first // place. //////////////////////////////////////////////////////////////////// void PPInstance:: url_notify(const char *url, NPReason reason, void *notifyData) { if (notifyData == NULL) { return; } PPDownloadRequest *req = (PPDownloadRequest *)notifyData; if (_failed) { // We're done; ignore this. delete req; return; } switch (req->_rtype) { case PPDownloadRequest::RT_user: if (!req->_notified_done) { // We shouldn't have gotten here without notifying the stream // unless the stream never got started (and hence we never // called destroy_stream(). nout << "Failure starting stream\n"; assert(reason != NPRES_DONE); P3D_instance_feed_url_stream_ptr(_p3d_inst, req->_user_id, P3D_RC_generic_error, 0, 0, NULL, 0); req->_notified_done = true; } break; case PPDownloadRequest::RT_contents_file: if (reason == NPRES_DONE) { downloaded_contents_file(_contents_temp_file._filename); } else { nout << "Failure downloading " << url << "\n"; if (reason == NPRES_USER_BREAK) { nout << "Failure due to user break\n"; } else { // Couldn't download a fresh contents.xml for some reason. If // there's an outstanding contents.xml file on disk, try to // load that one as a fallback. string contents_filename = _root_dir + "/contents.xml"; if (read_contents_file(contents_filename, false)) { get_core_api(); } else { nout << "Unable to read contents file " << contents_filename << "\n"; set_failed(); } } } break; case PPDownloadRequest::RT_core_dll: if (reason == NPRES_DONE) { downloaded_plugin(_core_dll_temp_file._filename); } else { nout << "Failure downloading " << url << "\n"; if (reason == NPRES_USER_BREAK) { nout << "Failure due to user break\n"; } else { // Couldn't download from this mirror. Try the next one. if (!_core_urls.empty()) { string url = _core_urls.back(); _core_urls.pop_back(); PPDownloadRequest *req2 = new PPDownloadRequest(PPDownloadRequest::RT_core_dll); start_download(url, req2); } } } break; default: nout << "Unexpected url_notify on stream type " << req->_rtype << "\n"; break; } delete req; } //////////////////////////////////////////////////////////////////// // Function: PPInstance::stream_as_file // Access: Public // Description: Called by the browser to report the filename that // contains the fully-downloaded stream contents. //////////////////////////////////////////////////////////////////// void PPInstance:: stream_as_file(NPStream *stream, const char *fname) { if (stream->notifyData == NULL) { nout << "Unexpected stream_as_file on " << stream->url << "\n"; return; } string filename = fname; #ifdef __APPLE__ // Safari seems to want to report the filename in the old-style form // "Macintosh HD:blah:blah:blah" instead of the new-style form // "/blah/blah/blah". How annoying. size_t colon = filename.find(':'); size_t slash = filename.find('/'); if (colon < slash) { // This might be such a filename. string fname2 = "/Volumes/"; for (size_t p = 0; p < filename.size(); ++p) { if (filename[p] == ':') { fname2 += '/'; } else { fname2 += filename[p]; } } if (access(fname2.c_str(), R_OK) == 0) { // Looks like we've converted it successfully. filename = fname2; // Here's another crazy hack. In addition to the weird filename // format, the file that Safari tells us about appears to be a // temporary file that Safari's about to delete. In order to // protect ourselves from this, we need to temporarily copy the // file somewhere else. char *name = tempnam(NULL, "p3d_"); // We prefer just making a hard link; it's quick and easy. if (link(filename.c_str(), name) != 0) { // But sometimes the hard link might fail, particularly if these // are two different file systems. In this case we have to open // the files and copy the data by hand. copy_file(filename, name); } filename = name; free(name); // TODO: remove this temporary file when we're done with it. } } #endif // __APPLE__ PPDownloadRequest *req = (PPDownloadRequest *)(stream->notifyData); downloaded_file(req, filename); } //////////////////////////////////////////////////////////////////// // Function: PPInstance::handle_request // Access: Public // Description: Handles a request from the Core API or the // application, forwarding it to the browser as // appropriate. Returns true if we should continue the // request loop, or false to return (temporarily) to // JavaScript. //////////////////////////////////////////////////////////////////// bool PPInstance:: handle_request(P3D_request *request) { if (_p3d_inst == NULL || _failed) { return false; } assert(request->_instance == _p3d_inst); bool handled = false; bool continue_loop = true; switch (request->_request_type) { case P3D_RT_stop: if (_p3d_inst != NULL) { P3D_instance_finish_ptr(_p3d_inst); _p3d_inst = NULL; } cleanup_window(); // Guess the browser doesn't really care. handled = true; break; case P3D_RT_get_url: { PPDownloadRequest *req = new PPDownloadRequest(PPDownloadRequest::RT_user, request->_request._get_url._unique_id); start_download(request->_request._get_url._url, req); } break; case P3D_RT_notify: // We mostly ignore notifies, since these are handled by the core // API. But we do check for the "onwindowopen" notify, at which // point we start spamming the refresh requests. if (strcmp(request->_request._notify._message, "onwindowopen") == 0) { _python_window_open = true; if (_got_window) { NPRect rect = { 0, 0, (unsigned short)_window.height, (unsigned short)_window.width }; browser->invalidaterect(_npp_instance, &rect); } } break; case P3D_RT_refresh: if (_got_window) { NPRect rect = { 0, 0, (unsigned short)_window.height, (unsigned short)_window.width }; browser->invalidaterect(_npp_instance, &rect); } break; case P3D_RT_callback: // In the case of a callback, yield control temporarily to JavaScript. continue_loop = false; break; default: // Some request types are not handled. nout << "Unhandled request: " << request->_request_type << "\n"; break; }; P3D_request_finish_ptr(request, handled); return continue_loop; } //////////////////////////////////////////////////////////////////// // Function: PPInstance::generic_browser_call // Access: Public, Static // Description: This method is called from strategically-chosen // browser callback functions. Its purpose is to // provide another hook into the main thread callback, // particularly if the PluginAsyncCall function isn't // available. //////////////////////////////////////////////////////////////////// void PPInstance:: generic_browser_call() { /* if (!has_plugin_thread_async_call) { // If we can't ask Mozilla to call us back using // NPN_PluginThreadAsyncCall(), then we'll do it explicitly now, // since we know we're in the main thread here. handle_request_loop(); } */ } //////////////////////////////////////////////////////////////////// // Function: PPInstance::handle_event // Access: Public // Description: Called by the browser as new window events are // generated. Returns true if the event is handled, // false if ignored. //////////////////////////////////////////////////////////////////// bool PPInstance:: handle_event(void *event) { bool retval = false; P3D_event_data edata; memset(&edata, 0, sizeof(edata)); edata._event_type = _event_type; EventAuxData aux_data; if (_event_type == P3D_ET_osx_event_record) { #ifdef __APPLE__ edata._event._osx_event_record._event = (EventRecord *)event; #endif // __APPLE__ #ifdef MACOSX_HAS_EVENT_MODELS } else if (_event_type == P3D_ET_osx_cocoa) { // Copy the NPCocoaEvent structure componentwise into a // P3DCocoaEvent structure. NPCocoaEvent *np_event = (NPCocoaEvent *)event; P3DCocoaEvent *p3d_event = &edata._event._osx_cocoa._event; copy_cocoa_event(p3d_event, np_event, aux_data); if (_got_window) { handle_cocoa_event(p3d_event); } #endif // MACOSX_HAS_EVENT_MODELS } if (_p3d_inst != NULL) { if (P3D_instance_handle_event_ptr(_p3d_inst, &edata)) { retval = true; } } return retval; } //////////////////////////////////////////////////////////////////// // Function: PPInstance::get_panda_script_object // Access: Public // Description: Returns a toplevel object that JavaScript or whatever // can read and/or modify to control the instance. //////////////////////////////////////////////////////////////////// NPObject *PPInstance:: get_panda_script_object() { if (_script_object != NULL) { // NPRuntime "steals" a reference to this object. browser->retainobject(_script_object); return _script_object; } P3D_object *main = NULL; if (_p3d_inst != NULL) { main = P3D_instance_get_panda_script_object_ptr(_p3d_inst); } nout << "get_panda_script_object, main = " << main << "\n"; _script_object = PPToplevelObject::make_new(this); _script_object->set_main(main); browser->retainobject(_script_object); return _script_object; } //////////////////////////////////////////////////////////////////// // Function: PPInstance::set_xembed // Access: Public // Description: Sets the use_xembed flag, telling the instance what // kind of window object to expect from NPAPI. If this // is true, the window object is an XID following the // XEmbed specification; if false, it is a normal window // handle. //////////////////////////////////////////////////////////////////// void PPInstance:: set_xembed(bool use_xembed) { _use_xembed = use_xembed; } //////////////////////////////////////////////////////////////////// // Function: PPInstance::p3dobj_to_variant // Access: Public // Description: Converts the indicated P3D_object to the equivalent // NPVariant, and stores it in result. //////////////////////////////////////////////////////////////////// void PPInstance:: p3dobj_to_variant(NPVariant *result, P3D_object *object) { switch (P3D_OBJECT_GET_TYPE(object)) { case P3D_OT_undefined: VOID_TO_NPVARIANT(*result); break; case P3D_OT_none: NULL_TO_NPVARIANT(*result); break; case P3D_OT_bool: BOOLEAN_TO_NPVARIANT(P3D_OBJECT_GET_BOOL(object), *result); break; case P3D_OT_int: INT32_TO_NPVARIANT(P3D_OBJECT_GET_INT(object), *result); break; case P3D_OT_float: DOUBLE_TO_NPVARIANT(P3D_OBJECT_GET_FLOAT(object), *result); break; case P3D_OT_string: { int size = P3D_OBJECT_GET_STRING(object, NULL, 0); char *buffer = (char *)browser->memalloc(size); P3D_OBJECT_GET_STRING(object, buffer, size); STRINGN_TO_NPVARIANT(buffer, size, *result); } break; case P3D_OT_object: { PPPandaObject *ppobj = PPPandaObject::make_new(this, object); OBJECT_TO_NPVARIANT(ppobj, *result); } break; } } //////////////////////////////////////////////////////////////////// // Function: PPInstance::variant_to_p3dobj // Access: Public // Description: Converts the indicated NPVariant to the equivalent // P3D_object, and returns it (newly-allocated). The // caller is responsible for freeing the returned object // later. //////////////////////////////////////////////////////////////////// P3D_object *PPInstance:: variant_to_p3dobj(const NPVariant *variant) { if (NPVARIANT_IS_VOID(*variant)) { return P3D_new_undefined_object_ptr(); } else if (NPVARIANT_IS_NULL(*variant)) { return P3D_new_none_object_ptr(); } else if (NPVARIANT_IS_BOOLEAN(*variant)) { return P3D_new_bool_object_ptr(NPVARIANT_TO_BOOLEAN(*variant)); } else if (NPVARIANT_IS_INT32(*variant)) { return P3D_new_int_object_ptr(NPVARIANT_TO_INT32(*variant)); } else if (NPVARIANT_IS_DOUBLE(*variant)) { return P3D_new_float_object_ptr(NPVARIANT_TO_DOUBLE(*variant)); } else if (NPVARIANT_IS_STRING(*variant)) { NPString str = NPVARIANT_TO_STRING(*variant); const UC_NPString &uc_str = *(UC_NPString *)(&str); return P3D_new_string_object_ptr(uc_str.UTF8Characters, uc_str.UTF8Length); } else if (NPVARIANT_IS_OBJECT(*variant)) { NPObject *object = NPVARIANT_TO_OBJECT(*variant); if (object->_class == &PPPandaObject::_object_class) { // This is really a PPPandaObject. PPPandaObject *ppobject = (PPPandaObject *)object; P3D_object *obj = ppobject->get_p3d_object(); return obj; } // It's a generic NPObject of some kind. return new PPBrowserObject(this, object); } // Hmm, none of the above? return P3D_new_none_object_ptr(); } //////////////////////////////////////////////////////////////////// // Function: PPInstance::output_np_variant // Access: Public // Description: Outputs the variant value. //////////////////////////////////////////////////////////////////// void PPInstance:: output_np_variant(ostream &out, const NPVariant &result) { if (NPVARIANT_IS_NULL(result)) { out << "null"; } else if (NPVARIANT_IS_VOID(result)) { out << "void"; } else if (NPVARIANT_IS_BOOLEAN(result)) { out << "bool " << NPVARIANT_TO_BOOLEAN(result); } else if (NPVARIANT_IS_INT32(result)) { out << "int " << NPVARIANT_TO_INT32(result); } else if (NPVARIANT_IS_DOUBLE(result)) { out << "double " << NPVARIANT_TO_DOUBLE(result); } else if (NPVARIANT_IS_STRING(result)) { NPString str = NPVARIANT_TO_STRING(result); const UC_NPString &uc_str = *(UC_NPString *)(&str); out << "string " << string(uc_str.UTF8Characters, uc_str.UTF8Length); } else if (NPVARIANT_IS_OBJECT(result)) { NPObject *child = NPVARIANT_TO_OBJECT(result); out << "object " << child; } } //////////////////////////////////////////////////////////////////// // Function: PPInstance::find_host // Access: Private // Description: Scans the element for the matching // element. //////////////////////////////////////////////////////////////////// void PPInstance:: find_host(TiXmlElement *xcontents) { string host_url = PANDA_PACKAGE_HOST_URL; TiXmlElement *xhost = xcontents->FirstChildElement("host"); if (xhost != NULL) { const char *url = xhost->Attribute("url"); if (url != NULL && host_url == string(url)) { // We're the primary host. This is the normal case. read_xhost(xhost); return; } else { // We're not the primary host; perhaps we're an alternate host. TiXmlElement *xalthost = xhost->FirstChildElement("alt_host"); while (xalthost != NULL) { const char *url = xalthost->Attribute("url"); if (url != NULL && host_url == string(url)) { // Yep, we're this alternate host. read_xhost(xhost); return; } xalthost = xalthost->NextSiblingElement("alt_host"); } } // Hmm, didn't find the URL we used mentioned. Assume we're the // primary host. read_xhost(xhost); } } //////////////////////////////////////////////////////////////////// // Function: PPInstance::read_xhost // Access: Private // Description: Reads the host data from the (or ) // entry in the contents.xml file. //////////////////////////////////////////////////////////////////// void PPInstance:: read_xhost(TiXmlElement *xhost) { // Get the "download" URL, which is the source from which we // download everything other than the contents.xml file. const char *download_url = xhost->Attribute("download_url"); if (download_url != NULL) { _download_url_prefix = download_url; } else { _download_url_prefix = PANDA_PACKAGE_HOST_URL; } if (!_download_url_prefix.empty()) { if (_download_url_prefix[_download_url_prefix.size() - 1] != '/') { _download_url_prefix += "/"; } } TiXmlElement *xmirror = xhost->FirstChildElement("mirror"); while (xmirror != NULL) { const char *url = xmirror->Attribute("url"); if (url != NULL) { add_mirror(url); } xmirror = xmirror->NextSiblingElement("mirror"); } } //////////////////////////////////////////////////////////////////// // Function: PPInstance::add_mirror // Access: Private // Description: Adds a new URL to serve as a mirror for this host. // The mirrors will be consulted first, before // consulting the host directly. //////////////////////////////////////////////////////////////////// void PPInstance:: add_mirror(string mirror_url) { // Ensure the URL ends in a slash. if (!mirror_url.empty() && mirror_url[mirror_url.size() - 1] != '/') { mirror_url += '/'; } // Add it to the _mirrors list, but only if it's not already // there. if (find(_mirrors.begin(), _mirrors.end(), mirror_url) == _mirrors.end()) { _mirrors.push_back(mirror_url); } } //////////////////////////////////////////////////////////////////// // Function: PPInstance::choose_random_mirrors // Access: Public // Description: Selects num_mirrors elements, chosen at random, from // the _mirrors list. Adds the selected mirrors to // result. If there are fewer than num_mirrors elements // in the list, adds only as many mirrors as we can get. //////////////////////////////////////////////////////////////////// void PPInstance:: choose_random_mirrors(vector &result, int num_mirrors) { vector selected; size_t num_to_select = min(_mirrors.size(), (size_t)num_mirrors); while (num_to_select > 0) { size_t i = (size_t)(((double)rand() / (double)RAND_MAX) * _mirrors.size()); while (find(selected.begin(), selected.end(), i) != selected.end()) { // Already found this i, find a new one. i = (size_t)(((double)rand() / (double)RAND_MAX) * _mirrors.size()); } selected.push_back(i); result.push_back(_mirrors[i]); --num_to_select; } } //////////////////////////////////////////////////////////////////// // Function: PPInstance::request_ready // Access: Private, Static // Description: This function is attached as an asynchronous callback // to each instance; it will be notified when the // instance has a request ready. This function may be // called in a sub-thread. //////////////////////////////////////////////////////////////////// void PPInstance:: request_ready(P3D_instance *instance) { PPInstance *inst = (PPInstance *)(instance->_user_data); assert(inst != NULL); if (has_plugin_thread_async_call) { #ifdef HAS_PLUGIN_THREAD_ASYNC_CALL // Since we are running at least Gecko 1.9, and we have this very // useful function, let's use it to ask the browser to call us back // in the main thread. assert((void *)browser->pluginthreadasynccall != (void *)NULL); browser->pluginthreadasynccall(inst->_npp_instance, browser_sync_callback, NULL); #endif // HAS_PLUGIN_THREAD_ASYNC_CALL } else { // If we're using an older version of Gecko, we have to do this // some other, OS-dependent way. #ifdef _WIN32 // Use a Windows message to forward this event to the main thread. // Get the window handle for the window associated with this // instance. const NPWindow *win = inst->get_window(); if (win != NULL && win->type == NPWindowTypeWindow) { PostMessage((HWND)(win->window), WM_USER, 0, 0); } #endif // _WIN32 #ifdef __APPLE__ // Use an OSX timer to forward this event to the main thread. // Only set a new timer if we don't have one already started. ACQUIRE_LOCK(inst->_timer_lock); if (inst->_request_timer == NULL) { CFRunLoopTimerContext timer_context; memset(&timer_context, 0, sizeof(timer_context)); timer_context.info = inst; inst->_request_timer = CFRunLoopTimerCreate (NULL, 0, 0, 0, 0, timer_callback, &timer_context); CFRunLoopAddTimer(inst->_run_loop_main, inst->_request_timer, kCFRunLoopCommonModes); } RELEASE_LOCK(inst->_timer_lock); #endif // __APPLE__ // Doesn't appear to be a reliable way to simulate this in Linux. } } //////////////////////////////////////////////////////////////////// // Function: PPInstance::start_download // Access: Private // Description: Initiates a download request. //////////////////////////////////////////////////////////////////// void PPInstance:: start_download(const string &url, PPDownloadRequest *req) { nout << "start_download: " << url << "\n"; if (url.substr(0, 7) == "file://") { // If we're "downloading" a file URL, just go read the file directly. downloaded_file(req, get_filename_from_url(url)); delete req; } else { // Otherwise, ask the browser to download it. browser->geturlnotify(_npp_instance, url.c_str(), NULL, req); } } //////////////////////////////////////////////////////////////////// // Function: PPInstance::downloaded_contents_file // Access: Private // Description: The contents.xml file has been successfully downloaded; // copy it into place. //////////////////////////////////////////////////////////////////// void PPInstance:: downloaded_contents_file(const string &filename) { // Now we have the contents.xml file. Read this to get the // filename and md5 hash of our core API DLL. if (read_contents_file(filename, true)) { // Successfully downloaded and read, and it has been written // into its normal place. get_core_api(); } else { // Error reading the contents.xml file, or in loading the core // API that it references. nout << "Unable to read contents file " << filename << "\n"; // If there's an outstanding contents.xml file on disk, try to // load that one as a fallback. string contents_filename = _root_dir + "/contents.xml"; if (read_contents_file(contents_filename, false)) { get_core_api(); } else { nout << "Unable to read contents file " << contents_filename << "\n"; set_failed(); } } } //////////////////////////////////////////////////////////////////// // Function: PPInstance::read_contents_file // Access: Private // Description: Attempts to open and read the contents.xml file on // disk. Copies the file to its standard location // on success. Returns true on success, false on // failure. //////////////////////////////////////////////////////////////////// bool PPInstance:: read_contents_file(const string &contents_filename, bool fresh_download) { _download_url_prefix = _standard_url_prefix; _mirrors.clear(); TiXmlDocument doc(contents_filename.c_str()); if (!doc.LoadFile()) { return false; } bool found_core_package = false; TiXmlElement *xcontents = doc.FirstChildElement("contents"); if (xcontents != NULL) { int max_age = P3D_CONTENTS_DEFAULT_MAX_AGE; xcontents->Attribute("max_age", &max_age); // Get the latest possible expiration time, based on the max_age // indication. Any expiration time later than this is in error. time_t now = time(NULL); _contents_expiration = now + (time_t)max_age; if (fresh_download) { // Update the XML with the new download information. TiXmlElement *xorig = xcontents->FirstChildElement("orig"); while (xorig != NULL) { xcontents->RemoveChild(xorig); xorig = xcontents->FirstChildElement("orig"); } xorig = new TiXmlElement("orig"); xcontents->LinkEndChild(xorig); xorig->SetAttribute("expiration", (int)_contents_expiration); } else { // Read the expiration time from the XML. int expiration = 0; TiXmlElement *xorig = xcontents->FirstChildElement("orig"); if (xorig != NULL) { xorig->Attribute("expiration", &expiration); } _contents_expiration = min(_contents_expiration, (time_t)expiration); } nout << "read contents.xml, max_age = " << max_age << ", expires in " << max(_contents_expiration, now) - now << " s\n"; // Look for the entry; it might point us at a different // download URL, and it might mention some mirrors. find_host(xcontents); // Now look for the core API package. _coreapi_set_ver = ""; TiXmlElement *xpackage = xcontents->FirstChildElement("package"); while (xpackage != NULL) { const char *name = xpackage->Attribute("name"); if (name != NULL && strcmp(name, "coreapi") == 0) { const char *platform = xpackage->Attribute("platform"); if (platform != NULL && strcmp(platform, DTOOL_PLATFORM) == 0) { _coreapi_dll.load_xml(xpackage); const char *set_ver = xpackage->Attribute("set_ver"); if (set_ver != NULL) { _coreapi_set_ver = set_ver; } found_core_package = true; break; } } xpackage = xpackage->NextSiblingElement("package"); } } if (!found_core_package) { // Couldn't find the coreapi package description. nout << "No coreapi package defined in contents file for " << DTOOL_PLATFORM << "\n"; return false; } // Check the coreapi_set_ver token. If it is given, it specifies a // minimum Core API version number we expect to find. If we didn't // find that number, perhaps our contents.xml is out of date. string coreapi_set_ver = lookup_token("coreapi_set_ver"); if (!coreapi_set_ver.empty()) { nout << "Instance asked for Core API set_ver " << coreapi_set_ver << ", we found " << _coreapi_set_ver << "\n"; // But don't bother if we just freshly downloaded it. if (!fresh_download) { if (compare_seq(coreapi_set_ver, _coreapi_set_ver) > 0) { // The requested set_ver value is higher than the one we have on // file; our contents.xml file must be out of date after all. nout << "expiring contents.xml\n"; _contents_expiration = 0; } } } // Success. Now save the file in its proper place. string standard_filename = _root_dir + "/contents.xml"; mkfile_complete(standard_filename, nout); if (!doc.SaveFile(standard_filename.c_str())) { nout << "Couldn't rewrite " << standard_filename << "\n"; return false; } return true; } //////////////////////////////////////////////////////////////////// // Function: PPInstance::get_filename_from_url // Access: Private, Static // Description: Returns the actual filename referenced by a file:// // url. //////////////////////////////////////////////////////////////////// string PPInstance:: get_filename_from_url(const string &url) { string filename = url.substr(7); // Strip off a trailing query string. size_t query = filename.find('?'); if (query != string::npos) { filename = filename.substr(0, query); } #ifdef _WIN32 // On Windows, we have to munge the filename specially, because it's // been URL-munged. It might begin with a leading slash as well as // a drive letter. Clean up that nonsense. if (filename.length() >= 3 && (filename[0] == '/' || filename[0] == '\\') && isalpha(filename[1]) && filename[2] == ':') { filename = filename.substr(1); } #endif // _WIN32 return filename; } //////////////////////////////////////////////////////////////////// // Function: PPInstance::downloaded_file // Access: Private // Description: Called to receive the fully-downloaded contents of a // URL. //////////////////////////////////////////////////////////////////// void PPInstance:: downloaded_file(PPDownloadRequest *req, const string &filename) { // Since we're no longer using NP_ASFILEONLY, none of these URL // requests will normally come through this codepath (they'll go // through url_notify() above, instead), unless we short-circuited // the browser by "downloading" a file:// url. switch (req->_rtype) { case PPDownloadRequest::RT_contents_file: // The contents.xml file that gets things going. downloaded_contents_file(filename); break; case PPDownloadRequest::RT_core_dll: // This is the core API DLL (or dylib or whatever). Now that // we've downloaded it, we can load it. downloaded_plugin(filename); break; case PPDownloadRequest::RT_user: // Here's the user-requested file. It needs to be streamed to the // user, so we'll open the file and feed it to the user. feed_file(req, filename); break; default: // Don't know what this is. nout << "Unexpected downloaded file, type " << (int)req->_rtype << "\n"; } } //////////////////////////////////////////////////////////////////// // Function: PPInstance::feed_file // Access: Private // Description: Opens the named file (extracted from a file:// URL) // and feeds its contents to the core API. //////////////////////////////////////////////////////////////////// void PPInstance:: feed_file(PPDownloadRequest *req, const string &filename) { StreamingFileData *file_data = new StreamingFileData(req, filename, _p3d_inst); _file_datas.push_back(file_data); } //////////////////////////////////////////////////////////////////// // Function: PPInstance::send_p3d_temp_file_data // Access: Private // Description: Once the instance has been created, sends it all of // the data we have saved up for it while we were // waiting. //////////////////////////////////////////////////////////////////// void PPInstance:: send_p3d_temp_file_data() { assert(_p3d_temp_file._opened); nout << "Sending " << _p3d_temp_file._current_size << " preliminary bytes of " << _p3d_temp_file._total_size << " total p3d data\n"; static const size_t buffer_size = 4096; char buffer[buffer_size]; _p3d_temp_file.close(); ifstream in(_p3d_temp_file._filename.c_str(), ios::binary); in.read(buffer, buffer_size); size_t total = 0; size_t count = in.gcount(); while (count != 0) { P3D_instance_feed_url_stream_ptr(_p3d_inst, _p3d_instance_id, P3D_RC_in_progress, 0, _p3d_temp_file._total_size, buffer, count); total += count; in.read(buffer, buffer_size); count = in.gcount(); } nout << "sent " << count << " bytes.\n"; in.close(); if (_p3d_temp_file._finished) { // If we'd already finished the stream earlier, tell the plugin. P3D_instance_feed_url_stream_ptr(_p3d_inst, _p3d_instance_id, P3D_RC_done, 0, _p3d_temp_file._total_size, NULL, 0); } _p3d_temp_file.cleanup(); } //////////////////////////////////////////////////////////////////// // Function: PPInstance::get_core_api // Access: Private // Description: Checks the core API DLL file against the // specification in the contents file, and downloads it // if necessary. //////////////////////////////////////////////////////////////////// void PPInstance:: get_core_api() { if (_coreapi_dll.quick_verify(_root_dir)) { // The DLL file is good. Just load it. do_load_plugin(); } else { // The DLL file needs to be downloaded. Build up our list of // URL's to attempt to download it from, in reverse order. string url; // Our last act of desperation: hit the original host, with a // query uniquifier, to break through any caches. ostringstream strm; strm << _download_url_prefix << _coreapi_dll.get_filename() << "?" << time(NULL); url = strm.str(); _core_urls.push_back(url); // Before we try that, we'll hit the original host, without a // uniquifier. url = _download_url_prefix; url += _coreapi_dll.get_filename(); _core_urls.push_back(url); // And before we try that, we'll try two mirrors, at random. vector mirrors; choose_random_mirrors(mirrors, 2); for (vector::iterator si = mirrors.begin(); si != mirrors.end(); ++si) { url = (*si) + _coreapi_dll.get_filename(); _core_urls.push_back(url); } // Now pick the first URL off the list, and try it. assert(!_core_urls.empty()); url = _core_urls.back(); _core_urls.pop_back(); PPDownloadRequest *req = new PPDownloadRequest(PPDownloadRequest::RT_core_dll); start_download(url, req); } } //////////////////////////////////////////////////////////////////// // Function: PPInstance::downloaded_plugin // Access: Private // Description: The core API DLL has been successfully downloaded; // copy it into place. //////////////////////////////////////////////////////////////////// void PPInstance:: downloaded_plugin(const string &filename) { // We could have been downloading this file as a stream, but that // would cause problems with multiple instances downloading the // plugin at the same time. Instead, we let them all download the // file asfile, and then only one of them is allowed to copy it into // place. if (is_plugin_loaded()) { // Some other instance got there first. Just get started. create_instance(); return; } // Make sure the DLL was correctly downloaded before continuing. if (!_coreapi_dll.quick_verify_pathname(filename)) { nout << "After download, " << _coreapi_dll.get_filename() << " is no good.\n"; nout << "Expected:\n"; _coreapi_dll.write(nout); const FileSpec *actual = _coreapi_dll.force_get_actual_file(filename); if (actual != NULL) { nout << "Found:\n"; actual->write(nout); } // That DLL came out wrong. Try the next URL. if (!_core_urls.empty()) { string url = _core_urls.back(); _core_urls.pop_back(); _core_dll_temp_file.cleanup(); PPDownloadRequest *req = new PPDownloadRequest(PPDownloadRequest::RT_core_dll); start_download(url, req); return; } set_failed(); return; } // Copy the file onto the target. string pathname = _coreapi_dll.get_pathname(_root_dir); if (!copy_file(filename, pathname)) { nout << "Couldn't copy " << pathname << "\n"; set_failed(); return; } if (!_coreapi_dll.quick_verify(_root_dir)) { nout << "After copying, " << pathname << " is no good.\n"; set_failed(); return; } // We downloaded and installed it successfully. Now load it. do_load_plugin(); return; } //////////////////////////////////////////////////////////////////// // Function: PPInstance::do_load_plugin // Access: Private // Description: Once the core API DLL has been downloaded, loads it // into memory and starts the instance. //////////////////////////////////////////////////////////////////// void PPInstance:: do_load_plugin() { string pathname = _coreapi_dll.get_pathname(_root_dir); #ifdef P3D_PLUGIN_P3D_PLUGIN // This is a convenience macro for development. If defined and // nonempty, it indicates the name of the plugin DLL that we will // actually run, even after downloading a possibly different // (presumably older) version. Its purpose is to simplify iteration // on the plugin DLL. string override_filename = P3D_PLUGIN_P3D_PLUGIN; if (!override_filename.empty()) { pathname = override_filename; } #endif // P3D_PLUGIN_P3D_PLUGIN nout << "Attempting to load core API from " << pathname << "\n"; string contents_filename = _root_dir + "/contents.xml"; if (!load_plugin(pathname, contents_filename, PANDA_PACKAGE_HOST_URL, P3D_VC_normal, "", "", "", false, false, _root_dir, "", nout)) { nout << "Unable to launch core API in " << pathname << "\n"; set_failed(); return; } // Format the coreapi_timestamp as a string, for passing as a // parameter. ostringstream stream; stream << _coreapi_dll.get_timestamp(); string coreapi_timestamp = stream.str(); #ifdef PANDA_OFFICIAL_VERSION static const bool official = true; #else static const bool official = false; #endif P3D_set_plugin_version_ptr(P3D_PLUGIN_MAJOR_VERSION, P3D_PLUGIN_MINOR_VERSION, P3D_PLUGIN_SEQUENCE_VERSION, official, PANDA_DISTRIBUTOR, PANDA_PACKAGE_HOST_URL, coreapi_timestamp.c_str(), _coreapi_set_ver.c_str()); create_instance(); } //////////////////////////////////////////////////////////////////// // Function: PPInstance::create_instance // Access: Private // Description: Actually creates the internal P3D_instance object, if // possible and needed. //////////////////////////////////////////////////////////////////// void PPInstance:: create_instance() { if (_started) { // Already created. return; } if (!is_plugin_loaded()) { // Plugin is not loaded yet. return; } #ifdef __APPLE__ // We no longer need to twirl the icon. Stop the timer. if (_twirl_timer != NULL) { CFRunLoopTimerInvalidate(_twirl_timer); CFRelease(_twirl_timer); _twirl_timer = NULL; } #endif // __APPLE__ #ifdef HAVE_X11 x11_stop_twirl_subprocess(); #endif // HAVE_X11 // In the Windows case, we let the timer keep running, because it // also checks for wayward messages. P3D_token *tokens = NULL; if (!_tokens.empty()) { tokens = &_tokens[0]; } _started = true; _p3d_inst = P3D_new_instance_ptr(request_ready, tokens, _tokens.size(), 0, NULL, this); if (_p3d_inst == NULL) { set_failed(); return; } // Now get the browser's toplevel DOM object (called the "window" // object in JavaScript), to pass to the plugin. NPObject *window_object = NULL; if (browser->getvalue(_npp_instance, NPNVWindowNPObject, &window_object) == NPERR_NO_ERROR) { PPBrowserObject *pobj = new PPBrowserObject(this, window_object); P3D_instance_set_browser_script_object_ptr(_p3d_inst, pobj); browser->releaseobject(window_object); } else { nout << "Couldn't get window_object\n"; } if (_script_object != NULL) { // Now that we have a true instance, initialize our // script_object with the proper P3D_object pointer. P3D_object *main = P3D_instance_get_panda_script_object_ptr(_p3d_inst); nout << "new instance, setting main = " << main << "\n"; _script_object->set_main(main); } if (_got_instance_url) { // Create the user_id for streaming the p3d data into the instance. _p3d_instance_id = P3D_instance_start_stream_ptr(_p3d_inst, _instance_url.c_str()); nout << "p3d instance to stream " << _p3d_instance_id << "\n"; // If we have already started to receive any instance data, send it // to the plugin now. if (_p3d_temp_file._opened) { send_p3d_temp_file_data(); } } if (_got_window) { send_window(); } } //////////////////////////////////////////////////////////////////// // Function: PPInstance::send_window // Access: Private // Description: Actually issues the window parameters to the internal // P3D_instance object. //////////////////////////////////////////////////////////////////// void PPInstance:: send_window() { assert(_p3d_inst != NULL); int x = _window.x; int y = _window.y; P3D_window_handle parent_window; memset(&parent_window, 0, sizeof(parent_window)); parent_window._window_handle_type = P3D_WHT_none; if (_window.type == NPWindowTypeWindow) { // We have a "windowed" plugin. Parent our window to the one we // were given. In this case, we should also reset the offset to // (0, 0), since the window we were given is already placed in the // right spot. #ifdef _WIN32 assert(!_use_xembed); parent_window._window_handle_type = P3D_WHT_win_hwnd; parent_window._handle._win_hwnd._hwnd = (HWND)(_window.window); x = 0; y = 0; #elif defined(__APPLE__) assert(!_use_xembed); parent_window._window_handle_type = _window_handle_type; if (_window_handle_type == P3D_WHT_osx_port) { NP_Port *port = (NP_Port *)_window.window; parent_window._handle._osx_port._port = port->port; } else if (_window_handle_type == P3D_WHT_osx_cgcontext) { NP_CGContext *context = (NP_CGContext *)_window.window; if (context != NULL) { parent_window._handle._osx_cgcontext._context = context->context; parent_window._handle._osx_cgcontext._window = (WindowRef)context->window; } } #elif defined(HAVE_X11) if (_use_xembed) { // If we're using the XEmbed model, we've actually received an // XID for a GtkSocket. #ifdef HAVE_GTK // If we're using XEmbed, pass the X11 Window pointer of our // plug down to Panda. (Hmm, it would be nice to pass the XID // object and use this system in general within Panda, but // that's for the future, I think.) assert(_plug != NULL); parent_window._window_handle_type = P3D_WHT_x11_window; parent_window._handle._x11_window._xwindow = GDK_DRAWABLE_XID(_plug->window); #endif // HAVE_GTK } else { // If we're not using XEmbed, this is just a standard X11 Window // pointer. parent_window._window_handle_type = P3D_WHT_x11_window; parent_window._handle._x11_window._xwindow = (X11_Window)(_window.window); } x = 0; y = 0; #endif } else { // We have a "windowless" plugin. Parent our window directly to // the browser window. #ifdef _WIN32 HWND hwnd; if (browser->getvalue(_npp_instance, NPNVnetscapeWindow, &hwnd) == NPERR_NO_ERROR) { parent_window._window_handle_type = P3D_WHT_win_hwnd; parent_window._handle._win_hwnd._hwnd = hwnd; } #elif defined(__APPLE__) parent_window._window_handle_type = _window_handle_type; if (_window_handle_type == P3D_WHT_osx_port) { NP_Port *port = (NP_Port *)_window.window; parent_window._handle._osx_port._port = port->port; } else if (_window_handle_type == P3D_WHT_osx_cgcontext) { NP_CGContext *context = (NP_CGContext *)_window.window; if (context != NULL) { parent_window._handle._osx_cgcontext._context = context->context; parent_window._handle._osx_cgcontext._window = (WindowRef)context->window; } } #elif defined(HAVE_X11) unsigned long win; if (browser->getvalue(_npp_instance, NPNVnetscapeWindow, &win) == NPERR_NO_ERROR) { parent_window._window_handle_type = P3D_WHT_x11_window; parent_window._handle._x11_window._xwindow = win; } #endif } #ifdef HAVE_X11 // In the case of X11, grab the display as well. parent_window._handle._x11_window._xdisplay = 0; void *disp; if (browser->getvalue(_npp_instance, NPNVxDisplay, &disp) == NPERR_NO_ERROR) { parent_window._handle._x11_window._xdisplay = disp; } #endif P3D_window_type window_type = P3D_WT_embedded; if (_window.window == NULL && _event_type != P3D_ET_osx_cocoa) { // No parent window: it must be a hidden window. window_type = P3D_WT_hidden; } else if (_window.width == 0 || _window.height == 0) { // No size: hidden. window_type = P3D_WT_hidden; } P3D_instance_setup_window_ptr (_p3d_inst, window_type, x, y, _window.width, _window.height, &parent_window); } //////////////////////////////////////////////////////////////////// // Function: PPInstance::cleanup_window // Access: Private // Description: Called at instance shutdown, this restores the parent // window to its original state. //////////////////////////////////////////////////////////////////// void PPInstance:: cleanup_window() { if (_got_window) { #ifdef _WIN32 // Restore the parent window to its own window handler. HWND hwnd = (HWND)_window.window; SetWindowLongPtr(hwnd, GWL_WNDPROC, _orig_window_proc); InvalidateRect(hwnd, NULL, true); if (_bg_brush != NULL) { DeleteObject(_bg_brush); _bg_brush = NULL; } for (int step = 0; step < twirl_num_steps + 1; ++step) { if (_twirl_bitmaps[step] != NULL) { DeleteObject(_twirl_bitmaps[step]); _twirl_bitmaps[step] = NULL; } } #endif // _WIN32 #ifdef HAVE_X11 x11_stop_twirl_subprocess(); #ifdef HAVE_GTK if (_plug != NULL) { gtk_widget_destroy(_plug); _plug = NULL; } #endif // HAVE_GTK #endif // HAVE_X11 _got_window = false; } } //////////////////////////////////////////////////////////////////// // Function: PPInstance::copy_file // Access: Private // Description: Copies the data in the file named by from_filename // into the file named by to_filename. //////////////////////////////////////////////////////////////////// bool PPInstance:: copy_file(const string &from_filename, const string &to_filename) { mkfile_complete(to_filename, nout); ifstream in(from_filename.c_str(), ios::in | ios::binary); ofstream out(to_filename.c_str(), ios::out | ios::binary); static const size_t buffer_size = 4096; char buffer[buffer_size]; in.read(buffer, buffer_size); size_t count = in.gcount(); while (count != 0) { out.write(buffer, count); if (out.fail()) { return false; } in.read(buffer, buffer_size); count = in.gcount(); } if (!in.eof()) { return false; } return true; } //////////////////////////////////////////////////////////////////// // Function: PPInstance::lookup_token // Access: Private // Description: Returns the value associated with the first // appearance of the named token, or empty string if the // token does not appear. //////////////////////////////////////////////////////////////////// string PPInstance:: lookup_token(const string &keyword) const { Tokens::const_iterator ti; for (ti = _tokens.begin(); ti != _tokens.end(); ++ti) { if (keyword == (*ti)._keyword) { return (*ti)._value; } } return string(); } //////////////////////////////////////////////////////////////////// // Function: PPInstance::has_token // Access: Private // Description: Returns true if the named token appears in the list, // false otherwise. //////////////////////////////////////////////////////////////////// bool PPInstance:: has_token(const string &keyword) const { Tokens::const_iterator ti; for (ti = _tokens.begin(); ti != _tokens.end(); ++ti) { if ((*ti)._keyword == keyword) { return true; } } return false; } //////////////////////////////////////////////////////////////////// // Function: PPInstance::compare_seq // Access: Private, Static // Description: Compares the two dotted-integer sequence values // numerically. Returns -1 if seq_a sorts first, 1 if // seq_b sorts first, 0 if they are equivalent. //////////////////////////////////////////////////////////////////// int PPInstance:: compare_seq(const string &seq_a, const string &seq_b) { const char *num_a = seq_a.c_str(); const char *num_b = seq_b.c_str(); int comp = compare_seq_int(num_a, num_b); while (comp == 0) { if (*num_a != '.') { if (*num_b != '.') { // Both strings ran out together. return 0; } // a ran out first. return -1; } else if (*num_b != '.') { // b ran out first. return 1; } // Increment past the dot. ++num_a; ++num_b; comp = compare_seq(num_a, num_b); } return comp; } //////////////////////////////////////////////////////////////////// // Function: PPInstance::compare_seq_int // Access: Private, Static // Description: Numerically compares the formatted integer value at // num_a with num_b. Increments both num_a and num_b to // the next character following the valid integer. //////////////////////////////////////////////////////////////////// int PPInstance:: compare_seq_int(const char *&num_a, const char *&num_b) { long int a; char *next_a; long int b; char *next_b; a = strtol((char *)num_a, &next_a, 10); b = strtol((char *)num_b, &next_b, 10); num_a = next_a; num_b = next_b; if (a < b) { return -1; } else if (b < a) { return 1; } else { return 0; } } //////////////////////////////////////////////////////////////////// // Function: PPInstance::set_failed // Access: Private // Description: Called when something has gone wrong that prevents // the plugin instance from running. Specifically, this // means it failed to load the core API. //////////////////////////////////////////////////////////////////// void PPInstance:: set_failed() { if (!_failed) { _failed = true; nout << "Plugin failed.\n"; stop_outstanding_streams(); // Look for the "onpluginfail" token. string expression = lookup_token("onpluginfail"); if (!expression.empty()) { // Now attempt to evaluate the expression. NPObject *window_object = NULL; if (browser->getvalue(_npp_instance, NPNVWindowNPObject, &window_object) == NPERR_NO_ERROR) { NPString npexpr = { expression.c_str(), expression.length() }; NPVariant result; if (browser->evaluate(_npp_instance, window_object, &npexpr, &result)) { nout << "Eval " << expression << "\n"; browser->releasevariantvalue(&result); } else { nout << "Unable to eval " << expression << "\n"; } browser->releaseobject(window_object); } } if (_p3d_inst != NULL) { P3D_instance_finish_ptr(_p3d_inst); _p3d_inst = NULL; } cleanup_window(); } } //////////////////////////////////////////////////////////////////// // Function: PPInstance::handle_request_loop // Access: Private, Static // Description: Checks for any new requests from the plugin, and // dispatches them to the appropriate PPInstance. This // function is called only in the main thread. //////////////////////////////////////////////////////////////////// void PPInstance:: handle_request_loop() { if (!is_plugin_loaded()) { return; } P3D_instance *p3d_inst = P3D_check_request_ptr(0.0); while (p3d_inst != (P3D_instance *)NULL) { P3D_request *request = P3D_instance_get_request_ptr(p3d_inst); if (request != (P3D_request *)NULL) { PPInstance *inst = (PPInstance *)(p3d_inst->_user_data); assert(inst != NULL); if (!inst->handle_request(request)) { // If handling the request is meant to yield control // temporarily to JavaScript (e.g. P3D_RT_callback), then do // so now. return; } if (!is_plugin_loaded()) { // Oops, we may have unloaded the plugin as an indirect effect // of handling the request. If so, get out of here. return; } } p3d_inst = P3D_check_request_ptr(0.0); } // Also check to see if we have any file_data objects that have // finished and may be deleted. size_t num_file_datas = _file_datas.size(); size_t i = 0; while (i < num_file_datas) { if (!_file_datas[i]->is_done()) { // This one keeps going. ++i; } else { // This one is done. delete _file_datas[i]; _file_datas.erase(_file_datas.begin() + i); --num_file_datas; } } } //////////////////////////////////////////////////////////////////// // Function: PPInstance::browser_sync_callback // Access: Private, Static // Description: This callback hook is passed to // NPN_PluginThreadAsyncCall() (if that function is // available) to forward a request to the main thread. // The callback is actually called in the main thread. //////////////////////////////////////////////////////////////////// void PPInstance:: browser_sync_callback(void *) { handle_request_loop(); } #ifdef _WIN32 //////////////////////////////////////////////////////////////////// // Function: PPInstance::st_window_proc // Access: Private, Static // Description: We bind this function to the parent window we were // given in set_window, so we can spin the request_loop // when needed. This is only in the Windows case; other // platforms rely on explicit windows events. //////////////////////////////////////////////////////////////////// LONG PPInstance:: st_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { LONG_PTR self = GetWindowLongPtr(hwnd, GWLP_USERDATA); if (self == NULL) { // We haven't assigned the pointer yet (!?) return DefWindowProc(hwnd, msg, wparam, lparam); } return ((PPInstance *)self)->window_proc(hwnd, msg, wparam, lparam); } #endif // _WIN32 #ifdef _WIN32 //////////////////////////////////////////////////////////////////// // Function: PPInstance::window_proc // Access: Private // Description: The non-static window_proc() function. // // We bind this function to the parent window we were // given in set_window, so we can spin the request_loop // when needed. This is only in the Windows case; other // platforms rely on explicit windows events. //////////////////////////////////////////////////////////////////// LONG PPInstance:: window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { if (!has_plugin_thread_async_call) { // Since we're here in the main thread, call handle_request_loop() // to see if there are any new requests to be serviced by the main // thread. handle_request_loop(); } switch (msg) { case WM_ERASEBKGND: // Eat the WM_ERASEBKGND message, so the browser's intervening // window won't overdraw on top of our own window. return true; case WM_PAINT: if (!_started) { // If we haven't yet loaded the instance, we can paint a // twirling icon in the window. PAINTSTRUCT ps; HDC dc = BeginPaint(hwnd, &ps); win_paint_twirl(hwnd, dc); EndPaint(hwnd, &ps); } return true; case WM_TIMER: if (!_started) { InvalidateRect(_hwnd, NULL, FALSE); } break; case WM_USER: break; } return DefWindowProc(hwnd, msg, wparam, lparam); } #endif // _WIN32 #ifdef _WIN32 //////////////////////////////////////////////////////////////////// // Function: PPInstance::win_get_twirl_bitmaps // Access: Private // Description: Fills _twirl_bitmaps with an array of bitmaps for // drawing the twirling icon while we're waiting for the // instance to load. //////////////////////////////////////////////////////////////////// void PPInstance:: win_get_twirl_bitmaps() { BITMAPINFOHEADER bmih; bmih.biSize = sizeof(bmih); bmih.biWidth = twirl_width; bmih.biHeight = -twirl_height; bmih.biPlanes = 1; bmih.biBitCount = 32; bmih.biCompression = BI_RGB; bmih.biSizeImage = 0; bmih.biXPelsPerMeter = 0; bmih.biYPelsPerMeter = 0; bmih.biClrUsed = 0; bmih.biClrImportant = 0; BITMAPINFO bmi; memcpy(&bmi, &bmih, sizeof(bmih)); HDC dc = GetDC(_hwnd); static const size_t twirl_size = twirl_width * twirl_height; unsigned char twirl_data[twirl_size * 3]; unsigned char new_data[twirl_size * 4]; for (int step = 0; step < twirl_num_steps + 1; ++step) { get_twirl_data(twirl_data, twirl_size, step, _fgcolor_r, _fgcolor_g, _fgcolor_b, _bgcolor_r, _bgcolor_g, _bgcolor_b); // Expand out the RGB channels into RGBA. for (int yi = 0; yi < twirl_height; ++yi) { const unsigned char *sp = twirl_data + yi * twirl_width * 3; unsigned char *dp = new_data + yi * twirl_width * 4; for (int xi = 0; xi < twirl_width; ++xi) { // RGB <= BGR. dp[0] = sp[2]; dp[1] = sp[1]; dp[2] = sp[0]; dp[3] = (unsigned char)0xff; sp += 3; dp += 4; } } // Now load the image. _twirl_bitmaps[step] = CreateDIBitmap(dc, &bmih, CBM_INIT, (const char *)new_data, &bmi, 0); } ReleaseDC(_hwnd, dc); } #endif // _WIN32 #ifdef _WIN32 //////////////////////////////////////////////////////////////////// // Function: PPInstance::win_paint_twirl // Access: Private // Description: Paints the twirling icon into the browser window // before the instance has started. //////////////////////////////////////////////////////////////////// void PPInstance:: win_paint_twirl(HWND hwnd, HDC dc) { RECT rect; GetClientRect(_hwnd, &rect); int width = rect.right - rect.left; int height = rect.bottom - rect.top; // Double-buffer with an offscreen bitmap first. HDC bdc = CreateCompatibleDC(dc); HBITMAP buffer = CreateCompatibleBitmap(dc, width, height); SelectObject(bdc, buffer); // Start by painting the background color. FillRect(bdc, &rect, _bg_brush); if (!_started) { DWORD now = GetTickCount(); // Don't draw the twirling icon until at least half a second has // passed, so we don't distract people by drawing it // unnecessarily. if (_failed || (now - _init_time) >= 500) { // Which frame are we drawing? int step = (now / 100) % twirl_num_steps; if (_failed) { step = twirl_num_steps; } HBITMAP twirl = _twirl_bitmaps[step]; int left = rect.left + (width - twirl_width) / 2; int top = rect.top + (height - twirl_height) / 2; HDC mem_dc = CreateCompatibleDC(bdc); SelectObject(mem_dc, twirl); BitBlt(bdc, left, top, twirl_width, twirl_height, mem_dc, 0, 0, SRCCOPY); SelectObject(mem_dc, NULL); DeleteDC(mem_dc); } } // Now blit the buffer to the window. BitBlt(dc, 0, 0, width, height, bdc, 0, 0, SRCCOPY); } #endif // _WIN32 #ifdef MACOSX_HAS_EVENT_MODELS //////////////////////////////////////////////////////////////////// // Function: PPInstance::copy_cocoa_event // Access: Private, Static // Description: Copies the NPCocoaEvent structure componentwise into // a P3DCocoaEvent structure, for passing into the core // API. // // The aux_data object is used to manage temporary // storage on the strings created for the event. //////////////////////////////////////////////////////////////////// void PPInstance:: copy_cocoa_event(P3DCocoaEvent *p3d_event, NPCocoaEvent *np_event, EventAuxData &aux_data) { p3d_event->version = np_event->version; switch (np_event->type) { case NPCocoaEventDrawRect: p3d_event->type = P3DCocoaEventDrawRect; break; case NPCocoaEventMouseDown: p3d_event->type = P3DCocoaEventMouseDown; break; case NPCocoaEventMouseUp: p3d_event->type = P3DCocoaEventMouseUp; break; case NPCocoaEventMouseMoved: p3d_event->type = P3DCocoaEventMouseMoved; break; case NPCocoaEventMouseEntered: p3d_event->type = P3DCocoaEventMouseEntered; break; case NPCocoaEventMouseExited: p3d_event->type = P3DCocoaEventMouseExited; break; case NPCocoaEventMouseDragged: p3d_event->type = P3DCocoaEventMouseDragged; break; case NPCocoaEventKeyDown: p3d_event->type = P3DCocoaEventKeyDown; break; case NPCocoaEventKeyUp: p3d_event->type = P3DCocoaEventKeyUp; break; case NPCocoaEventFlagsChanged: p3d_event->type = P3DCocoaEventFlagsChanged; break; case NPCocoaEventFocusChanged: p3d_event->type = P3DCocoaEventFocusChanged; break; case NPCocoaEventWindowFocusChanged: p3d_event->type = P3DCocoaEventWindowFocusChanged; break; case NPCocoaEventScrollWheel: p3d_event->type = P3DCocoaEventScrollWheel; break; case NPCocoaEventTextInput: p3d_event->type = P3DCocoaEventTextInput; break; } switch (np_event->type) { case NPCocoaEventDrawRect: p3d_event->data.draw.context = np_event->data.draw.context; p3d_event->data.draw.x = np_event->data.draw.x; p3d_event->data.draw.y = np_event->data.draw.y; p3d_event->data.draw.width = np_event->data.draw.width; p3d_event->data.draw.height = np_event->data.draw.height; break; case NPCocoaEventMouseDown: case NPCocoaEventMouseUp: case NPCocoaEventMouseMoved: case NPCocoaEventMouseEntered: case NPCocoaEventMouseExited: case NPCocoaEventMouseDragged: case NPCocoaEventScrollWheel: p3d_event->data.mouse.modifierFlags = np_event->data.mouse.modifierFlags; p3d_event->data.mouse.pluginX = np_event->data.mouse.pluginX; p3d_event->data.mouse.pluginY = np_event->data.mouse.pluginY; p3d_event->data.mouse.buttonNumber = np_event->data.mouse.buttonNumber; p3d_event->data.mouse.clickCount = np_event->data.mouse.clickCount; p3d_event->data.mouse.deltaX = np_event->data.mouse.deltaX; p3d_event->data.mouse.deltaY = np_event->data.mouse.deltaY; p3d_event->data.mouse.deltaZ = np_event->data.mouse.deltaZ; break; case NPCocoaEventKeyDown: case NPCocoaEventKeyUp: case NPCocoaEventFlagsChanged: p3d_event->data.key.modifierFlags = np_event->data.key.modifierFlags; p3d_event->data.key.characters = make_ansi_string(aux_data._characters, np_event->data.key.characters); p3d_event->data.key.charactersIgnoringModifiers = make_ansi_string(aux_data._characters_im, np_event->data.key.charactersIgnoringModifiers); p3d_event->data.key.isARepeat = np_event->data.key.isARepeat; p3d_event->data.key.keyCode = np_event->data.key.keyCode; break; case NPCocoaEventFocusChanged: case NPCocoaEventWindowFocusChanged: p3d_event->data.focus.hasFocus = np_event->data.focus.hasFocus; break; case NPCocoaEventTextInput: p3d_event->data.text.text = make_ansi_string(aux_data._text, np_event->data.text.text); break; } } #endif // MACOSX_HAS_EVENT_MODELS #ifdef MACOSX_HAS_EVENT_MODELS //////////////////////////////////////////////////////////////////// // Function: PPInstance::make_ansi_string // Access: Private, Static // Description: OSX only: Fills result with the unicode characters in // the NPNSString. Also returns result.c_str(). //////////////////////////////////////////////////////////////////// const wchar_t *PPInstance:: make_ansi_string(wstring &result, NPNSString *ns_string) { result.clear(); if (ns_string != NULL) { // An NPNSString is really just an NSString, which is itself just a // CFString. CFStringRef cfstr = (CFStringRef)ns_string; CFIndex length = CFStringGetLength(cfstr); for (CFIndex i = 0; i < length; ++i) { result += (wchar_t)CFStringGetCharacterAtIndex(cfstr, i); } } return result.c_str(); } #endif // MACOSX_HAS_EVENT_MODELS #ifdef MACOSX_HAS_EVENT_MODELS //////////////////////////////////////////////////////////////////// // Function: PPInstance::handle_cocoa_event // Access: Private // Description: Locally processes a Cocoa event for the window before // sending it down to the Core API. This is used for // drawing a twirling icon in the window while the Core // API is downloading. //////////////////////////////////////////////////////////////////// void PPInstance:: handle_cocoa_event(const P3DCocoaEvent *p3d_event) { switch (p3d_event->type) { case P3DCocoaEventDrawRect: if (!_started) { CGContextRef context = p3d_event->data.draw.context; paint_twirl_osx_cgcontext(context); } break; default: break; } } #endif // MACOSX_HAS_EVENT_MODELS #ifdef MACOSX_HAS_EVENT_MODELS //////////////////////////////////////////////////////////////////// // Function: PPInstance::osx_get_twirl_images // Access: Private // Description: Fills _twirl_images with an array of images for // drawing the twirling icon while we're waiting for the // instance to load. //////////////////////////////////////////////////////////////////// void PPInstance:: osx_get_twirl_images() { if (_got_twirl_images) { return; } _got_twirl_images = true; static const size_t twirl_size = twirl_width * twirl_height; unsigned char twirl_data[twirl_size * 3]; for (int step = 0; step < twirl_num_steps + 1; ++step) { get_twirl_data(twirl_data, twirl_size, step, _fgcolor_r, _fgcolor_g, _fgcolor_b, _bgcolor_r, _bgcolor_g, _bgcolor_b); unsigned char *new_data = new unsigned char[twirl_size * 4]; // Expand the RGB channels into RGBA. Flip upside-down too. for (int yi = 0; yi < twirl_height; ++yi) { const unsigned char *sp = twirl_data + (twirl_height - 1 - yi) * twirl_width * 3; unsigned char *dp = new_data + yi * twirl_width * 4; for (int xi = 0; xi < twirl_width; ++xi) { // RGB <= BGR. dp[0] = sp[2]; dp[1] = sp[1]; dp[2] = sp[0]; dp[3] = (unsigned char)0xff; sp += 3; dp += 4; } } OsxImageData &image = _twirl_images[step]; image._raw_data = new_data; image._data = CFDataCreateWithBytesNoCopy(NULL, (const UInt8 *)image._raw_data, twirl_size * 4, kCFAllocatorNull); image._provider = CGDataProviderCreateWithCFData(image._data); image._color_space = CGColorSpaceCreateDeviceRGB(); image._image = CGImageCreate(twirl_width, twirl_height, 8, 32, twirl_width * 4, image._color_space, kCGImageAlphaFirst | kCGBitmapByteOrder32Little, image._provider, NULL, false, kCGRenderingIntentDefault); } } #endif // MACOSX_HAS_EVENT_MODELS #ifdef MACOSX_HAS_EVENT_MODELS //////////////////////////////////////////////////////////////////// // Function: PPInstance::osx_release_twirl_images // Access: Private // Description: Frees the twirl_images array. //////////////////////////////////////////////////////////////////// void PPInstance:: osx_release_twirl_images() { if (!_got_twirl_images) { return; } _got_twirl_images = false; for (int step = 0; step < twirl_num_steps + 1; ++step) { OsxImageData &image = _twirl_images[step]; if (image._image != NULL) { CGImageRelease(image._image); image._image = NULL; } if (image._color_space != NULL) { CGColorSpaceRelease(image._color_space); image._color_space = NULL; } if (image._provider != NULL) { CGDataProviderRelease(image._provider); image._provider = NULL; } if (image._data != NULL) { CFRelease(image._data); image._data = NULL; } if (image._raw_data != NULL) { delete[] image._raw_data; image._raw_data = NULL; } } } #endif // MACOSX_HAS_EVENT_MODELS #ifdef MACOSX_HAS_EVENT_MODELS //////////////////////////////////////////////////////////////////// // Function: PPInstance::paint_twirl_osx_cgcontext // Access: Private // Description: Actually paints the twirling icon in the OSX window, // using Core Graphics. (We don't bother painting it in // the older Cocoa interface.) //////////////////////////////////////////////////////////////////// void PPInstance:: paint_twirl_osx_cgcontext(CGContextRef context) { // Clear the whole region to the bgcolor before beginning. float bg_components[] = { _bgcolor_r / 255.0f, _bgcolor_g / 255.0f, _bgcolor_b / 255.0f, 1 }; CGColorSpaceRef rgb_space = CGColorSpaceCreateDeviceRGB(); CGColorRef bg = CGColorCreate(rgb_space, bg_components); CGRect region = { { 0, 0 }, { _window.width, _window.height } }; CGContextBeginPath(context); CGContextSetFillColorWithColor(context, bg); CGContextAddRect(context, region); CGContextFillPath(context); CGColorRelease(bg); CGColorSpaceRelease(rgb_space); if (_failed) { // Draw the failed icon if something went wrong. osx_paint_image(context, _twirl_images[twirl_num_steps]); } else { struct timeval tv; gettimeofday(&tv, (struct timezone *)NULL); double now = (double)(tv.tv_sec - _init_sec) + (double)(tv.tv_usec - _init_usec) / 1000000.0; // Don't draw the twirling icon until at least half a second has // passed, so we don't distract people by drawing it // unnecessarily. if (now >= 0.5) { int step = ((int)(now * 10.0)) % twirl_num_steps; osx_paint_image(context, _twirl_images[step]); } } } #endif // MACOSX_HAS_EVENT_MODELS #ifdef MACOSX_HAS_EVENT_MODELS //////////////////////////////////////////////////////////////////// // Function: PPInstance::osx_paint_image // Access: Private // Description: Draws the indicated image, centered within the // window. Returns true on success, false if the image // is not defined. //////////////////////////////////////////////////////////////////// bool PPInstance:: osx_paint_image(CGContextRef context, const OsxImageData &image) { if (image._image == NULL) { return false; } // Determine the relative size of image and window. int win_cx = _window.width / 2; int win_cy = _window.height / 2; CGRect rect = { { 0, 0 }, { 0, 0 } }; // The bitmap fits within the window; center it. // This is the top-left corner of the bitmap in window coordinates. int p_x = win_cx - twirl_width / 2; int p_y = win_cy - twirl_height / 2; rect.origin.x += p_x; rect.origin.y += p_y; rect.size.width = twirl_width; rect.size.height = twirl_height; CGContextDrawImage(context, rect, image._image); return true; } #endif // MACOSX_HAS_EVENT_MODELS #ifdef __APPLE__ //////////////////////////////////////////////////////////////////// // Function: PPInstance::timer_callback // Access: Private, Static // Description: OSX only: this callback is associated with a // CFRunLoopTimer; it's used to forward request messages // to the main thread. //////////////////////////////////////////////////////////////////// void PPInstance:: timer_callback(CFRunLoopTimerRef timer, void *info) { PPInstance *self = (PPInstance *)info; ACQUIRE_LOCK(self->_timer_lock); if (self->_request_timer != NULL) { CFRunLoopTimerInvalidate(self->_request_timer); CFRelease(self->_request_timer); self->_request_timer = NULL; } RELEASE_LOCK(self->_timer_lock); self->handle_request_loop(); } #endif // __APPLE__ #ifdef __APPLE__ //////////////////////////////////////////////////////////////////// // Function: PPInstance::st_twirl_timer_callback // Access: Private, Static // Description: OSX only: this callback is used to twirl the icon // before the instance loads. //////////////////////////////////////////////////////////////////// void PPInstance:: st_twirl_timer_callback(CFRunLoopTimerRef timer, void *info) { PPInstance *self = (PPInstance *)info; self->twirl_timer_callback(); } #endif // __APPLE__ #ifdef __APPLE__ //////////////////////////////////////////////////////////////////// // Function: PPInstance::twirl_timer_callback // Access: Private // Description: OSX only: this callback is used to twirl the icon // before the instance loads. //////////////////////////////////////////////////////////////////// void PPInstance:: twirl_timer_callback() { if (_got_window) { NPRect rect = { 0, 0, (unsigned short)_window.height, (unsigned short)_window.width }; browser->invalidaterect(_npp_instance, &rect); } } #endif // __APPLE__ #ifdef HAVE_X11 //////////////////////////////////////////////////////////////////// // Function: PPInstance::x11_start_twirl_subprocess // Access: Public // Description: Spawns a separate process to twirl the loading icon // in the X11 browser window. //////////////////////////////////////////////////////////////////// void PPInstance:: x11_start_twirl_subprocess() { if (_twirl_subprocess_pid != -1) { // Already started. return; } // Fork and exec. pid_t child = fork(); if (child < 0) { perror("fork"); return; } if (child == 0) { // Here we are in the child process. x11_twirl_subprocess_run(); _exit(99); } // In the parent process. _twirl_subprocess_pid = child; nout << "Started twirl subprocess, pid " << _twirl_subprocess_pid << "\n"; } #endif // HAVE_X11 #ifdef HAVE_X11 //////////////////////////////////////////////////////////////////// // Function: PPInstance::x11_stop_twirl_subprocess // Access: Public // Description: Kills the twirl process that was started earlier. //////////////////////////////////////////////////////////////////// void PPInstance:: x11_stop_twirl_subprocess() { if (_twirl_subprocess_pid == -1) { // Already stopped. return; } kill(_twirl_subprocess_pid, SIGKILL); int status; pid_t result = waitpid(_twirl_subprocess_pid, &status, 0); nout << "Twirl window process has successfully stopped.\n"; if (WIFEXITED(status)) { nout << " exited normally, status = " << WEXITSTATUS(status) << "\n"; } else if (WIFSIGNALED(status)) { nout << " signalled by " << WTERMSIG(status) << ", core = " << WCOREDUMP(status) << "\n"; } else if (WIFSTOPPED(status)) { nout << " stopped by " << WSTOPSIG(status) << "\n"; } _twirl_subprocess_pid = -1; } #endif // HAVE_X11 #ifdef HAVE_X11 //////////////////////////////////////////////////////////////////// // Function: PPInstance::x11_twirl_subprocess_run // Access: Public // Description: The code that is run within a subprocess. This code // is responsible for twirling the loading icon // endlessly. //////////////////////////////////////////////////////////////////// void PPInstance:: x11_twirl_subprocess_run() { // Since everything within this function happens within a subprocess // that will just exit, we can be a little sloppy with our resource // allocation. It is all done directly within this function, and we // don't need to worry about freeing stuff. // First, sleep for 0.5 seconds, so we don't start twirling right // away (to avoid distracting the user unnecessarily). struct timespec req; req.tv_sec = 0; req.tv_nsec = 500000000; // 500 ms nanosleep(&req, NULL); // We haven't been killed yet, so the plugin is still loading. // Start twirling. // First, embed a window. X11_Display *display = XOpenDisplay(NULL); assert(display != NULL); int screen = DefaultScreen(display); int depth = DefaultDepth(display, screen); Visual *dvisual = DefaultVisual(display, screen); long event_mask = ExposureMask; // Allocate the foreground and background colors. Colormap colormap = DefaultColormap(display, screen); XColor fg; fg.red = _fgcolor_r * 0x101; fg.green = _fgcolor_g * 0x101; fg.blue = _fgcolor_b * 0x101; fg.flags = DoRed | DoGreen | DoBlue; unsigned long fg_pixel = -1; if (XAllocColor(display, colormap, &fg)) { fg_pixel = fg.pixel; } XColor bg; bg.red = _bgcolor_r * 0x101; bg.green = _bgcolor_g * 0x101; bg.blue = _bgcolor_b * 0x101; bg.flags = DoRed | DoGreen | DoBlue; unsigned long bg_pixel = -1; if (XAllocColor(display, colormap, &bg)) { bg_pixel = bg.pixel; } // Initialize window attributes XSetWindowAttributes wa; wa.background_pixel = XWhitePixel(display, screen); if (bg_pixel != -1) { wa.background_pixel = bg_pixel; } wa.border_pixel = 0; wa.event_mask = event_mask; unsigned long attrib_mask = CWBackPixel | CWBorderPixel | CWEventMask; X11_Window parent = 0; if (_use_xembed) { #ifdef HAVE_GTK assert(_plug != NULL); parent = GDK_DRAWABLE_XID(_plug->window); #endif // HAVE_GTK } else { parent = (X11_Window)(_window.window); } X11_Window window = XCreateWindow (display, parent, 0, 0, _window.width, _window.height, 0, depth, InputOutput, dvisual, attrib_mask, &wa); XMapWindow(display, window); // Create a graphics context. XGCValues gcval; gcval.function = GXcopy; gcval.plane_mask = AllPlanes; gcval.foreground = BlackPixel(display, screen); if (fg_pixel != -1) { gcval.foreground = fg_pixel; } gcval.background = WhitePixel(display, screen); if (bg_pixel != -1) { gcval.background = bg_pixel; } GC graphics_context = XCreateGC(display, window, GCFunction | GCPlaneMask | GCForeground | GCBackground, &gcval); // Load up the twirling images. XImage *images[twirl_num_steps]; double r_ratio = dvisual->red_mask / 255.0; double g_ratio = dvisual->green_mask / 255.0; double b_ratio = dvisual->blue_mask / 255.0; static const size_t twirl_size = twirl_width * twirl_height; unsigned char twirl_data[twirl_size * 3]; for (int step = 0; step < twirl_num_steps; ++step) { get_twirl_data(twirl_data, twirl_size, step, _fgcolor_r, _fgcolor_g, _fgcolor_b, _bgcolor_r, _bgcolor_g, _bgcolor_b); uint32_t *new_data = new uint32_t[twirl_size]; int j = 0; for (int i = 0; i < twirl_size * 3; i += 3) { unsigned int r, g, b; r = (unsigned int)(twirl_data[i+0] * r_ratio); g = (unsigned int)(twirl_data[i+1] * g_ratio); b = (unsigned int)(twirl_data[i+2] * b_ratio); new_data[j++] = ((r & dvisual->red_mask) | (g & dvisual->green_mask) | (b & dvisual->blue_mask)); } // Now load the image. images[step] = XCreateImage(display, CopyFromParent, DefaultDepth(display, screen), ZPixmap, 0, (char *)new_data, twirl_width, twirl_height, 32, 0); } // Now twirl indefinitely, until our parent process kills us. bool needs_redraw = true; int last_step = -1; while (true) { // First, scan for X events. XEvent event; while (XCheckWindowEvent(display, window, ~0, &event)) { switch (event.type) { case Expose: case GraphicsExpose: needs_redraw = true; break; } // We should probably track the resize event, but this window // will be short-lived (and probably won't have an opportunity // to resize anyway) so we don't bother. } // What step are we on now? struct timeval tv; gettimeofday(&tv, (struct timezone *)NULL); double now = (double)(tv.tv_sec - _init_sec) + (double)(tv.tv_usec - _init_usec) / 1000000.0; int step = ((int)(now * 10.0)) % twirl_num_steps; if (step != last_step) { needs_redraw = true; } if (needs_redraw) { XClearWindow(display, window); int xo = (_window.width - twirl_width) / 2; int yo = (_window.height - twirl_height) / 2; XPutImage(display, window, graphics_context, images[step], 0, 0, xo, yo, twirl_width, twirl_height); XFlush(display); needs_redraw = false; last_step = step; } struct timespec req; req.tv_sec = 0; req.tv_nsec = 100000000; // 100 ms nanosleep(&req, NULL); } } #endif // HAVE_X11 //////////////////////////////////////////////////////////////////// // Function: PPInstance::StreamingFileData::Constructor // Access: Public // Description: //////////////////////////////////////////////////////////////////// PPInstance::StreamingFileData:: StreamingFileData(PPDownloadRequest *req, const string &filename, P3D_instance *p3d_inst) : _p3d_inst(p3d_inst), _user_id(req->_user_id), _filename(filename), _file(filename.c_str(), ios::in | ios::binary) { // First, seek to the end to get the file size. _file.seekg(0, ios::end); _file_size = _file.tellg(); _total_count = 0; // Then return to the beginning to read the data. _file.seekg(0, ios::beg); // Now start up the thread. _thread_done = false; _thread_continue = true; INIT_THREAD(_thread); SPAWN_THREAD(_thread, thread_run, this); } //////////////////////////////////////////////////////////////////// // Function: PPInstance::StreamingFileData::Destructor // Access: Public // Description: //////////////////////////////////////////////////////////////////// PPInstance::StreamingFileData:: ~StreamingFileData() { // Time to stop. _thread_continue = false; JOIN_THREAD(_thread); } //////////////////////////////////////////////////////////////////// // Function: PPInstance::StreamingFileData::is_done // Access: Public // Description: Returns true if the file has been fully read and this // object is ready to be deleted, or false if there is // more work to do. //////////////////////////////////////////////////////////////////// bool PPInstance::StreamingFileData:: is_done() const { return _thread_done; } //////////////////////////////////////////////////////////////////// // Function: PPInstance::StreamingFileData::thread_run // Access: Private // Description: The main function of the file thread. This reads the // file contents and feeds it to the core API. //////////////////////////////////////////////////////////////////// void PPInstance::StreamingFileData:: thread_run() { static const size_t buffer_size = 81920; //static const size_t buffer_size = 512; char buffer[buffer_size]; _file.read(buffer, buffer_size); size_t count = _file.gcount(); while (count != 0) { _total_count += count; bool download_ok = P3D_instance_feed_url_stream_ptr (_p3d_inst, _user_id, P3D_RC_in_progress, 0, _file_size, (const unsigned char *)buffer, count); if (!download_ok) { // Never mind. _thread_done = true; return; } if (!_thread_continue) { // Interrupted by the main thread. Presumably we're being shut // down. _thread_done = true; return; } // So far, so good. Read some more. _file.read(buffer, buffer_size); count = _file.gcount(); // This is useful for development, to slow things down enough to // see the progress bar move. #ifdef _WIN32 Sleep(10); #else struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 10000; select(0, NULL, NULL, NULL, &tv); #endif } // End of file. P3D_result_code result = P3D_RC_done; if (_file.fail() && !_file.eof()) { // Got an error while reading. result = P3D_RC_generic_error; } P3D_instance_feed_url_stream_ptr (_p3d_inst, _user_id, result, 0, _total_count, NULL, 0); // All done. _thread_done = true; } //////////////////////////////////////////////////////////////////// // Function: PPInstance::StreamTempFile::Constructor // Access: Public // Description: //////////////////////////////////////////////////////////////////// PPInstance::StreamTempFile:: StreamTempFile() { _opened = false; _finished = false; _current_size = 0; _total_size = 0; } //////////////////////////////////////////////////////////////////// // Function: PPInstance::StreamTempFile::Destructor // Access: Public // Description: //////////////////////////////////////////////////////////////////// PPInstance::StreamTempFile:: ~StreamTempFile() { cleanup(); } //////////////////////////////////////////////////////////////////// // Function: PPInstance::StreamTempFile::open // Access: Public // Description: Creates the temp file and prepares to write to it. // It is not normally necessary to call this explicitly; // it will be called automatically on the first call to // feed(). //////////////////////////////////////////////////////////////////// void PPInstance::StreamTempFile:: open() { assert(!_opened); _opened = true; _finished = false; _current_size = 0; _total_size = 0; char *name = tempnam(NULL, "p3d_"); _filename = name; free(name); _stream.clear(); _stream.open(_filename.c_str(), ios::binary); if (!_stream) { nout << "Unable to open temp file " << _filename << "\n"; } else { nout << "Opening " << _filename << "\n"; } } //////////////////////////////////////////////////////////////////// // Function: PPInstance::StreamTempFile::feed // Access: Public // Description: Receives new data from the URL and writes it to the // temp file. Returns true on success, false on // failure. //////////////////////////////////////////////////////////////////// bool PPInstance::StreamTempFile:: feed(size_t total_expected_data, const void *this_data, size_t this_data_size) { if (_finished) { nout << "feed(" << total_expected_data << ", " << (void *)this_data << ", " << this_data_size << ") to " << _filename << ", but already finished at " << _current_size << "\n"; return false; } if (!_opened) { open(); } _stream.write((const char *)this_data, this_data_size); _current_size += this_data_size; _total_size = total_expected_data; if (!_stream) { return false; } return true; } //////////////////////////////////////////////////////////////////// // Function: PPInstance::StreamTempFile::finish // Access: Public // Description: Marks the end of the data received from the URL. The // file is closed but not yet deleted; it remains on // disk and may be read at leisure. //////////////////////////////////////////////////////////////////// void PPInstance::StreamTempFile:: finish() { if (!_finished) { _finished = true; _total_size = _current_size; } _stream.close(); } //////////////////////////////////////////////////////////////////// // Function: PPInstance::StreamTempFile::close // Access: Public // Description: Closes the stream for more data. The file is not yet // deleted; it remains on disk and may be read at // leisure. //////////////////////////////////////////////////////////////////// void PPInstance::StreamTempFile:: close() { _stream.close(); } //////////////////////////////////////////////////////////////////// // Function: PPInstance::StreamTempFile::cleanup // Access: Public // Description: Closes all open processes and removes the temp file // from disk. //////////////////////////////////////////////////////////////////// void PPInstance::StreamTempFile:: cleanup() { finish(); if (!_filename.empty()) { nout << "Deleting " << _filename << "\n"; unlink(_filename.c_str()); _filename.clear(); } _opened = false; _finished = false; }