Peter Hansen avatar Peter Hansen committed db78dfd

add support for PushService, for handling invocation data received as a
bb.action.PUSH, and a composeEmail() routine that can invoke
the email composer using an InvokeQuery defined in QML (though the SendEmail
sample shows how it can be done purely in Python instead)

Comments (0)

Files changed (10)

TartStart/TartStart.pro

 # PRE_TARGETDEPS += libpython3.2m.so
 
 LIBS += -lpython3.2m \
+    -lbb \
     -lbbdata \
     -lbbplatform \
     -lQtLocationSubset \
     -lscreen \
     -lbbcascadespickers \
     -lbbcascadesadvertisement \
-    -lbbdevice
+    -lbbdevice \
+    -lbbnetwork
 
 include(config.pri)

TartStart/config.pri

 SOURCES +=  \
     $$BASEDIR/src/app.cpp \
     $$BASEDIR/src/main.cpp \
-    $$BASEDIR/src/tart.cpp
+    $$BASEDIR/src/tart.cpp \
+    $$BASEDIR/src/push.cpp
 
 HEADERS +=  \
     $$BASEDIR/src/app.hpp \
-    $$BASEDIR/src/tart.hpp
+    $$BASEDIR/src/tart.hpp \
+    $$BASEDIR/src/push.hpp
 
 CONFIG += precompile_header
 PRECOMPILED_HEADER = $$BASEDIR/precompiled.h

TartStart/src/app.cpp

 #include <bb/data/DataSource>
 #include <bb/device/DisplayInfo>
 #include <bb/device/DisplayAspectType>
-#include <bb/system/LocaleHandler>
 #include <bb/system/LocaleType>
 
 #include <bb/platform/Notification>
 #include <bb/platform/NotificationError>
 #include <bb/platform/NotificationResult>
 
+#include <bb/system/ApplicationStartupMode>
+
+#include <bb/network/PushPayload>
+
+#include <bb/system/InvokeManager>
+#include <bb/system/InvokeRequest>
+
 #include <QString>
 #include <QTimer>
 #include <QLocale>
+#include <QDebug>
 
 using namespace bb::cascades;
 
 // routines that Tart makes available as separate packages,
 // with the advantage that it wouldn't be so monolithic either.
 //
-App::App(bb::cascades::Application * app, Tart * tart, QString qmlpath)
+App::App(Application * app, Tart * tart, QString qmlpath)
 : QObject(app)
+, localeHandler(NULL)
+, m_invokeManager(new bb::system::InvokeManager(this))
 {
-	localeHandler = NULL;
+    bool ok;
+    Q_UNUSED(ok);
+
+    ok = connect(m_invokeManager, SIGNAL(invoked(const bb::system::InvokeRequest&)),
+        this, SLOT(onInvoked(const bb::system::InvokeRequest&)));
+    Q_ASSERT(ok);
+
+    bb::system::ApplicationStartupMode::Type mode = m_invokeManager->startupMode();
+    // LaunchApplication = 0
+    // InvokeApplication = 1
+    // InvokeCard = 3
+    qDebug() << "startupMode" << mode;
+
+    ok = connect(this,
+        SIGNAL(pushReceived(const QString &, const QByteArray &, bool)),
+        tart,
+        SLOT(pushReceived(const QString &, const QByteArray &, bool))
+        );
+    Q_ASSERT(ok);
 
     // Register the DataSource class as a QML type so that it's accessible in QML
     bb::data::DataSource::registerQmlTypes();
     qmlRegisterUncreatableType<bb::platform::NotificationError>("bb.platform", 1, 0, "NotificationError", "");
     qmlRegisterUncreatableType<bb::platform::NotificationResult>("bb.platform", 1, 0, "NotificationResult", "");
 
+    qmlRegisterType<bb::cascades::InvokeQuery>("bb.cascades", 1, 0, "InvokeQuery");
+
     // create scene document from main.qml asset
     // set parent to created document to ensure it exists for the whole application lifetime
 	QmlDocument *qml = QmlDocument::create(qmlpath).parent(this);
 
     return (int) displayInfo.aspectType();
 }
