mirror of
https://github.com/rizinorg/cutter.git
synced 2024-11-27 06:40:52 +00:00
Remove Jupyter Integration (#1398)
* Remove Jupyter Integration Replaced by https://github.com/radareorg/cutter-jupyter * Remove duplicate vars in .appveyor.yml
This commit is contained in:
parent
eac91ed9c8
commit
1710829267
@ -49,8 +49,8 @@ before_build:
|
||||
|
||||
# Build config
|
||||
build_script:
|
||||
- cmd: if defined QMAKE ( call prepare_r2.bat && call build.bat CUTTER_ENABLE_PYTHON=true CUTTER_ENABLE_PYTHON_BINDINGS=false CUTTER_ENABLE_JUPYTER=true CUTTER_ENABLE_QTWEBENGINE=false CUTTER_APPVEYOR_R2DEC=true CUTTER_ENABLE_PYTHON=true CUTTER_ENABLE_PYTHON_BINDINGS=true SHIBOKEN_EXECUTABLE="%CUTTER_DEPS_DIR%\pyside\bin\shiboken2.exe" SHIBOKEN_INCLUDEDIR="%CUTTER_DEPS_DIR%/pyside/include/shiboken2" SHIBOKEN_LIBRARY="%CUTTER_DEPS_DIR%/pyside/lib/shiboken2.cp36-win_amd64.lib" PYSIDE_INCLUDEDIR="%CUTTER_DEPS_DIR%/pyside/include/PySide2" PYSIDE_LIBRARY="%CUTTER_DEPS_DIR%/pyside/lib/pyside2.cp36-win_amd64.lib" PYSIDE_TYPESYSTEMS="%CUTTER_DEPS_DIR%/pyside/share/PySide2/typesystems")
|
||||
- cmd: if defined MESON ( python meson.py --release --dist=%ARTIFACT_PATH% --backend=%BACKEND% --python --jupyter )
|
||||
- cmd: if defined QMAKE ( call prepare_r2.bat && call build.bat CUTTER_APPVEYOR_R2DEC=true CUTTER_ENABLE_PYTHON=true CUTTER_ENABLE_PYTHON_BINDINGS=true SHIBOKEN_EXECUTABLE="%CUTTER_DEPS_DIR%\pyside\bin\shiboken2.exe" SHIBOKEN_INCLUDEDIR="%CUTTER_DEPS_DIR%/pyside/include/shiboken2" SHIBOKEN_LIBRARY="%CUTTER_DEPS_DIR%/pyside/lib/shiboken2.cp36-win_amd64.lib" PYSIDE_INCLUDEDIR="%CUTTER_DEPS_DIR%/pyside/include/PySide2" PYSIDE_LIBRARY="%CUTTER_DEPS_DIR%/pyside/lib/pyside2.cp36-win_amd64.lib" PYSIDE_TYPESYSTEMS="%CUTTER_DEPS_DIR%/pyside/share/PySide2/typesystems")
|
||||
- cmd: if defined MESON ( python meson.py --release --dist=%ARTIFACT_PATH% --backend=%BACKEND% --python )
|
||||
|
||||
after_build:
|
||||
- cmd: if defined QMAKE ( set "PATH=%CD%\r2_dist_%ARCH%;%PATH%" && powershell scripts\bundle_r2dec.ps1 "%CD%\%ARTIFACT_PATH%" )
|
||||
|
@ -68,7 +68,6 @@ addons:
|
||||
install:
|
||||
- scripts/fetch_deps.sh
|
||||
- source cutter-deps/env.sh
|
||||
- python3 -m pip install -r scripts/pip_requirements.txt
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export PATH=/usr/local/opt/llvm/bin:$PATH; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export LD_LIBRARY_PATH="`llvm-config --libdir`:$LD_LIBRARY_PATH"; fi
|
||||
|
||||
@ -90,8 +89,6 @@ script:
|
||||
qmake
|
||||
CUTTER_ENABLE_PYTHON=true
|
||||
CUTTER_ENABLE_PYTHON_BINDINGS=true
|
||||
CUTTER_ENABLE_JUPYTER=true
|
||||
CUTTER_ENABLE_QTWEBENGINE=false
|
||||
PREFIX=/usr
|
||||
APPIMAGE=1
|
||||
../src &&
|
||||
@ -104,8 +101,6 @@ script:
|
||||
-DPYTHON_EXECUTABLE="$CUTTER_DEPS_PYTHON_PREFIX/bin/python3"
|
||||
-DCUTTER_ENABLE_PYTHON=ON
|
||||
-DCUTTER_ENABLE_PYTHON_BINDINGS=ON
|
||||
-DCUTTER_ENABLE_JUPYTER=ON
|
||||
-DCUTTER_ENABLE_QTWEBENGINE=OFF
|
||||
../src &&
|
||||
make -j4;
|
||||
fi
|
||||
@ -114,8 +109,6 @@ script:
|
||||
qmake
|
||||
CUTTER_ENABLE_PYTHON=true
|
||||
CUTTER_ENABLE_PYTHON_BINDINGS=true
|
||||
CUTTER_ENABLE_JUPYTER=true
|
||||
CUTTER_ENABLE_QTWEBENGINE=false
|
||||
CUTTER_BUNDLE_R2_APPBUNDLE=true
|
||||
PYTHON_FRAMEWORK_DIR=$CUTTER_DEPS_PYTHON_FRAMEWORK_DIR
|
||||
../src &&
|
||||
@ -128,8 +121,6 @@ script:
|
||||
-DPYTHON_EXECUTABLE="$CUTTER_DEPS_PYTHON_PREFIX/bin/python3"
|
||||
-DCUTTER_ENABLE_PYTHON=ON
|
||||
-DCUTTER_ENABLE_PYTHON_BINDINGS=ON
|
||||
-DCUTTER_ENABLE_JUPYTER=ON
|
||||
-DCUTTER_ENABLE_QTWEBENGINE=OFF
|
||||
../src &&
|
||||
make -j4;
|
||||
fi
|
||||
|
1
build.sh
1
build.sh
@ -9,7 +9,6 @@ ERR=0
|
||||
BUILD="build"
|
||||
QMAKE_CONF=""
|
||||
ROOT_DIR=`pwd`
|
||||
#QMAKE_CONF="CUTTER_ENABLE_JUPYTER=false CUTTER_ENABLE_QTWEBENGINE=false"
|
||||
|
||||
# Create translations
|
||||
lrelease ./src/Cutter.pro
|
||||
|
@ -24,8 +24,8 @@ Building options
|
||||
----------------
|
||||
|
||||
Note that there are two major building options available:
|
||||
- ``CUTTER_ENABLE_JUPYTER`` is used to compile Cutter with bundled Python and Jupyter module
|
||||
- ``CUTTER_ENABLE_QTWEBENGINE`` is used to compile Cutter with bundled QtWebEngine (to ease jupyter console usage)
|
||||
- ``CUTTER_ENABLE_PYTHON`` compile with Python support
|
||||
- ``CUTTER_ENABLE_PYTHON_BINDINGS`` automatically generate Python Bindings with Shiboken2, required for Python plugins!
|
||||
|
||||
--------------
|
||||
|
||||
@ -124,24 +124,6 @@ containing bin/, lib/, include/, etc.) and specify it to CMake using
|
||||
rm CMakeCache.txt # the cache may be polluted with unwanted libraries found before
|
||||
cmake -DCMAKE_PREFIX_PATH=/opt/Qt/5.9.1/gcc_64 ..
|
||||
|
||||
..
|
||||
|
||||
``ModuleNotFoundError`` upon starting Cutter.
|
||||
|
||||
This can be resolved by either: 1. Disabling the optional jupyter
|
||||
support during building by modifying ``build.sh`` as follows:
|
||||
|
||||
- Uncomment
|
||||
``#QMAKE_CONF="CUTTER_ENABLE_JUPYTER=false CUTTER_ENABLE_QTWEBENGINE=false"``
|
||||
- Comment out the prior empty ``QMAKE_CONF=""``
|
||||
|
||||
2. Or alternatively by installing the two python dependencies manually
|
||||
afterwards via:
|
||||
|
||||
::
|
||||
|
||||
pip3 install notebook jupyter_client
|
||||
|
||||
Building with Meson (Windows)
|
||||
-----------------------------
|
||||
|
||||
|
8
meson.py
8
meson.py
@ -48,9 +48,7 @@ def build(args):
|
||||
cutter_builddir = os.path.join(ROOT, args.dir)
|
||||
if not os.path.exists(cutter_builddir):
|
||||
defines = ['-Denable_python=%s' % str(args.python).lower(),
|
||||
'-Denable_python_bindings=%s' % str(args.python_bindings).lower(),
|
||||
'-Denable_jupyter=%s' % str(args.jupyter).lower(),
|
||||
'-Denable_webengine=%s' % str(args.webengine).lower()]
|
||||
'-Denable_python_bindings=%s' % str(args.python_bindings).lower()]
|
||||
if os.name == 'nt':
|
||||
defines.append('-Dradare2:r2_incdir=radare2/include')
|
||||
defines.append('-Dradare2:r2_libdir=radare2/lib')
|
||||
@ -79,10 +77,6 @@ def main():
|
||||
help='Enable Python support')
|
||||
parser.add_argument('--python-bindings', action='store_true',
|
||||
help='Enable Python Bindings')
|
||||
parser.add_argument('--jupyter', action='store_true',
|
||||
help='Enable Jupyter support')
|
||||
parser.add_argument('--webengine', action='store_true',
|
||||
help='Enable QtWebEngine support')
|
||||
parser.add_argument('--release', action='store_true',
|
||||
help='Set the build as Release (remove debug info)')
|
||||
parser.add_argument('--nobuild', action='store_true',
|
||||
|
@ -12,5 +12,4 @@ Copy-Item .\python_embed\${py_base}.zip -Destination $dist\$py_base
|
||||
Copy-Item .\python_embed\*.pyd -Destination $dist\$py_base
|
||||
Copy-Item .\python_embed\sqlite3.dll -Destination $dist\$py_base
|
||||
Copy-Item .\python_embed\python*.dll -Destination $dist
|
||||
& python -m pip install -I --no-compile -t "${dist}\${py_base}\site-packages" jupyter ipykernel==4.8.2 jsonschema==2.6.0 pyzmq==17.1.2 notebook==5.6.0 tornado==5.1.1
|
||||
[System.IO.File]::WriteAllLines("${dist}\${py_base}._pth", "${py_base}`r`n${py_base}\${py_base}.zip`r`n${py_base}\site-packages")
|
||||
|
@ -1,3 +0,0 @@
|
||||
jupyter
|
||||
ipykernel==4.10.0
|
||||
pyzmq==17.1.2
|
@ -10,8 +10,6 @@ set(CUTTER_PYTHON_MIN 3.5)
|
||||
|
||||
option(CUTTER_ENABLE_PYTHON "Enable Python integration. Requires Python >= ${CUTTER_PYTHON_MIN}." OFF)
|
||||
option(CUTTER_ENABLE_PYTHON_BINDINGS "Enable generating Python bindings with Shiboken2. Unused if CUTTER_ENABLE_PYTHON=OFF." OFF)
|
||||
option(CUTTER_ENABLE_JUPYTER "Enable Jupyter integration. Unused if CUTTER_ENABLE_PYTHON=OFF." OFF)
|
||||
option(CUTTER_ENABLE_QTWEBENGINE "Use QtWebEngine for in-app Jupyter Browser. Unused if CUTTER_ENABLE_JUPYTER=OFF." OFF)
|
||||
|
||||
if(NOT CUTTER_ENABLE_PYTHON)
|
||||
set(CUTTER_ENABLE_PYTHON_BINDINGS OFF)
|
||||
@ -45,14 +43,6 @@ set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
find_package(Qt5 REQUIRED COMPONENTS Core Widgets Gui Svg Network)
|
||||
if(CUTTER_ENABLE_JUPYTER AND CUTTER_ENABLE_QTWEBENGINE)
|
||||
find_package(Qt5 COMPONENTS WebEngineWidgets)
|
||||
if(NOT Qt5_FOUND)
|
||||
message(FATAL_ERROR "QtWebEngine could not be found which is required for the in-app Jupyter Browser.
|
||||
If you do not want to enable this in-app Browser, re-run CMake with -DCUTTER_ENABLE_QTWEBENGINE=OFF.")
|
||||
endif()
|
||||
add_definitions(-DCUTTER_ENABLE_QTWEBENGINE)
|
||||
endif()
|
||||
|
||||
|
||||
if(WIN32)
|
||||
@ -80,10 +70,6 @@ if(CUTTER_ENABLE_PYTHON)
|
||||
include_directories(${PYTHON_INCLUDE_DIRS})
|
||||
add_definitions(-DCUTTER_ENABLE_PYTHON)
|
||||
|
||||
if(CUTTER_ENABLE_JUPYTER)
|
||||
add_definitions(-DCUTTER_ENABLE_JUPYTER)
|
||||
endif()
|
||||
|
||||
if(CUTTER_ENABLE_PYTHON_BINDINGS)
|
||||
find_package(PythonInterp REQUIRED)
|
||||
find_package(Shiboken2 "${Qt5_VERSION}" REQUIRED)
|
||||
@ -107,8 +93,6 @@ message(STATUS "Building Cutter version ${CUTTER_VERSION_FULL}")
|
||||
message(STATUS "Options:")
|
||||
message(STATUS "- Python: ${CUTTER_ENABLE_PYTHON}")
|
||||
message(STATUS "- Python Bindings: ${CUTTER_ENABLE_PYTHON_BINDINGS}")
|
||||
message(STATUS "- Jupyter: ${CUTTER_ENABLE_JUPYTER}")
|
||||
message(STATUS "- QtWebEngine: ${CUTTER_ENABLE_QTWEBENGINE}")
|
||||
message(STATUS "")
|
||||
|
||||
|
||||
@ -172,7 +156,4 @@ if(CUTTER_ENABLE_PYTHON)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(CUTTER_ENABLE_PYTHON AND CUTTER_ENABLE_JUPYTER AND CUTTER_ENABLE_QTWEBENGINE)
|
||||
target_link_libraries(Cutter Qt5::WebEngineWidgets)
|
||||
endif()
|
||||
|
||||
|
@ -43,16 +43,6 @@ equals(CUTTER_ENABLE_PYTHON, true) {
|
||||
}
|
||||
}
|
||||
|
||||
!defined(CUTTER_ENABLE_JUPYTER, var) CUTTER_ENABLE_JUPYTER=false
|
||||
equals(CUTTER_ENABLE_PYTHON, true) {
|
||||
equals(CUTTER_ENABLE_JUPYTER, true) CONFIG += CUTTER_ENABLE_JUPYTER
|
||||
}
|
||||
|
||||
!defined(CUTTER_ENABLE_QTWEBENGINE, var) CUTTER_ENABLE_QTWEBENGINE=false
|
||||
equals(CUTTER_ENABLE_JUPYTER, true) {
|
||||
equals(CUTTER_ENABLE_QTWEBENGINE, true) CONFIG += CUTTER_ENABLE_QTWEBENGINE
|
||||
}
|
||||
|
||||
!defined(CUTTER_BUNDLE_R2_APPBUNDLE, var) CUTTER_BUNDLE_R2_APPBUNDLE=false
|
||||
equals(CUTTER_BUNDLE_R2_APPBUNDLE, true) CONFIG += CUTTER_BUNDLE_R2_APPBUNDLE
|
||||
|
||||
@ -75,21 +65,6 @@ CUTTER_ENABLE_PYTHON_BINDINGS {
|
||||
message("Python Bindings disabled. (requires CUTTER_ENABLE_PYTHON=true)")
|
||||
}
|
||||
|
||||
CUTTER_ENABLE_JUPYTER {
|
||||
message("Jupyter support enabled.")
|
||||
DEFINES += CUTTER_ENABLE_JUPYTER
|
||||
} else {
|
||||
message("Jupyter support disabled. (requires CUTTER_ENABLE_PYTHON=true)")
|
||||
}
|
||||
|
||||
CUTTER_ENABLE_QTWEBENGINE {
|
||||
message("QtWebEngine support enabled.")
|
||||
DEFINES += CUTTER_ENABLE_QTWEBENGINE
|
||||
QT += webenginewidgets
|
||||
} else {
|
||||
message("QtWebEngine support disabled. (requires CUTTER_ENABLE_JUPYTER=true)")
|
||||
}
|
||||
|
||||
INCLUDEPATH *= . core widgets dialogs common plugins
|
||||
|
||||
win32 {
|
||||
@ -290,10 +265,7 @@ SOURCES += \
|
||||
widgets/HeadersWidget.cpp \
|
||||
widgets/SearchWidget.cpp \
|
||||
CutterApplication.cpp \
|
||||
common/JupyterConnection.cpp \
|
||||
widgets/JupyterWidget.cpp \
|
||||
common/PythonAPI.cpp \
|
||||
common/NestedIPyKernel.cpp \
|
||||
dialogs/R2PluginsDialog.cpp \
|
||||
widgets/CutterDockWidget.cpp \
|
||||
widgets/CutterTreeWidget.cpp \
|
||||
@ -404,10 +376,7 @@ HEADERS += \
|
||||
widgets/TypesWidget.h \
|
||||
widgets/HeadersWidget.h \
|
||||
widgets/SearchWidget.h \
|
||||
common/JupyterConnection.h \
|
||||
widgets/JupyterWidget.h \
|
||||
common/PythonAPI.h \
|
||||
common/NestedIPyKernel.h \
|
||||
dialogs/R2PluginsDialog.h \
|
||||
widgets/CutterDockWidget.h \
|
||||
widgets/CutterTreeWidget.h \
|
||||
@ -496,7 +465,6 @@ FORMS += \
|
||||
widgets/TypesWidget.ui \
|
||||
widgets/HeadersWidget.ui \
|
||||
widgets/SearchWidget.ui \
|
||||
widgets/JupyterWidget.ui \
|
||||
dialogs/R2PluginsDialog.ui \
|
||||
dialogs/VersionInfoDialog.ui \
|
||||
widgets/ZignaturesWidget.ui \
|
||||
|
@ -1,8 +1,5 @@
|
||||
#include "common/PythonManager.h"
|
||||
#include "CutterApplication.h"
|
||||
#ifdef CUTTER_ENABLE_JUPYTER
|
||||
#include "common/JupyterConnection.h"
|
||||
#endif
|
||||
#include "plugins/PluginManager.h"
|
||||
#include "CutterConfig.h"
|
||||
|
||||
|
@ -34,6 +34,8 @@ int main(int argc, char *argv[])
|
||||
settings.setValue("settings_migrated", true);
|
||||
}
|
||||
|
||||
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); // needed for QtWebEngine inside Plugins
|
||||
|
||||
CutterApplication a(argc, argv);
|
||||
|
||||
if (Config()->getAutoUpdateEnabled()) {
|
||||
|
@ -1,148 +0,0 @@
|
||||
#ifdef CUTTER_ENABLE_JUPYTER
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#include "JupyterConnection.h"
|
||||
#include "NestedIPyKernel.h"
|
||||
#include "PythonManager.h"
|
||||
#include "QtResImporter.h"
|
||||
|
||||
#include <QVariant>
|
||||
#include <QDebug>
|
||||
|
||||
Q_GLOBAL_STATIC(JupyterConnection, uniqueInstance)
|
||||
|
||||
JupyterConnection *JupyterConnection::getInstance()
|
||||
{
|
||||
return uniqueInstance;
|
||||
}
|
||||
|
||||
JupyterConnection::JupyterConnection(QObject *parent) : QObject(parent)
|
||||
{
|
||||
connect(Python(), &PythonManager::willShutDown, this, &JupyterConnection::stop);
|
||||
}
|
||||
|
||||
JupyterConnection::~JupyterConnection()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void JupyterConnection::start()
|
||||
{
|
||||
if (notebookInstanceExists) {
|
||||
return;
|
||||
}
|
||||
|
||||
notebookInstanceExists = startJupyterNotebook();
|
||||
|
||||
emit urlReceived(getUrl());
|
||||
}
|
||||
|
||||
void JupyterConnection::stop()
|
||||
{
|
||||
if (cutterNotebookAppInstance) {
|
||||
Python()->restoreThread();
|
||||
auto stopFunc = PyObject_GetAttrString(cutterNotebookAppInstance, "stop");
|
||||
PyObject_CallObject(stopFunc, nullptr);
|
||||
Py_DECREF(cutterNotebookAppInstance);
|
||||
Python()->saveThread();
|
||||
}
|
||||
}
|
||||
|
||||
QString JupyterConnection::getUrl()
|
||||
{
|
||||
if (!notebookInstanceExists) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QString url = getJupyterUrl();
|
||||
return url;
|
||||
}
|
||||
|
||||
long JupyterConnection::startNestedIPyKernel(const QStringList &argv)
|
||||
{
|
||||
NestedIPyKernel *kernel = NestedIPyKernel::start(argv);
|
||||
|
||||
if (!kernel) {
|
||||
qWarning() << "Could not start nested IPyKernel.";
|
||||
return 0;
|
||||
}
|
||||
|
||||
long id = nextKernelId++;
|
||||
kernels.insert(id, kernel);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
NestedIPyKernel *JupyterConnection::getNestedIPyKernel(long id)
|
||||
{
|
||||
auto it = kernels.find(id);
|
||||
if (it == kernels.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return *it;
|
||||
}
|
||||
|
||||
QVariant JupyterConnection::pollNestedIPyKernel(long id)
|
||||
{
|
||||
auto it = kernels.find(id);
|
||||
if (it == kernels.end()) {
|
||||
return QVariant(0);
|
||||
}
|
||||
|
||||
NestedIPyKernel *kernel = *it;
|
||||
QVariant v = kernel->poll();
|
||||
|
||||
if (!v.isNull()) {
|
||||
// if poll of kernel returns anything but None, it has already quit and should be cleaned up
|
||||
PyThreadState *subinterpreterState = kernel->getThreadState();
|
||||
delete kernel;
|
||||
kernels.erase(it);
|
||||
|
||||
PyThreadState *parentThreadState = PyThreadState_Swap(subinterpreterState);
|
||||
Py_EndInterpreter(subinterpreterState);
|
||||
PyThreadState_Swap(parentThreadState);
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
bool JupyterConnection::startJupyterNotebook()
|
||||
{
|
||||
PythonManager::ThreadHolder threadHolder;
|
||||
|
||||
if (!cutterJupyterModule) {
|
||||
cutterJupyterModule = QtResImport("cutter_jupyter");
|
||||
if (!cutterJupyterModule) {
|
||||
qWarning() << "Failed to load cutter_jupyter module. Make sure jupyter is installed.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
PyObject *startFunc = PyObject_GetAttrString(cutterJupyterModule, "start_jupyter");
|
||||
if (!startFunc) {
|
||||
qWarning() << "Couldn't get attribute start_jupyter.";
|
||||
return false;
|
||||
}
|
||||
|
||||
cutterNotebookAppInstance = PyObject_CallObject(startFunc, nullptr);
|
||||
|
||||
return cutterNotebookAppInstance != nullptr;
|
||||
}
|
||||
|
||||
QString JupyterConnection::getJupyterUrl()
|
||||
{
|
||||
Python()->restoreThread();
|
||||
|
||||
auto urlWithToken = PyObject_GetAttrString(cutterNotebookAppInstance, "url_with_token");
|
||||
auto asciiBytes = PyUnicode_AsASCIIString(urlWithToken);
|
||||
auto urlWithTokenString = QString::fromUtf8(PyBytes_AsString(asciiBytes));
|
||||
Py_DECREF(asciiBytes);
|
||||
Py_DECREF(urlWithToken);
|
||||
|
||||
Python()->saveThread();
|
||||
|
||||
return urlWithTokenString;
|
||||
}
|
||||
|
||||
#endif
|
@ -1,54 +0,0 @@
|
||||
#ifndef JUPYTERCONNECTION_H
|
||||
#define JUPYTERCONNECTION_H
|
||||
|
||||
#ifdef CUTTER_ENABLE_JUPYTER
|
||||
|
||||
#include <QProcess>
|
||||
#include <QMap>
|
||||
|
||||
class NestedIPyKernel;
|
||||
typedef struct _object PyObject;
|
||||
|
||||
class JupyterConnection : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static JupyterConnection *getInstance();
|
||||
|
||||
JupyterConnection(QObject *parent = nullptr);
|
||||
~JupyterConnection();
|
||||
|
||||
void start();
|
||||
QString getUrl();
|
||||
|
||||
long startNestedIPyKernel(const QStringList &argv);
|
||||
NestedIPyKernel *getNestedIPyKernel(long id);
|
||||
QVariant pollNestedIPyKernel(long id);
|
||||
|
||||
public slots:
|
||||
void stop();
|
||||
|
||||
signals:
|
||||
void urlReceived(const QString &url);
|
||||
void creationFailed();
|
||||
|
||||
private:
|
||||
QMap<long, NestedIPyKernel *> kernels;
|
||||
long nextKernelId = 1;
|
||||
|
||||
bool notebookInstanceExists = false;
|
||||
|
||||
PyObject *cutterJupyterModule = nullptr;
|
||||
PyObject *cutterNotebookAppInstance = nullptr;
|
||||
|
||||
bool startJupyterNotebook();
|
||||
QString getJupyterUrl();
|
||||
};
|
||||
|
||||
|
||||
#define Jupyter() (JupyterConnection::getInstance())
|
||||
|
||||
#endif // CUTTER_ENABLE_JUPYTER
|
||||
|
||||
#endif // JUPYTERCONNECTION_H
|
@ -1,87 +0,0 @@
|
||||
|
||||
#ifdef CUTTER_ENABLE_JUPYTER
|
||||
|
||||
#include <Python.h>
|
||||
#include <csignal>
|
||||
|
||||
#include "core/Cutter.h"
|
||||
#include "NestedIPyKernel.h"
|
||||
#include "QtResImporter.h"
|
||||
|
||||
NestedIPyKernel *NestedIPyKernel::start(const QStringList &argv)
|
||||
{
|
||||
PyThreadState *parentThreadState = PyThreadState_Get();
|
||||
|
||||
PyThreadState *threadState = Py_NewInterpreter();
|
||||
if (!threadState) {
|
||||
qWarning() << "Could not create subinterpreter.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RegQtResImporter();
|
||||
auto cutterIPykernelModule = QtResImport("cutter_ipykernel");
|
||||
if (!cutterIPykernelModule) {
|
||||
qWarning() << "Could not import cutter_ipykernel.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto kernel = new NestedIPyKernel(cutterIPykernelModule, argv);
|
||||
|
||||
PyThreadState_Swap(parentThreadState);
|
||||
|
||||
return kernel;
|
||||
}
|
||||
|
||||
NestedIPyKernel::NestedIPyKernel(PyObject *cutterIPykernelModule, const QStringList &argv)
|
||||
{
|
||||
threadState = PyThreadState_Get();
|
||||
|
||||
auto launchFunc = PyObject_GetAttrString(cutterIPykernelModule, "launch_ipykernel");
|
||||
|
||||
PyObject *argvListObject = PyList_New(argv.size());
|
||||
for (int i = 0; i < argv.size(); i++) {
|
||||
QString s = argv[i];
|
||||
PyList_SetItem(argvListObject, i, PyUnicode_DecodeUTF8(s.toUtf8().constData(), s.length(),
|
||||
nullptr));
|
||||
}
|
||||
|
||||
kernel = PyObject_CallFunction(launchFunc, "O", argvListObject);
|
||||
}
|
||||
|
||||
NestedIPyKernel::~NestedIPyKernel()
|
||||
{
|
||||
auto parentThreadState = PyThreadState_Swap(threadState);
|
||||
auto ret = PyObject_CallMethod(kernel, "cleanup", nullptr);
|
||||
if (!ret) {
|
||||
PyErr_Print();
|
||||
}
|
||||
PyThreadState_Swap(parentThreadState);
|
||||
}
|
||||
|
||||
void NestedIPyKernel::sendSignal(long signum)
|
||||
{
|
||||
auto parentThreadState = PyThreadState_Swap(threadState);
|
||||
auto ret = PyObject_CallMethod(kernel, "send_signal", "l", signum);
|
||||
if (!ret) {
|
||||
PyErr_Print();
|
||||
}
|
||||
PyThreadState_Swap(parentThreadState);
|
||||
}
|
||||
|
||||
QVariant NestedIPyKernel::poll()
|
||||
{
|
||||
QVariant ret;
|
||||
auto parentThreadState = PyThreadState_Swap(threadState);
|
||||
PyObject *pyRet = PyObject_CallMethod(kernel, "poll", nullptr);
|
||||
if (pyRet) {
|
||||
if (PyLong_Check(pyRet)) {
|
||||
ret = (qlonglong)PyLong_AsLong(pyRet);
|
||||
}
|
||||
} else {
|
||||
PyErr_Print();
|
||||
}
|
||||
PyThreadState_Swap(parentThreadState);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
@ -1,38 +0,0 @@
|
||||
|
||||
#ifndef NESTEDIPYKERNEL_H
|
||||
#define NESTEDIPYKERNEL_H
|
||||
|
||||
#ifdef CUTTER_ENABLE_JUPYTER
|
||||
|
||||
#include <QStringList>
|
||||
|
||||
struct _object;
|
||||
typedef _object PyObject;
|
||||
|
||||
struct _ts;
|
||||
typedef _ts PyThreadState;
|
||||
|
||||
class NestedIPyKernel
|
||||
{
|
||||
public:
|
||||
static NestedIPyKernel *start(const QStringList &argv);
|
||||
~NestedIPyKernel();
|
||||
|
||||
void sendSignal(long signum);
|
||||
QVariant poll();
|
||||
|
||||
PyThreadState *getThreadState()
|
||||
{
|
||||
return threadState;
|
||||
}
|
||||
|
||||
private:
|
||||
NestedIPyKernel(PyObject *cutterIPykernelModule, const QStringList &argv);
|
||||
|
||||
PyThreadState *threadState;
|
||||
PyObject *kernel;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif //NESTEDIPYKERNEL_H
|
@ -84,118 +84,4 @@ PyObject *PyInit_api()
|
||||
return PyModule_Create(&CutterModule);
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
|
||||
#ifdef CUTTER_ENABLE_JUPYTER
|
||||
#include "JupyterConnection.h"
|
||||
#include "NestedIPyKernel.h"
|
||||
|
||||
PyObject *api_internal_launch_ipykernel(PyObject *self, PyObject *args, PyObject *kw)
|
||||
{
|
||||
Q_UNUSED(self);
|
||||
Q_UNUSED(kw);
|
||||
|
||||
QStringList argv;
|
||||
PyObject *argvListObject;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O", &argvListObject)
|
||||
|| !PyList_Check(argvListObject)) {
|
||||
const char *msg = "Invalid args passed to api_internal_launch_ipykernel().";
|
||||
qWarning() << msg;
|
||||
PyErr_SetString(PyExc_RuntimeError, msg);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (int i = 0; i < PyList_Size(argvListObject); i++) {
|
||||
PyObject *o = PyList_GetItem(argvListObject, i);
|
||||
QString s = QString::fromUtf8(PyUnicode_AsUTF8(o));
|
||||
argv.append(s);
|
||||
}
|
||||
|
||||
long id = Jupyter()->startNestedIPyKernel(argv);
|
||||
|
||||
return PyLong_FromLong(id);
|
||||
}
|
||||
|
||||
PyObject *api_internal_kernel_interface_send_signal(PyObject *, PyObject *args)
|
||||
{
|
||||
long id;
|
||||
long signum;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "ll", &id, &signum)) {
|
||||
const char *msg = "Invalid args passed to api_internal_kernel_interface_send_signal().";
|
||||
qWarning() << msg;
|
||||
PyErr_SetString(PyExc_RuntimeError, msg);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
NestedIPyKernel *kernel = Jupyter()->getNestedIPyKernel(id);
|
||||
if (kernel) {
|
||||
kernel->sendSignal(signum);
|
||||
}
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PyObject *api_internal_kernel_interface_poll(PyObject *, PyObject *args)
|
||||
{
|
||||
long id;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "l", &id)) {
|
||||
const char *msg = "Invalid args passed to api_internal_kernel_interface_poll().";
|
||||
qWarning() << msg;
|
||||
PyErr_SetString(PyExc_RuntimeError, msg);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QVariant v = Jupyter()->pollNestedIPyKernel(id);
|
||||
bool ok;
|
||||
auto ret = static_cast<long>(v.toLongLong(&ok));
|
||||
if (ok) {
|
||||
return PyLong_FromLong(ret);
|
||||
} else {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
PyObject *api_internal_thread_set_async_exc(PyObject *, PyObject *args)
|
||||
{
|
||||
long id;
|
||||
PyObject *exc;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "lO", &id, &exc)) {
|
||||
const char *msg = "Invalid args passed to api_internal_thread_set_async_exc().";
|
||||
qWarning() << msg;
|
||||
PyErr_SetString(PyExc_RuntimeError, msg);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int ret = PyThreadState_SetAsyncExc(id, exc);
|
||||
return PyLong_FromLong(ret);
|
||||
}
|
||||
|
||||
PyMethodDef CutterInternalMethods[] = {
|
||||
{
|
||||
"launch_ipykernel", reinterpret_cast<PyCFunction>((void *)api_internal_launch_ipykernel), METH_VARARGS | METH_KEYWORDS,
|
||||
"Launch an IPython Kernel in a subinterpreter"
|
||||
},
|
||||
{"kernel_interface_send_signal", (PyCFunction)api_internal_kernel_interface_send_signal, METH_VARARGS, ""},
|
||||
{"kernel_interface_poll", (PyCFunction)api_internal_kernel_interface_poll, METH_VARARGS, ""},
|
||||
{"thread_set_async_exc", (PyCFunction)api_internal_thread_set_async_exc, METH_VARARGS, ""},
|
||||
{NULL, NULL, 0, NULL}
|
||||
};
|
||||
|
||||
PyModuleDef CutterInternalModule = {
|
||||
PyModuleDef_HEAD_INIT, "cutter_internal", NULL, -1, CutterInternalMethods,
|
||||
NULL, NULL, NULL, NULL
|
||||
};
|
||||
|
||||
|
||||
PyObject *PyInit_api_internal()
|
||||
{
|
||||
return PyModule_Create(&CutterInternalModule);
|
||||
}
|
||||
|
||||
#endif // CUTTER_ENABLE_JUPYTER
|
||||
|
||||
#endif // CUTTER_ENABLE_PYTHON
|
||||
|
@ -74,9 +74,6 @@ void PythonManager::initialize()
|
||||
initPythonHome();
|
||||
|
||||
PyImport_AppendInittab("_cutter", &PyInit_api);
|
||||
#ifdef CUTTER_ENABLE_JUPYTER
|
||||
PyImport_AppendInittab("cutter_internal", &PyInit_api_internal);
|
||||
#endif
|
||||
PyImport_AppendInittab("_qtres", &PyInit_qtres);
|
||||
#ifdef CUTTER_ENABLE_PYTHON_BINDINGS
|
||||
PyImport_AppendInittab("CutterBindings", &PyInit_CutterBindings);
|
||||
|
@ -52,7 +52,6 @@
|
||||
#include "widgets/ClassesWidget.h"
|
||||
#include "widgets/ResourcesWidget.h"
|
||||
#include "widgets/VTablesWidget.h"
|
||||
#include "widgets/JupyterWidget.h"
|
||||
#include "widgets/HeadersWidget.h"
|
||||
#include "widgets/ZignaturesWidget.h"
|
||||
#include "widgets/DebugActions.h"
|
||||
@ -268,12 +267,6 @@ void MainWindow::initDocks()
|
||||
memoryMapDock = new MemoryMapWidget(this, ui->actionMemoryMap);
|
||||
breakpointDock = new BreakpointWidget(this, ui->actionBreakpoint);
|
||||
registerRefsDock = new RegisterRefsWidget(this, ui->actionRegisterRefs);
|
||||
#ifdef CUTTER_ENABLE_JUPYTER
|
||||
jupyterDock = new JupyterWidget(this, ui->actionJupyter);
|
||||
#else
|
||||
ui->actionJupyter->setEnabled(false);
|
||||
ui->actionJupyter->setVisible(false);
|
||||
#endif
|
||||
dashboardDock = new Dashboard(this, ui->actionDashboard);
|
||||
sdbDock = new SdbWidget(this, ui->actionSDBBrowser);
|
||||
classesDock = new ClassesWidget(this, ui->actionClasses);
|
||||
@ -790,9 +783,6 @@ void MainWindow::restoreDocks()
|
||||
tabifyDockWidget(dashboardDock, memoryMapDock);
|
||||
tabifyDockWidget(dashboardDock, breakpointDock);
|
||||
tabifyDockWidget(dashboardDock, registerRefsDock);
|
||||
#ifdef CUTTER_ENABLE_JUPYTER
|
||||
tabifyDockWidget(dashboardDock, jupyterDock);
|
||||
#endif
|
||||
|
||||
updateDockActionsChecked();
|
||||
}
|
||||
@ -827,9 +817,6 @@ void MainWindow::showZenDocks()
|
||||
hexdumpDock,
|
||||
searchDock,
|
||||
importsDock,
|
||||
#ifdef CUTTER_ENABLE_JUPYTER
|
||||
jupyterDock
|
||||
#endif
|
||||
};
|
||||
for (auto w : dockWidgets) {
|
||||
if (zenDocks.contains(w)) {
|
||||
|
@ -43,9 +43,6 @@ class TypesWidget;
|
||||
class HeadersWidget;
|
||||
class ZignaturesWidget;
|
||||
class SearchWidget;
|
||||
#ifdef CUTTER_ENABLE_JUPYTER
|
||||
class JupyterWidget;
|
||||
#endif
|
||||
class QDockWidget;
|
||||
class DisassemblyWidget;
|
||||
class GraphWidget;
|
||||
@ -251,9 +248,6 @@ private:
|
||||
NewFileDialog *newFileDialog = nullptr;
|
||||
QDockWidget *breakpointDock = nullptr;
|
||||
QDockWidget *registerRefsDock = nullptr;
|
||||
#ifdef CUTTER_ENABLE_JUPYTER
|
||||
JupyterWidget *jupyterDock = nullptr;
|
||||
#endif
|
||||
|
||||
void initToolBar();
|
||||
void initDocks();
|
||||
|
@ -213,7 +213,6 @@ QToolTip {
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionComments"/>
|
||||
<addaction name="actionConsole"/>
|
||||
<addaction name="actionJupyter"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="menuAddExtraWidget"/>
|
||||
<addaction name="menuPlugins"/>
|
||||
@ -1107,14 +1106,6 @@ QToolTip {
|
||||
<string>Show/Hide Zignatures panel</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionJupyter">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Jupyter</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionExport_as_code">
|
||||
<property name="text">
|
||||
<string>Export as code</string>
|
||||
|
@ -30,15 +30,15 @@ AboutDialog::AboutDialog(QWidget *parent) :
|
||||
+ tr("Version") + " " CUTTER_VERSION_FULL "<br/>"
|
||||
+ tr("Using r2-") + R2_GITTAP
|
||||
+ "<p><b>" + tr("Optional Features:") + "</b><br/>"
|
||||
+ QString("Jupyter: %1<br/>").arg(
|
||||
#ifdef CUTTER_ENABLE_JUPYTER
|
||||
+ QString("Python: %1<br/>").arg(
|
||||
#ifdef CUTTER_ENABLE_PYTHON
|
||||
"ON"
|
||||
#else
|
||||
"OFF"
|
||||
#endif
|
||||
)
|
||||
+ QString("QtWebEngine: %2</p>").arg(
|
||||
#ifdef CUTTER_ENABLE_QTWEBENGINE
|
||||
+ QString("Python Bindings: %2</p>").arg(
|
||||
#ifdef CUTTER_ENABLE_PYTHON_BINDINGS
|
||||
"ON"
|
||||
#else
|
||||
"OFF"
|
||||
|
@ -13,16 +13,6 @@ if get_option('enable_python')
|
||||
message('Python Bindings are enabled')
|
||||
feature_define_args += ['-DCUTTER_ENABLE_PYTHON_BINDINGS']
|
||||
endif
|
||||
|
||||
if get_option('enable_jupyter')
|
||||
message('Jupyter support enabled')
|
||||
feature_define_args += ['-DCUTTER_ENABLE_JUPYTER']
|
||||
if get_option('enable_webengine')
|
||||
message('QtWebEngine support enabled')
|
||||
feature_define_args += ['-DCUTTER_ENABLE_QTWEBENGINE']
|
||||
qt_modules += 'WebEngineWidgets'
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
add_project_arguments(feature_define_args, language: 'cpp')
|
||||
|
@ -1,4 +1,2 @@
|
||||
option('enable_python', type: 'boolean', value: true)
|
||||
option('enable_python_bindings', type: 'boolean', value: true)
|
||||
option('enable_jupyter', type: 'boolean', value: false)
|
||||
option('enable_webengine', type: 'boolean', value: false)
|
||||
|
@ -1,84 +0,0 @@
|
||||
|
||||
import logging
|
||||
import threading
|
||||
import signal
|
||||
import cutter_internal
|
||||
import zmq
|
||||
from ipykernel.kernelapp import IPKernelApp
|
||||
from ipykernel.ipkernel import IPythonKernel
|
||||
|
||||
|
||||
class IPyKernelInterfaceKernel:
|
||||
def __init__(self, thread, app):
|
||||
self._thread = thread
|
||||
self._app = app
|
||||
|
||||
def send_signal(self, signum):
|
||||
if not self._thread.is_alive():
|
||||
return
|
||||
|
||||
if signum == signal.SIGKILL or signum == signal.SIGTERM:
|
||||
self._app.io_loop.stop()
|
||||
elif signum == signal.SIGINT and self._app.kernel.interruptable:
|
||||
self._app.log.debug("Sending KeyboardInterrupt to ioloop thread.")
|
||||
cutter_internal.thread_set_async_exc(self._thread.ident, KeyboardInterrupt())
|
||||
|
||||
def poll(self):
|
||||
if self._thread.is_alive():
|
||||
return None
|
||||
else:
|
||||
return 0
|
||||
|
||||
def cleanup(self):
|
||||
self._app.heartbeat.context.destroy()
|
||||
self._thread.join()
|
||||
self._app.heartbeat.join()
|
||||
self._app.iopub_thread.stop()
|
||||
try:
|
||||
self._app.kernel.shell.history_manager.save_thread.stop()
|
||||
except AttributeError:
|
||||
pass
|
||||
zmq.Context.instance().destroy()
|
||||
# successful if only the main thread remains
|
||||
return len(threading.enumerate()) == 1
|
||||
|
||||
|
||||
class CutterIPythonKernel(IPythonKernel):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.interruptable = False
|
||||
|
||||
def pre_handler_hook(self):
|
||||
self.interruptable = True
|
||||
|
||||
def post_handler_hook(self):
|
||||
self.interruptable = False
|
||||
|
||||
|
||||
class CutterIPKernelApp(IPKernelApp):
|
||||
def init_signal(self):
|
||||
# This would call signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
# Not needed in supinterpreter.
|
||||
pass
|
||||
|
||||
def log_connection_info(self):
|
||||
# Just skip this. It would only pollute Cutter's output.
|
||||
pass
|
||||
|
||||
|
||||
def launch_ipykernel(argv):
|
||||
app = CutterIPKernelApp.instance()
|
||||
|
||||
def run_kernel():
|
||||
import asyncio
|
||||
asyncio.set_event_loop(asyncio.new_event_loop())
|
||||
app.kernel_class = CutterIPythonKernel
|
||||
# app.log_level = logging.DEBUG
|
||||
app.initialize(argv[3:])
|
||||
app.start()
|
||||
|
||||
thread = threading.Thread(target=run_kernel)
|
||||
thread.start()
|
||||
|
||||
return IPyKernelInterfaceKernel(thread, app)
|
||||
|
@ -1,140 +0,0 @@
|
||||
|
||||
import asyncio
|
||||
import queue
|
||||
import sys
|
||||
from jupyter_client.ioloop import IOLoopKernelManager
|
||||
from notebook.notebookapp import *
|
||||
import cutter_internal
|
||||
|
||||
|
||||
class IPyKernelInterfaceJupyter:
|
||||
def __init__(self, id):
|
||||
self._id = id
|
||||
|
||||
def send_signal(self, signum):
|
||||
cutter_internal.kernel_interface_send_signal(self._id, signum)
|
||||
|
||||
def kill(self):
|
||||
self.send_signal(signal.SIGKILL)
|
||||
|
||||
def terminate(self):
|
||||
self.send_signal(signal.SIGTERM)
|
||||
|
||||
def poll(self):
|
||||
return cutter_internal.kernel_interface_poll(self._id)
|
||||
|
||||
def wait(self, timeout=None):
|
||||
if timeout is not None:
|
||||
start_time = time.process_time()
|
||||
else:
|
||||
start_time = None
|
||||
while timeout is None or time.process_time() - start_time < timeout:
|
||||
if self.poll() is not None:
|
||||
return
|
||||
time.sleep(0.1)
|
||||
|
||||
|
||||
class CutterInternalIPyKernelManager(IOLoopKernelManager):
|
||||
def start_kernel(self, **kw):
|
||||
self.write_connection_file()
|
||||
|
||||
self._launch_args = kw.copy()
|
||||
|
||||
extra_arguments = kw.pop('extra_arguments', [])
|
||||
kernel_cmd = self.format_kernel_cmd(extra_arguments=extra_arguments)
|
||||
env = kw.pop('env', os.environ).copy()
|
||||
# Don't allow PYTHONEXECUTABLE to be passed to kernel process.
|
||||
# If set, it can bork all the things.
|
||||
env.pop('PYTHONEXECUTABLE', None)
|
||||
if not self.kernel_cmd:
|
||||
# If kernel_cmd has been set manually, don't refer to a kernel spec
|
||||
# Environment variables from kernel spec are added to os.environ
|
||||
env.update(self.kernel_spec.env or {})
|
||||
|
||||
# launch the kernel subprocess
|
||||
id = cutter_internal.launch_ipykernel(kernel_cmd, env=env, **kw)
|
||||
self.kernel = IPyKernelInterfaceJupyter(id)
|
||||
# self._launch_kernel(kernel_cmd, env=env, **kw)
|
||||
|
||||
self.start_restarter()
|
||||
self._connect_control_socket()
|
||||
|
||||
def signal_kernel(self, signum):
|
||||
self.kernel.send_signal(signum)
|
||||
|
||||
|
||||
def kernel_manager_factory(kernel_name, **kwargs):
|
||||
if kernel_name in {"python", "python2", "python3"}:
|
||||
return CutterInternalIPyKernelManager(kernel_name=kernel_name, **kwargs)
|
||||
else:
|
||||
return IOLoopKernelManager(kernel_name=kernel_name, **kwargs)
|
||||
|
||||
|
||||
class CutterNotebookApp(NotebookApp):
|
||||
def __init__(self, **kwargs):
|
||||
self.thread = None
|
||||
self.io_loop = None
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def start(self):
|
||||
""" see NotebookApp.start() """
|
||||
self.kernel_manager.kernel_manager_factory = kernel_manager_factory
|
||||
|
||||
super(NotebookApp, self).start()
|
||||
|
||||
self.write_server_info_file()
|
||||
|
||||
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()
|
||||
if self.thread is not None:
|
||||
self.thread.join()
|
||||
|
||||
def init_signal(self):
|
||||
# This would call signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
# Not needed in supinterpreter.
|
||||
pass
|
||||
|
||||
@property
|
||||
def url_with_token(self):
|
||||
return url_concat(self.connection_url, {'token': self.token})
|
||||
|
||||
|
||||
def start_jupyter():
|
||||
q = queue.Queue()
|
||||
|
||||
def start_jupyter_async():
|
||||
# workaround for misbehavior under certain circumstances
|
||||
# with argumentparser and jupyter accessing out of bounds
|
||||
# program name via argv[0]
|
||||
if not sys.argv:
|
||||
sys.argv.append("Cutter")
|
||||
|
||||
asyncio.set_event_loop(asyncio.new_event_loop())
|
||||
app = CutterNotebookApp()
|
||||
# app.log_level = logging.DEBUG
|
||||
app.thread = threading.current_thread()
|
||||
app.initialize()
|
||||
q.put(app)
|
||||
app.start()
|
||||
|
||||
threading.Thread(target=start_jupyter_async).start()
|
||||
return q.get()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = start_jupyter()
|
||||
print("Started " + app.url_with_token)
|
@ -77,8 +77,6 @@
|
||||
<file>img/cutter_plain.svg</file>
|
||||
<file>img/cutter_white_plain.svg</file>
|
||||
<file>img/cutter.svg</file>
|
||||
<file>python/cutter_jupyter.py</file>
|
||||
<file>python/cutter_ipykernel.py</file>
|
||||
<file>img/icons/copy.svg</file>
|
||||
<file>img/icons/delete.svg</file>
|
||||
<file>img/icons/previous.svg</file>
|
||||
|
@ -1,179 +0,0 @@
|
||||
#ifdef CUTTER_ENABLE_JUPYTER
|
||||
|
||||
#include "common/JupyterConnection.h"
|
||||
#include "JupyterWidget.h"
|
||||
#include "ui_JupyterWidget.h"
|
||||
|
||||
#include <QTabWidget>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
|
||||
#ifdef CUTTER_ENABLE_QTWEBENGINE
|
||||
#include <QWebEngineSettings>
|
||||
#endif
|
||||
|
||||
JupyterWidget::JupyterWidget(MainWindow *main, QAction *action) :
|
||||
CutterDockWidget(main, action),
|
||||
ui(new Ui::JupyterWidget)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
ui->tabWidget->setTabsClosable(true);
|
||||
ui->tabWidget->setMovable(true);
|
||||
|
||||
QWidget *cornerWidget = new QWidget(ui->tabWidget);
|
||||
QHBoxLayout *cornerWidgetLayout = new QHBoxLayout(cornerWidget);
|
||||
cornerWidget->setLayout(cornerWidgetLayout);
|
||||
cornerWidgetLayout->setContentsMargins(4, 4, 4, 4);
|
||||
homeButton = new QPushButton(cornerWidget);
|
||||
homeButton->setStyleSheet("QPushButton { padding: 2px; background-color: palette(light); border-radius: 4px; }"
|
||||
"QPushButton:pressed { background-color: palette(dark); }");
|
||||
homeButton->setIcon(QIcon(":/img/icons/home.svg"));
|
||||
homeButton->setEnabled(false);
|
||||
cornerWidgetLayout->addWidget(homeButton);
|
||||
ui->tabWidget->setCornerWidget(cornerWidget);
|
||||
|
||||
connect(homeButton, &QAbstractButton::clicked, this, &JupyterWidget::openHomeTab);
|
||||
connect(ui->tabWidget, &QTabWidget::tabCloseRequested, this, &JupyterWidget::tabCloseRequested);
|
||||
|
||||
connect(Jupyter(), &JupyterConnection::urlReceived, this, &JupyterWidget::urlReceived);
|
||||
connect(Jupyter(), &JupyterConnection::creationFailed, this, &JupyterWidget::creationFailed);
|
||||
Jupyter()->start();
|
||||
}
|
||||
|
||||
JupyterWidget::~JupyterWidget()
|
||||
{
|
||||
}
|
||||
|
||||
#ifdef CUTTER_ENABLE_QTWEBENGINE
|
||||
JupyterWebView *JupyterWidget::createNewTab()
|
||||
{
|
||||
auto webView = new JupyterWebView(this);
|
||||
int index = ui->tabWidget->addTab(webView, "Tab");
|
||||
webView->setTabWidget(ui->tabWidget);
|
||||
ui->tabWidget->setCurrentIndex(index);
|
||||
return webView;
|
||||
}
|
||||
#endif
|
||||
|
||||
void JupyterWidget::urlReceived(const QString &url)
|
||||
{
|
||||
#ifdef CUTTER_ENABLE_QTWEBENGINE
|
||||
Q_UNUSED(url);
|
||||
openHomeTab();
|
||||
homeButton->setEnabled(true);
|
||||
#else
|
||||
clearTabs();
|
||||
QWidget *failPage = new QWidget(this);
|
||||
QLabel *label = new QLabel(failPage);
|
||||
label->setText(
|
||||
tr("Cutter has been built without QtWebEngine.<br />Open the following URL in your Browser to use Jupyter:<br /><a href=\"%1\">%1</a>").arg(
|
||||
url));
|
||||
label->setTextFormat(Qt::RichText);
|
||||
label->setTextInteractionFlags(Qt::TextBrowserInteraction);
|
||||
label->setOpenExternalLinks(true);
|
||||
QHBoxLayout *layout = new QHBoxLayout();
|
||||
layout->addWidget(label);
|
||||
layout->setAlignment(label, Qt::AlignCenter);
|
||||
failPage->setLayout(layout);
|
||||
ui->tabWidget->addTab(failPage, tr("Jupyter"));
|
||||
homeButton->setEnabled(false);
|
||||
ui->tabWidget->setTabsClosable(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
void JupyterWidget::creationFailed()
|
||||
{
|
||||
clearTabs();
|
||||
QWidget *failPage = new QWidget(this);
|
||||
QLabel *label = new QLabel(failPage);
|
||||
label->setText(
|
||||
tr("An error occurred while opening jupyter. Make sure Jupyter is installed system-wide."));
|
||||
QHBoxLayout *layout = new QHBoxLayout();
|
||||
layout->addWidget(label);
|
||||
layout->setAlignment(label, Qt::AlignCenter);
|
||||
failPage->setLayout(layout);
|
||||
ui->tabWidget->addTab(failPage, tr("Error"));
|
||||
homeButton->setEnabled(false);
|
||||
ui->tabWidget->setTabsClosable(false);
|
||||
}
|
||||
|
||||
void JupyterWidget::openHomeTab()
|
||||
{
|
||||
#ifdef CUTTER_ENABLE_QTWEBENGINE
|
||||
QString url = Jupyter()->getUrl();
|
||||
if (!url.isNull()) {
|
||||
createNewTab()->load(QUrl(url));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void JupyterWidget::tabCloseRequested(int index)
|
||||
{
|
||||
removeTab(index);
|
||||
if (ui->tabWidget->count() == 0) {
|
||||
openHomeTab();
|
||||
}
|
||||
}
|
||||
|
||||
void JupyterWidget::removeTab(int index)
|
||||
{
|
||||
QWidget *widget = ui->tabWidget->widget(index);
|
||||
ui->tabWidget->removeTab(index);
|
||||
delete widget;
|
||||
}
|
||||
|
||||
void JupyterWidget::clearTabs()
|
||||
{
|
||||
while (ui->tabWidget->count() > 0) {
|
||||
removeTab(0);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CUTTER_ENABLE_QTWEBENGINE
|
||||
JupyterWebView::JupyterWebView(JupyterWidget *mainWidget, QWidget *parent) : QWebEngineView(parent)
|
||||
{
|
||||
this->mainWidget = mainWidget;
|
||||
this->tabWidget = nullptr;
|
||||
|
||||
connect(this, &QWebEngineView::titleChanged, this, &JupyterWebView::onTitleChanged);
|
||||
}
|
||||
|
||||
void JupyterWebView::setTabWidget(QTabWidget *tabWidget)
|
||||
{
|
||||
this->tabWidget = tabWidget;
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
QWebEngineView *JupyterWebView::createWindow(QWebEnginePage::WebWindowType type)
|
||||
{
|
||||
switch (type) {
|
||||
case QWebEnginePage::WebBrowserTab:
|
||||
return mainWidget->createNewTab();
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void JupyterWebView::onTitleChanged(const QString &)
|
||||
{
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
void JupyterWebView::updateTitle()
|
||||
{
|
||||
if (!tabWidget) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString title = this->title();
|
||||
if (title.isEmpty()) {
|
||||
title = tr("Jupyter");
|
||||
}
|
||||
tabWidget->setTabText(tabWidget->indexOf(this), title);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
@ -1,78 +0,0 @@
|
||||
|
||||
#ifndef JUPYTERWIDGET_H
|
||||
#define JUPYTERWIDGET_H
|
||||
|
||||
#ifdef CUTTER_ENABLE_JUPYTER
|
||||
|
||||
#include "CutterDockWidget.h"
|
||||
|
||||
#include <memory>
|
||||
#include <QAbstractButton>
|
||||
|
||||
namespace Ui {
|
||||
class JupyterWidget;
|
||||
}
|
||||
|
||||
class JupyterWebView;
|
||||
class QTabWidget;
|
||||
class MainWindow;
|
||||
|
||||
class JupyterWidget : public CutterDockWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
JupyterWidget(MainWindow *main, QAction *action = nullptr);
|
||||
~JupyterWidget();
|
||||
|
||||
#ifdef CUTTER_ENABLE_QTWEBENGINE
|
||||
JupyterWebView *createNewTab();
|
||||
#endif
|
||||
|
||||
private slots:
|
||||
void urlReceived(const QString &url);
|
||||
void creationFailed();
|
||||
|
||||
void openHomeTab();
|
||||
void tabCloseRequested(int index);
|
||||
|
||||
private:
|
||||
std::unique_ptr<Ui::JupyterWidget> ui;
|
||||
|
||||
QAbstractButton *homeButton;
|
||||
|
||||
void removeTab(int index);
|
||||
void clearTabs();
|
||||
};
|
||||
|
||||
#ifdef CUTTER_ENABLE_QTWEBENGINE
|
||||
|
||||
#include <QWebEngineView>
|
||||
|
||||
class JupyterWebView : public QWebEngineView
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
JupyterWebView(JupyterWidget *mainWidget, QWidget *parent = nullptr);
|
||||
|
||||
void setTabWidget(QTabWidget *tabWidget);
|
||||
|
||||
protected:
|
||||
QWebEngineView *createWindow(QWebEnginePage::WebWindowType type) override;
|
||||
|
||||
private slots:
|
||||
void onTitleChanged(const QString &title);
|
||||
|
||||
private:
|
||||
JupyterWidget *mainWidget;
|
||||
QTabWidget *tabWidget;
|
||||
|
||||
void updateTitle();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#endif //JUPYTERWIDGET_H
|
@ -1,38 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>JupyterWidget</class>
|
||||
<widget class="QDockWidget" name="JupyterWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>644</width>
|
||||
<height>484</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Jupyter</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="dockWidgetContents">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
Loading…
Reference in New Issue
Block a user