/** * 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." * * @file ppInstance.cxx * @author drose * @date 2009-06-19 */ #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 using std::ios; using std::ostream; using std::ostringstream; using std::string; using std::vector; PPInstance::FileDatas PPInstance::_file_datas; /** * 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 = nullptr; _npp_instance = instance; _npp_mode = mode; _window_handle_type = window_handle_type; _event_type = event_type; _script_object = nullptr; _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] != nullptr) { const char *v = argv[i]; if (v == nullptr) { // 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 = nullptr; #endif // HAVE_GTK #endif // HAVE_X11 #ifdef _WIN32 _hwnd = nullptr; _bg_brush = nullptr; #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, nullptr); _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 = nullptr; INIT_LOCK(_timer_lock); // Also set up a timer to twirl the icon until the instance loads. _twirl_timer = nullptr; CFRunLoopTimerContext timer_context; memset(&timer_context, 0, sizeof(timer_context)); timer_context.info = this; _twirl_timer = CFRunLoopTimerCreate (nullptr, 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 } /** * */ PPInstance:: ~PPInstance() { cleanup_window(); #ifdef __APPLE__ if (_twirl_timer != nullptr) { CFRunLoopTimerInvalidate(_twirl_timer); CFRelease(_twirl_timer); _twirl_timer = nullptr; } if (_request_timer != nullptr) { CFRunLoopTimerInvalidate(_request_timer); CFRelease(_request_timer); _request_timer = nullptr; } _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 != nullptr) { P3D_instance_finish_ptr(_p3d_inst); _p3d_inst = nullptr; } assert(_streams.empty()); assert(_file_datas.empty()); if (_script_object != nullptr) { 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(); } /** * 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(nullptr) < _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(nullptr); url = strm.str(); PPDownloadRequest *req = new PPDownloadRequest(PPDownloadRequest::RT_contents_file); start_download(url, req); } } handle_request_loop(); } /** * 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 = nullptr; 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, GWLP_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, nullptr); } #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 != nullptr) { gtk_widget_destroy(_plug); _plug = nullptr; } // 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 == nullptr) { create_instance(); } else { send_window(); } } } /** * 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 == nullptr) { // 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 != nullptr) { _got_instance_url = true; _instance_url = stream->url; stream->notifyData = new PPDownloadRequest(PPDownloadRequest::RT_instance_data); if (_p3d_inst != nullptr) { // 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; } /** * 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(); } /** * 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; } /** * 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 == nullptr) { 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 == nullptr) { // 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; } /** * 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 == nullptr) { 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, nullptr, 0); req->_notified_done = true; } break; case PPDownloadRequest::RT_instance_data: if (!req->_notified_done) { if (_p3d_inst == nullptr) { // 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, nullptr, 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; } /** * 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 == nullptr) { 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, nullptr, 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; } /** * 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 == nullptr) { 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 // "blahblahblah". 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(nullptr, "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); } /** * 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 == nullptr || _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 != nullptr) { P3D_instance_finish_ptr(_p3d_inst); _p3d_inst = nullptr; } 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; } /** * 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(); } */ } /** * 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 != nullptr) { if (P3D_instance_handle_event_ptr(_p3d_inst, &edata)) { retval = true; } } return retval; } /** * 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 != nullptr) { // NPRuntime "steals" a reference to this object. browser->retainobject(_script_object); return _script_object; } P3D_object *main = nullptr; if (_p3d_inst != nullptr) { 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; } /** * 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; } /** * 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, nullptr, 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; } } /** * 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(); } /** * 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; } } /** * 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 != nullptr) { const char *url = xhost->Attribute("url"); if (url != nullptr && 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 != nullptr) { const char *url = xalthost->Attribute("url"); if (url != nullptr && 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); } } /** * 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 != nullptr) { _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 != nullptr) { const char *url = xmirror->Attribute("url"); if (url != nullptr) { add_mirror(url); } xmirror = xmirror->NextSiblingElement("mirror"); } } /** * 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); } } /** * 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 = std::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; } } /** * 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 != nullptr); 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 != nullptr); browser->pluginthreadasynccall(inst->_npp_instance, browser_sync_callback, nullptr); #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 != nullptr && 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 == nullptr) { CFRunLoopTimerContext timer_context; memset(&timer_context, 0, sizeof(timer_context)); timer_context.info = inst; inst->_request_timer = CFRunLoopTimerCreate (nullptr, 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. } } /** * 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(), nullptr, req); } } /** * 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(); } } } /** * 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 != nullptr) { 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(nullptr); _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 != nullptr) { 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 != nullptr) { xorig->Attribute("expiration", &expiration); } _contents_expiration = std::min(_contents_expiration, (time_t)expiration); } nout << "read contents.xml, max_age = " << max_age << ", expires in " << std::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 != nullptr) { const char *name = xpackage->Attribute("name"); if (name != nullptr && strcmp(name, "coreapi") == 0) { const char *platform = xpackage->Attribute("platform"); if (platform != nullptr && strcmp(platform, DTOOL_PLATFORM) == 0) { _coreapi_dll.load_xml(xpackage); const char *set_ver = xpackage->Attribute("set_ver"); if (set_ver != nullptr) { _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; } /** * 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; } /** * 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"; } } /** * 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); } /** * 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, nullptr, 0); } _p3d_temp_file.cleanup(); } /** * 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(nullptr); 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); } } /** * 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 != nullptr) { 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; } /** * 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(); } /** * 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 != nullptr) { CFRunLoopTimerInvalidate(_twirl_timer); CFRelease(_twirl_timer); _twirl_timer = nullptr; } #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 = nullptr; if (!_tokens.empty()) { tokens = &_tokens[0]; } _started = true; _p3d_inst = P3D_new_instance_ptr(request_ready, tokens, _tokens.size(), 0, nullptr, this); if (_p3d_inst == nullptr) { 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 = nullptr; 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 != nullptr) { // 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(); } } /** * Actually issues the window parameters to the internal P3D_instance object. */ void PPInstance:: send_window() { assert(_p3d_inst != nullptr); 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) { #if !__LP64__ NP_Port *port = (NP_Port *)_window.window; parent_window._handle._osx_port._port = port->port; #endif } else if (_window_handle_type == P3D_WHT_osx_cgcontext) { NP_CGContext *context = (NP_CGContext *)_window.window; if (context != nullptr) { 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 != nullptr); 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) { #if !__LP64__ NP_Port *port = (NP_Port *)_window.window; parent_window._handle._osx_port._port = port->port; #endif } else if (_window_handle_type == P3D_WHT_osx_cgcontext) { NP_CGContext *context = (NP_CGContext *)_window.window; if (context != nullptr) { 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 == nullptr && _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); } /** * 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, GWLP_WNDPROC, _orig_window_proc); InvalidateRect(hwnd, nullptr, true); if (_bg_brush != nullptr) { DeleteObject(_bg_brush); _bg_brush = nullptr; } for (int step = 0; step < twirl_num_steps + 1; ++step) { if (_twirl_bitmaps[step] != nullptr) { DeleteObject(_twirl_bitmaps[step]); _twirl_bitmaps[step] = nullptr; } } #endif // _WIN32 #ifdef HAVE_X11 x11_stop_twirl_subprocess(); #ifdef HAVE_GTK if (_plug != nullptr) { gtk_widget_destroy(_plug); _plug = nullptr; } #endif // HAVE_GTK #endif // HAVE_X11 _got_window = false; } } /** * 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; } /** * 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(); } /** * 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; } /** * 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; } /** * 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; } } /** * 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 = nullptr; if (browser->getvalue(_npp_instance, NPNVWindowNPObject, &window_object) == NPERR_NO_ERROR) { NPString npexpr = { expression.c_str(), (uint32_t)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 != nullptr) { P3D_instance_finish_ptr(_p3d_inst); _p3d_inst = nullptr; } cleanup_window(); } } /** * 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 != nullptr) { P3D_request *request = P3D_instance_get_request_ptr(p3d_inst); if (request != nullptr) { PPInstance *inst = (PPInstance *)(p3d_inst->_user_data); assert(inst != nullptr); 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; } } } /** * 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 /** * 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 == nullptr) { // 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 /** * 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, nullptr, FALSE); } break; case WM_USER: break; } return DefWindowProc(hwnd, msg, wparam, lparam); } #endif // _WIN32 #ifdef _WIN32 /** * 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 /** * 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, nullptr); 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 /** * 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 /** * OSX only: Fills result with the unicode characters in the NPNSString. Also * returns result.c_str(). */ const wchar_t *PPInstance:: make_ansi_string(std::wstring &result, NPNSString *ns_string) { result.clear(); if (ns_string != nullptr) { // 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 /** * 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 /** * 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(nullptr, (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, nullptr, false, kCGRenderingIntentDefault); } } #endif // MACOSX_HAS_EVENT_MODELS #ifdef MACOSX_HAS_EVENT_MODELS /** * 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 != nullptr) { CGImageRelease(image._image); image._image = nullptr; } if (image._color_space != nullptr) { CGColorSpaceRelease(image._color_space); image._color_space = nullptr; } if (image._provider != nullptr) { CGDataProviderRelease(image._provider); image._provider = nullptr; } if (image._data != nullptr) { CFRelease(image._data); image._data = nullptr; } if (image._raw_data != nullptr) { delete[] image._raw_data; image._raw_data = nullptr; } } } #endif // MACOSX_HAS_EVENT_MODELS #ifdef MACOSX_HAS_EVENT_MODELS /** * 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, nullptr); 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 /** * 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 == nullptr) { 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__ /** * 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 != nullptr) { CFRunLoopTimerInvalidate(self->_request_timer); CFRelease(self->_request_timer); self->_request_timer = nullptr; } RELEASE_LOCK(self->_timer_lock); self->handle_request_loop(); } #endif // __APPLE__ #ifdef __APPLE__ /** * 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__ /** * 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 /** * 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 /** * 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 /** * 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, nullptr); // We haven't been killed yet, so the plugin is still loading. Start // twirling. // First, embed a window. X11_Display *display = XOpenDisplay(nullptr); assert(display != nullptr); 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 != nullptr); 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, nullptr); 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, nullptr); } } #endif // HAVE_X11 /** * */ 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); } /** * */ PPInstance::StreamingFileData:: ~StreamingFileData() { // Time to stop. _thread_continue = false; JOIN_THREAD(_thread); } /** * 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; } /** * 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, nullptr, nullptr, nullptr, &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, nullptr, 0); // All done. _thread_done = true; } /** * */ PPInstance::StreamTempFile:: StreamTempFile() { _opened = false; _finished = false; _current_size = 0; _total_size = 0; } /** * */ PPInstance::StreamTempFile:: ~StreamTempFile() { cleanup(); } /** * 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(nullptr, "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"; } } /** * 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; } /** * 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(); } /** * 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(); } /** * 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; }