mirror of
https://github.com/rizinorg/cutter.git
synced 2024-12-19 19:36:11 +00:00
Embed Jupyter using Python/C API
This commit is contained in:
parent
cea55dfdb1
commit
3164b5c118
3
.gitignore
vendored
3
.gitignore
vendored
@ -79,3 +79,6 @@ ninja.exe
|
|||||||
|
|
||||||
#Mesonbuild
|
#Mesonbuild
|
||||||
src/subprojects/
|
src/subprojects/
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__
|
||||||
|
@ -35,6 +35,10 @@ include_directories(${RADARE2_INCLUDE_DIRS})
|
|||||||
link_directories(${RADARE2_LIBRARY_DIRS})
|
link_directories(${RADARE2_LIBRARY_DIRS})
|
||||||
|
|
||||||
|
|
||||||
|
find_package(PythonLibs 3 REQUIRED)
|
||||||
|
include_directories(${PYTHON_INCLUDE_DIRS})
|
||||||
|
|
||||||
|
|
||||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU"
|
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU"
|
||||||
OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||||
add_definitions(-Wall -Wextra)
|
add_definitions(-Wall -Wextra)
|
||||||
@ -67,4 +71,5 @@ add_definitions("-DAPP_VERSION=\"${CUTTER_VERSION_FULL}\"")
|
|||||||
add_executable(cutter ${UI_FILES} ${QRC_FILES} ${SOURCE_FILES} ${HEADER_FILES})
|
add_executable(cutter ${UI_FILES} ${QRC_FILES} ${SOURCE_FILES} ${HEADER_FILES})
|
||||||
qt5_use_modules(cutter Core Widgets Gui Svg WebEngineWidgets)
|
qt5_use_modules(cutter Core Widgets Gui Svg WebEngineWidgets)
|
||||||
target_link_libraries(cutter ${RADARE2_LIBRARIES})
|
target_link_libraries(cutter ${RADARE2_LIBRARIES})
|
||||||
|
target_link_libraries(cutter ${PYTHON_LIBRARIES})
|
||||||
|
|
||||||
|
58
src/python/cutter_jupyter.py
Normal file
58
src/python/cutter_jupyter.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
from notebook.notebookapp import *
|
||||||
|
|
||||||
|
|
||||||
|
class CutterNotebookApp(NotebookApp):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.thread = None
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
""" see NotebookApp.start() """
|
||||||
|
|
||||||
|
super(NotebookApp, self).start()
|
||||||
|
|
||||||
|
self.write_server_info_file()
|
||||||
|
|
||||||
|
class NotebookThread(threading.Thread):
|
||||||
|
def __init__(self, notebook_app):
|
||||||
|
super().__init__()
|
||||||
|
self.notebook_app = notebook_app
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.notebook_app.run()
|
||||||
|
|
||||||
|
self.thread = NotebookThread(self)
|
||||||
|
self.thread.start()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.io_loop = ioloop.IOLoop.current()
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
# add no-op to wake every 5s
|
||||||
|
# to handle signals that may be ignored by the inner loop
|
||||||
|
pc = ioloop.PeriodicCallback(lambda: None, 5000)
|
||||||
|
pc.start()
|
||||||
|
try:
|
||||||
|
self.io_loop.start()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
self.log.info(_("Interrupted..."))
|
||||||
|
finally:
|
||||||
|
self.remove_server_info_file()
|
||||||
|
self.cleanup_kernels()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
super().stop()
|
||||||
|
self.thread.join()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_with_token(self):
|
||||||
|
return url_concat(self.connection_url, {'token': self.token})
|
||||||
|
|
||||||
|
|
||||||
|
def start_jupyter():
|
||||||
|
app = CutterNotebookApp()
|
||||||
|
app.initialize()
|
||||||
|
app.start()
|
||||||
|
return app
|
@ -1,4 +1,6 @@
|
|||||||
|
|
||||||
|
#include <Python.h>
|
||||||
|
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
@ -9,59 +11,42 @@
|
|||||||
|
|
||||||
JupyterConnection::JupyterConnection(QObject *parent) : QObject(parent)
|
JupyterConnection::JupyterConnection(QObject *parent) : QObject(parent)
|
||||||
{
|
{
|
||||||
process = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
JupyterConnection::~JupyterConnection()
|
JupyterConnection::~JupyterConnection()
|
||||||
{
|
{
|
||||||
cmdServer->stop();
|
if (cutterNotebookAppInstance)
|
||||||
process->terminate();
|
{
|
||||||
}
|
PyEval_RestoreThread(pyThreadState);
|
||||||
|
|
||||||
const char *jupyterPyCode = "import sys\n"
|
auto stopFunc = PyObject_GetAttrString(cutterNotebookAppInstance, "stop");
|
||||||
"from notebook.notebookapp import *\n"
|
PyObject_CallObject(stopFunc, nullptr);
|
||||||
"\n"
|
Py_DECREF(cutterNotebookAppInstance);
|
||||||
"\n"
|
|
||||||
"class CutterNotebookApp(NotebookApp):\n"
|
Py_FinalizeEx();
|
||||||
" def start(self):\n"
|
}
|
||||||
" \"\"\" see NotebookApp.start() \"\"\"\n"
|
|
||||||
"\n"
|
cmdServer->stop();
|
||||||
" super(NotebookApp, self).start()\n"
|
}
|
||||||
"\n"
|
|
||||||
" self.write_server_info_file()\n"
|
|
||||||
"\n"
|
|
||||||
" if self.token and self._token_generated:\n"
|
|
||||||
" url = url_concat(self.connection_url, {'token': self.token})\n"
|
|
||||||
" sys.stdout.write(url + \"\\n\")\n"
|
|
||||||
" sys.stdout.flush()\n"
|
|
||||||
"\n"
|
|
||||||
" self.io_loop = ioloop.IOLoop.current()\n"
|
|
||||||
" if sys.platform.startswith('win'):\n"
|
|
||||||
" # add no-op to wake every 5s\n"
|
|
||||||
" # to handle signals that may be ignored by the inner loop\n"
|
|
||||||
" pc = ioloop.PeriodicCallback(lambda: None, 5000)\n"
|
|
||||||
" pc.start()\n"
|
|
||||||
" try:\n"
|
|
||||||
" self.io_loop.start()\n"
|
|
||||||
" except KeyboardInterrupt:\n"
|
|
||||||
" self.log.info(_(\"Interrupted...\"))\n"
|
|
||||||
" finally:\n"
|
|
||||||
" self.remove_server_info_file()\n"
|
|
||||||
" self.cleanup_kernels()\n"
|
|
||||||
"\n"
|
|
||||||
"\n"
|
|
||||||
"if __name__ == \"__main__\":\n"
|
|
||||||
" CutterNotebookApp.launch_instance()";
|
|
||||||
|
|
||||||
void JupyterConnection::start()
|
void JupyterConnection::start()
|
||||||
{
|
{
|
||||||
process = new QProcess(this);
|
Py_Initialize();
|
||||||
connect(process, &QProcess::readyReadStandardError, this, &JupyterConnection::readStandardError);
|
PyEval_InitThreads();
|
||||||
connect(process, &QProcess::readyReadStandardOutput, this, &JupyterConnection::readStandardOutput);
|
|
||||||
connect(process, &QProcess::errorOccurred, this, [](QProcess::ProcessError error){ qWarning() << "Jupyter error occurred:" << error; });
|
|
||||||
process->start("python3", {"-c", jupyterPyCode});
|
|
||||||
|
|
||||||
QThread *cmdServerThread = new QThread(this);
|
cutterJupyterModule = PyImport_ImportModule("cutter_jupyter");
|
||||||
|
auto startFunc = PyObject_GetAttrString(cutterJupyterModule, "start_jupyter");
|
||||||
|
cutterNotebookAppInstance = PyObject_CallObject(startFunc, nullptr);
|
||||||
|
auto urlWithToken = PyObject_GetAttrString(cutterNotebookAppInstance, "url_with_token");
|
||||||
|
auto asciiBytes = PyUnicode_AsASCIIString(urlWithToken);
|
||||||
|
emit urlReceived(QString::fromUtf8(PyBytes_AsString(asciiBytes)));
|
||||||
|
Py_DECREF(asciiBytes);
|
||||||
|
Py_DECREF(urlWithToken);
|
||||||
|
|
||||||
|
pyThreadState = PyEval_SaveThread();
|
||||||
|
|
||||||
|
|
||||||
|
cmdServerThread = new QThread(this);
|
||||||
cmdServer = new CommandServer();
|
cmdServer = new CommandServer();
|
||||||
cmdServer->moveToThread(cmdServerThread);
|
cmdServer->moveToThread(cmdServerThread);
|
||||||
connect(cmdServer, &CommandServer::error, this, [](QString err){ qWarning() << "CmdServer error:" << err; });
|
connect(cmdServer, &CommandServer::error, this, [](QString err){ qWarning() << "CmdServer error:" << err; });
|
||||||
@ -70,17 +55,4 @@ void JupyterConnection::start()
|
|||||||
connect(cmdServer, SIGNAL (finished()), cmdServer, SLOT (deleteLater()));
|
connect(cmdServer, SIGNAL (finished()), cmdServer, SLOT (deleteLater()));
|
||||||
connect(cmdServerThread, SIGNAL (finished()), cmdServerThread, SLOT (deleteLater()));
|
connect(cmdServerThread, SIGNAL (finished()), cmdServerThread, SLOT (deleteLater()));
|
||||||
cmdServerThread->start();
|
cmdServerThread->start();
|
||||||
}
|
|
||||||
|
|
||||||
void JupyterConnection::readStandardError()
|
|
||||||
{
|
|
||||||
auto data = process->readAllStandardError();
|
|
||||||
printf("Jupyter stderr: %s\n", data.constData());
|
|
||||||
}
|
|
||||||
|
|
||||||
void JupyterConnection::readStandardOutput()
|
|
||||||
{
|
|
||||||
auto data = process->readAllStandardOutput();
|
|
||||||
printf("Jupyter stdout: %s\n", data.constData());
|
|
||||||
emit urlReceived(data);
|
|
||||||
}
|
}
|
@ -4,6 +4,12 @@
|
|||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
#include "CommandServer.h"
|
#include "CommandServer.h"
|
||||||
|
|
||||||
|
struct _object;
|
||||||
|
typedef _object PyObject;
|
||||||
|
|
||||||
|
struct _ts;
|
||||||
|
typedef _ts PyThreadState;
|
||||||
|
|
||||||
class JupyterConnection : public QObject
|
class JupyterConnection : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@ -18,12 +24,13 @@ signals:
|
|||||||
void urlReceived(const QString &url);
|
void urlReceived(const QString &url);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QProcess *process;
|
|
||||||
CommandServer *cmdServer;
|
CommandServer *cmdServer;
|
||||||
|
QThread *cmdServerThread;
|
||||||
|
|
||||||
private slots:
|
PyObject *cutterJupyterModule = nullptr;
|
||||||
void readStandardError();
|
PyObject *cutterNotebookAppInstance = nullptr;
|
||||||
void readStandardOutput();
|
|
||||||
|
PyThreadState *pyThreadState = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //JUPYTERCONNECTION_H
|
#endif //JUPYTERCONNECTION_H
|
||||||
|
Loading…
Reference in New Issue
Block a user