+
+
+//---------------------------------------------------------
+// Another provisional routine. This one allows creating an
+// InvokeQuery in QML, populating it with the required properties,
+// and sending via app.composeEmail(queryId) to have the Composer
+// brought up with prepopulated fields including attachments.
+// See SendEmail sample.
+//
+void App::composeEmail(bb::cascades::InvokeQuery * query) {
+    bb::system::InvokeRequest request;
+    request.setAction(query->invokeActionId());
+    request.setMimeType(query->mimeType());
+
+    // See http://supportforums.blackberry.com/t5/Cascades-Development/Invoke-Email-with-Attachment/m-p/2251453#M17589
+    // for the source of this particular magic, and note that even the trailing
+    // newline is required or this won't work.
+    request.setData("data:json:" + query->data() + "\n");
+    // qDebug() << "PPS data" << request.data();
+
+    m_invokeManager->invoke(request);
+}
+
+
+//---------------------------------------------------------
+// This one for now handles just invocation via bb.action.PUSH, and
+// dispatches the payload to Python via Tart's pushReceived() slot.
+//
+void App::onInvoked(const bb::system::InvokeRequest &request)
+{
+    // qDebug() << "request" << request.action();
+    // qDebug() << "metadata" << request.metadata();
+    // qDebug() << "mimeType" << request.mimeType();
+    // qDebug() << "fileTransferMode" << request.fileTransferMode();
+    // // qDebug() << "listId" << request.listId(); 10.2 only
+    // qDebug() << "perimeter" << request.perimeter();
+    // qDebug() << "target" << request.target();
+    // qDebug() << "source" << request.source().groupId() << request.source().installId();
+    // qDebug() << "targetTypes" << (int) request.targetTypes();
+    // qDebug() << "uri" << request.uri();
+    // qDebug() << "data" << request.data();
+    // request "bb.action.PUSH"
+    // metadata QMap()
+    // mimeType "application/vnd.push"
+    // fileTransferMode 0
+    // perimeter 2
+    // target "com.whatever.YourApp"
+    // source 361 "sys.service.internal.361"
+    // targetTypes 0
+    // uri  QUrl( "" )
+    // data "pushData:json:{"pushId":"<app id here>","pushDataLen":22,
+    //      "appLevelAck":0,"httpHeaders":{"OST / HTTP/1.1":"POST / HTTP/1.1",
+    //          "Content-Type":"text/plain","Connection":"close",
+    //          "X-RIM-PUSH-SERVICE-ID":"<app id here>",
+    //          "x-rim-deviceid":"<PIN here>","Content-Length":"22"}}
+
+    if (request.action().compare(BB_PUSH_INVOCATION_ACTION) == 0) {
+        // Received an incoming push
+        // Extract it from the invoke request and then process it
+        bb::network::PushPayload payload(request);
+        if (payload.isValid()) {
+            qDebug() << "payload:" << payload.id() << "headers:" << payload.headers();
+            // qDebug() << "data:" << payload.data();
+            emit pushReceived(payload.id(), payload.data(), payload.isAckRequired());
+        } else {
+            // Should we be checking isAckRequired() here, and rejectPush()?
+            // Not sure the docs are clear on how isValid() relates to those.
+            // Could also pass isValid() down to Python to take into account.
+            qDebug() << "invalid payload!";
+        }
+    }
+}

TartStart/src/app.hpp

 
 #include <bb/cascades/Application>
 #include <bb/system/LocaleHandler>
+#include <bb/system/InvokeManager>
+#include <bb/system/InvokeRequest>
+#include <bb/cascades/InvokeQuery>
 
 #include "tart.hpp"
 
+
 /*!
  * @brief Application GUI object
  */
 
     Q_INVOKABLE QString getLocaleInfo(const QString & name);
     Q_INVOKABLE int displayAspectType();
+    Q_INVOKABLE void composeEmail(bb::cascades::InvokeQuery * query);
+
+signals:
+    void pushReceived(const QString & id, const QByteArray & bytes, bool wantsAck);
+
+private slots:
+    void onInvoked(const bb::system::InvokeRequest &request);
 
 private:
     bb::system::LocaleHandler * localeHandler;
