mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-08-24 23:46:41 +00:00
Load debugpy when possible to allow python debugging in-program
This commit is contained in:
@@ -364,6 +364,21 @@ DECLARE_REFLECTION_STRUCT(BugReport);
|
||||
"type: List[str]"); \
|
||||
CONFIG_SETTING(public, QVariantList, rdcarray<rdcstr>, TextureViewer_ShaderDirs) \
|
||||
\
|
||||
DOCUMENT( \
|
||||
"``True`` if debugging should be enabled at startup for python, including UI extensions " \
|
||||
"and scripts in the python shell.\n" \
|
||||
"\n" \
|
||||
"Defaults to ``True``." \
|
||||
"" \
|
||||
":type: bool"); \
|
||||
CONFIG_SETTING_VAL(public, bool, bool, Python_DebugEnabled, true) \
|
||||
\
|
||||
DOCUMENT( \
|
||||
"The path to an copy of the ``debugpy`` module which should be loaded for debugging." \
|
||||
"" \
|
||||
":type: str"); \
|
||||
CONFIG_SETTING_VAL(public, QString, rdcstr, Python_DebugPyDir, "") \
|
||||
\
|
||||
DOCUMENT( \
|
||||
"List of extra directories to write python stubs into.\n" \
|
||||
"\n:" \
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <QApplication>
|
||||
#include <QMutexLocker>
|
||||
#include <QProgressDialog>
|
||||
#include "Code/pyrenderdoc/PythonContext.h"
|
||||
#include "CaptureContext.h"
|
||||
#include "QRDUtils.h"
|
||||
|
||||
@@ -55,7 +56,11 @@ void ReplayManager::OpenCapture(const QString &capturefile, const ReplayOptions
|
||||
int proxyRenderer = -1;
|
||||
|
||||
m_Thread = new LambdaThread([this, proxyRenderer, capturefile, opts, progress]() {
|
||||
PythonContext::AddDebuggableThread();
|
||||
|
||||
run(proxyRenderer, capturefile, opts, progress);
|
||||
|
||||
PythonContext::RemoveDebuggableThread();
|
||||
});
|
||||
m_Thread->setName(lit("ReplayManager"));
|
||||
m_Thread->start(QThread::HighestPriority);
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
// must be included first
|
||||
#include <Python.h>
|
||||
#include <pythread.h>
|
||||
|
||||
#include "3rdparty/pythoncapi_compat.h"
|
||||
|
||||
@@ -67,6 +68,7 @@ PyTypeObject **SbkPySide2_QtWidgetsTypes = NULL;
|
||||
#include <QElapsedTimer>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QSettings>
|
||||
#include <QStandardPaths>
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
@@ -139,6 +141,9 @@ static PyMethodDef OutputRedirector_methods[] = {
|
||||
{NULL}};
|
||||
|
||||
PyObject *PythonContext::main_dict = NULL;
|
||||
PyObject *PythonContext::m_DebugPy = NULL;
|
||||
PyObject *PythonContext::m_CallWrapper = NULL;
|
||||
PyObject *PythonContext::m_CallWrapperGlobals = NULL;
|
||||
QMap<rdcstr, PyObject *> PythonContext::extensions;
|
||||
|
||||
static PyObject *current_global_handle = NULL;
|
||||
@@ -146,6 +151,7 @@ static PyObject *current_global_handle = NULL;
|
||||
static QMutex decrefQueueMutex;
|
||||
static QList<PyObject *> decrefQueue;
|
||||
extern "C" void ProcessDecRefQueue();
|
||||
extern "C" void HandleException(PyObject *global_handle);
|
||||
|
||||
// helper overload to give us the semantics we want - return NULL without an exception if the attr doesn't exist
|
||||
PyObject *PyObject_SafeGetAttrString(PyObject *obj, const char *string)
|
||||
@@ -405,6 +411,16 @@ void PythonContext::GenerateStubs(const rdcarray<rdcstr> &extraPaths)
|
||||
qInfo() << "Stubs generation processed in" << timer.elapsed() << "ms";
|
||||
}
|
||||
|
||||
void PythonContext::PrepareDebugTracing()
|
||||
{
|
||||
// prep the frame for debugpy if we have it enabled, as if we're calling straight from C++ debugpy
|
||||
// won't be enabled and breakpoints won't get hit
|
||||
if(m_DebugPy && m_CallWrapper && m_CallWrapperGlobals)
|
||||
{
|
||||
Py_XDECREF(PyEval_EvalCode(m_CallWrapper, m_CallWrapperGlobals, NULL));
|
||||
}
|
||||
}
|
||||
|
||||
void PythonContext::GlobalInit(PersistantConfig &config)
|
||||
{
|
||||
// must happen on the UI thread
|
||||
@@ -444,6 +460,12 @@ void PythonContext::GlobalInit(PersistantConfig &config)
|
||||
}
|
||||
#endif
|
||||
|
||||
// pydev complains about the zip of standard library files, but that's fine
|
||||
// and the recommendation is to ignore this warning: https://github.com/microsoft/debugpy/issues/890
|
||||
//
|
||||
// we need to set this early so it's snapshotted by python for os.getenv/os.environ
|
||||
qputenv("PYDEVD_DISABLE_FILE_VALIDATION", lit("1").toUtf8());
|
||||
|
||||
#if PY_VERSION_HEX > 0x030B0000
|
||||
pyconfig.program_name = program_name;
|
||||
|
||||
@@ -537,6 +559,240 @@ void PythonContext::GlobalInit(PersistantConfig &config)
|
||||
output->block = false;
|
||||
}
|
||||
|
||||
// load debugpy from installed vscode if we find it. This requires a minimum of python 3.8
|
||||
#if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 8
|
||||
if(config.Python_DebugEnabled)
|
||||
{
|
||||
QString debugpy_path;
|
||||
|
||||
// use the user's specified debugpy path if it exists
|
||||
if(QDir(config.Python_DebugPyDir).exists() &&
|
||||
QDir(config.Python_DebugPyDir).exists(lit("__init__.py")))
|
||||
{
|
||||
debugpy_path = config.Python_DebugPyDir;
|
||||
}
|
||||
|
||||
// next load debugpy from VS Code, if it exists
|
||||
if(debugpy_path.isEmpty())
|
||||
{
|
||||
QString homePath = QStandardPaths::standardLocations(QStandardPaths::HomeLocation)[0];
|
||||
QDir homeDir(homePath);
|
||||
homeDir.cd(lit(".vscode/extensions"));
|
||||
|
||||
if(homeDir.exists())
|
||||
{
|
||||
QStringList debugpy_exts = homeDir.entryList(QStringList() << lit("ms-python.debugpy*"));
|
||||
|
||||
if(debugpy_exts.size() >= 1)
|
||||
{
|
||||
// assume that sorting works by version, so if there are multiple versions of the
|
||||
// extension we will pick the latest
|
||||
debugpy_exts.sort();
|
||||
|
||||
QDir debugpy_libs = homeDir;
|
||||
debugpy_libs.cd(debugpy_exts.last());
|
||||
debugpy_libs.cd(lit("bundled/libs"));
|
||||
|
||||
if(debugpy_libs.exists())
|
||||
{
|
||||
debugpy_path = debugpy_libs.absolutePath();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// next try pycharm, if we can find it
|
||||
if(debugpy_path.isEmpty())
|
||||
{
|
||||
#ifdef Q_OS_WIN32
|
||||
QDir programBase(lit("C:/Program Files/JetBrains"));
|
||||
#else
|
||||
QDir programBase(lit("/opt"));
|
||||
#endif
|
||||
|
||||
QString pycharm;
|
||||
|
||||
QStringList pycharms =
|
||||
programBase.entryList(QStringList() << lit("PyCharm*") << lit("pycharm-*"));
|
||||
|
||||
if(!pycharms.empty())
|
||||
{
|
||||
// assume sorting will pick the latest
|
||||
pycharms.sort();
|
||||
|
||||
pycharm = programBase.absoluteFilePath(pycharms.last());
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
if(pycharm.isEmpty())
|
||||
{
|
||||
// if we didn't find it in the default location, check in the registry on windows
|
||||
QSettings pycharm_reg(lit("HKEY_LOCAL_MACHINE\\SOFTWARE\\JetBrains\\PyCharm"),
|
||||
QSettings::NativeFormat);
|
||||
|
||||
QStringList groups = pycharm_reg.childGroups();
|
||||
|
||||
if(!groups.empty())
|
||||
{
|
||||
// assume sorting will pick the latest
|
||||
groups.sort();
|
||||
|
||||
pycharm = pycharm_reg.value(groups.last() + lit("/Default")).toString();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if(!pycharm.isEmpty())
|
||||
{
|
||||
QDir helpers(pycharm);
|
||||
helpers.cd(lit("plugins/python-ce/helpers"));
|
||||
|
||||
if(helpers.exists() && helpers.exists(lit("debugpy")))
|
||||
{
|
||||
debugpy_path = helpers.absolutePath();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we got a path to debugpy, try to load it
|
||||
if(!debugpy_path.isEmpty())
|
||||
{
|
||||
PyObject *syspath = PyObject_SafeGetAttrString(sysobj, "path");
|
||||
|
||||
if(!syspath)
|
||||
{
|
||||
qCritical() << "couldn't get sys.path";
|
||||
}
|
||||
else
|
||||
{
|
||||
qInfo() << "Importing debugpy from" << debugpy_path;
|
||||
|
||||
PyObject *str = PyUnicode_FromString(debugpy_path.toUtf8().data());
|
||||
|
||||
PyList_Append(syspath, str);
|
||||
Py_DecRef(str);
|
||||
|
||||
m_DebugPy = PyImport_ImportModule("debugpy");
|
||||
|
||||
if(!m_DebugPy)
|
||||
{
|
||||
qCritical() << "Failed to import debugpy";
|
||||
HandleException(NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
PyObject *configure = PyObject_SafeGetAttrString(m_DebugPy, "configure");
|
||||
|
||||
// don't let debugpy create a subprocess, for obvious reasons
|
||||
if(configure)
|
||||
{
|
||||
PyObject *props = PyDict_New();
|
||||
PyDict_SetItemString(props, "subProcess", Py_False);
|
||||
PyObject *ret = PyObject_CallFunction(configure, "O", props);
|
||||
|
||||
if(!ret)
|
||||
{
|
||||
qCritical() << "Failed calling debugpy.configure";
|
||||
HandleException(NULL);
|
||||
Py_XDECREF(m_DebugPy);
|
||||
m_DebugPy = NULL;
|
||||
}
|
||||
|
||||
Py_XDECREF(ret);
|
||||
Py_XDECREF(props);
|
||||
}
|
||||
else
|
||||
{
|
||||
qCritical() << "Couldn't find debugpy.configure";
|
||||
}
|
||||
|
||||
Py_XDECREF(configure);
|
||||
|
||||
if(m_DebugPy)
|
||||
{
|
||||
PyObject *listen = PyObject_SafeGetAttrString(m_DebugPy, "listen");
|
||||
|
||||
if(listen)
|
||||
{
|
||||
// listen on default port
|
||||
PyObject *args = PyTuple_Pack(1, PyLong_FromLong(5678));
|
||||
PyObject *kwargs = PyDict_New();
|
||||
PyDict_SetItemString(kwargs, "in_process_debug_adapter", Py_True);
|
||||
|
||||
PyObject *ret = PyObject_Call(listen, args, kwargs);
|
||||
|
||||
if(!ret)
|
||||
{
|
||||
qCritical() << "Failed calling debugpy.listen";
|
||||
HandleException(NULL);
|
||||
Py_XDECREF(m_DebugPy);
|
||||
m_DebugPy = NULL;
|
||||
}
|
||||
|
||||
Py_XDECREF(ret);
|
||||
|
||||
Py_XDECREF(args);
|
||||
Py_XDECREF(kwargs);
|
||||
}
|
||||
else
|
||||
{
|
||||
qCritical() << "Couldn't find debugpy.listen";
|
||||
}
|
||||
|
||||
Py_XDECREF(listen);
|
||||
}
|
||||
}
|
||||
|
||||
// remove the search path from sys.path now
|
||||
Py_XDECREF(PyObject_CallMethod(syspath, "pop", NULL));
|
||||
Py_DecRef(syspath);
|
||||
|
||||
AddDebuggableThread();
|
||||
|
||||
if(m_DebugPy)
|
||||
{
|
||||
m_CallWrapper =
|
||||
Py_CompileString("debugpy.trace_this_thread(True)", "__callwrapper.py", Py_eval_input);
|
||||
m_CallWrapperGlobals = PyDict_Copy(main_dict);
|
||||
PyDict_SetItemString(m_CallWrapperGlobals, "debugpy", m_DebugPy);
|
||||
|
||||
// don't pollute the globals
|
||||
PyObject *tmpGlobals = PyDict_Copy(main_dict);
|
||||
|
||||
// monkey patch to get around a bug in debugpy/pydevd: https://github.com/microsoft/debugpy/issues/2011
|
||||
PyObject *ret = PyRun_String(R"(
|
||||
try:
|
||||
monkey_class = sys.modules['_pydevd_bundle'].pydevd_process_net_command_json.PyDevJsonCommandProcessor
|
||||
orig = monkey_class.on_continue_request
|
||||
|
||||
def on_continue_request_hack(self, py_db, request):
|
||||
request.arguments.threadId = '*'
|
||||
orig(self, py_db, request)
|
||||
|
||||
monkey_class.on_continue_request = on_continue_request_hack
|
||||
|
||||
# while we're here, disable termination. No clean way to do this
|
||||
# otherwise
|
||||
def _request_terminate_process_hack(self, py_db):
|
||||
self.api.request_resume_thread('*')
|
||||
|
||||
monkey_class._request_terminate_process = _request_terminate_process_hack
|
||||
|
||||
print("Monkey-patched debugpy successfully")
|
||||
except:
|
||||
print("Failed to monkey-patch debugpy")
|
||||
pass
|
||||
)",
|
||||
Py_file_input, tmpGlobals, NULL);
|
||||
|
||||
Py_XDECREF(ret);
|
||||
Py_XDECREF(tmpGlobals);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// if we need to append to sys.path to locate PySide2, do that now
|
||||
#if defined(PYSIDE2_SYS_PATH)
|
||||
{
|
||||
@@ -822,6 +1078,8 @@ void PythonContext::ProcessExtensionWork(std::function<void()> callback)
|
||||
{
|
||||
PyGILState_STATE gil = PyGILState_Ensure();
|
||||
|
||||
PrepareDebugTracing();
|
||||
|
||||
callback();
|
||||
|
||||
PyGILState_Release(gil);
|
||||
@@ -960,6 +1218,8 @@ QString PythonContext::LoadExtension(ICaptureContext &ctx, const rdcstr &extensi
|
||||
|
||||
if(ext)
|
||||
{
|
||||
PrepareDebugTracing();
|
||||
|
||||
// if import succeeded, call register()
|
||||
PyObject *register_func = PyObject_SafeGetAttrString(ext, "register");
|
||||
|
||||
@@ -1132,13 +1392,38 @@ void PythonContext::executeString(const QString &filename, const QString &source
|
||||
return;
|
||||
}
|
||||
|
||||
location.file = filename;
|
||||
QString tempFilename = filename;
|
||||
|
||||
if(filename.isEmpty())
|
||||
{
|
||||
tempFilename = lit("<interactive.py>");
|
||||
}
|
||||
else if(!QFile::exists(filename))
|
||||
{
|
||||
for(QString path : QStandardPaths::standardLocations(QStandardPaths::AppDataLocation))
|
||||
{
|
||||
QDir tmpDir(path);
|
||||
tmpDir.mkpath(lit("pytmp"));
|
||||
tmpDir.cd(lit("pytmp"));
|
||||
if(tmpDir.exists())
|
||||
{
|
||||
tempFilename = tmpDir.absoluteFilePath(filename);
|
||||
QFile f(tempFilename);
|
||||
f.open(QFile::Truncate | QFile::WriteOnly);
|
||||
f.write(source.toUtf8().data());
|
||||
f.close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
location.file = tempFilename;
|
||||
location.line = 1;
|
||||
|
||||
PyGILState_STATE gil = PyGILState_Ensure();
|
||||
|
||||
PyObject *compiled =
|
||||
Py_CompileString(source.toUtf8().data(), filename.toUtf8().data(),
|
||||
Py_CompileString(source.toUtf8().data(), tempFilename.toUtf8().data(),
|
||||
source.count(QLatin1Char('\n')) == 0 ? Py_single_input : Py_file_input);
|
||||
|
||||
bool caughtException = false;
|
||||
@@ -1149,6 +1434,8 @@ void PythonContext::executeString(const QString &filename, const QString &source
|
||||
|
||||
if(compiled)
|
||||
{
|
||||
PrepareDebugTracing();
|
||||
|
||||
PyObject *sys = PyImport_ImportModule("sys");
|
||||
PyObject *settrace = NULL;
|
||||
PyObject *gettrace = NULL;
|
||||
@@ -1214,7 +1501,7 @@ void PythonContext::executeString(const QString &filename, const QString &source
|
||||
|
||||
void PythonContext::executeString(const QString &source)
|
||||
{
|
||||
executeString(lit("<interactive.py>"), source);
|
||||
executeString(QString(), source);
|
||||
}
|
||||
|
||||
void PythonContext::executeFile(const QString &filename)
|
||||
@@ -1386,6 +1673,105 @@ QStringList PythonContext::completionOptions(QString base)
|
||||
return ret;
|
||||
}
|
||||
|
||||
void PythonContext::AddDebuggableThread()
|
||||
{
|
||||
if(!m_DebugPy)
|
||||
return;
|
||||
|
||||
PyGILState_STATE gil = PyGILState_Ensure();
|
||||
|
||||
PyObject *debug_this_thread = PyObject_SafeGetAttrString(m_DebugPy, "debug_this_thread");
|
||||
|
||||
if(debug_this_thread)
|
||||
{
|
||||
PyObject *ret = PyObject_CallNoArgs(debug_this_thread);
|
||||
|
||||
if(!ret)
|
||||
qCritical() << "Failed calling debugpy.debug_this_thread";
|
||||
|
||||
Py_XDECREF(ret);
|
||||
}
|
||||
else
|
||||
{
|
||||
qCritical() << "Couldn't find debugpy.debug_this_thread";
|
||||
}
|
||||
|
||||
Py_XDECREF(debug_this_thread);
|
||||
|
||||
QString threadName = QThread::currentThread()->objectName();
|
||||
|
||||
if(!threadName.isEmpty())
|
||||
{
|
||||
PyObject *sys = PyImport_ImportModule("sys");
|
||||
Q_ASSERT(sys);
|
||||
PyObject *modules = PyObject_SafeGetAttrString(sys, "modules");
|
||||
Q_ASSERT(modules);
|
||||
|
||||
PyObject *threading = PyDict_GetItemString(modules, "threading");
|
||||
|
||||
// expect to load threading
|
||||
if(threading)
|
||||
{
|
||||
PyObject *current_thread = PyObject_SafeGetAttrString(threading, "current_thread");
|
||||
|
||||
Q_ASSERT(current_thread);
|
||||
|
||||
PyObject *cur = PyObject_CallNoArgs(current_thread);
|
||||
|
||||
if(cur)
|
||||
{
|
||||
if(PyObject_HasAttrString(cur, "name"))
|
||||
PyObject_SetAttrString(cur, "name", PyUnicode_FromString(threadName.toUtf8().data()));
|
||||
|
||||
Py_XDECREF(cur);
|
||||
}
|
||||
|
||||
Py_XDECREF(current_thread);
|
||||
}
|
||||
|
||||
Py_XDECREF(modules);
|
||||
Py_XDECREF(sys);
|
||||
}
|
||||
|
||||
PyGILState_Release(gil);
|
||||
}
|
||||
|
||||
void PythonContext::RemoveDebuggableThread()
|
||||
{
|
||||
if(!m_DebugPy)
|
||||
return;
|
||||
|
||||
// 3.13 fixes this to track the lifetime of dummy/external threads, before that we do it manually
|
||||
#if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION < 13
|
||||
PyGILState_STATE gil = PyGILState_Ensure();
|
||||
|
||||
PyObject *sys = PyImport_ImportModule("sys");
|
||||
Q_ASSERT(sys);
|
||||
PyObject *modules = PyObject_SafeGetAttrString(sys, "modules");
|
||||
Q_ASSERT(modules);
|
||||
|
||||
PyObject *threading = PyDict_GetItemString(modules, "threading");
|
||||
|
||||
// expect to load threading
|
||||
if(threading)
|
||||
{
|
||||
PyObject *_active = PyObject_SafeGetAttrString(threading, "_active");
|
||||
|
||||
if(_active)
|
||||
{
|
||||
PyObject *key = PyLong_FromInt32(PyThread_get_thread_ident());
|
||||
if(PyDict_Contains(_active, key) == 1)
|
||||
PyDict_DelItem(_active, key);
|
||||
Py_XDECREF(key);
|
||||
}
|
||||
|
||||
Py_XDECREF(_active);
|
||||
}
|
||||
|
||||
PyGILState_Release(gil);
|
||||
#endif
|
||||
}
|
||||
|
||||
PyObject *PythonContext::QtObjectToPython(const char *typeName, QObject *object)
|
||||
{
|
||||
#if PYSIDE2_ENABLED
|
||||
@@ -1726,7 +2112,7 @@ extern "C" void HandleException(PyObject *global_handle)
|
||||
{
|
||||
emit redirector->context->exception(typeStr, valueStr, finalLine, frames);
|
||||
}
|
||||
else if(redirector && !redirector->context)
|
||||
else
|
||||
{
|
||||
// if still NULL we're running in the extension context
|
||||
rdcstr exString;
|
||||
@@ -1786,6 +2172,14 @@ extern "C" void QueueDecRef(PyObject *obj)
|
||||
decrefQueue.push_back(obj);
|
||||
}
|
||||
|
||||
extern "C" PyObject *DoFunctionCall(PyObject *object, PyObject *args)
|
||||
{
|
||||
if(!PyCFunction_Check(object))
|
||||
PythonContext::PrepareDebugTracing();
|
||||
|
||||
return PyObject_Call(object, args, NULL);
|
||||
}
|
||||
|
||||
extern "C" void ProcessDecRefQueue()
|
||||
{
|
||||
QMutexLocker lock(&decrefQueueMutex);
|
||||
|
||||
@@ -67,6 +67,8 @@ public:
|
||||
|
||||
static void GenerateStubs(const rdcarray<rdcstr> &extraPaths);
|
||||
|
||||
static void PrepareDebugTracing();
|
||||
|
||||
bool CheckInterfaces(rdcstr &log);
|
||||
|
||||
QString versionString();
|
||||
@@ -104,6 +106,8 @@ public:
|
||||
bool shouldAbort() { return m_Abort; }
|
||||
QString currentFile() { return location.file; }
|
||||
int currentLine() { return location.line; }
|
||||
static void AddDebuggableThread();
|
||||
static void RemoveDebuggableThread();
|
||||
signals:
|
||||
void traceLine(const QString &file, int line);
|
||||
void exception(const QString &type, const QString &value, int finalLine, QList<QString> frames);
|
||||
@@ -121,6 +125,13 @@ private:
|
||||
// python context
|
||||
static PyObject *main_dict;
|
||||
|
||||
// this is the debugpy module
|
||||
static PyObject *m_DebugPy;
|
||||
// these are used for callbacks where we have no python frame (e.g. C code on the UI calling into
|
||||
// a registered python callback) so need to quickly set up things for debugging if enabled
|
||||
static PyObject *m_CallWrapper;
|
||||
static PyObject *m_CallWrapperGlobals;
|
||||
|
||||
// the list of extension objects, to be able to reload them
|
||||
static QMap<rdcstr, PyObject *> extensions;
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ extern "C" bool IsThreadBlocking(PyObject *global_handle);
|
||||
extern "C" void SetThreadBlocking(PyObject *global_handle, bool block);
|
||||
extern "C" void QueueDecRef(PyObject *obj);
|
||||
extern "C" void ProcessDecRefQueue();
|
||||
extern "C" PyObject *DoFunctionCall(PyObject *object, PyObject *args);
|
||||
|
||||
struct ExceptionData
|
||||
{
|
||||
@@ -246,7 +247,7 @@ struct varfunc
|
||||
|
||||
ProcessDecRefQueue();
|
||||
|
||||
PyObject *result = PyObject_Call(func, args, 0);
|
||||
PyObject *result = DoFunctionCall(func, args);
|
||||
|
||||
Py_DECREF(args);
|
||||
|
||||
|
||||
@@ -72,6 +72,11 @@ extern "C" void ProcessDecRefQueue()
|
||||
{
|
||||
}
|
||||
|
||||
extern "C" PyObject *DoFunctionCall(PyObject *object, PyObject *args)
|
||||
{
|
||||
return PyObject_Call(object, args, NULL);
|
||||
}
|
||||
|
||||
extern "C" RENDERDOC_EXPORT_API int _rd_python_minor_version()
|
||||
{
|
||||
return PY_MINOR_VERSION;
|
||||
|
||||
@@ -307,6 +307,10 @@ SettingsDialog::SettingsDialog(ICaptureContext &ctx, QWidget *parent)
|
||||
ui->EventBrowser_ApplyColors->setChecked(m_Ctx.Config().EventBrowser_ApplyColors);
|
||||
ui->EventBrowser_ColorEventRow->setChecked(m_Ctx.Config().EventBrowser_ColorEventRow);
|
||||
|
||||
ui->Python_DebugEnabled->setChecked(m_Ctx.Config().Python_DebugEnabled);
|
||||
|
||||
ui->Python_DebugPyDir->setText(m_Ctx.Config().Python_DebugPyDir);
|
||||
|
||||
ui->Comments_ShowOnLoad->setChecked(m_Ctx.Config().Comments_ShowOnLoad);
|
||||
|
||||
ui->Formatter_MinFigures->setValue(m_Ctx.Config().Formatter_MinFigures);
|
||||
@@ -845,6 +849,35 @@ void SettingsDialog::on_Python_StubPaths_clicked()
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsDialog::on_Python_DebugPyDirBrowse_clicked()
|
||||
{
|
||||
QString dir = RDDialog::getExistingDirectory(this, tr("Choose location of debugpy module"),
|
||||
m_Ctx.Config().Python_DebugPyDir);
|
||||
|
||||
if(!dir.isEmpty() && QDir(dir).exists() && QDir(dir).exists(lit("__init__.py")))
|
||||
{
|
||||
m_Ctx.Config().Python_DebugPyDir = dir;
|
||||
ui->Python_DebugPyDir->setText(dir);
|
||||
}
|
||||
|
||||
m_Ctx.Config().Save();
|
||||
}
|
||||
|
||||
void SettingsDialog::on_Python_DebugPyDir_textEdited(const QString &dir)
|
||||
{
|
||||
if((QDir(dir).exists() && QDir(dir).exists(lit("__init__.py"))) || dir.isEmpty())
|
||||
m_Ctx.Config().Python_DebugPyDir = dir;
|
||||
|
||||
m_Ctx.Config().Save();
|
||||
}
|
||||
|
||||
void SettingsDialog::on_Python_DebugEnabled_toggled(bool checked)
|
||||
{
|
||||
m_Ctx.Config().Python_DebugEnabled = ui->Python_DebugEnabled->isChecked();
|
||||
|
||||
m_Ctx.Config().Save();
|
||||
}
|
||||
|
||||
// texture viewer
|
||||
void SettingsDialog::on_TextureViewer_PerTexSettings_toggled(bool checked)
|
||||
{
|
||||
|
||||
@@ -83,6 +83,9 @@ private slots:
|
||||
|
||||
// python
|
||||
void on_Python_StubPaths_clicked();
|
||||
void on_Python_DebugPyDirBrowse_clicked();
|
||||
void on_Python_DebugPyDir_textEdited(const QString &dir);
|
||||
void on_Python_DebugEnabled_toggled(bool checked);
|
||||
|
||||
// texture viewer
|
||||
void on_TextureViewer_PerTexSettings_toggled(bool checked);
|
||||
|
||||
@@ -720,7 +720,38 @@ After interop is enabled you will need to reload any capture.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="Python_DebugEnabledLabel">
|
||||
<property name="text">
|
||||
<string>Enable python debugging (restart required)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="Python_DebugEnabled">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QLabel" name="Python_DebugPyDirLabel">
|
||||
<property name="text">
|
||||
<string>Location of custom debugpy module for debugging</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLineEdit" name="Python_DebugPyDir"/>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QPushButton" name="Python_DebugPyDirBrowse">
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<spacer name="verticalSpacer_9">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
|
||||
@@ -1074,6 +1074,8 @@ void PythonShell::RunScript()
|
||||
enableButtons(false);
|
||||
|
||||
LambdaThread *thread = new LambdaThread([this, script, context]() {
|
||||
PythonContext::AddDebuggableThread();
|
||||
|
||||
scriptContext = context;
|
||||
context->executeString(lit("script.py"), script);
|
||||
scriptContext = NULL;
|
||||
@@ -1082,6 +1084,8 @@ void PythonShell::RunScript()
|
||||
context->Finish();
|
||||
enableButtons(true);
|
||||
});
|
||||
|
||||
PythonContext::RemoveDebuggableThread();
|
||||
});
|
||||
|
||||
thread->setName(lit("Python script"));
|
||||
|
||||
@@ -1843,6 +1843,21 @@
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<Visible>false</Visible>
|
||||
</Content>
|
||||
<Content Include="$(PythonDLLsDir)\pyexpat.pyd" Condition="Exists('$(PythonDLLsDir)\pyexpat.pyd')">
|
||||
<Link>pyexpat.pyd</Link>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<Visible>false</Visible>
|
||||
</Content>
|
||||
<Content Include="$(PythonDLLsDir)\_socket.pyd" Condition="Exists('$(PythonDLLsDir)\_socket.pyd')">
|
||||
<Link>_socket.pyd</Link>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<Visible>false</Visible>
|
||||
</Content>
|
||||
<Content Include="$(PythonDLLsDir)\_queue.pyd" Condition="Exists('$(PythonDLLsDir)\_queue.pyd')">
|
||||
<Link>_queue.pyd</Link>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<Visible>false</Visible>
|
||||
</Content>
|
||||
<Content Include="$(PythonDLLsDir)\unicodedata.pyd" Condition="Exists('$(PythonDLLsDir)\unicodedata.pyd')">
|
||||
<Link>unicodedata.pyd</Link>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
|
||||
Reference in New Issue
Block a user