|
45 | 45 | #include <QCoreApplication> |
46 | 46 | #include <cstddef> |
47 | 47 | #include <functional> |
| 48 | +#include <iostream> |
48 | 49 | #include <map> |
49 | 50 | #include <memory> |
50 | 51 | #include <string> |
|
55 | 56 |
|
56 | 57 | namespace gui { |
57 | 58 |
|
58 | | -// https://stackoverflow.com/questions/4307187/how-to-catch-python-stdout-in-c-code/8335297#8335297 |
59 | | -namespace emb { |
60 | | - |
61 | 59 | using stdout_write_type = std::function<void(std::string)>; |
62 | 60 |
|
63 | | -struct Stdout |
| 61 | +struct StreamCatcher |
64 | 62 | { |
65 | | - PyObject_HEAD stdout_write_type write; |
| 63 | + PyObject_HEAD; |
| 64 | + stdout_write_type write; |
| 65 | + std::string buffer; |
| 66 | + bool is_stderr; // distinguish stdout vs stderr |
66 | 67 | }; |
67 | 68 |
|
68 | | -static PyObject* Stdout_write(PyObject* self, PyObject* args) |
| 69 | +// method implementation |
| 70 | +static PyObject* StreamCatcher_write(PyObject* self, PyObject* args) |
69 | 71 | { |
70 | | - std::size_t written(0); |
71 | | - Stdout* selfimpl = reinterpret_cast<Stdout*>(self); |
72 | | - if (selfimpl->write) { |
73 | | - char* data; |
74 | | - if (!PyArg_ParseTuple(args, "s", &data)) { |
75 | | - return nullptr; |
76 | | - } |
| 72 | + const char* text = nullptr; |
| 73 | + if (!PyArg_ParseTuple(args, "s", &text)) { |
| 74 | + Py_RETURN_NONE; |
| 75 | + } |
77 | 76 |
|
78 | | - std::string str(data); |
79 | | - selfimpl->write(str); |
80 | | - written = str.size(); |
| 77 | + if (text[0] == '\0') { |
| 78 | + Py_RETURN_NONE; |
81 | 79 | } |
82 | | - return PyLong_FromSize_t(written); |
83 | | -} |
84 | 80 |
|
85 | | -static PyObject* Stdout_flush(PyObject* self, PyObject* args) |
86 | | -{ |
87 | | - // no-op |
88 | | - return Py_BuildValue(""); |
89 | | -} |
| 81 | + auto* catcher = reinterpret_cast<StreamCatcher*>(self); |
| 82 | + catcher->buffer += text; |
90 | 83 |
|
91 | | -static PyMethodDef Stdout_methods[] = { |
92 | | - {"write", Stdout_write, METH_VARARGS, "sys.stdout.write"}, |
93 | | - {"flush", Stdout_flush, METH_VARARGS, "sys.stdout.flush"}, |
94 | | - {nullptr, nullptr, 0, nullptr} // sentinel |
95 | | -}; |
96 | | - |
97 | | -static PyTypeObject StdoutType = { |
98 | | - PyVarObject_HEAD_INIT(0, 0) "emb.StdoutType", /* tp_name */ |
99 | | - sizeof(Stdout), /* tp_basicsize */ |
100 | | - 0, /* tp_itemsize */ |
101 | | - nullptr, /* tp_dealloc */ |
102 | | - 0, /* tp_print */ |
103 | | - nullptr, /* tp_getattr */ |
104 | | - nullptr, /* tp_setattr */ |
105 | | - nullptr, /* tp_reserved */ |
106 | | - nullptr, /* tp_repr */ |
107 | | - nullptr, /* tp_as_number */ |
108 | | - nullptr, /* tp_as_sequence */ |
109 | | - nullptr, /* tp_as_mapping */ |
110 | | - nullptr, /* tp_hash */ |
111 | | - nullptr, /* tp_call */ |
112 | | - nullptr, /* tp_str */ |
113 | | - nullptr, /* tp_getattro */ |
114 | | - nullptr, /* tp_setattro */ |
115 | | - nullptr, /* tp_as_buffer */ |
116 | | - Py_TPFLAGS_DEFAULT, /* tp_flags */ |
117 | | - "emb.Stdout objects", /* tp_doc */ |
118 | | - nullptr, /* tp_traverse */ |
119 | | - nullptr, /* tp_clear */ |
120 | | - nullptr, /* tp_richcompare */ |
121 | | - 0, /* tp_weaklistoffset */ |
122 | | - nullptr, /* tp_iter */ |
123 | | - nullptr, /* tp_iternext */ |
124 | | - Stdout_methods, /* tp_methods */ |
125 | | - nullptr, /* tp_members */ |
126 | | - nullptr, /* tp_getset */ |
127 | | - nullptr, /* tp_base */ |
128 | | - nullptr, /* tp_dict */ |
129 | | - nullptr, /* tp_descr_get */ |
130 | | - nullptr, /* tp_descr_set */ |
131 | | - 0, /* tp_dictoffset */ |
132 | | - nullptr, /* tp_init */ |
133 | | - nullptr, /* tp_alloc */ |
134 | | - nullptr, /* tp_new */ |
135 | | -}; |
| 84 | + if (catcher->buffer.back() == '\n') { |
| 85 | + const size_t end = catcher->buffer.find_last_not_of("\r\n"); |
| 86 | + catcher->buffer.erase(end + 1); |
| 87 | + } else { |
| 88 | + Py_RETURN_NONE; |
| 89 | + } |
136 | 90 |
|
137 | | -static PyModuleDef embmodule = { |
138 | | - PyModuleDef_HEAD_INIT, |
139 | | - "emb", |
140 | | - nullptr, |
141 | | - -1, |
142 | | - nullptr, |
143 | | -}; |
| 91 | + catcher->write(catcher->buffer); |
| 92 | + catcher->buffer.clear(); |
144 | 93 |
|
145 | | -// Internal state |
146 | | -struct StreamPair |
147 | | -{ |
148 | | - PyObject* saved = nullptr; |
149 | | - PyObject* current = nullptr; |
150 | | -}; |
151 | | -static std::map<std::string, StreamPair> streams; |
| 94 | + Py_RETURN_NONE; |
| 95 | +} |
152 | 96 |
|
153 | | -PyMODINIT_FUNC PyInit_emb(void) |
| 97 | +static PyMethodDef StreamCatcher_methods[] |
| 98 | + = {{"write", (PyCFunction) StreamCatcher_write, METH_VARARGS, "Write text"}, |
| 99 | + {nullptr, nullptr, 0, nullptr}}; |
| 100 | + |
| 101 | +static PyType_Slot StreamCatcher_slots[] |
| 102 | + = {{Py_tp_methods, StreamCatcher_methods}, |
| 103 | + {Py_tp_doc, (void*) "C++ StreamCatcher for stdout/stderr"}, |
| 104 | + {0, nullptr}}; |
| 105 | + |
| 106 | +static PyType_Spec StreamCatcher_spec = {"embed.StreamCatcher", |
| 107 | + sizeof(StreamCatcher), |
| 108 | + 0, |
| 109 | + Py_TPFLAGS_DEFAULT, |
| 110 | + StreamCatcher_slots}; |
| 111 | + |
| 112 | +// Keep global references so they can be restored |
| 113 | +static PyObject* original_stdout = nullptr; |
| 114 | +static PyObject* original_stderr = nullptr; |
| 115 | +static StreamCatcher* stdout_catcher = nullptr; |
| 116 | +static StreamCatcher* stderr_catcher = nullptr; |
| 117 | + |
| 118 | +static StreamCatcher* create_catcher(const bool is_stderr, |
| 119 | + stdout_write_type write) |
154 | 120 | { |
155 | | - StdoutType.tp_new = PyType_GenericNew; |
156 | | - if (PyType_Ready(&StdoutType) < 0) { |
| 121 | + PyObject* type = PyType_FromSpec(&StreamCatcher_spec); |
| 122 | + if (!type) { |
157 | 123 | return nullptr; |
158 | 124 | } |
159 | 125 |
|
160 | | - PyObject* m = PyModule_Create(&embmodule); |
161 | | - if (m) { |
162 | | - Py_INCREF(&StdoutType); |
163 | | - PyModule_AddObject(m, "Stdout", reinterpret_cast<PyObject*>(&StdoutType)); |
| 126 | + PyObject* obj = PyObject_CallObject(type, nullptr); |
| 127 | + Py_DECREF(type); |
| 128 | + if (!obj) { |
| 129 | + return nullptr; |
164 | 130 | } |
165 | | - return m; |
| 131 | + |
| 132 | + auto* catcher = reinterpret_cast<StreamCatcher*>(obj); |
| 133 | + catcher->is_stderr = is_stderr; |
| 134 | + catcher->write = std::move(write); |
| 135 | + return catcher; |
166 | 136 | } |
167 | 137 |
|
168 | | -static void set_stream(const std::string& stream, stdout_write_type write) |
| 138 | +// Redirect sys.stdout and sys.stderr |
| 139 | +bool redirect_python_output(stdout_write_type stdout_write, |
| 140 | + stdout_write_type stderr_write) |
169 | 141 | { |
170 | | - auto& stream_pair = streams[stream]; |
171 | | - if (stream_pair.current == nullptr) { |
172 | | - stream_pair.saved = PySys_GetObject(stream.c_str()); |
173 | | - stream_pair.current = StdoutType.tp_new(&StdoutType, nullptr, nullptr); |
| 142 | + PyObject* sysmod = PyImport_ImportModule("sys"); |
| 143 | + if (!sysmod) { |
| 144 | + return false; |
| 145 | + } |
| 146 | + |
| 147 | + // Save original streams |
| 148 | + original_stdout = PyObject_GetAttrString(sysmod, "stdout"); |
| 149 | + original_stderr = PyObject_GetAttrString(sysmod, "stderr"); |
| 150 | + |
| 151 | + // Create new catchers |
| 152 | + stdout_catcher = create_catcher(false, stdout_write); |
| 153 | + stderr_catcher = create_catcher(true, stderr_write); |
| 154 | + |
| 155 | + if (!stdout_catcher || !stderr_catcher) { |
| 156 | + Py_DECREF(sysmod); |
| 157 | + PyErr_Print(); |
| 158 | + return false; |
174 | 159 | } |
175 | 160 |
|
176 | | - Stdout* impl = reinterpret_cast<Stdout*>(stream_pair.current); |
177 | | - impl->write = std::move(write); |
178 | | - PySys_SetObject(stream.c_str(), stream_pair.current); |
| 161 | + // Replace Python's sys.stdout/stderr |
| 162 | + PyObject_SetAttrString( |
| 163 | + sysmod, "stdout", reinterpret_cast<PyObject*>(stdout_catcher)); |
| 164 | + PyObject_SetAttrString( |
| 165 | + sysmod, "stderr", reinterpret_cast<PyObject*>(stderr_catcher)); |
| 166 | + |
| 167 | + Py_DECREF(sysmod); |
| 168 | + return true; |
179 | 169 | } |
180 | 170 |
|
181 | | -static void reset_streams() |
| 171 | +// Restore original sys.stdout/stderr |
| 172 | +void restore_python_output() |
182 | 173 | { |
183 | | - for (auto& [stream, stream_pair] : streams) { |
184 | | - if (stream_pair.saved != nullptr) { |
185 | | - PySys_SetObject(stream.c_str(), stream_pair.saved); |
186 | | - } |
187 | | - Py_XDECREF(stream_pair.current); |
188 | | - stream_pair.current = nullptr; |
| 174 | + PyObject* sysmod = PyImport_ImportModule("sys"); |
| 175 | + if (!sysmod) { |
| 176 | + return; |
189 | 177 | } |
190 | | -} |
191 | 178 |
|
192 | | -} // namespace emb |
| 179 | + if (original_stdout) { |
| 180 | + PyObject_SetAttrString(sysmod, "stdout", original_stdout); |
| 181 | + Py_DECREF(original_stdout); |
| 182 | + original_stdout = nullptr; |
| 183 | + } |
| 184 | + if (original_stderr) { |
| 185 | + PyObject_SetAttrString(sysmod, "stderr", original_stderr); |
| 186 | + Py_DECREF(original_stderr); |
| 187 | + original_stderr = nullptr; |
| 188 | + } |
| 189 | + |
| 190 | + Py_XDECREF(stdout_catcher); |
| 191 | + Py_XDECREF(stderr_catcher); |
| 192 | + stdout_catcher = nullptr; |
| 193 | + stderr_catcher = nullptr; |
| 194 | + |
| 195 | + Py_DECREF(sysmod); |
| 196 | +} |
193 | 197 |
|
194 | 198 | static PythonCmdInputWidget* widget = nullptr; |
195 | 199 |
|
@@ -220,7 +224,6 @@ PythonCmdInputWidget::PythonCmdInputWidget(QWidget* parent) |
220 | 224 |
|
221 | 225 | PythonCmdInputWidget::~PythonCmdInputWidget() |
222 | 226 | { |
223 | | - emb::reset_streams(); |
224 | 227 | widget = nullptr; |
225 | 228 | } |
226 | 229 |
|
@@ -310,47 +313,30 @@ void PythonCmdInputWidget::exitHandler() |
310 | 313 |
|
311 | 314 | void PythonCmdInputWidget::init() |
312 | 315 | { |
313 | | - PyImport_AppendInittab("emb", emb::PyInit_emb); |
314 | 316 | ord::pyAppInit(false); |
315 | 317 |
|
316 | | - PyImport_ImportModule("emb"); |
317 | | - |
318 | | - Py_AtExit(&exitHandler); |
| 318 | + auto* openroad = ord::OpenRoad::openRoad(); |
| 319 | + if (openroad == nullptr) { |
| 320 | + return; |
| 321 | + } |
| 322 | + auto* logger = openroad->getLogger(); |
| 323 | + if (logger == nullptr) { |
| 324 | + return; |
| 325 | + } |
319 | 326 |
|
320 | | - emb::stdout_write_type stdout_stream = [](std::string s) { |
321 | | - auto* openroad = ord::OpenRoad::openRoad(); |
322 | | - if (openroad == nullptr) { |
323 | | - return; |
324 | | - } |
325 | | - auto* logger = openroad->getLogger(); |
326 | | - if (logger == nullptr) { |
327 | | - return; |
328 | | - } |
329 | | - s.erase(s.find_last_not_of("\r\n") + 1); |
330 | | - s.erase(s.find_last_not_of('\n') + 1); |
331 | | - if (s.empty()) { |
332 | | - return; |
333 | | - } |
334 | | - logger->report("{}", s); |
335 | | - }; |
336 | | - emb::set_stream("stdout", stdout_stream); |
337 | | - emb::stdout_write_type stderr_stream = [this](std::string s) { |
338 | | - auto* openroad = ord::OpenRoad::openRoad(); |
339 | | - if (openroad == nullptr) { |
340 | | - return; |
341 | | - } |
342 | | - auto* logger = openroad->getLogger(); |
343 | | - if (logger == nullptr) { |
344 | | - return; |
345 | | - } |
346 | | - s.erase(s.find_last_not_of("\r\n") + 1); |
347 | | - s.erase(s.find_last_not_of('\n') + 1); |
348 | | - if (s.empty()) { |
349 | | - return; |
350 | | - } |
| 327 | + stdout_write_type stdout_write |
| 328 | + = [logger](std::string s) { logger->report("{}", s); }; |
| 329 | + stdout_write_type stderr_write = [this](std::string s) { |
351 | 330 | emit addResultToOutput(QString::fromStdString(s), false); |
352 | 331 | }; |
353 | | - emb::set_stream("stderr", stderr_stream); |
| 332 | + |
| 333 | + if (!redirect_python_output(stdout_write, stderr_write)) { |
| 334 | + std::cerr << "Failed to redirect Python output" << std::endl; |
| 335 | + Py_Finalize(); |
| 336 | + return; |
| 337 | + } |
| 338 | + |
| 339 | + Py_AtExit(&exitHandler); |
354 | 340 | } |
355 | 341 |
|
356 | 342 | } // namespace gui |
|
0 commit comments