+    bb::system::InvokeManager * m_invokeManager;
 };
 
 #endif // ifndef APP_H

TartStart/src/main.cpp

         // unexplained reason they changed the template so it's heap-allocated
         // now, which begs the question... how does the cleanup work, what
         // was wrong with it before, and is there anything wrong with it now?
-        new App(&app, &tart, qmlpath);
+        App * mainApp = new App(&app, &tart, qmlpath);
+        Q_UNUSED(mainApp);
 
         //-- we complete the transaction started in the app constructor and start
         // the client event loop here

TartStart/src/push.cpp

+/*
+ * push.cpp
+ *
+ *  Created on: 2013-08-09
+ *      Author: phansen
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <iconv.h>
+#include <time.h>
+
+#include <Python.h>
+
+#include <QObject>
+#include <QDebug>
+#include <QByteArray>
+
+#include "push.hpp"
+
+
+static struct {
+    PushService *   service;
+    PushHandler *   handler;
+
+    PyObject *      dispatcher;
+}   _push;
+
+
+//---------------------------------------------------------
+// Initialize the PushService.
+//
+static PyObject *
+push_initialize(PyObject * self, PyObject * args)
+{
+    Q_UNUSED(self);
+    char * appId;
+    char * invokeId;
+    PyObject * dispatcher;
+
+    if(!PyArg_ParseTuple(args, "ssO:initialize", &appId, &invokeId, &dispatcher))
+        return NULL;
+    Py_XINCREF(dispatcher);  // because we'll keep it
+
+    PyObject * dispatcher_repr = PyObject_Repr(dispatcher);
+    PyObject * dispatcher_bytes = PyUnicode_AsASCIIString(dispatcher_repr);
+    Py_XDECREF(dispatcher_repr);
+
+    qDebug() << QThread::currentThreadId() << "push_initialize" << appId << invokeId << PyBytes_AsString(dispatcher_bytes);
+    Py_XDECREF(dispatcher_bytes);
+
+    _push.service = new PushService(QString::fromUtf8(appId), QString::fromUtf8(invokeId));
+    _push.handler = new PushHandler(_push.service);
+    _push.dispatcher = dispatcher;
+
+    Py_RETURN_NONE;
+}
+
+
+//---------------------------------------------------------
+// Create a session.
+//
+static PyObject *
+push_createSession(PyObject *self, PyObject *args)
+{
+    Q_UNUSED(self);
+
+    if(!PyArg_ParseTuple(args, ":createSession"))
+        return NULL;
+
+    qDebug() << QThread::currentThreadId() << "push_createSession";
+
+    _push.service->createSession();
+
+    Py_RETURN_NONE;
+}
+
+
+//---------------------------------------------------------
+// Create a channel.
+//
+static PyObject *
+push_createChannel(PyObject *self, PyObject *args)
+{
+    Q_UNUSED(self);
+    char * ppg_url;
+
+    if(!PyArg_ParseTuple(args, "s:createChannel", &ppg_url))
+        return NULL;
+
+    qDebug() << QThread::currentThreadId() << "push_createChannel" << ppg_url;
+
+    _push.service->createChannel(QUrl(QString::fromUtf8(ppg_url)));
+
+    Py_RETURN_NONE;
+}
+
+
+//---------------------------------------------------------
+// Register to launch app via invocation when push received.
+//
+static PyObject *
+push_registerToLaunch(PyObject *self, PyObject *args)
+{
+    Q_UNUSED(self);
+
+    if(!PyArg_ParseTuple(args, ":registerToLaunch"))
+        return NULL;
+
+    qDebug() << QThread::currentThreadId() << "push_registerToLaunch";
+
+    _push.service->registerToLaunch();
+
+    Py_RETURN_NONE;
+}
+
+
+//---------------------------------------------------------
+// Unregister from launch app via invocation when push received.
+//
+static PyObject *
+push_unregisterFromLaunch(PyObject *self, PyObject *args)
+{
+    Q_UNUSED(self);
+
+    if(!PyArg_ParseTuple(args, ":unregisterFromLaunch"))
+        return NULL;
+
+    qDebug() << QThread::currentThreadId() << "push_unregisterFromLaunch";
+
+    _push.service->unregisterFromLaunch();
+
+    Py_RETURN_NONE;
+}
+
+
+//---------------------------------------------------------
+// Accept a push payload.
+//
+static PyObject *
+push_acceptPush(PyObject *self, PyObject *args)
+{
+    Q_UNUSED(self);
+    char * id;  // will be UTF-8 if input is a string, else bytes
+
+    if(!PyArg_ParseTuple(args, "s:acceptPush", &id))
+        return NULL;
+
+    qDebug() << QThread::currentThreadId() << "push_acceptPush" << id;
+
+    _push.service->acceptPush(QString::fromUtf8(id));
+
+    Py_RETURN_NONE;
+}
+
+
+//---------------------------------------------------------
+// Reject a push payload.
+//
+static PyObject *
+push_rejectPush(PyObject *self, PyObject *args)
+{
+    Q_UNUSED(self);
+    char * id;
+
+    if(!PyArg_ParseTuple(args, "s:rejectPush", &id))
+        return NULL;
+
+    qDebug() << QThread::currentThreadId() << "push_rejectPush" << id;
+
+    _push.service->rejectPush(QString::fromUtf8(id));
+
+    Py_RETURN_NONE;
+}
+
+
+//---------------------------------------------------------
+// processing and cleanup after a dispatch call, to reduce duplicate code
+//
+static void _dispatched(PyGILState_STATE & gil_state, PyObject * result)
+{
+    // TODO handle exceptions from the call, either by exiting the event
+    // loop (maybe only during development?) or by dumping a traceback,
+    // setting a flag, and continuing on.
+    bool is_SystemExit = false;
+
+    if (result == NULL) {   // exception during call
+        // see http://computer-programming-forum.com/56-python/a81eae52ca74e6c1.htm
+        // Calling PyErr_Print() will actually terminate the process if
+        // SystemExit is the exception!
+        if (PyErr_ExceptionMatches(PyExc_SystemExit))
+            is_SystemExit = true;
+        else
+            PyErr_Print();
+    }
+    else
+        Py_DECREF(result);
+
+    PyGILState_Release(gil_state);
+
+    if (is_SystemExit)
+        {
+        qDebug() << QThread::currentThreadId() << "_push: SystemExit";
+        QThread::currentThread()->exit(4);
+        }
+
+    return;
+}
+
+
+//---------------------------------------------------------
+// Define callables for the "_tart" builtin module in Python.
+//
+static PyMethodDef PushMethods[] = {
+    {"initialize", push_initialize,  METH_VARARGS,
+        PyDoc_STR("initialize(obj)")},
+    {"createSession", push_createSession, METH_VARARGS,
+        PyDoc_STR("PushService.createSession()")},
+    {"createChannel", push_createChannel, METH_VARARGS,
+        PyDoc_STR("PushService.createChannel(ppg_url='')")},
+    {"registerToLaunch", push_registerToLaunch, METH_VARARGS,
+        PyDoc_STR("PushService.registerToLaunch()")},
+    {"unregisterFromLaunch",  push_unregisterFromLaunch, METH_VARARGS,
+        PyDoc_STR("PushService.unregisterFromLaunch()")},
+    {"acceptPush",  push_acceptPush, METH_VARARGS,
+        PyDoc_STR("PushService.acceptPush(payload_id)")},
+    {"rejectPush",  push_rejectPush, METH_VARARGS,
+        PyDoc_STR("PushService.rejectPush(payload_id)")},
+    {NULL, NULL, 0, NULL}
+};
+
+
+//---------------------------------------------------------
+// Define the "_tart" builtin module for Python code to use.
+//
+static PyModuleDef PushModule = {
+    PyModuleDef_HEAD_INIT, "_push", NULL, -1, PushMethods,
+    NULL, NULL, NULL, NULL
+};
+
+
+//---------------------------------------------------------
+// More boilerplate code to set up builtin module.
+//
+PyMODINIT_FUNC
+PyInit_push(void)
+{
+    return PyModule_Create(&PushModule);
+}
+
+void init_push(void) {
+    PyImport_AppendInittab(PushModule.m_name, &PyInit_push);
+}
+
+
+PushHandler::PushHandler(PushService * pushService)
+: m_pushService(pushService)
+{
+    bool ok;
+    Q_UNUSED(ok);
+
+    // Connect the signals
+    ok = connect(m_pushService, SIGNAL(createSessionCompleted(const bb::network::PushStatus&)),
+        this, SLOT(onCreateSessionCompleted(const bb::network::PushStatus&)));
+    Q_ASSERT(ok);
+    ok = connect(m_pushService, SIGNAL(createChannelCompleted(const bb::network::PushStatus&, const QString)),
+        this, SLOT(onCreateChannelCompleted(const bb::network::PushStatus&, const QString)));
+    Q_ASSERT(ok);
+    ok = connect(m_pushService, SIGNAL(destroyChannelCompleted(const bb::network::PushStatus&)),
+        this, SLOT(onDestroyChannelCompleted(const bb::network::PushStatus&)));
+    Q_ASSERT(ok);
+    ok = connect(m_pushService, SIGNAL(registerToLaunchCompleted(const bb::network::PushStatus&)),
+        this, SLOT(onRegisterToLaunchCompleted(const bb::network::PushStatus&)));
+    Q_ASSERT(ok);
+    ok = connect(m_pushService, SIGNAL(unregisterFromLaunchCompleted(const bb::network::PushStatus&)),
+        this, SLOT(onUnregisterFromLaunchCompleted(const bb::network::PushStatus&)));
+    Q_ASSERT(ok);
+    ok = connect(m_pushService, SIGNAL(simChanged()),
+        this, SLOT(onSimChanged()));
+    Q_ASSERT(ok);
+    ok = connect(m_pushService, SIGNAL(pushTransportReady(bb::network::PushCommand::Type)),
+        this, SLOT(onPushTransportReady(bb::network::PushCommand::Type)));
+    Q_ASSERT(ok);
+}
+
+
+void PushHandler::onCreateSessionCompleted(const bb::network::PushStatus &status)
+{
+    qDebug() << QThread::currentThreadId() << "PushService: onCreateSessionCompleted, status" << status.code();
+    if (status.isError()) {
+        qDebug() << "Push error" << status.errorDescription();
+    }
+    // else {
+    //     m_pushService->registerToLaunch();
+
+    //     m_pushService->createChannel(QUrl(PPG_URL));
+    // }
+    PyGILState_STATE gil_state = PyGILState_Ensure();
+    _dispatched(gil_state,
+        PyObject_CallMethod(_push.dispatcher,
+            "onCreateSessionCompleted",
+            "i", status.code(), NULL));
+}
+
+void PushHandler::onCreateChannelCompleted(const bb::network::PushStatus &status, const QString &token)
+{
+    qDebug() << QThread::currentThreadId() << "PushService: onCreateChannelCompleted, status" << status.code() << "token" << token;
+    if (status.isError()) {
+        qDebug() << "Push error" << status.errorDescription();
+    }
+
+    PyGILState_STATE gil_state = PyGILState_Ensure();
+    _dispatched(gil_state,
+        PyObject_CallMethod(_push.dispatcher,
+            "onCreateChannelCompleted",
+            "is", status.code(), token.toUtf8().constData(), NULL));
+}
+
+void PushHandler::onDestroyChannelCompleted(const bb::network::PushStatus &status)
+{
+    qDebug() << QThread::currentThreadId() << "PushService: onDestroyChannelCompleted, status" << status.code();
+    if (status.isError()) {
+        qDebug() << "Push error" << status.errorDescription();
+    }
+
+    PyGILState_STATE gil_state = PyGILState_Ensure();
+    _dispatched(gil_state,
+        PyObject_CallMethod(_push.dispatcher,
+            "onDestroyChannelCompleted",
+            "i", status.code(), NULL));
+}
+
+void PushHandler::onRegisterToLaunchCompleted(const bb::network::PushStatus &status)
+{
+    qDebug() << QThread::currentThreadId() << "PushService: onRegisterToLaunchCompleted, status" << status.code();
+    if (status.isError()) {
+        qDebug() << "Push error" << status.errorDescription();
+    }
+
+    PyGILState_STATE gil_state = PyGILState_Ensure();
+    _dispatched(gil_state,
+        PyObject_CallMethod(_push.dispatcher,
+            "onRegisterToLaunchCompleted",
+            "i", status.code(), NULL));
+}
+
+void PushHandler::onUnregisterFromLaunchCompleted(const bb::network::PushStatus &status)
+{
+    qDebug() << QThread::currentThreadId() << "PushService: onUnregisterFromLaunchCompleted, status" << status.code();
+    if (status.isError()) {
+        qDebug() << "Push error" << status.errorDescription();
+    }
+
+    PyGILState_STATE gil_state = PyGILState_Ensure();
+    _dispatched(gil_state,
+        PyObject_CallMethod(_push.dispatcher,
+            "onUnregisterFromLaunchCompleted",
+            "i", status.code(), NULL));
+}
+
+void PushHandler::onSimChanged()
+{
+    qDebug() << QThread::currentThreadId() << "PushService: onSimChanged";
+
+    PyGILState_STATE gil_state = PyGILState_Ensure();
+    _dispatched(gil_state,
+        PyObject_CallMethod(_push.dispatcher,
+            "onSimChanged", "s",
+            NULL));
+}
+
+void PushHandler::onPushTransportReady(bb::network::PushCommand::Type command)
+{
+    qDebug() << QThread::currentThreadId() << "PushService: onPushTransportReady, command" << command;
+
+    PyGILState_STATE gil_state = PyGILState_Ensure();
+    _dispatched(gil_state,
+        PyObject_CallMethod(_push.dispatcher,
+            "onPushTransportReady",
+            "i", static_cast<int>(command), NULL));
+}
+
+
+// EOF

TartStart/src/push.hpp

+#ifndef PUSH_H
+#define PUSH_H
+
+#include <QObject>
+#include <QString>
+
+#include <bb/network/PushService>
+#include <bb/network/PushStatus>
+#include <bb/network/PushCommand>
+#include <bb/network/PushErrorCode>
+
+
+// initialize the push extension module so we can "import _push"
+void init_push(void);
+
+using namespace bb::network;
+
+class PushHandler : public QObject
+{
+    Q_OBJECT
+public:
+    PushHandler(PushService * pushService);
+
+    // This wasn't here before Gold SDK but the new Momentics project template
+    // puts it in, so here it is for now.  Do we need it?  Something else?
+    virtual ~PushHandler() {}
+
+private slots:
+    void onCreateSessionCompleted(const bb::network::PushStatus &status);
+    void onCreateChannelCompleted(const bb::network::PushStatus &status, const QString &token);
+    void onDestroyChannelCompleted(const bb::network::PushStatus &status);
+    void onRegisterToLaunchCompleted(const bb::network::PushStatus &status);
+    void onUnregisterFromLaunchCompleted(const bb::network::PushStatus &status);
+    void onSimChanged();
+    void onPushTransportReady(bb::network::PushCommand::Type command);
+
+private:
+    PushService *   m_pushService;
+};
+
+#endif // ifndef PUSH_H
+

TartStart/src/tart.cpp

 
 
 //---------------------------------------------------------
+// Convert one argument to Unicode, ignoring locale.
+//
+static wchar_t * char2wchar(const char * arg) {
+    wchar_t * res = NULL;
+    size_t count;
+    size_t argsize = strlen(arg);
+
+    if (argsize != (size_t) -1) {
+        res = (wchar_t *) malloc((argsize + 1) * sizeof(wchar_t));
+        if (res) {
+            count = mbstowcs(res, arg, argsize + 1);
+            if (count == (size_t)-1) {
+                free(res);
+                res = NULL;
+            }
+        }
+    }
+
+    return res;
+}
+
+
+//---------------------------------------------------------
+// Convert arguments in platform default encoding to Unicode,
+// non-locale-aware version.
+//
+static wchar_t ** args_to_wargv(int argc, char ** argv) {
+    int i;
+    wchar_t ** wargv = NULL;
+
+    wargv = (wchar_t **) malloc(argc * sizeof(wchar_t *));
+    if (!wargv)
+        return wargv;
+
+    for (i = 0; i < argc; i++) {
+        wargv[i] = char2wchar(argv[i]);
+    }
+
+    return wargv;
+}
+
+
+//---------------------------------------------------------
+// Release memory held by converted argument list.
+//
+static void free_wargv(int wargc, wchar_t ** wargv) {
+    int i = 0;
+    if (wargv) {
+        for (i = 0; i < wargc; i++)
+            free(wargv[i]);
+
+        free(wargv);
+    }
+
+    return;
+}
+
+
+//---------------------------------------------------------
 //
 TartThread::TartThread(QSemaphore * sem)
     : m_sem(sem)
 //
 static PyThreadState * tart_pystate;
 static PyObject * event_callback = NULL;
+static PyObject * push_callback = NULL;
 
 static PyObject *
 tart_event_loop(PyObject *self, PyObject *args)
     }
     Py_XINCREF(temp);               // Add a reference to new callback
     event_hook_callback = temp;     // Remember new callback
-    // qDebug() << "event_loop: callback is" << event_callback;
 
     orig_eventFilter = QAbstractEventDispatcher::instance()->setEventFilter(Tart_eventFilter);
     qDebug() << "orig_eventFilter" << orig_eventFilter;
 }
 
 
+static PyObject *
+tart_register_push_callback(PyObject *self, PyObject *args)
+{
+    qDebug() << QThread::currentThreadId() << "_tart.register_push_callback()";
+
+    // code for callback based on
+    // http://docs.python.org/3.2/extending/extending.html#calling-python-functions-from-c
+    PyObject * temp;
+
+    if(!PyArg_ParseTuple(args, "O:register_push_callback", &temp))
+        return NULL;
+
+    // TODO: support None so we can unhook the filter.
+
+    if (!PyCallable_Check(temp)) {
+        PyErr_SetString(PyExc_TypeError, "parameter must be callable");
+        return NULL;
+    }
+    Py_XINCREF(temp);               // Add a reference to new callback
+    push_callback = temp;     // Remember new callback
+
+    Py_RETURN_NONE;
+}
+
+
 //---------------------------------------------------------
 // Define callables for the "_tart" builtin module in Python.
 //
         PyDoc_STR("Enter Tart event loop.")},
     {"hook_events", tart_hook_events,   METH_VARARGS,
         PyDoc_STR("Install a Qt event filter.")},
+    {"register_push_callback",  tart_register_push_callback,    METH_VARARGS,
+        PyDoc_STR("Install callback to receive pushes.")},
     {NULL, NULL, 0, NULL}
 };
 
 //---------------------------------------------------------
 // More boilerplate code to set up builtin module.
 //
-static PyObject*
+PyMODINIT_FUNC
 PyInit_tart(void)
 {
     return PyModule_Create(&TartModule);
 
     PyImport_AppendInittab(TartModule.m_name, &PyInit_tart);
 
+    extern void init_push(void);
+    init_push();
+
     // only Py_SetProgramName and Py_SetPath may come before this
     Py_Initialize();
 
     qDebug("Python initialized");
 
-    //    m_wargv = args_to_wargv(args.size(), argv);
-    //    m_wargc = argc;
-    //    PySys_SetArgvEx(argc, m_wargv, 0);
+
+    m_wargv = args_to_wargv(argc, argv);
+    m_wargc = argc;
+    PySys_SetArgvEx(argc, m_wargv, 0);
 
     qDebug() << QThread::currentThreadId() << "Tart: initialized";
 }
 
 
 //---------------------------------------------------------
+//
+//
+const char * Tart::getScriptPath() {
+    int i = m_argc;
+
+    // We can't blindly take the last argument as being the script to run
+    // because when we're invoked (at least with bb.action.PUSH) we can
+    // sometimes see spurious arguments at the end of the command line, such
+    // as invoke://localhost or ORIENTS=auto. Until someone sorts that out
+    // or improves this code, this is a hack to just pick whatever argument
+    // ends with blackberry_tart.py[c]
+    if (m_argv) {
+        while (--i > 0) {
+            QString arg(m_argv[i]);
+            if (arg.endsWith("blackberry_tart.py") || arg.endsWith("blackberry_tart.pyc")) {
+                return m_argv[i];
+            }
+        }
+    }
+
+    return NULL;
+}
+
+
+//---------------------------------------------------------
 // Launch the secondary thread in which the Python interpreter
 // is executed, since this call comes from the main Application thread.
 //
         m_thread = NULL;
     }
 
-//  free_wargv(m_wargc, m_wargv);
+    free_wargv(m_wargc, m_wargv);
 
     sm_instance = NULL;
     qDebug() << QThread::currentThreadId() << "Tart: done cleanup";
 
 
 //---------------------------------------------------------
+// Pass push messages into the Python interpreter main thread,
+// if it has implemented an onPushReceived() method.
+//
+void Tart::pushReceived(const QString & id, const QByteArray & bytes, bool wantsAck) {
+    qDebug() << QThread::currentThreadId() << "Tart: pushReceived" << id << wantsAck << bytes.size();
+
+    // FIXME: this should probably be checked inside the tart_pystate context
+    // where the GIL is held, not outside.
+    // Ignore data if no callback has been registered.
+    if (!push_callback)
+        return;
+
+    PyEval_RestoreThread(tart_pystate);
+
+    PyObject * arglist = Py_BuildValue("(sy#i)",
+        id.toUtf8().constData(),
+        bytes.constData(), bytes.size(),
+        wantsAck
+        );
+    // ran out of memory: fail!
+    if (arglist == NULL) {
+        qDebug() << "Py_BuildValue() returned NULL!";
+        // should probably do something more significant here
+        tart_pystate = PyEval_SaveThread();
+        return;
+    }
+
+    // call the callback to send message to Python
+    PyObject * result = PyObject_CallObject(push_callback, arglist);
+    Py_DECREF(arglist);
+
+    // TODO handle exceptions from the call, either by exiting the event
+    // loop (maybe only during development?) or by dumping a traceback,
+    // setting a flag, and continuing on.
+    bool is_SystemExit = false;
+
+    if (result == NULL) {   // exception during call
+        // see http://computer-programming-forum.com/56-python/a81eae52ca74e6c1.htm
+        // Calling PyErr_Print() will actually terminate the process if
+        // SystemExit is the exception!
+        if (PyErr_ExceptionMatches(PyExc_SystemExit))
+            is_SystemExit = true;
+        else
+            PyErr_Print();
+    }
+    else
+        Py_DECREF(result);
+
+    tart_pystate = PyEval_SaveThread();
+
+    if (is_SystemExit) {
+        qDebug() << "do_post: SystemExit from delivery";
+        m_thread->exit(3);
+    }
+
+    return;
+}
+
+
+//---------------------------------------------------------
 // Experimental utility routine to write image data to a file.
 // This is intended to work with data retrieved from an HTML
 // canvas running in a WebView, where canvas.toDataURL() retrieves

TartStart/src/tart.hpp

     Tart(int argc, char ** argv);
 //    ~Tart();
 
-    const char *    getScriptPath() { return (m_argc && m_argv) ? m_argv[m_argc - 1] : NULL; }
+    const char *    getScriptPath();
 
     void            start();
     TartThread *    getThread() { return m_thread; }
 
 public slots:
     void cleanup();
+    void pushReceived(const QString & id, const QByteArray & bytes, bool wantsAck);
 
 signals:
     void postMessage(QString msg);
 
 private:
     // storage for argument list converted from multibyte to Unicode
-    //    wchar_t ** m_wargv;
-    //    int m_wargc;
+    wchar_t **      m_wargv;
+    int             m_wargc;
+
     TartThread *    m_thread;
     bool            m_cleanup;
     int             m_argc;

samples/HelloWorld/tart-project.ini

     destination: HelloWorld
 
     filters:
-        include /tart.js
         include /app
         include /assets
         exclude /media
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.