genode/repos/libports/src/lib/qt5/qpluginwidget/qpluginwidget.cpp

479 lines
12 KiB
C++
Raw Normal View History

2013-08-09 15:04:52 +02:00
/*
* \brief A Qt Widget that can load a plugin application and show its Nitpicker view
* \author Christian Prochaska
* \date 2010-08-26
*/
#include <base/env.h>
#include <dataspace/client.h>
#include <os/timed_semaphore.h>
#include <rom_session/connection.h>
#include <util/arg_string.h>
#include <string.h>
#include <zlib.h>
#include <QtGui>
#include <qnitpickerplatformwindow.h>
2013-08-09 15:04:52 +02:00
#include <qpluginwidget/qpluginwidget.h>
QPluginWidget *QPluginWidget::_last = 0;
using namespace Genode;
const char *config = " \
<config> \
<parent-provides> \
<service name=\"CAP\"/> \
<service name=\"CPU\"/> \
<service name=\"LOG\"/> \
<service name=\"PD\"/> \
<service name=\"RAM\"/> \
<service name=\"RM\"/> \
<service name=\"ROM\"/> \
<service name=\"SIGNAL\"/> \
<service name=\"Timer\"/> \
<service name=\"Nitpicker\"/> \
</parent-provides> \
<default-route> \
<any-service> <parent/> <any-child/> </any-service> \
</default-route> \
<start name=\"tar_rom\"> \
<resource name=\"RAM\" quantum=\"1M\"/> \
<provides> <service name=\"ROM\"/> </provides> \
<config> \
<archive name=\"plugin.tar\"/> \
</config> \
</start> \
<start name=\"init\"> \
<resource name=\"RAM\" quantum=\"2G\"/> \
<configfile name=\"config.plugin\"/> \
<route> \
<service name=\"ROM\"> \
<if-arg key=\"filename\" value=\"config.plugin\" /> \
<child name=\"tar_rom\"/> \
</service> \
<any-service> <parent /> </any-service> \
</route> \
</start> \
</config> \
";
class Signal_wait_thread : public QThread
{
private:
Signal_receiver &_signal_receiver;
Timed_semaphore &_timeout_semaphore;
protected:
void run()
{
_signal_receiver.wait_for_signal();
_timeout_semaphore.up();
}
public:
Signal_wait_thread(Signal_receiver &signal_receiver, Timed_semaphore &timeout_semaphore)
: _signal_receiver(signal_receiver),
_timeout_semaphore(timeout_semaphore) { }
};
PluginStarter::PluginStarter(QUrl plugin_url, QString &args,
int max_width, int max_height,
Nitpicker::View_capability parent_view)
:
_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)
{ }
2013-08-09 15:04:52 +02:00
void PluginStarter::_start_plugin(QString &file_name, QByteArray const &file_buf)
{
if (file_name.endsWith(".gz")) {
file_name.remove(".gz");
uint32_t file_size = *(uint32_t*)(file_buf.constData() + file_buf.size() - sizeof(uint32_t));
PDBG("file_size_uncompressed = %u", file_size);
size_t ram_quota = Arg_string::find_arg(_args.constData(), "ram_quota").ulong_value(0) + file_size;
2013-08-09 15:04:52 +02:00
if ((long)env()->ram_session()->avail() - (long)ram_quota < QPluginWidget::RAM_QUOTA) {
PERR("quota exceeded");
_plugin_loading_state = QUOTA_EXCEEDED_ERROR;
return;
}
_pc = new Loader::Connection(ram_quota);
Dataspace_capability ds = _pc->alloc_rom_module(file_name.toUtf8().constData(), file_size);
if (ds.valid()) {
void *ds_addr = env()->rm_session()->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) {
PERR("inflateInit2() failed");
_plugin_loading_state = INFLATE_ERROR;
inflateEnd(&zs);
env()->rm_session()->detach(ds_addr);
return;
}
/* uncompress */
if (inflate(&zs, Z_SYNC_FLUSH) != Z_STREAM_END) {
PERR("inflate() failed");
_plugin_loading_state = INFLATE_ERROR;
inflateEnd(&zs);
env()->rm_session()->detach(ds_addr);
return;
}
inflateEnd(&zs);
env()->rm_session()->detach(ds_addr);
_pc->commit_rom_module(file_name.toUtf8().constData());
}
} else {
size_t ram_quota = Arg_string::find_arg(_args.constData(), "ram_quota").ulong_value(0);
2013-08-09 15:04:52 +02:00
if ((long)env()->ram_session()->avail() - (long)ram_quota < QPluginWidget::RAM_QUOTA) {
_plugin_loading_state = QUOTA_EXCEEDED_ERROR;
return;
}
_pc = new Loader::Connection(ram_quota);
Dataspace_capability plugin_ds = _pc->alloc_rom_module("plugin.tar", file_buf.size());
if (plugin_ds.valid()) {
void *plugin_ds_addr = env()->rm_session()->attach(plugin_ds);
::memcpy(plugin_ds_addr, file_buf.constData(), file_buf.size());
env()->rm_session()->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_session()->attach(config_ds);
::memcpy(config_ds_addr, config, ::strlen(config) + 1);
env()->rm_session()->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);
2013-08-09 15:04:52 +02:00
_pc->start("init", "init");
Timed_semaphore view_ready_semaphore;
Signal_wait_thread signal_wait_thread(sig_rec, view_ready_semaphore);
signal_wait_thread.start();
try {
view_ready_semaphore.down(10000);
_plugin_loading_state = LOADED;
} catch (Timeout_exception) {
_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(file_name.toLatin1().constData());
Dataspace_capability rom_ds = rc.dataspace();
char const *rom_ds_addr = (char const *)env()->rm_session()->attach(rom_ds);
QByteArray file_buf = QByteArray::fromRawData(rom_ds_addr, Dataspace_client(rom_ds).size());
_start_plugin(file_name, file_buf);
env()->rm_session()->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()
2013-08-09 15:04:52 +02:00
{
return _pc ? _pc->view_size() : Loader::Area();
2013-08-09 15:04:52 +02:00
}
void PluginStarter::view_geometry(Loader::Rect rect, Loader::Point offset)
{
if (_pc)
_pc->view_geometry(rect, offset);
}
/*******************
** QPluginWidget **
*******************/
2013-08-09 15:04:52 +02:00
QPluginWidget::QPluginWidget(QWidget *parent, QUrl plugin_url, QString &args,
int max_width, int max_height)
:
QEmbeddedViewWidget(parent),
2013-08-09 15:04:52 +02:00
_plugin_loading_state(LOADING),
_plugin_starter(0),
_plugin_starter_started(false),
_plugin_url(plugin_url),
_plugin_args(args),
2013-08-09 15:04:52 +02:00
_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 {
2013-08-09 15:04:52 +02:00
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 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<QNitpickerPlatformWindow*>(window()->windowHandle()->handle());
_plugin_starter = new PluginStarter(_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);
}
2013-08-09 15:04:52 +02:00
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);
2013-08-09 15:04:52 +02:00
} else {
_plugin_loading_error_string = _plugin_starter->plugin_loading_error_string();
setFixedSize((_max_width > -1) ? _max_width : 100,
2013-08-09 15:04:52 +02:00
(_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();
typedef Nitpicker::Session::Command Command;
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);
}
}