/* * \brief A Qt Widget that can load a plugin application and show its Nitpicker view * \author Christian Prochaska * \date 2010-08-26 */ /* Genode includes */ #include #include #include #include /* libc includes */ #include #include #include /* Qt includes */ #include #include #include Libc::Env *QPluginWidget::_env = 0; QPluginWidget *QPluginWidget::_last = 0; using namespace Genode; const char *config = " \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "; class Signal_wait_thread : public QThread { private: Signal_receiver &_signal_receiver; QMutex &_timeout_mutex; protected: void run() { _signal_receiver.wait_for_signal(); _timeout_mutex.unlock(); } public: Signal_wait_thread(Signal_receiver &signal_receiver, QMutex &timeout_mutex) : _signal_receiver(signal_receiver), _timeout_mutex(timeout_mutex) { } }; PluginStarter::PluginStarter(Libc::Env *env, QUrl plugin_url, QString &args, int max_width, int max_height, Nitpicker::View_capability parent_view) : _env(env), _plugin_url(plugin_url), _args(args.toLatin1()), _max_width(max_width), _max_height(max_height), _parent_view(parent_view), _pc(0), _plugin_loading_state(LOADING), _qnam(0), _reply(0) { } void PluginStarter::_start_plugin(QString &file_name, QByteArray const &file_buf) { Genode::size_t caps = Arg_string::find_arg(_args.constData(), "caps").ulong_value(0); if ((long)_env->pd().avail_caps().value - (long)caps < QPluginWidget::PRESERVED_CAPS) { Genode::error("Cannot donate ", caps, " capabilities to the plugin (quota exceeded)."); _plugin_loading_state = CAP_QUOTA_EXCEEDED_ERROR; return; } if (file_name.endsWith(".gz")) { file_name.remove(".gz"); uint32_t file_size = *(uint32_t*)(file_buf.constData() + file_buf.size() - sizeof(uint32_t)); Genode::log(__func__, ": file_size_uncompressed=", file_size); Genode::size_t ram_quota = Arg_string::find_arg(_args.constData(), "ram_quota").ulong_value(0) + file_size; if (((long)_env->pd().avail_ram().value - (long)ram_quota) < QPluginWidget::PRESERVED_RAM_QUOTA) { Genode::error("Cannot donate ", ram_quota, " bytes of RAM to the plugin (quota exceeded)."); _plugin_loading_state = RAM_QUOTA_EXCEEDED_ERROR; return; } _pc = new Loader::Connection(*_env, Genode::Ram_quota{ram_quota}, Genode::Cap_quota{caps}); Dataspace_capability ds = _pc->alloc_rom_module(file_name.toUtf8().constData(), file_size); if (ds.valid()) { void *ds_addr = _env->rm().attach(ds); z_stream zs; zs.next_in = (Bytef*)(file_buf.data()); zs.avail_in = file_buf.size(); zs.total_in = 0; zs.next_out = (Bytef*)ds_addr; zs.avail_out = file_size; zs.total_out = 0; zs.zalloc = Z_NULL; zs.zfree = Z_NULL; /* enable gzip format detection */ if (inflateInit2(&zs, 16 + MAX_WBITS) != Z_OK) { Genode::error("inflateInit2() failed"); _plugin_loading_state = INFLATE_ERROR; inflateEnd(&zs); _env->rm().detach(ds_addr); return; } /* uncompress */ if (inflate(&zs, Z_SYNC_FLUSH) != Z_STREAM_END) { Genode::error("inflate() failed"); _plugin_loading_state = INFLATE_ERROR; inflateEnd(&zs); _env->rm().detach(ds_addr); return; } inflateEnd(&zs); _env->rm().detach(ds_addr); _pc->commit_rom_module(file_name.toUtf8().constData()); } } else { Genode::size_t ram_quota = Arg_string::find_arg(_args.constData(), "ram_quota").ulong_value(0); if (((long)_env->pd().avail_ram().value - (long)ram_quota) < QPluginWidget::PRESERVED_RAM_QUOTA) { _plugin_loading_state = RAM_QUOTA_EXCEEDED_ERROR; return; } _pc = new Loader::Connection(*_env, Genode::Ram_quota{ram_quota}, Genode::Cap_quota{caps}); Dataspace_capability plugin_ds = _pc->alloc_rom_module("plugin.tar", file_buf.size()); if (plugin_ds.valid()) { void *plugin_ds_addr = _env->rm().attach(plugin_ds); ::memcpy(plugin_ds_addr, file_buf.constData(), file_buf.size()); _env->rm().detach(plugin_ds_addr); _pc->commit_rom_module("plugin.tar"); } } Dataspace_capability config_ds = _pc->alloc_rom_module("config", ::strlen(config) + 1); if (config_ds.valid()) { void *config_ds_addr = _env->rm().attach(config_ds); ::memcpy(config_ds_addr, config, ::strlen(config) + 1); _env->rm().detach(config_ds_addr); _pc->commit_rom_module("config"); } Signal_context sig_ctx; Signal_receiver sig_rec; _pc->view_ready_sigh(sig_rec.manage(&sig_ctx)); _pc->constrain_geometry(Loader::Area(_max_width, _max_height)); _pc->parent_view(_parent_view); _pc->start("init", "init"); QMutex view_ready_mutex; Signal_wait_thread signal_wait_thread(sig_rec, view_ready_mutex); signal_wait_thread.start(); if (view_ready_mutex.tryLock(10000)) { _plugin_loading_state = LOADED; } else { _plugin_loading_state = TIMEOUT_EXCEPTION; signal_wait_thread.terminate(); } signal_wait_thread.wait(); } void PluginStarter::run() { if (_plugin_url.scheme() == "rom") { QString file_name = _plugin_url.path().remove("/"); try { Rom_connection rc(*_env, file_name.toLatin1().constData()); Dataspace_capability rom_ds = rc.dataspace(); char const *rom_ds_addr = (char const *)_env->rm().attach(rom_ds); QByteArray file_buf = QByteArray::fromRawData(rom_ds_addr, Dataspace_client(rom_ds).size()); _start_plugin(file_name, file_buf); _env->rm().detach(rom_ds_addr); } catch (Rom_connection::Rom_connection_failed) { _plugin_loading_state = ROM_CONNECTION_FAILED_EXCEPTION; } emit finished(); } else if (_plugin_url.scheme() == "http") { _qnam = new QNetworkAccessManager(); _reply = _qnam->get(QNetworkRequest(_plugin_url)); connect(_reply, SIGNAL(finished()), this, SLOT(networkReplyFinished())); } exec(); delete _pc; moveToThread(QApplication::instance()->thread()); } void PluginStarter::networkReplyFinished() { if (_reply->error() != QNetworkReply::NoError) { _plugin_loading_state = NETWORK_ERROR; _plugin_loading_error_string = _reply->errorString(); _reply->deleteLater(); emit finished(); return; } qDebug() << "download finished, size = " << _reply->size(); QString file_name = _plugin_url.path().remove("/");; QByteArray file_buf = _reply->readAll(); _start_plugin(file_name, file_buf); _reply->deleteLater(); _qnam->deleteLater(); emit finished(); } Loader::Area PluginStarter::view_size() { return _pc ? _pc->view_size() : Loader::Area(); } void PluginStarter::view_geometry(Loader::Rect rect, Loader::Point offset) { if (_pc) _pc->view_geometry(rect, offset); } /******************* ** QPluginWidget ** *******************/ QPluginWidget::QPluginWidget(QWidget *parent, QUrl plugin_url, QString &args, int max_width, int max_height) : QEmbeddedViewWidget(parent), _plugin_loading_state(LOADING), _plugin_starter(0), _plugin_starter_started(false), _plugin_url(plugin_url), _plugin_args(args), _max_width(max_width), _max_height(max_height) { qDebug() << "plugin_url = " << plugin_url; qDebug() << "plugin_url.scheme() = " << plugin_url.scheme(); qDebug() << "plugin_url.path() = " << plugin_url.path(); qDebug() << "plugin_url.toLocalFile() = " << plugin_url.toLocalFile(); qDebug() << "args =" << args; if (_last) { _last->cleanup(); _last = this; } } QPluginWidget::~QPluginWidget() { cleanup(); if (_last == this) _last = 0; } void QPluginWidget::cleanup() { if (_plugin_starter) { /* make the thread leave the event loop */ _plugin_starter->exit(); /* wait until the thread has left the run() function */ _plugin_starter->wait(); /* delete the QThread object */ delete _plugin_starter; _plugin_starter = 0; } } void QPluginWidget::paintEvent(QPaintEvent *event) { QWidget::paintEvent(event); if (_plugin_loading_state == LOADED) { QEmbeddedViewWidget::View_geometry const view_geometry = QEmbeddedViewWidget::_calc_view_geometry(); if (mask().isEmpty()) { Loader::Rect const geometry(Loader::Point(view_geometry.x, view_geometry.y), Loader::Area(view_geometry.w, view_geometry.h)); Loader::Point const offset(view_geometry.buf_x, view_geometry.buf_y); _plugin_starter->view_geometry(geometry, offset); } else { Loader::Rect const geometry(Loader::Point(mapToGlobal(mask().boundingRect().topLeft()).x(), mapToGlobal(mask().boundingRect().topLeft()).y()), Loader::Area(mask().boundingRect().width(), mask().boundingRect().height())); Loader::Point const offset(view_geometry.buf_x, view_geometry.buf_y); _plugin_starter->view_geometry(geometry, offset); } } else { QPainter painter(this); painter.drawRect(0, 0, width() - 1, height() - 1); switch (_plugin_loading_state) { case LOADING: painter.drawText(rect(), Qt::AlignCenter, tr("Loading plugin...")); break; case NETWORK_ERROR: painter.drawText(rect(), Qt::AlignCenter, tr("Could not load plugin: ") + _plugin_loading_error_string); break; case INFLATE_ERROR: painter.drawText(rect(), Qt::AlignCenter, tr("Could not load plugin: error decompressing gzipped file.")); break; case CAP_QUOTA_EXCEEDED_ERROR: painter.drawText(rect(), Qt::AlignCenter, tr("Could not load plugin: not enough capabilities.")); break; case RAM_QUOTA_EXCEEDED_ERROR: painter.drawText(rect(), Qt::AlignCenter, tr("Could not load plugin: not enough memory.")); break; case TIMEOUT_EXCEPTION: painter.drawText(rect(), Qt::AlignCenter, tr("Could not load plugin: timeout.")); break; case ROM_CONNECTION_FAILED_EXCEPTION: painter.drawText(rect(), Qt::AlignCenter, tr("Could not load plugin: file not found.")); break; default: break; } } } void QPluginWidget::showEvent(QShowEvent *event) { /* only now do we know the parent widget for sure */ if (!_plugin_starter_started) { QNitpickerPlatformWindow *platform_window = dynamic_cast(window()->windowHandle()->handle()); assert(_env != nullptr); _plugin_starter = new PluginStarter(_env, _plugin_url, _plugin_args, _max_width, _max_height, platform_window->view_cap()); _plugin_starter->moveToThread(_plugin_starter); connect(_plugin_starter, SIGNAL(finished()), this, SLOT(pluginStartFinished())); _plugin_starter->start(); _plugin_starter_started = true; } QEmbeddedViewWidget::showEvent(event); } void QPluginWidget::pluginStartFinished() { _plugin_loading_state = _plugin_starter->plugin_loading_state(); if (_plugin_loading_state == LOADED) { Loader::Area const size = _plugin_starter->view_size(); QEmbeddedViewWidget::_orig_geometry(size.w(), size.h(), 0, 0); int w = (_max_width > -1) ? qMin(size.w(), (unsigned)_max_width) : size.w(); int h = (_max_height > -1) ? qMin(size.h(), (unsigned)_max_height) : size.h(); setFixedSize(w, h); } else { _plugin_loading_error_string = _plugin_starter->plugin_loading_error_string(); setFixedSize((_max_width > -1) ? _max_width : 100, (_max_height > -1) ? _max_height : 100); cleanup(); } update(); } void QPluginWidget::hideEvent(QHideEvent *event) { QWidget::hideEvent(event); if (_plugin_loading_state == LOADED) { QEmbeddedViewWidget::View_geometry const view_geometry = QEmbeddedViewWidget::_calc_view_geometry(); Loader::Rect geometry(Loader::Point(mapToGlobal(pos()).x(), mapToGlobal(pos()).y()), Loader::Area(0, 0)); Loader::Point offset(view_geometry.buf_x, view_geometry.buf_y); _plugin_starter->view_geometry(geometry, offset); } }