Merge PR #406: Add browsing support to python plugin API
This commit is contained in:
commit
07327b8bb4
121
libnymea-core/integrations/python/pybrowseractioninfo.h
Normal file
121
libnymea-core/integrations/python/pybrowseractioninfo.h
Normal file
@ -0,0 +1,121 @@
|
||||
#ifndef PYBROWSERACTIONINFO_H
|
||||
#define PYBROWSERACTIONINFO_H
|
||||
|
||||
#include <Python.h>
|
||||
#include "structmember.h"
|
||||
|
||||
#include "pything.h"
|
||||
|
||||
#include "integrations/browseractioninfo.h"
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Winvalid-offsetof"
|
||||
#pragma GCC diagnostic ignored "-Wwrite-strings"
|
||||
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
|
||||
|
||||
/* Note:
|
||||
*
|
||||
* When using this, make sure to call PyBrowserActionInfo_setInfo() while holding the GIL to initialize
|
||||
* stuff after constructing it. Also set info to nullptr while holding the GIL when the info object vanishes.
|
||||
*
|
||||
* The BrowserActionInfo class is not threadsafe and self->info is owned by nymeas main thread.
|
||||
* So we must never directly access anything of it in here.
|
||||
*
|
||||
* For writing to it, invoking methods with QueuedConnections will thread-decouple stuff.
|
||||
* Make sure to check if the info object is still valid (it might not be if nymea finished
|
||||
* the setup and destroyed it but the PyThingSetupInfo is not garbage collected yet.
|
||||
*
|
||||
* For reading access, we keep copies of the thing properties here and sync them
|
||||
* over to the according py* members when they change.
|
||||
*
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
BrowserActionInfo* info;
|
||||
PyThing *pyThing;
|
||||
PyObject *pyItemId;
|
||||
} PyBrowserActionInfo;
|
||||
|
||||
|
||||
static PyObject* PyBrowserActionInfo_new(PyTypeObject *type, PyObject */*args*/, PyObject */*kwds*/) {
|
||||
PyBrowserActionInfo *self = (PyBrowserActionInfo*)type->tp_alloc(type, 0);
|
||||
if (self == NULL) {
|
||||
return nullptr;
|
||||
}
|
||||
qCDebug(dcPythonIntegrations()) << "+++ PyBrowserActionInfo";
|
||||
return (PyObject*)self;
|
||||
}
|
||||
|
||||
void PyBrowserActionInfo_setInfo(PyBrowserActionInfo *self, BrowserActionInfo *info, PyThing *pyThing)
|
||||
{
|
||||
self->info = info;
|
||||
self->pyThing = pyThing;
|
||||
Py_INCREF(pyThing);
|
||||
self->pyItemId = PyUnicode_FromString(info->browserAction().itemId().toUtf8());
|
||||
}
|
||||
|
||||
static void PyBrowserActionInfo_dealloc(PyBrowserActionInfo * self)
|
||||
{
|
||||
qCDebug(dcPythonIntegrations()) << "--- PyBrowserActionInfo";
|
||||
Py_DECREF(self->pyThing);
|
||||
Py_DECREF(self->pyItemId);
|
||||
Py_TYPE(self)->tp_free(self);
|
||||
}
|
||||
|
||||
static PyObject * PyBrowserActionInfo_finish(PyBrowserActionInfo* self, PyObject* args) {
|
||||
int status;
|
||||
char *message = nullptr;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "i|s", &status, &message)) {
|
||||
PyErr_SetString(PyExc_TypeError, "Invalid arguments in finish call. Expected: finish(ThingError, message = \"\")");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Thing::ThingError thingError = static_cast<Thing::ThingError>(status);
|
||||
QString displayMessage = message != nullptr ? QString(message) : QString();
|
||||
|
||||
if (self->info) {
|
||||
QMetaObject::invokeMethod(self->info, "finish", Qt::QueuedConnection, Q_ARG(Thing::ThingError, thingError), Q_ARG(QString, displayMessage));
|
||||
}
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyMemberDef PyBrowserActionInfo_members[] = {
|
||||
{"thing", T_OBJECT_EX, offsetof(PyBrowserActionInfo, pyThing), 0, "Thing this action is for"},
|
||||
{"itemId", T_OBJECT_EX, offsetof(PyBrowserActionInfo, pyItemId), 0, "The browser item id to be executed"},
|
||||
{nullptr, 0, 0, 0, nullptr} /* Sentinel */
|
||||
};
|
||||
|
||||
static PyMethodDef PyBrowserActionInfo_methods[] = {
|
||||
{ "finish", (PyCFunction)PyBrowserActionInfo_finish, METH_VARARGS, "finish an action" },
|
||||
{nullptr, nullptr, 0, nullptr} // sentinel
|
||||
};
|
||||
|
||||
static PyTypeObject PyBrowserActionInfoType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
"nymea.BrowserActionInfo", /* tp_name */
|
||||
sizeof(PyBrowserActionInfo), /* tp_basicsize */
|
||||
0, /* tp_itemsize */
|
||||
(destructor)PyBrowserActionInfo_dealloc, /* tp_dealloc */
|
||||
};
|
||||
|
||||
static void registerBrowserActionInfoType(PyObject *module)
|
||||
{
|
||||
PyBrowserActionInfoType.tp_new = (newfunc)PyBrowserActionInfo_new;
|
||||
PyBrowserActionInfoType.tp_flags = Py_TPFLAGS_DEFAULT;
|
||||
PyBrowserActionInfoType.tp_methods = PyBrowserActionInfo_methods;
|
||||
PyBrowserActionInfoType.tp_members = PyBrowserActionInfo_members;
|
||||
PyBrowserActionInfoType.tp_doc = "The BrowserActionInfo is used to execute browser items";
|
||||
|
||||
if (PyType_Ready(&PyBrowserActionInfoType) < 0) {
|
||||
return;
|
||||
}
|
||||
PyModule_AddObject(module, "BrowserActionInfo", (PyObject *)&PyBrowserActionInfoType);
|
||||
}
|
||||
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
#endif // PYBROWSERACTIONINFO_H
|
||||
171
libnymea-core/integrations/python/pybrowseresult.h
Normal file
171
libnymea-core/integrations/python/pybrowseresult.h
Normal file
@ -0,0 +1,171 @@
|
||||
#ifndef PYBROWSERESULT_H
|
||||
#define PYBROWSERESULT_H
|
||||
|
||||
#include <Python.h>
|
||||
#include "structmember.h"
|
||||
|
||||
#include "pything.h"
|
||||
|
||||
#include "integrations/browseresult.h"
|
||||
#include "pybrowseritem.h"
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Winvalid-offsetof"
|
||||
#pragma GCC diagnostic ignored "-Wwrite-strings"
|
||||
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
|
||||
|
||||
/* Note:
|
||||
*
|
||||
* When using this, make sure to call PyBrowseResult_setBrowseResult() while holding the GIL to initialize
|
||||
* stuff after constructing it. Also set broeseResult to nullptr while holding the GIL when the browseResult object vanishes.
|
||||
*
|
||||
* The BrowseResult class is not threadsafe and self->browseResult is owned by nymeas main thread.
|
||||
* So we must never directly access anything of it in here.
|
||||
*
|
||||
* For writing to it, invoking methods with QueuedConnections will thread-decouple stuff.
|
||||
* Make sure to check if the info object is still valid (it might not be if nymea finished
|
||||
* the setup and destroyed it but the PyThingSetupInfo is not garbage collected yet.
|
||||
*
|
||||
* For reading access, we keep copies of the BrowseResults properties here.
|
||||
*
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
BrowseResult* browseResult;
|
||||
PyThing *pyThing;
|
||||
PyObject *pyItemId;
|
||||
PyObject *pyLocale;
|
||||
} PyBrowseResult;
|
||||
|
||||
|
||||
static PyObject* PyBrowseResult_new(PyTypeObject *type, PyObject */*args*/, PyObject */*kwds*/) {
|
||||
PyBrowseResult *self = (PyBrowseResult*)type->tp_alloc(type, 0);
|
||||
if (self == NULL) {
|
||||
return nullptr;
|
||||
}
|
||||
qCDebug(dcPythonIntegrations()) << "+++ PyBrowseResult";
|
||||
return (PyObject*)self;
|
||||
}
|
||||
|
||||
void PyBrowseResult_setBrowseResult(PyBrowseResult *self, BrowseResult *browseResult, PyThing *pyThing)
|
||||
{
|
||||
self->browseResult = browseResult;
|
||||
self->pyThing = pyThing;
|
||||
Py_INCREF(pyThing);
|
||||
self->pyItemId = PyUnicode_FromString(browseResult->itemId().toUtf8());
|
||||
self->pyLocale = PyUnicode_FromString(browseResult->locale().name().toUtf8());
|
||||
}
|
||||
|
||||
|
||||
static void PyBrowseResult_dealloc(PyBrowseResult* self)
|
||||
{
|
||||
qCDebug(dcPythonIntegrations()) << "--- PyBrowseResult";
|
||||
Py_DECREF(self->pyThing);
|
||||
Py_DECREF(self->pyItemId);
|
||||
Py_DECREF(self->pyLocale);
|
||||
Py_TYPE(self)->tp_free(self);
|
||||
}
|
||||
|
||||
static PyObject* PyBrowseResult_addItem(PyBrowseResult *self, PyObject *args) {
|
||||
Q_UNUSED(self)
|
||||
PyObject *pyObj = nullptr;
|
||||
if (!PyArg_ParseTuple(args, "O", &pyObj)) {
|
||||
PyErr_SetString(PyExc_TypeError, "Invalid arguments in addItem call. Expected: addItem(BrowserItem)");
|
||||
return nullptr;
|
||||
}
|
||||
if (pyObj->ob_type != &PyBrowserItemType) {
|
||||
PyErr_SetString(PyExc_ValueError, "Invalid argument to BrowseResult.addItem(BrowserItem). Not a BrowserItem.");
|
||||
return nullptr;
|
||||
}
|
||||
PyBrowserItem *pyBrowserItem = (PyBrowserItem*)pyObj;
|
||||
|
||||
QString id;
|
||||
if (pyBrowserItem->pyId) {
|
||||
id = QString::fromUtf8(PyUnicode_AsUTF8(pyBrowserItem->pyId));
|
||||
}
|
||||
QString displayName;
|
||||
if (pyBrowserItem->pyDisplayName) {
|
||||
displayName = QString::fromUtf8(PyUnicode_AsUTF8(pyBrowserItem->pyDisplayName));
|
||||
}
|
||||
|
||||
BrowserItem browserItem(id, displayName);
|
||||
if (pyBrowserItem->pyDescription) {
|
||||
browserItem.setDescription(QString::fromUtf8(PyUnicode_AsUTF8(pyBrowserItem->pyDescription)));
|
||||
}
|
||||
|
||||
if (pyBrowserItem->pyThumbnail) {
|
||||
browserItem.setThumbnail(QString::fromUtf8(PyUnicode_AsUTF8(pyBrowserItem->pyThumbnail)));
|
||||
}
|
||||
|
||||
browserItem.setBrowsable(pyBrowserItem->browsable);
|
||||
browserItem.setExecutable(pyBrowserItem->executable);
|
||||
browserItem.setDisabled(pyBrowserItem->disabled);
|
||||
browserItem.setIcon(static_cast<BrowserItem::BrowserIcon>(pyBrowserItem->icon));
|
||||
|
||||
if (self->browseResult) {
|
||||
QMetaObject::invokeMethod(self->browseResult, "addItem", Qt::QueuedConnection, Q_ARG(BrowserItem, browserItem));
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject* PyBrowseResult_finish(PyBrowseResult* self, PyObject* args) {
|
||||
int status;
|
||||
char *message = nullptr;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "i|s", &status, &message)) {
|
||||
PyErr_SetString(PyExc_TypeError, "Invalid arguments in finish call. Expected: finish(ThingError, message = \"\")");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Thing::ThingError thingError = static_cast<Thing::ThingError>(status);
|
||||
QString displayMessage = message != nullptr ? QString(message) : QString();
|
||||
|
||||
if (self->browseResult) {
|
||||
QMetaObject::invokeMethod(self->browseResult, "finish", Qt::QueuedConnection, Q_ARG(Thing::ThingError, thingError), Q_ARG(QString, displayMessage));
|
||||
}
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
static PyMemberDef PyBrowseResult_members[] = {
|
||||
{"thing", T_OBJECT_EX, offsetof(PyBrowseResult, pyThing), 0, "Thing this browse request is for"},
|
||||
{"itemId", T_OBJECT_EX, offsetof(PyBrowseResult, pyItemId), 0, "The itemId of the item that should be browsed. Empty if the root item is requested"},
|
||||
{"locale", T_OBJECT_EX, offsetof(PyBrowseResult, pyLocale), 0, "The locale strings should be translated to."},
|
||||
{nullptr, 0, 0, 0, nullptr} /* Sentinel */
|
||||
};
|
||||
|
||||
static PyMethodDef PyBrowseResult_methods[] = {
|
||||
{ "addItem", (PyCFunction)PyBrowseResult_addItem, METH_VARARGS, "Add a browser item to the result"},
|
||||
{ "finish", (PyCFunction)PyBrowseResult_finish, METH_VARARGS, "Finish a browse request" },
|
||||
{nullptr, nullptr, 0, nullptr} // sentinel
|
||||
};
|
||||
|
||||
static PyTypeObject PyBrowseResultType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
"nymea.BrowseResult", /* tp_name */
|
||||
sizeof(PyBrowseResult), /* tp_basicsize */
|
||||
0, /* tp_itemsize */
|
||||
(destructor)PyBrowseResult_dealloc, /* tp_dealloc */
|
||||
};
|
||||
|
||||
static void registerBrowseResultType(PyObject *module)
|
||||
{
|
||||
PyBrowseResultType.tp_new = (newfunc)PyBrowseResult_new;
|
||||
PyBrowseResultType.tp_flags = Py_TPFLAGS_DEFAULT;
|
||||
PyBrowseResultType.tp_methods = PyBrowseResult_methods;
|
||||
PyBrowseResultType.tp_members = PyBrowseResult_members;
|
||||
PyBrowseResultType.tp_doc = "The BrowseResult is used fetch browser entries from things";
|
||||
|
||||
if (PyType_Ready(&PyBrowseResultType) < 0) {
|
||||
return;
|
||||
}
|
||||
PyModule_AddObject(module, "BrowseResult", (PyObject *)&PyBrowseResultType);
|
||||
}
|
||||
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
|
||||
#endif // PYBROWSERESULT_H
|
||||
118
libnymea-core/integrations/python/pybrowseritem.h
Normal file
118
libnymea-core/integrations/python/pybrowseritem.h
Normal file
@ -0,0 +1,118 @@
|
||||
#ifndef PYBROWSERITEM_H
|
||||
#define PYBROWSERITEM_H
|
||||
|
||||
#include <Python.h>
|
||||
#include "structmember.h"
|
||||
|
||||
#include <QMetaEnum>
|
||||
|
||||
#include "types/browseritem.h"
|
||||
#include "loggingcategories.h"
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Winvalid-offsetof"
|
||||
#pragma GCC diagnostic ignored "-Wwrite-strings"
|
||||
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
|
||||
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
PyObject* pyId;
|
||||
PyObject* pyDisplayName;
|
||||
PyObject* pyDescription;
|
||||
PyObject* pyThumbnail;
|
||||
bool browsable = false;
|
||||
bool executable = false;
|
||||
bool disabled = false;
|
||||
int icon = (int)BrowserItem::BrowserIconNone;
|
||||
} PyBrowserItem;
|
||||
|
||||
|
||||
static PyMemberDef PyBrowserItem_members[] = {
|
||||
{"id", T_OBJECT_EX, offsetof(PyBrowserItem, pyId), 0, "BrowserItem id"},
|
||||
{"displayName", T_OBJECT_EX, offsetof(PyBrowserItem, pyDisplayName), 0, "The name of this item"},
|
||||
{"description", T_OBJECT_EX, offsetof(PyBrowserItem, pyDescription), 0, "The description of this item"},
|
||||
{"tumbnail", T_OBJECT_EX, offsetof(PyBrowserItem, pyThumbnail), 0, "An URL pointing to the thumbnail"},
|
||||
{"browsable", T_OBJECT_EX, offsetof(PyBrowserItem, browsable), 0, "A boolean if this item can be browsed (e.g. a folder)"},
|
||||
{"executable", T_OBJECT_EX, offsetof(PyBrowserItem, executable), 0, "A boolean if this item can be launched"},
|
||||
{"disabled", T_OBJECT_EX, offsetof(PyBrowserItem, disabled), 0, "A boolean if this item is disabled"},
|
||||
{"icon", T_OBJECT_EX, offsetof(PyBrowserItem, icon), 0, "The icon to be used"},
|
||||
{nullptr, 0, 0, 0, nullptr} /* Sentinel */
|
||||
};
|
||||
|
||||
static int PyBrowserItem_init(PyBrowserItem *self, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
static char *kwlist[] = {"id", "displayName", "description", "thumbnail", "browsable", "executable", "disabled", "icon", nullptr};
|
||||
PyObject *id = nullptr, *displayName = nullptr, *description = nullptr, *thumbnail = nullptr;
|
||||
bool browsable = false, executable = false, disabled = false;
|
||||
int icon = (int)BrowserItem::BrowserIconNone;
|
||||
|
||||
qCDebug(dcPythonIntegrations()) << "+++ PyBrowserItem";
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOObbbi", kwlist, &id, &displayName, &description, &thumbnail, &browsable, &executable, &disabled, &icon))
|
||||
return -1;
|
||||
|
||||
if (id) {
|
||||
Py_INCREF(id);
|
||||
self->pyId = id;
|
||||
}
|
||||
if (displayName) {
|
||||
Py_INCREF(displayName);
|
||||
self->pyDisplayName = displayName;
|
||||
}
|
||||
if (description) {
|
||||
Py_INCREF(description);
|
||||
self->pyDescription = description;
|
||||
}
|
||||
if (thumbnail) {
|
||||
Py_INCREF(thumbnail);
|
||||
self->pyThumbnail = thumbnail;
|
||||
}
|
||||
self->browsable = browsable;
|
||||
self->executable = executable;
|
||||
self->disabled = disabled;
|
||||
self->icon = icon;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void PyBrowserItem_dealloc(PyBrowserItem* self)
|
||||
{
|
||||
qCDebug(dcPythonIntegrations()) << "--- PyBrowserItem";
|
||||
Py_XDECREF(self->pyId);
|
||||
Py_XDECREF(self->pyDisplayName);
|
||||
Py_XDECREF(self->pyDescription);
|
||||
Py_XDECREF(self->pyThumbnail);
|
||||
Py_TYPE(self)->tp_free(self);
|
||||
}
|
||||
|
||||
static PyTypeObject PyBrowserItemType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
"nymea.BrowserItem", /* tp_name */
|
||||
sizeof(PyBrowserItem), /* tp_basicsize */
|
||||
0, /* tp_itemsize */
|
||||
(destructor)PyBrowserItem_dealloc, /* tp_dealloc */
|
||||
};
|
||||
|
||||
|
||||
|
||||
static void registerBrowserItemType(PyObject *module)
|
||||
{
|
||||
PyBrowserItemType.tp_new = PyType_GenericNew;
|
||||
PyBrowserItemType.tp_members = PyBrowserItem_members;
|
||||
PyBrowserItemType.tp_init = reinterpret_cast<initproc>(PyBrowserItem_init);
|
||||
PyBrowserItemType.tp_doc = "BrowserItems are used to return entries in a things browser to nymea.";
|
||||
PyBrowserItemType.tp_flags = Py_TPFLAGS_DEFAULT;
|
||||
|
||||
if (PyType_Ready(&PyBrowserItemType) < 0) {
|
||||
return;
|
||||
}
|
||||
PyModule_AddObject(module, "BrowserItem", reinterpret_cast<PyObject*>(&PyBrowserItemType));
|
||||
|
||||
QMetaEnum browserIconEnum = QMetaEnum::fromType<BrowserItem::BrowserIcon>();
|
||||
for (int i = 0; i < browserIconEnum.keyCount(); i++) {
|
||||
PyModule_AddObject(module, browserIconEnum.key(i), PyLong_FromLong(browserIconEnum.value(i)));
|
||||
}
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
#endif // PYBROWSERITEM_H
|
||||
164
libnymea-core/integrations/python/pybrowseritemresult.h
Normal file
164
libnymea-core/integrations/python/pybrowseritemresult.h
Normal file
@ -0,0 +1,164 @@
|
||||
#ifndef PYBROWSERITEMRESULT_H
|
||||
#define PYBROWSERITEMRESULT_H
|
||||
|
||||
#include <Python.h>
|
||||
#include "structmember.h"
|
||||
|
||||
#include "pything.h"
|
||||
|
||||
#include "integrations/browseritemresult.h"
|
||||
#include "pybrowseritem.h"
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Winvalid-offsetof"
|
||||
#pragma GCC diagnostic ignored "-Wwrite-strings"
|
||||
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
|
||||
|
||||
/* Note:
|
||||
*
|
||||
* When using this, make sure to call PyBrowseResult_setBrowseResult() while holding the GIL to initialize
|
||||
* stuff after constructing it. Also set broeseResult to nullptr while holding the GIL when the browseResult object vanishes.
|
||||
*
|
||||
* The BrowseResult class is not threadsafe and self->browseResult is owned by nymeas main thread.
|
||||
* So we must never directly access anything of it in here.
|
||||
*
|
||||
* For writing to it, invoking methods with QueuedConnections will thread-decouple stuff.
|
||||
* Make sure to check if the info object is still valid (it might not be if nymea finished
|
||||
* the setup and destroyed it but the PyThingSetupInfo is not garbage collected yet.
|
||||
*
|
||||
* For reading access, we keep copies of the BrowseResults properties here.
|
||||
*
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
BrowserItemResult* browserItemResult;
|
||||
PyThing *pyThing;
|
||||
PyObject *pyItemId;
|
||||
PyObject *pyLocale;
|
||||
} PyBrowserItemResult;
|
||||
|
||||
|
||||
static PyObject* PyBrowserItemResult_new(PyTypeObject *type, PyObject */*args*/, PyObject */*kwds*/) {
|
||||
PyBrowserItemResult *self = (PyBrowserItemResult*)type->tp_alloc(type, 0);
|
||||
if (self == NULL) {
|
||||
return nullptr;
|
||||
}
|
||||
qCDebug(dcPythonIntegrations()) << "+++ PyBrowserItemResult";
|
||||
return (PyObject*)self;
|
||||
}
|
||||
|
||||
void PyBrowserItemResult_setBrowserItemResult(PyBrowserItemResult *self, BrowserItemResult *browserItemResult, PyThing *pyThing)
|
||||
{
|
||||
self->browserItemResult = browserItemResult;
|
||||
self->pyThing = pyThing;
|
||||
Py_INCREF(pyThing);
|
||||
self->pyItemId = PyUnicode_FromString(browserItemResult->itemId().toUtf8());
|
||||
self->pyLocale = PyUnicode_FromString(browserItemResult->locale().name().toUtf8());
|
||||
}
|
||||
|
||||
|
||||
static void PyBrowserItemResult_dealloc(PyBrowserItemResult* self)
|
||||
{
|
||||
qCDebug(dcPythonIntegrations()) << "--- PyBrowserItemResult";
|
||||
Py_DECREF(self->pyThing);
|
||||
Py_DECREF(self->pyItemId);
|
||||
Py_DECREF(self->pyLocale);
|
||||
Py_TYPE(self)->tp_free(self);
|
||||
}
|
||||
|
||||
static PyObject* PyBrowserItemResult_finish(PyBrowserItemResult* self, PyObject* args) {
|
||||
PyObject *pyObj;
|
||||
int status;
|
||||
char *message = nullptr;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "|Ois", &pyObj, &status, &message)) {
|
||||
PyErr_SetString(PyExc_TypeError, "Invalid arguments in finish call. Expected: finish(BrowserItem) or finish(ThingError, message = \"\")");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (pyObj != nullptr) {
|
||||
if (pyObj->ob_type != &PyBrowserItemType) {
|
||||
PyErr_SetString(PyExc_ValueError, "Invalid argument to BrowserItemResult.finish(BrowserItem). Not a BrowserItem.");
|
||||
return nullptr;
|
||||
}
|
||||
PyBrowserItem *pyBrowserItem = (PyBrowserItem*)pyObj;
|
||||
QString id;
|
||||
if (pyBrowserItem->pyId) {
|
||||
id = QString::fromUtf8(PyUnicode_AsUTF8(pyBrowserItem->pyId));
|
||||
}
|
||||
QString displayName;
|
||||
if (pyBrowserItem->pyDisplayName) {
|
||||
displayName = QString::fromUtf8(PyUnicode_AsUTF8(pyBrowserItem->pyDisplayName));
|
||||
}
|
||||
|
||||
BrowserItem browserItem(id, displayName);
|
||||
if (pyBrowserItem->pyDescription) {
|
||||
browserItem.setDescription(QString::fromUtf8(PyUnicode_AsUTF8(pyBrowserItem->pyDescription)));
|
||||
}
|
||||
|
||||
if (pyBrowserItem->pyThumbnail) {
|
||||
browserItem.setThumbnail(QString::fromUtf8(PyUnicode_AsUTF8(pyBrowserItem->pyThumbnail)));
|
||||
}
|
||||
|
||||
browserItem.setBrowsable(pyBrowserItem->browsable);
|
||||
browserItem.setExecutable(pyBrowserItem->executable);
|
||||
browserItem.setDisabled(pyBrowserItem->disabled);
|
||||
browserItem.setIcon(static_cast<BrowserItem::BrowserIcon>(pyBrowserItem->icon));
|
||||
|
||||
if (self->browserItemResult) {
|
||||
QMetaObject::invokeMethod(self->browserItemResult, "finish", Qt::QueuedConnection, Q_ARG(BrowserItem, browserItem));
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
Thing::ThingError thingError = static_cast<Thing::ThingError>(status);
|
||||
QString displayMessage = message != nullptr ? QString(message) : QString();
|
||||
|
||||
if (self->browserItemResult) {
|
||||
QMetaObject::invokeMethod(self->browserItemResult, "finish", Qt::QueuedConnection, Q_ARG(Thing::ThingError, thingError), Q_ARG(QString, displayMessage));
|
||||
}
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
static PyMemberDef PyBrowserItemResult_members[] = {
|
||||
{"thing", T_OBJECT_EX, offsetof(PyBrowserItemResult, pyThing), 0, "Thing this browse request is for"},
|
||||
{"itemId", T_OBJECT_EX, offsetof(PyBrowserItemResult, pyItemId), 0, "The itemId of the item to be returned."},
|
||||
{"locale", T_OBJECT_EX, offsetof(PyBrowserItemResult, pyLocale), 0, "The locale strings should be translated to."},
|
||||
{nullptr, 0, 0, 0, nullptr} /* Sentinel */
|
||||
};
|
||||
|
||||
static PyMethodDef PyBrowserItemResult_methods[] = {
|
||||
{ "finish", (PyCFunction)PyBrowserItemResult_finish, METH_VARARGS, "Finish a browser item request" },
|
||||
{nullptr, nullptr, 0, nullptr} // sentinel
|
||||
};
|
||||
|
||||
static PyTypeObject PyBrowserItemResultType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
"nymea.BrowserItemResult", /* tp_name */
|
||||
sizeof(PyBrowserItemResult), /* tp_basicsize */
|
||||
0, /* tp_itemsize */
|
||||
(destructor)PyBrowserItemResult_dealloc, /* tp_dealloc */
|
||||
};
|
||||
|
||||
static void registerBrowserItemResultType(PyObject *module)
|
||||
{
|
||||
PyBrowserItemResultType.tp_new = (newfunc)PyBrowserItemResult_new;
|
||||
PyBrowserItemResultType.tp_flags = Py_TPFLAGS_DEFAULT;
|
||||
PyBrowserItemResultType.tp_methods = PyBrowserItemResult_methods;
|
||||
PyBrowserItemResultType.tp_members = PyBrowserItemResult_members;
|
||||
PyBrowserItemResultType.tp_doc = "The BrowserItemResult is used fetch an individual entry from the thing browser";
|
||||
|
||||
if (PyType_Ready(&PyBrowserItemResultType) < 0) {
|
||||
return;
|
||||
}
|
||||
PyModule_AddObject(module, "BrowserItemResult", (PyObject *)&PyBrowserItemResultType);
|
||||
}
|
||||
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
#endif // PYBROWSERITEMRESULT_H
|
||||
@ -13,6 +13,10 @@
|
||||
#include "pythingpairinginfo.h"
|
||||
#include "pypluginstorage.h"
|
||||
#include "pyapikeystorage.h"
|
||||
#include "pybrowseresult.h"
|
||||
#include "pybrowseritem.h"
|
||||
#include "pybrowseractioninfo.h"
|
||||
#include "pybrowseritemresult.h"
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Winvalid-offsetof"
|
||||
@ -31,6 +35,10 @@ static int nymea_exec(PyObject *m) {
|
||||
registerThingActionInfoType(m);
|
||||
registerPluginStorageType(m);
|
||||
registerApiKeyStorageType(m);
|
||||
registerBrowseResultType(m);
|
||||
registerBrowserItemType(m);
|
||||
registerBrowserActionInfoType(m);
|
||||
registerBrowserItemResultType(m);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -574,13 +574,11 @@ void PythonIntegrationPlugin::executeAction(ThingActionInfo *info)
|
||||
PyEval_ReleaseThread(m_threadState);
|
||||
|
||||
connect(info, &ThingActionInfo::destroyed, this, [=](){
|
||||
qCDebug(dcPythonIntegrations()) << "Info destroyed";
|
||||
qCDebug(dcPythonIntegrations()) << "ThingActionInfo destroyed";
|
||||
PyEval_RestoreThread(m_threadState);
|
||||
qCDebug(dcPythonIntegrations()) << "Info destroyed2";
|
||||
pyInfo->info = nullptr;
|
||||
Py_DECREF(pyInfo);
|
||||
PyEval_ReleaseThread(m_threadState);
|
||||
qCDebug(dcPythonIntegrations()) << "Info destroyed3";
|
||||
});
|
||||
|
||||
bool success = callPluginFunction("executeAction", reinterpret_cast<PyObject*>(pyInfo));
|
||||
@ -595,6 +593,81 @@ void PythonIntegrationPlugin::thingRemoved(Thing *thing)
|
||||
callPluginFunction("thingRemoved", reinterpret_cast<PyObject*>(pyThing));
|
||||
}
|
||||
|
||||
void PythonIntegrationPlugin::browseThing(BrowseResult *result)
|
||||
{
|
||||
PyThing *pyThing = m_things.value(result->thing());
|
||||
|
||||
PyEval_RestoreThread(m_threadState);
|
||||
|
||||
PyBrowseResult *pyBrowseResult = (PyBrowseResult*)PyObject_CallObject((PyObject*)&PyBrowseResultType, NULL);
|
||||
PyBrowseResult_setBrowseResult(pyBrowseResult, result, pyThing);
|
||||
|
||||
PyEval_ReleaseThread(m_threadState);
|
||||
|
||||
connect(result, &BrowseResult::destroyed, this, [=](){
|
||||
qCDebug(dcPythonIntegrations()) << "BrowseResult destroyed";
|
||||
PyEval_RestoreThread(m_threadState);
|
||||
pyBrowseResult->browseResult = nullptr;
|
||||
Py_DECREF(pyBrowseResult);
|
||||
PyEval_ReleaseThread(m_threadState);
|
||||
});
|
||||
|
||||
bool success = callPluginFunction("browseThing", reinterpret_cast<PyObject*>(pyBrowseResult));
|
||||
if (!success) {
|
||||
result->finish(Thing::ThingErrorUnsupportedFeature);
|
||||
}
|
||||
}
|
||||
|
||||
void PythonIntegrationPlugin::executeBrowserItem(BrowserActionInfo *info)
|
||||
{
|
||||
PyThing *pyThing = m_things.value(info->thing());
|
||||
|
||||
PyEval_RestoreThread(m_threadState);
|
||||
|
||||
PyBrowserActionInfo *pyBrowserActionInfo = (PyBrowserActionInfo*)PyObject_CallObject((PyObject*)&PyBrowserActionInfoType, NULL);
|
||||
PyBrowserActionInfo_setInfo(pyBrowserActionInfo, info, pyThing);
|
||||
|
||||
PyEval_ReleaseThread(m_threadState);
|
||||
|
||||
connect(info, &BrowserActionInfo::destroyed, this, [=](){
|
||||
qCDebug(dcPythonIntegrations()) << "BrowserActionInfo destroyed";
|
||||
PyEval_RestoreThread(m_threadState);
|
||||
pyBrowserActionInfo->info = nullptr;
|
||||
Py_DECREF(pyBrowserActionInfo);
|
||||
PyEval_ReleaseThread(m_threadState);
|
||||
});
|
||||
|
||||
bool success = callPluginFunction("executeBrowserItem", reinterpret_cast<PyObject*>(pyBrowserActionInfo));
|
||||
if (!success) {
|
||||
info->finish(Thing::ThingErrorUnsupportedFeature);
|
||||
}
|
||||
}
|
||||
|
||||
void PythonIntegrationPlugin::browserItem(BrowserItemResult *result)
|
||||
{
|
||||
PyThing *pyThing = m_things.value(result->thing());
|
||||
|
||||
PyEval_RestoreThread(m_threadState);
|
||||
|
||||
PyBrowserItemResult *pyBrowserItemResult = (PyBrowserItemResult*)PyObject_CallObject((PyObject*)&PyBrowserItemResultType, NULL);
|
||||
PyBrowserItemResult_setBrowserItemResult(pyBrowserItemResult, result, pyThing);
|
||||
|
||||
PyEval_ReleaseThread(m_threadState);
|
||||
|
||||
connect(result, &BrowserItemResult::destroyed, this, [=](){
|
||||
qCDebug(dcPythonIntegrations()) << "BrowseItemResult destroyed";
|
||||
PyEval_RestoreThread(m_threadState);
|
||||
pyBrowserItemResult->browserItemResult = nullptr;
|
||||
Py_DECREF(pyBrowserItemResult);
|
||||
PyEval_ReleaseThread(m_threadState);
|
||||
});
|
||||
|
||||
bool success = callPluginFunction("browserItem", reinterpret_cast<PyObject*>(pyBrowserItemResult));
|
||||
if (!success) {
|
||||
result->finish(Thing::ThingErrorUnsupportedFeature);
|
||||
}
|
||||
}
|
||||
|
||||
void PythonIntegrationPlugin::exportIds()
|
||||
{
|
||||
qCDebug(dcThingManager()) << "Exporting plugin IDs:";
|
||||
|
||||
@ -37,6 +37,9 @@ public:
|
||||
void postSetupThing(Thing *thing) override;
|
||||
void executeAction(ThingActionInfo *info) override;
|
||||
void thingRemoved(Thing *thing) override;
|
||||
void browseThing(BrowseResult *result) override;
|
||||
void executeBrowserItem(BrowserActionInfo *info) override;
|
||||
void browserItem(BrowserItemResult *result) override;
|
||||
|
||||
|
||||
static PyObject* pyConfiguration(PyObject* self, PyObject* args);
|
||||
|
||||
@ -47,6 +47,10 @@ HEADERS += nymeacore.h \
|
||||
integrations/apikeysprovidersloader.h \
|
||||
integrations/plugininfocache.h \
|
||||
integrations/python/pyapikeystorage.h \
|
||||
integrations/python/pybrowseractioninfo.h \
|
||||
integrations/python/pybrowseresult.h \
|
||||
integrations/python/pybrowseritem.h \
|
||||
integrations/python/pybrowseritemresult.h \
|
||||
integrations/python/pypluginstorage.h \
|
||||
integrations/thingmanagerimplementation.h \
|
||||
integrations/translator.h \
|
||||
|
||||
@ -122,6 +122,7 @@ protected:
|
||||
QList<ActionTypeId> m_actionTypeIds;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(BrowserItem)
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(BrowserItem::ExtendedPropertiesFlags)
|
||||
|
||||
|
||||
|
||||
@ -1037,7 +1037,7 @@ void IntegrationPluginMock::generateBrowseItems()
|
||||
item = BrowserItem("004", "Item 3", false, true);
|
||||
item.setDescription("I have a nice thumbnail");
|
||||
item.setIcon(BrowserItem::BrowserIconFile);
|
||||
item.setThumbnail("https://github.com/guh/nymea/raw/master/icons/nymea-logo-256x256.png");
|
||||
item.setThumbnail("https://github.com/nymea/nymea/raw/master/icons/nymea-logo-256x256.png");
|
||||
item.setActionTypeIds({mockAddToFavoritesBrowserItemActionTypeId});
|
||||
m_virtualFs->addChild(new VirtualFsNode(item));
|
||||
|
||||
|
||||
@ -49,6 +49,7 @@
|
||||
"displayName": "Python mock thing",
|
||||
"createMethods": ["user"],
|
||||
"setupMethod": "justAdd",
|
||||
"browsable": true,
|
||||
"eventTypes": [
|
||||
{
|
||||
"id": "de6c2425-0dee-413f-8f4c-bb0929e83c0d",
|
||||
|
||||
@ -7,7 +7,7 @@ loopRunning = False
|
||||
|
||||
def init():
|
||||
global loopRunning
|
||||
loopRunning = True
|
||||
loopRunning = True
|
||||
|
||||
logger.log("Python mock plugin init")
|
||||
logger.warn("Python mock warning")
|
||||
@ -139,6 +139,27 @@ def thingSettingChanged(thing, paramTypeId, value):
|
||||
logger.log("Thing setting changed:", thing.name, paramTypeId, value)
|
||||
|
||||
|
||||
def browseThing(result):
|
||||
logger.log("browseThing called", result.thing.name, result.itemId)
|
||||
if result.itemId == "":
|
||||
result.addItem(nymea.BrowserItem("001", "Item 0", "I'm a folder", browsable=True, icon=nymea.BrowserIconFolder))
|
||||
result.addItem(nymea.BrowserItem("002", "Item 1", "I'm executable", executable=True, icon=nymea.BrowserIconApplication))
|
||||
result.addItem(nymea.BrowserItem("003", "Item 2", "I'm a file", icon=nymea.BrowserIconFile))
|
||||
result.addItem(nymea.BrowserItem("004", "Item 3", "I have a nice thumbnail", thumbnail="https://github.com/nymea/nymea/raw/master/icons/nymea-logo-256x256.png"))
|
||||
result.addItem(nymea.BrowserItem("005", "Item 4", "I'm disabled", disabled=True, icon=nymea.BrowserIconFile))
|
||||
result.addItem(nymea.BrowserItem("favorites", "Favorites", "I'm the best!", icon=nymea.BrowserIconFavorites))
|
||||
|
||||
if result.itemId == "001":
|
||||
result.addItem(nymea.BrowserItem("011", "Item in subdir", "I'm in a subfolder", icon=nymea.BrowserIconFile))
|
||||
|
||||
result.finish(nymea.ThingErrorNoError)
|
||||
|
||||
|
||||
def executeBrowserItem(info):
|
||||
logger.log("executeBrowserItem called for thing", info.thing.name, "and item", info.itemId)
|
||||
info.finish(nymea.ThingErrorNoError)
|
||||
|
||||
|
||||
# Intentionally commented out to also have a test case for unimplmented functions
|
||||
# def thingRemoved(thing):
|
||||
# logger.log("thingRemoved for", thing.name)
|
||||
|
||||
Reference in New Issue
Block a user