diff --git a/src/hobbits-core/bitcontainer.cpp b/src/hobbits-core/bitcontainer.cpp index 4f0833c8..393cec79 100644 --- a/src/hobbits-core/bitcontainer.cpp +++ b/src/hobbits-core/bitcontainer.cpp @@ -3,6 +3,8 @@ #include "settingsmanager.h" #include #include +#include +#include BitContainer::BitContainer(QObject *parent) : QObject(parent), diff --git a/src/hobbits-core/bitcontainer.h b/src/hobbits-core/bitcontainer.h index f950f011..10cd5180 100644 --- a/src/hobbits-core/bitcontainer.h +++ b/src/hobbits-core/bitcontainer.h @@ -3,12 +3,9 @@ #include "frame.h" -#include -#include #include #include #include -#include #include #include #include "bitinfo.h" @@ -18,6 +15,9 @@ class PluginActionLineage; class PluginAction; +class QColor; +class QPixmap; + class HOBBITSCORESHARED_EXPORT BitContainer : public QObject { Q_OBJECT diff --git a/src/hobbits-core/bitcontainerlistmodel.cpp b/src/hobbits-core/bitcontainerlistmodel.cpp index a9ecc9eb..81f81637 100644 --- a/src/hobbits-core/bitcontainerlistmodel.cpp +++ b/src/hobbits-core/bitcontainerlistmodel.cpp @@ -1,5 +1,6 @@ #include "bitcontainerlistmodel.h" #include "displayhelper.h" +#include BitContainerListModel::BitContainerListModel(QObject *parent) : QAbstractListModel(parent) diff --git a/src/hobbits-core/bitcontainertreemodel.cpp b/src/hobbits-core/bitcontainertreemodel.cpp index 18b9a07f..e0b0070c 100644 --- a/src/hobbits-core/bitcontainertreemodel.cpp +++ b/src/hobbits-core/bitcontainertreemodel.cpp @@ -1,5 +1,6 @@ #include "bitcontainertreemodel.h" #include "displayhelper.h" +#include BitContainerTreeModel::BitContainerTreeModel(QObject *parent) : QAbstractItemModel(parent) diff --git a/src/hobbits-core/bitinfo.cpp b/src/hobbits-core/bitinfo.cpp index 314a6269..93076996 100644 --- a/src/hobbits-core/bitinfo.cpp +++ b/src/hobbits-core/bitinfo.cpp @@ -42,6 +42,14 @@ void BitInfo::setFrames(QVector frames) initFrames(); } +void BitInfo::setFramesFromInfo(QSharedPointer frameSource) +{ + m_mutex.lock(); + m_ranges = frameSource->m_ranges; + m_mutex.unlock(); + initFrames(); +} + qint64 BitInfo::maxFrameWidth() const { return m_maxFrameWidth; diff --git a/src/hobbits-core/bitinfo.h b/src/hobbits-core/bitinfo.h index afb2f712..2c590cd8 100644 --- a/src/hobbits-core/bitinfo.h +++ b/src/hobbits-core/bitinfo.h @@ -22,6 +22,7 @@ class HOBBITSCORESHARED_EXPORT BitInfo : public QObject void setBits(QSharedPointer bits); void setFrames(QVector frames); + void setFramesFromInfo(QSharedPointer frameSource); void addHighlight(RangeHighlight highlight); void addHighlights(QList highlights); void setMetadata(QString key, QVariant value); diff --git a/src/hobbits-core/displaybase.cpp b/src/hobbits-core/displaybase.cpp index 764f8f0f..095d525c 100644 --- a/src/hobbits-core/displaybase.cpp +++ b/src/hobbits-core/displaybase.cpp @@ -194,6 +194,9 @@ void DisplayBase::drawHighlights( if (m_displayHandle->getContainer().isNull()) { return; } + if (frameOffset < 0 || bitOffset < 0) { + return; + } painter->setPen(Qt::transparent); for (QString category: m_displayHandle->getContainer()->bitInfo()->highlightCategories()) { diff --git a/src/hobbits-core/displayhelper.cpp b/src/hobbits-core/displayhelper.cpp index b4799440..608484ab 100644 --- a/src/hobbits-core/displayhelper.cpp +++ b/src/hobbits-core/displayhelper.cpp @@ -1,5 +1,6 @@ #include "displayhelper.h" #include "settingsmanager.h" +#include QImage DisplayHelper::getBitRasterImage(QSharedPointer bits, qint64 x, qint64 y, int w, int h) { diff --git a/src/hobbits-core/rangehighlight.cpp b/src/hobbits-core/rangehighlight.cpp index 8530836e..02be81ae 100644 --- a/src/hobbits-core/rangehighlight.cpp +++ b/src/hobbits-core/rangehighlight.cpp @@ -1,10 +1,11 @@ #include "rangehighlight.h" +#include RangeHighlight::RangeHighlight(QString category, QString label, Range range, QColor color, QList children) : m_category(category), m_label(label), m_range(range), - m_color(color), + m_color(color.rgba()), m_children(children) { @@ -13,7 +14,7 @@ RangeHighlight::RangeHighlight(QString category, QString label, Range range, QCo RangeHighlight::RangeHighlight(QString category, QString label, QList children, QColor color) : m_category(category), m_label(label), - m_color(color), + m_color(color.rgba()), m_children(children) { std::sort(m_children.begin(), m_children.end()); @@ -23,6 +24,11 @@ RangeHighlight::RangeHighlight(QString category, QString label, QList RangeHighlight::children() const { return m_children; @@ -81,10 +92,12 @@ QDataStream& operator>>(QDataStream& stream, RangeHighlight& highlight) QString version; stream >> version; if (version == VERSION_1 || version == VERSION_2) { + QColor c; stream >> highlight.m_category; stream >> highlight.m_label; stream >> highlight.m_range; - stream >> highlight.m_color; + stream >> c; + highlight.m_color = c.rgba(); if (version == VERSION_2) { stream >> highlight.m_children; } diff --git a/src/hobbits-core/rangehighlight.h b/src/hobbits-core/rangehighlight.h index 6160609a..895a5edb 100644 --- a/src/hobbits-core/rangehighlight.h +++ b/src/hobbits-core/rangehighlight.h @@ -1,11 +1,12 @@ #ifndef RANGEHIGHLIGHT_H #define RANGEHIGHLIGHT_H -#include #include #include "range.h" #include "hobbits-core_global.h" +class QColor; + class HOBBITSCORESHARED_EXPORT RangeHighlight { public: @@ -17,9 +18,12 @@ class HOBBITSCORESHARED_EXPORT RangeHighlight RangeHighlight(QString category, QString label, Range range, QColor color, QList children = {}); RangeHighlight(QString category, QString label, QList children, QColor color); + static RangeHighlight simple(QString category, QString label, Range range, unsigned int color); + QString label() const; QString category() const; Range range() const; + unsigned int rgbaColor() const; QColor color() const; QList children() const; QList allDescendants() const; @@ -31,7 +35,7 @@ class HOBBITSCORESHARED_EXPORT RangeHighlight QString m_category; QString m_label; Range m_range; - QColor m_color; + unsigned int m_color; QList m_children; }; diff --git a/src/hobbits-plugins/operators/PythonRunner/pythonrunner.cpp b/src/hobbits-plugins/operators/PythonRunner/pythonrunner.cpp index e07b94ec..7a757958 100644 --- a/src/hobbits-plugins/operators/PythonRunner/pythonrunner.cpp +++ b/src/hobbits-plugins/operators/PythonRunner/pythonrunner.cpp @@ -16,7 +16,8 @@ PythonRunner::PythonRunner() : ui(new Ui::PythonRunner()), - m_stateHelper(new PluginStateHelper()) + m_stateHelper(new PluginStateHelper()), + m_hasUi(false) { m_stateHelper->addTextEditStringParameter("script", [this](){return ui->te_pythonScript;}); } @@ -36,6 +37,7 @@ void PythonRunner::provideCallback(QSharedPointer pluginCallback void PythonRunner::applyToWidget(QWidget *widget) { ui->setupUi(widget); + m_hasUi = true; ui->te_pluginOutput->hide(); @@ -117,9 +119,11 @@ QSharedPointer PythonRunner::operateOnContainers( userScriptFile.close(); auto outputBits = QSharedPointer(new BitArray()); + auto outputInfo = QSharedPointer(new BitInfo()); auto pyRequest = PythonRequest::create(userScriptFile.fileName())->setFunctionName("process_bits"); - pyRequest->addArg(PythonArg::constBitArray(inputContainers.at(0)->bits())); + pyRequest->addArg(PythonArg::constBitContainer(inputContainers.at(0))); pyRequest->addArg(PythonArg::bitArray(outputBits)); + pyRequest->addArg(PythonArg::bitInfo(outputInfo)); auto watcher = HobbitsPython::getInstance().runProcessScript(pyRequest, progressTracker); watcher->watcher()->future().waitForFinished(); auto result = watcher->result(); @@ -144,6 +148,8 @@ QSharedPointer PythonRunner::operateOnContainers( QSharedPointer outputContainer = QSharedPointer(new BitContainer()); outputContainer->setBits(outputBits); + outputInfo->setFramesFromInfo(outputContainer->bitInfo()); + outputContainer->setBitInfo(outputInfo); outputContainer->setName(QString("python <- %1").arg(inputContainers.at(0)->name())); return OperatorResult::result({outputContainer}, recallablePluginState); @@ -161,12 +167,16 @@ OperatorInterface* PythonRunner::createDefaultOperator() void PythonRunner::updateOutputText(QString text) { - ui->te_pluginOutput->appendPlainText(text); - ui->te_pluginOutput->show(); + if (m_hasUi) { + ui->te_pluginOutput->appendPlainText(text); + ui->te_pluginOutput->show(); + } } void PythonRunner::clearOutputText() { - ui->te_pluginOutput->hide(); - ui->te_pluginOutput->clear(); + if (m_hasUi) { + ui->te_pluginOutput->hide(); + ui->te_pluginOutput->clear(); + } } diff --git a/src/hobbits-plugins/operators/PythonRunner/pythonrunner.h b/src/hobbits-plugins/operators/PythonRunner/pythonrunner.h index 5683ba0f..f87477d9 100644 --- a/src/hobbits-plugins/operators/PythonRunner/pythonrunner.h +++ b/src/hobbits-plugins/operators/PythonRunner/pythonrunner.h @@ -49,6 +49,7 @@ public Q_SLOTS: QSharedPointer m_pluginCallback; QSharedPointer m_stateHelper; QString m_outputText; + bool m_hasUi; }; #endif // PYTHONRUNNER_H diff --git a/src/hobbits-plugins/operators/PythonRunner/pythonrunner.ui b/src/hobbits-plugins/operators/PythonRunner/pythonrunner.ui index 9b78cf66..fb66756c 100644 --- a/src/hobbits-plugins/operators/PythonRunner/pythonrunner.ui +++ b/src/hobbits-plugins/operators/PythonRunner/pythonrunner.ui @@ -48,15 +48,17 @@ QPlainTextEdit::NoWrap - -def process_bits(input, output, progress): - output.resize(input.size()) - for i in range(0, input.size()): - output.set(i, not input.at(i)) + # example of inverting and highlighting +def process_bits(input, output_bits, output_info, progress): + if input.bits.size() > 3000: + output_info.add_highlight("python", "mychunk", 20, 3000) + output_bits.resize(input.bits.size()) + for i in range(0, input.bits.size()): + output_bits.set(i, not input.bits.at(i)) if progress.is_cancelled(): return - progress.set_progress(i, input.size()) + progress.set_progress(i, input.bits.size()) diff --git a/src/hobbits-python/hobbits-python.pro b/src/hobbits-python/hobbits-python.pro index 2f2923e2..cb819dce 100644 --- a/src/hobbits-python/hobbits-python.pro +++ b/src/hobbits-python/hobbits-python.pro @@ -26,6 +26,8 @@ SOURCES += \ hobbitspython.cpp \ py_actionprogress.cpp \ py_bitarray.cpp \ + py_bitcontainer.cpp \ + py_bitinfo.cpp \ py_hobbits.cpp \ pythonarg.cpp \ pythoninterpreter.cpp \ @@ -37,6 +39,8 @@ HEADERS += \ hobbits-python_global.h \ py_actionprogress.h \ py_bitarray.h \ + py_bitcontainer.h \ + py_bitinfo.h \ py_hobbits.h \ pythonarg.h \ pythoninterpreter.h \ diff --git a/src/hobbits-python/py_bitcontainer.cpp b/src/hobbits-python/py_bitcontainer.cpp new file mode 100644 index 00000000..ca6c0e1d --- /dev/null +++ b/src/hobbits-python/py_bitcontainer.cpp @@ -0,0 +1,287 @@ +#include "py_bitcontainer.h" +#include "bitcontainer.h" +#include + +#include + +#define BITCON(X) static_cast(PyCapsule_GetPointer(X, nullptr)) + +typedef struct { + PyObject_HEAD + PyObject* bitContainerCapsule; +} BitContainerPyObj; + +static void BitContainerPy_dealloc(BitContainerPyObj *self) +{ + Py_XDECREF(self->bitContainerCapsule); + Py_TYPE(self)->tp_free((PyObject*) self); +} + +static PyObject* BitContainerPy_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + BitContainerPyObj *self; + self = reinterpret_cast(type->tp_alloc(type, 0)); + if (self != nullptr) { + self->bitContainerCapsule = nullptr; + } + return (PyObject*) self; +} + +static int BitContainerPy_init(BitContainerPyObj *self, PyObject *args, PyObject *kwds) +{ + PyObject *bitContainerCapsule; + if (!PyArg_ParseTuple(args, "O", &bitContainerCapsule)) { + PyErr_SetString(PyExc_TypeError, "invalid arguments - requires a bit array capsule"); + return -1; + } + + Py_INCREF(bitContainerCapsule); + self->bitContainerCapsule = bitContainerCapsule; + return 0; +} + +static PyObject* BitContainerPy_get_bits(BitContainerPyObj *self, void *Py_UNUSED(closure)) +{ + BitContainer* container = BITCON(self->bitContainerCapsule); + + PyObject* hobbitsModuleName = PyUnicode_FromString("hobbits"); + PyObject* hobbitsModule = PyImport_GetModule(hobbitsModuleName); + Py_XDECREF(hobbitsModuleName); + PyObject* type = PyObject_GetAttrString(hobbitsModule, "ImmutableBitArray"); + PyObject* capsule = PyCapsule_New(const_cast(container->bits().data()), nullptr, nullptr); + PyObject* bits = PyObject_CallFunction(type, "O", capsule); + Py_DECREF(type); + Py_DECREF(capsule); + + return bits; +} + +static PyObject* BitContainerPy_get_info(BitContainerPyObj *self, void *Py_UNUSED(closure)) +{ + BitContainer* container = BITCON(self->bitContainerCapsule); + + PyObject* hobbitsModuleName = PyUnicode_FromString("hobbits"); + PyObject* hobbitsModule = PyImport_GetModule(hobbitsModuleName); + Py_XDECREF(hobbitsModuleName); + PyObject* type = PyObject_GetAttrString(hobbitsModule, "BitInfo"); + PyObject* capsule = PyCapsule_New(container->bitInfo().data(), nullptr, nullptr); + PyObject* info = PyObject_CallFunction(type, "O", capsule); + Py_DECREF(type); + Py_DECREF(capsule); + + return info; +} + +static PyObject* ImmutableBitContainerPy_get_info(BitContainerPyObj *self, void *Py_UNUSED(closure)) +{ + BitContainer* container = BITCON(self->bitContainerCapsule); + + PyObject* hobbitsModuleName = PyUnicode_FromString("hobbits"); + PyObject* hobbitsModule = PyImport_GetModule(hobbitsModuleName); + Py_XDECREF(hobbitsModuleName); + PyObject* type = PyObject_GetAttrString(hobbitsModule, "ImmutableBitInfo"); + PyObject* capsule = PyCapsule_New(container->bitInfo().data(), nullptr, nullptr); + PyObject* info = PyObject_CallFunction(type, "O", capsule); + Py_DECREF(type); + Py_DECREF(capsule); + + return info; +} + +static PyMethodDef BitContainerPy_methods[] = { + {} /* Sentinel */ +}; + +static PyMemberDef BitContainerPy_members[] = { + {} /* Sentinel */ +}; + +static PyGetSetDef BitContainerPy_getsets[] = { + {"bits", getter(BitContainerPy_get_bits), nullptr, nullptr, nullptr}, + {"info", getter(BitContainerPy_get_info), nullptr, nullptr, nullptr}, + {} /* Sentinel */ +}; + +static PyMethodDef ImmutableBitContainerPy_methods[] = { + {} /* Sentinel */ +}; + +static PyMemberDef ImmutableBitContainerPy_members[] = { + {} /* Sentinel */ +}; + +static PyGetSetDef ImmutableBitContainerPy_getsets[] = { + {"bits", getter(BitContainerPy_get_bits), nullptr, nullptr, nullptr}, + {"info", getter(ImmutableBitContainerPy_get_info), nullptr, nullptr, nullptr}, + {} /* Sentinel */ +}; + +extern PyTypeObject PyBitContainer = { + PyVarObject_HEAD_INIT(nullptr, 0) + + "hobbits.BitContainer", // const char *tp_name; /* For printing, in format "." */ + sizeof(BitContainerPyObj), // Py_ssize_t tp_basicsize, ; /* For allocation */ + 0, //tp_itemsize + + /* Methods to implement standard operations */ + + destructor(BitContainerPy_dealloc), // destructor tp_dealloc; + 0, // Py_ssize_t tp_vectorcall_offset; + nullptr, // getattrfunc tp_getattr; + nullptr, // setattrfunc tp_setattr; + nullptr, // PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2) or tp_reserved (Python 3) */ + nullptr, // reprfunc tp_repr; + + /* Method suites for standard classes */ + + nullptr, // PyNumberMethods *tp_as_number; + nullptr, // PySequenceMethods *tp_as_sequence; + nullptr, // PyMappingMethods *tp_as_mapping; + + /* More standard operations (here for binary compatibility) */ + + nullptr, // hashfunc tp_hash; + nullptr, // ternaryfunc tp_call; + nullptr, // reprfunc tp_str; + nullptr, // getattrofunc tp_getattro; + nullptr, // setattrofunc tp_setattro; + + /* Functions to access object as input/output buffer */ + nullptr, // PyBufferProcs *tp_as_buffer; + + /* Flags to define presence of optional/expanded features */ + Py_TPFLAGS_DEFAULT, // unsigned long tp_flags; + + "Hobbits Bit Container", // const char *tp_doc; /* Documentation string */ + + /* Assigned meaning in release 2.0 */ + /* call function for all accessible objects */ + nullptr, // traverseproc tp_traverse; + + /* delete references to contained objects */ + nullptr, // inquiry tp_clear; + + /* Assigned meaning in release 2.1 */ + /* rich comparisons */ + nullptr, // richcmpfunc tp_richcompare; + + /* weak reference enabler */ + 0, // Py_ssize_t tp_weaklistoffset; + + /* Iterators */ + nullptr, // getiterfunc tp_iter; + nullptr, // iternextfunc tp_iternext; + + /* Attribute descriptor and subclassing stuff */ + BitContainerPy_methods, // struct PyMethodDef *tp_methods; + BitContainerPy_members, // struct PyMemberDef *tp_members; + BitContainerPy_getsets, // struct PyGetSetDef *tp_getset; + nullptr, // struct _typeobject *tp_base; + nullptr, // PyObject *tp_dict; + nullptr, // descrgetfunc tp_descr_get; + nullptr, // descrsetfunc tp_descr_set; + 0, // Py_ssize_t tp_dictoffset; + initproc(BitContainerPy_init), // initproc tp_init; + nullptr, // allocfunc tp_alloc; + BitContainerPy_new, // newfunc tp_new; + nullptr, // freefunc tp_free; /* Low-level free-memory routine */ + nullptr, // inquiry tp_is_gc; /* For PyObject_IS_GC */ + nullptr, // PyObject *tp_bases; + nullptr, // PyObject *tp_mro; /* method resolution order */ + nullptr, // PyObject *tp_cache; + nullptr, // PyObject *tp_subclasses; + nullptr, // PyObject *tp_weaklist; + nullptr, // destructor tp_del; + + /* Type attribute cache version tag. Added in version 2.6 */ + 0, // unsigned int tp_version_tag; + + nullptr, // destructor tp_finalize; + nullptr, // vectorcallfunc tp_vectorcall; + // Py_DEPRECATED(3.8) int (*tp_print)(PyObject *, FILE *, int); +}; + +extern PyTypeObject PyImmutableBitContainer = { + PyVarObject_HEAD_INIT(nullptr, 0) + + "hobbits.ImmutableBitContainer", // const char *tp_name; /* For printing, in format "." */ + sizeof(BitContainerPyObj), // Py_ssize_t tp_basicsize, ; /* For allocation */ + 0, //tp_itemsize + + /* Methods to implement standard operations */ + + destructor(BitContainerPy_dealloc), // destructor tp_dealloc; + 0, // Py_ssize_t tp_vectorcall_offset; + nullptr, // getattrfunc tp_getattr; + nullptr, // setattrfunc tp_setattr; + nullptr, // PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2) or tp_reserved (Python 3) */ + nullptr, // reprfunc tp_repr; + + /* Method suites for standard classes */ + + nullptr, // PyNumberMethods *tp_as_number; + nullptr, // PySequenceMethods *tp_as_sequence; + nullptr, // PyMappingMethods *tp_as_mapping; + + /* More standard operations (here for binary compatibility) */ + + nullptr, // hashfunc tp_hash; + nullptr, // ternaryfunc tp_call; + nullptr, // reprfunc tp_str; + nullptr, // getattrofunc tp_getattro; + nullptr, // setattrofunc tp_setattro; + + /* Functions to access object as input/output buffer */ + nullptr, // PyBufferProcs *tp_as_buffer; + + /* Flags to define presence of optional/expanded features */ + Py_TPFLAGS_DEFAULT, // unsigned long tp_flags; + + "Immutable Hobbits Bit Container", // const char *tp_doc; /* Documentation string */ + + /* Assigned meaning in release 2.0 */ + /* call function for all accessible objects */ + nullptr, // traverseproc tp_traverse; + + /* delete references to contained objects */ + nullptr, // inquiry tp_clear; + + /* Assigned meaning in release 2.1 */ + /* rich comparisons */ + nullptr, // richcmpfunc tp_richcompare; + + /* weak reference enabler */ + 0, // Py_ssize_t tp_weaklistoffset; + + /* Iterators */ + nullptr, // getiterfunc tp_iter; + nullptr, // iternextfunc tp_iternext; + + /* Attribute descriptor and subclassing stuff */ + ImmutableBitContainerPy_methods, // struct PyMethodDef *tp_methods; + ImmutableBitContainerPy_members, // struct PyMemberDef *tp_members; + ImmutableBitContainerPy_getsets, // struct PyGetSetDef *tp_getset; + nullptr, // struct _typeobject *tp_base; + nullptr, // PyObject *tp_dict; + nullptr, // descrgetfunc tp_descr_get; + nullptr, // descrsetfunc tp_descr_set; + 0, // Py_ssize_t tp_dictoffset; + initproc(BitContainerPy_init), // initproc tp_init; + nullptr, // allocfunc tp_alloc; + BitContainerPy_new, // newfunc tp_new; + nullptr, // freefunc tp_free; /* Low-level free-memory routine */ + nullptr, // inquiry tp_is_gc; /* For PyObject_IS_GC */ + nullptr, // PyObject *tp_bases; + nullptr, // PyObject *tp_mro; /* method resolution order */ + nullptr, // PyObject *tp_cache; + nullptr, // PyObject *tp_subclasses; + nullptr, // PyObject *tp_weaklist; + nullptr, // destructor tp_del; + + /* Type attribute cache version tag. Added in version 2.6 */ + 0, // unsigned int tp_version_tag; + + nullptr, // destructor tp_finalize; + nullptr, // vectorcallfunc tp_vectorcall; + // Py_DEPRECATED(3.8) int (*tp_print)(PyObject *, FILE *, int); +}; diff --git a/src/hobbits-python/py_bitcontainer.h b/src/hobbits-python/py_bitcontainer.h new file mode 100644 index 00000000..c723bcb0 --- /dev/null +++ b/src/hobbits-python/py_bitcontainer.h @@ -0,0 +1,15 @@ +#ifndef PY_BITCONTAINER_H +#define PY_BITCONTAINER_H + +#define PY_SSIZE_T_CLEAN +#include + + +extern "C" { + +extern PyTypeObject PyBitContainer; +extern PyTypeObject PyImmutableBitContainer; + +} + +#endif // PY_BITCONTAINER_H diff --git a/src/hobbits-python/py_bitinfo.cpp b/src/hobbits-python/py_bitinfo.cpp new file mode 100644 index 00000000..295d1e14 --- /dev/null +++ b/src/hobbits-python/py_bitinfo.cpp @@ -0,0 +1,275 @@ +#include "py_bitinfo.h" +#include "bitinfo.h" +#include + +#include + +#define BITINFO(X) static_cast(PyCapsule_GetPointer(X, nullptr)) + +typedef struct { + PyObject_HEAD + PyObject* bitInfoCapsule; +} BitInfoPyObj; + +static void BitInfoPy_dealloc(BitInfoPyObj *self) +{ + Py_XDECREF(self->bitInfoCapsule); + Py_TYPE(self)->tp_free((PyObject*) self); +} + +static PyObject* BitInfoPy_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + BitInfoPyObj *self; + self = reinterpret_cast(type->tp_alloc(type, 0)); + if (self != nullptr) { + self->bitInfoCapsule = nullptr; + } + return (PyObject*) self; +} + +static int BitInfoPy_init(BitInfoPyObj *self, PyObject *args, PyObject *kwds) +{ + PyObject *bitArrayCapsule; + if (!PyArg_ParseTuple(args, "O", &bitArrayCapsule)) { + PyErr_SetString(PyExc_TypeError, "invalid arguments - requires a bit array capsule"); + return -1; + } + + Py_INCREF(bitArrayCapsule); + self->bitInfoCapsule = bitArrayCapsule; + return 0; +} + +static char* addHighlightKwargs[] = {"category", "label", "start", "end", "color", nullptr}; +static PyObject* BitInfoPy_add_highlight(BitInfoPyObj *self, PyObject *args, PyObject *kwds) +{ + char *category; + char *label; + long long start; + long long end; + unsigned int color = 0; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "ssLL|$I", addHighlightKwargs, &category, &label, &start, &end, &color)) { + return nullptr; + } + if (color == 0) { + color = 0x44ff8800; + } + BitInfo* info = BITINFO(self->bitInfoCapsule); + info->addHighlight(RangeHighlight::simple(category, label, Range(start, end), color)); + Py_RETURN_NONE; +} + +static PyObject* buildRangeHighlight(RangeHighlight highlight) { + return Py_BuildValue("{sssssLsLsI}", + "category", highlight.category().toStdString().c_str(), + "label", highlight.label().toStdString().c_str(), + "start", highlight.range().start(), + "end", highlight.range().end(), + "color", highlight.rgbaColor()); +} + +static char* getHighlightsKwargs[] = {"category", nullptr}; +static PyObject* BitInfoPy_get_highlights(BitInfoPyObj *self, PyObject *args, PyObject *kwds) +{ + char *category; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", getHighlightsKwargs, &category)) { + return nullptr; + } + BitInfo* info = BITINFO(self->bitInfoCapsule); + auto highlights = info->highlights(category); + PyObject* listResult = PyList_New(highlights.size()); + for (int i = 0; i < highlights.size(); i++) { + PyList_SetItem(listResult, i, buildRangeHighlight(highlights.at(i))); + } + + return listResult; +} + +static PyMethodDef BitInfoPy_methods[] = { + { "add_highlight", PyCFunction(BitInfoPy_add_highlight), METH_VARARGS | METH_KEYWORDS, nullptr }, + { "get_highlights", PyCFunction(BitInfoPy_get_highlights), METH_VARARGS | METH_KEYWORDS, nullptr }, + {} /* Sentinel */ +}; + +static PyMemberDef BitInfoPy_members[] = { + {} /* Sentinel */ +}; + +static PyMethodDef ImmutableBitInfoPy_methods[] = { + { "get_highlights", PyCFunction(BitInfoPy_get_highlights), METH_VARARGS | METH_KEYWORDS, nullptr }, + {} /* Sentinel */ +}; + +static PyMemberDef ImmutableBitInfoPy_members[] = { + {} /* Sentinel */ +}; + +extern PyTypeObject PyBitInfo = { + PyVarObject_HEAD_INIT(nullptr, 0) + + "hobbits.BitInfo", // const char *tp_name; /* For printing, in format "." */ + sizeof(BitInfoPyObj), // Py_ssize_t tp_basicsize, ; /* For allocation */ + 0, //tp_itemsize + + /* Methods to implement standard operations */ + + destructor(BitInfoPy_dealloc), // destructor tp_dealloc; + 0, // Py_ssize_t tp_vectorcall_offset; + nullptr, // getattrfunc tp_getattr; + nullptr, // setattrfunc tp_setattr; + nullptr, // PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2) or tp_reserved (Python 3) */ + nullptr, // reprfunc tp_repr; + + /* Method suites for standard classes */ + + nullptr, // PyNumberMethods *tp_as_number; + nullptr, // PySequenceMethods *tp_as_sequence; + nullptr, // PyMappingMethods *tp_as_mapping; + + /* More standard operations (here for binary compatibility) */ + + nullptr, // hashfunc tp_hash; + nullptr, // ternaryfunc tp_call; + nullptr, // reprfunc tp_str; + nullptr, // getattrofunc tp_getattro; + nullptr, // setattrofunc tp_setattro; + + /* Functions to access object as input/output buffer */ + nullptr, // PyBufferProcs *tp_as_buffer; + + /* Flags to define presence of optional/expanded features */ + Py_TPFLAGS_DEFAULT, // unsigned long tp_flags; + + "Hobbits Bit Info", // const char *tp_doc; /* Documentation string */ + + /* Assigned meaning in release 2.0 */ + /* call function for all accessible objects */ + nullptr, // traverseproc tp_traverse; + + /* delete references to contained objects */ + nullptr, // inquiry tp_clear; + + /* Assigned meaning in release 2.1 */ + /* rich comparisons */ + nullptr, // richcmpfunc tp_richcompare; + + /* weak reference enabler */ + 0, // Py_ssize_t tp_weaklistoffset; + + /* Iterators */ + nullptr, // getiterfunc tp_iter; + nullptr, // iternextfunc tp_iternext; + + /* Attribute descriptor and subclassing stuff */ + BitInfoPy_methods, // struct PyMethodDef *tp_methods; + BitInfoPy_members, // struct PyMemberDef *tp_members; + nullptr, // struct PyGetSetDef *tp_getset; + nullptr, // struct _typeobject *tp_base; + nullptr, // PyObject *tp_dict; + nullptr, // descrgetfunc tp_descr_get; + nullptr, // descrsetfunc tp_descr_set; + 0, // Py_ssize_t tp_dictoffset; + initproc(BitInfoPy_init), // initproc tp_init; + nullptr, // allocfunc tp_alloc; + BitInfoPy_new, // newfunc tp_new; + nullptr, // freefunc tp_free; /* Low-level free-memory routine */ + nullptr, // inquiry tp_is_gc; /* For PyObject_IS_GC */ + nullptr, // PyObject *tp_bases; + nullptr, // PyObject *tp_mro; /* method resolution order */ + nullptr, // PyObject *tp_cache; + nullptr, // PyObject *tp_subclasses; + nullptr, // PyObject *tp_weaklist; + nullptr, // destructor tp_del; + + /* Type attribute cache version tag. Added in version 2.6 */ + 0, // unsigned int tp_version_tag; + + nullptr, // destructor tp_finalize; + nullptr, // vectorcallfunc tp_vectorcall; + // Py_DEPRECATED(3.8) int (*tp_print)(PyObject *, FILE *, int); +}; + +extern PyTypeObject PyImmutableBitInfo = { + PyVarObject_HEAD_INIT(nullptr, 0) + + "hobbits.ImmutableBitInfo", // const char *tp_name; /* For printing, in format "." */ + sizeof(BitInfoPyObj), // Py_ssize_t tp_basicsize, ; /* For allocation */ + 0, //tp_itemsize + + /* Methods to implement standard operations */ + + destructor(BitInfoPy_dealloc), // destructor tp_dealloc; + 0, // Py_ssize_t tp_vectorcall_offset; + nullptr, // getattrfunc tp_getattr; + nullptr, // setattrfunc tp_setattr; + nullptr, // PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2) or tp_reserved (Python 3) */ + nullptr, // reprfunc tp_repr; + + /* Method suites for standard classes */ + + nullptr, // PyNumberMethods *tp_as_number; + nullptr, // PySequenceMethods *tp_as_sequence; + nullptr, // PyMappingMethods *tp_as_mapping; + + /* More standard operations (here for binary compatibility) */ + + nullptr, // hashfunc tp_hash; + nullptr, // ternaryfunc tp_call; + nullptr, // reprfunc tp_str; + nullptr, // getattrofunc tp_getattro; + nullptr, // setattrofunc tp_setattro; + + /* Functions to access object as input/output buffer */ + nullptr, // PyBufferProcs *tp_as_buffer; + + /* Flags to define presence of optional/expanded features */ + Py_TPFLAGS_DEFAULT, // unsigned long tp_flags; + + "Immutable Hobbits Bit Info", // const char *tp_doc; /* Documentation string */ + + /* Assigned meaning in release 2.0 */ + /* call function for all accessible objects */ + nullptr, // traverseproc tp_traverse; + + /* delete references to contained objects */ + nullptr, // inquiry tp_clear; + + /* Assigned meaning in release 2.1 */ + /* rich comparisons */ + nullptr, // richcmpfunc tp_richcompare; + + /* weak reference enabler */ + 0, // Py_ssize_t tp_weaklistoffset; + + /* Iterators */ + nullptr, // getiterfunc tp_iter; + nullptr, // iternextfunc tp_iternext; + + /* Attribute descriptor and subclassing stuff */ + ImmutableBitInfoPy_methods, // struct PyMethodDef *tp_methods; + ImmutableBitInfoPy_members, // struct PyMemberDef *tp_members; + nullptr, // struct PyGetSetDef *tp_getset; + nullptr, // struct _typeobject *tp_base; + nullptr, // PyObject *tp_dict; + nullptr, // descrgetfunc tp_descr_get; + nullptr, // descrsetfunc tp_descr_set; + 0, // Py_ssize_t tp_dictoffset; + initproc(BitInfoPy_init), // initproc tp_init; + nullptr, // allocfunc tp_alloc; + BitInfoPy_new, // newfunc tp_new; + nullptr, // freefunc tp_free; /* Low-level free-memory routine */ + nullptr, // inquiry tp_is_gc; /* For PyObject_IS_GC */ + nullptr, // PyObject *tp_bases; + nullptr, // PyObject *tp_mro; /* method resolution order */ + nullptr, // PyObject *tp_cache; + nullptr, // PyObject *tp_subclasses; + nullptr, // PyObject *tp_weaklist; + nullptr, // destructor tp_del; + + /* Type attribute cache version tag. Added in version 2.6 */ + 0, // unsigned int tp_version_tag; + + nullptr, // destructor tp_finalize; + nullptr, // vectorcallfunc tp_vectorcall; + // Py_DEPRECATED(3.8) int (*tp_print)(PyObject *, FILE *, int); +}; diff --git a/src/hobbits-python/py_bitinfo.h b/src/hobbits-python/py_bitinfo.h new file mode 100644 index 00000000..d6d92045 --- /dev/null +++ b/src/hobbits-python/py_bitinfo.h @@ -0,0 +1,15 @@ +#ifndef PY_BITINFO_H +#define PY_BITINFO_H + +#define PY_SSIZE_T_CLEAN +#include + + +extern "C" { + +extern PyTypeObject PyBitInfo; +extern PyTypeObject PyImmutableBitInfo; + +} + +#endif // PY_BITINFO_H diff --git a/src/hobbits-python/py_hobbits.cpp b/src/hobbits-python/py_hobbits.cpp index 70792d63..1b2df925 100644 --- a/src/hobbits-python/py_hobbits.cpp +++ b/src/hobbits-python/py_hobbits.cpp @@ -1,6 +1,8 @@ #include "py_hobbits.h" #include "py_bitarray.h" #include "py_actionprogress.h" +#include "py_bitinfo.h" +#include "py_bitcontainer.h" static struct PyModuleDef HobbitsModuleDef = { @@ -25,6 +27,18 @@ PyObject* PyInit_hobbits(void){ if (PyType_Ready(&PyActionProgress) < 0) return nullptr; + if (PyType_Ready(&PyBitContainer) < 0) + return nullptr; + + if (PyType_Ready(&PyImmutableBitContainer) < 0) + return nullptr; + + if (PyType_Ready(&PyBitInfo) < 0) + return nullptr; + + if (PyType_Ready(&PyImmutableBitInfo) < 0) + return nullptr; + PyObject *m = PyModule_Create(&HobbitsModuleDef); if (m == nullptr) return nullptr; @@ -32,12 +46,24 @@ PyObject* PyInit_hobbits(void){ Py_INCREF(&PyBitArray); Py_INCREF(&PyImmutableBitArray); Py_INCREF(&PyActionProgress); + Py_INCREF(&PyBitInfo); + Py_INCREF(&PyImmutableBitInfo); + Py_INCREF(&PyBitContainer); + Py_INCREF(&PyImmutableBitContainer); if (PyModule_AddObject(m, "BitArray", (PyObject*)&PyBitArray) < 0 || PyModule_AddObject(m, "ImmutableBitArray", (PyObject*)&PyImmutableBitArray) < 0 - || PyModule_AddObject(m, "ActionProgress", (PyObject*)&PyActionProgress) < 0) { + || PyModule_AddObject(m, "ActionProgress", (PyObject*)&PyActionProgress) < 0 + || PyModule_AddObject(m, "BitInfo", (PyObject*)&PyBitInfo) < 0 + || PyModule_AddObject(m, "ImmutableBitInfo", (PyObject*)&PyImmutableBitInfo) < 0 + || PyModule_AddObject(m, "BitContainer", (PyObject*)&PyBitContainer) < 0 + || PyModule_AddObject(m, "ImmutableBitContainer", (PyObject*)&PyImmutableBitContainer) < 0) { Py_DECREF(&PyBitArray); Py_DECREF(&PyImmutableBitArray); Py_DECREF(&PyActionProgress); + Py_DECREF(&PyBitInfo); + Py_DECREF(&PyImmutableBitInfo); + Py_DECREF(&PyBitContainer); + Py_DECREF(&PyImmutableBitContainer); Py_DECREF(m); return nullptr; } diff --git a/src/hobbits-python/pythonarg.cpp b/src/hobbits-python/pythonarg.cpp index ce0b4d7a..7c55d29d 100644 --- a/src/hobbits-python/pythonarg.cpp +++ b/src/hobbits-python/pythonarg.cpp @@ -61,6 +61,46 @@ PythonArg *PythonArg::actionProgress(QSharedPointer progress) return arg; } +PythonArg *PythonArg::bitContainer(QSharedPointer container) +{ + PythonArg *arg = new PythonArg(); + arg->m_type = Type::HobbitsWrapper; + arg->m_wrapType = "BitContainer"; + arg->m_argSymbol = "O"; + arg->m_pointer = container.data(); + return arg; +} + +PythonArg *PythonArg::constBitContainer(QSharedPointer container) +{ + PythonArg *arg = new PythonArg(); + arg->m_type = Type::HobbitsWrapper; + arg->m_wrapType = "ImmutableBitContainer"; + arg->m_argSymbol = "O"; + arg->m_pointer = const_cast(container.data()); + return arg; +} + +PythonArg *PythonArg::bitInfo(QSharedPointer info) +{ + PythonArg *arg = new PythonArg(); + arg->m_type = Type::HobbitsWrapper; + arg->m_wrapType = "BitInfo"; + arg->m_argSymbol = "O"; + arg->m_pointer = info.data(); + return arg; +} + +PythonArg *PythonArg::constBitInfo(QSharedPointer info) +{ + PythonArg *arg = new PythonArg(); + arg->m_type = Type::HobbitsWrapper; + arg->m_wrapType = "ImmutableBitInfo"; + arg->m_argSymbol = "O"; + arg->m_pointer = const_cast(info.data()); + return arg; +} + PythonArg *PythonArg::qString(QString s) { PythonArg *arg = new PythonArg(); diff --git a/src/hobbits-python/pythonarg.h b/src/hobbits-python/pythonarg.h index 620a7ab7..49d9134c 100644 --- a/src/hobbits-python/pythonarg.h +++ b/src/hobbits-python/pythonarg.h @@ -2,8 +2,8 @@ #define PYTHONARG_H #include -#include "bitarray.h" #include "actionprogress.h" +#include "bitcontainer.h" #include "hobbits-python_global.h" class HOBBITSPYTHONSHARED_EXPORT PythonArg @@ -17,6 +17,10 @@ class HOBBITSPYTHONSHARED_EXPORT PythonArg static PythonArg* bitArray(QSharedPointer bitArray); static PythonArg* constBitArray(QSharedPointer bitArray); static PythonArg* actionProgress(QSharedPointer progress); + static PythonArg* bitContainer(QSharedPointer container); + static PythonArg* constBitContainer(QSharedPointer container); + static PythonArg* bitInfo(QSharedPointer info); + static PythonArg* constBitInfo(QSharedPointer info); static PythonArg* qString(QString s); Type type() const; diff --git a/src/hobbits-python/pythoninterpreter.cpp b/src/hobbits-python/pythoninterpreter.cpp index 5880156d..75125e80 100644 --- a/src/hobbits-python/pythoninterpreter.cpp +++ b/src/hobbits-python/pythoninterpreter.cpp @@ -172,6 +172,8 @@ PyObject* parseArg(PyObject *hobbitsModule, PythonArg *arg) QSharedPointer finalize(QFile &stdoutFile, QFile &stderrFile, QStringList errors) { + PyRun_SimpleString("sys.stderr.close()"); + PyRun_SimpleString("sys.stdout.close()"); if (Py_FinalizeEx() != 0) { errors.append("Error encountered in Py_FinalizeEx()"); } diff --git a/src/hobbits-runner/hobbits-runner.pro b/src/hobbits-runner/hobbits-runner.pro index 53e4ba04..7a06ffb8 100644 --- a/src/hobbits-runner/hobbits-runner.pro +++ b/src/hobbits-runner/hobbits-runner.pro @@ -31,12 +31,23 @@ LIBS += -L$$OUT_PWD/../hobbits-core/ -lhobbits-core INCLUDEPATH += $$PWD/../hobbits-core DEPENDPATH += $$PWD/../hobbits-core +defined(HOBBITS_PYPATH, var) { + message(Building hobbits-gui with python support...) + DEFINES += HAS_EMBEDDED_PYTHON + + LIBS += -L$$OUT_PWD/../hobbits-python/ -lhobbits-python + INCLUDEPATH += $$PWD/../hobbits-python + DEPENDPATH += $$PWD/../hobbits-python + + include($$PWD/../hobbits-python/python-link.pri) +} + unix:!mac { QMAKE_LFLAGS_RPATH= - QMAKE_LFLAGS += "-Wl,-rpath,\'\$$ORIGIN/../lib:\$$ORIGIN\'" + QMAKE_LFLAGS += "-Wl,-rpath,\'\$$ORIGIN/../lib:\$$ORIGIN/../python/lib:\$$ORIGIN\'" } mac { QMAKE_LFLAGS_RPATH= - QMAKE_LFLAGS += "-Wl,-rpath,\'@executable_path/../Frameworks\'" + QMAKE_LFLAGS += "-Wl,-rpath,\'@executable_path/../Frameworks,-rpath,@executable_path/../Frameworks/python/lib\'" } diff --git a/src/hobbits-runner/main.cpp b/src/hobbits-runner/main.cpp index d8e29306..68d60847 100644 --- a/src/hobbits-runner/main.cpp +++ b/src/hobbits-runner/main.cpp @@ -12,6 +12,10 @@ #include "settingsmanager.h" #include +#ifdef HAS_EMBEDDED_PYTHON +#include "hobbitspython.h" +#endif + int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); @@ -19,12 +23,16 @@ int main(int argc, char *argv[]) QCoreApplication::setApplicationName("Hobbits Runner"); QCoreApplication::setApplicationVersion(HobbitsRunnerInfo::getRunnerVersion()); + QString description = "Command-line interface for bitstream analysis and processing"; +#ifdef HAS_EMBEDDED_PYTHON + description += QString("(built with embedded python support from %1)").arg(HobbitsPython::pythonVersion()); +#endif + QCommandLineParser parser; - parser.setApplicationDescription("Command-line interface for bitstream analysis and processing"); + parser.setApplicationDescription(description); parser.addHelpOption(); parser.addVersionOption(); - parser.addPositionalArgument( "mode", "The mode to run in\ @@ -64,6 +72,12 @@ int main(int argc, char *argv[]) QCoreApplication::translate("main", "file")); parser.addOption(configFilePathOption); + QCommandLineOption pythonHomePathOption( + QStringList() << "python-home", + QCoreApplication::translate("main", "Override the default embedded python location"), + QCoreApplication::translate("main", "path")); + parser.addOption(pythonHomePathOption); + parser.process(a); QTextStream out(stdout); @@ -91,6 +105,11 @@ int main(int argc, char *argv[]) SettingsManager::getInstance().setConfigFilePath(parser.value(configFilePathOption)); } + if (parser.isSet(pythonHomePathOption)) { + SettingsManager::getInstance().setTransientSetting(SettingsData::PYTHON_HOME_KEY, parser.value(pythonHomePathOption)); + err << QString("Setting python home: '%1'").arg(parser.value(pythonHomePathOption)) << endl; + } + // Initialize some stuff QSharedPointer pluginManager = QSharedPointer(new HobbitsPluginManager); QSharedPointer pluginActionManager = QSharedPointer( diff --git a/tests/test-archive.7z b/tests/test-archive.7z index b735c80e..0f1cece6 100644 Binary files a/tests/test-archive.7z and b/tests/test-archive.7z differ diff --git a/tests/test_hobbits.js b/tests/test_hobbits.js index d66b3e42..f9fb252a 100755 --- a/tests/test_hobbits.js +++ b/tests/test_hobbits.js @@ -6,11 +6,13 @@ const { execFileSync } = require("child_process"); const glob = require('glob'); const filecompare = require('filecompare'); -const argv = require('yargs').command('* ', 'Tests hobbits processing with known input/output files for various batches', (yargs) => { +const argv = require('yargs').command('* [python_home]', 'Tests hobbits processing with known input/output files for various batches', (yargs) => { yargs.positional('hobbits_runner', { describe: 'the path of the hobbits-runner binary you want to test', type: 'string' - }); + }).positional('python_home', { + describe: 'optional PYTHONHOME path to pass to hobbits_runner' + }) }).help().alias('help', 'h').argv; @@ -61,6 +63,11 @@ async function runTests() { '-o', testOutputPrefix ] + if (argv.python_home) { + args.push('--python-home') + args.push(argv.python_home) + } + inputMatches.sort() inputMatches.forEach(input => { args.push('-i')