1 /* TUI windows implemented in Python
2
3 Copyright (C) 2020 Free Software Foundation, Inc.
4
5 This file is part of GDB.
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 3 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>. */
19
20
21 #include "defs.h"
22 #include "arch-utils.h"
23 #include "python-internal.h"
24
25 #ifdef TUI
26
27 /* Note that Python's public headers may define HAVE_NCURSES_H, so if
28 we unconditionally include this (outside the #ifdef above), then we
29 can get a compile error when ncurses is not in fact installed. See
30 PR tui/25597; or the upstream Python bug
31 https://bugs.python.org/issue20768. */
32 #include "gdb_curses.h"
33
34 #include "tui/tui-data.h"
35 #include "tui/tui-io.h"
36 #include "tui/tui-layout.h"
37 #include "tui/tui-wingeneral.h"
38 #include "tui/tui-winsource.h"
39
40 class tui_py_window;
41
42 /* A PyObject representing a TUI window. */
43
44 struct gdbpy_tui_window
45 {
46 PyObject_HEAD
47
48 /* The TUI window, or nullptr if the window has been deleted. */
49 tui_py_window *window;
50 };
51
52 extern PyTypeObject gdbpy_tui_window_object_type
53 CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("gdbpy_tui_window");
54
55 /* A TUI window written in Python. */
56
57 class tui_py_window : public tui_win_info
58 {
59 public:
60
tui_py_window(const char * name,gdbpy_ref<gdbpy_tui_window> wrapper)61 tui_py_window (const char *name, gdbpy_ref<gdbpy_tui_window> wrapper)
62 : m_name (name),
63 m_wrapper (std::move (wrapper))
64 {
65 m_wrapper->window = this;
66 }
67
68 ~tui_py_window ();
69
70 DISABLE_COPY_AND_ASSIGN (tui_py_window);
71
72 /* Set the "user window" to the indicated reference. The user
73 window is the object returned the by user-defined window
74 constructor. */
set_user_window(gdbpy_ref<> && user_window)75 void set_user_window (gdbpy_ref<> &&user_window)
76 {
77 m_window = std::move (user_window);
78 }
79
name()80 const char *name () const override
81 {
82 return m_name.c_str ();
83 }
84
85 void rerender () override;
86 void do_scroll_vertical (int num_to_scroll) override;
87 void do_scroll_horizontal (int num_to_scroll) override;
88
89 /* Erase and re-box the window. */
erase()90 void erase ()
91 {
92 if (is_visible ())
93 {
94 werase (handle.get ());
95 check_and_display_highlight_if_needed ();
96 cursor_x = 0;
97 cursor_y = 0;
98 }
99 }
100
101 /* Write STR to the window. */
102 void output (const char *str);
103
104 /* A helper function to compute the viewport width. */
viewport_width()105 int viewport_width () const
106 {
107 return std::max (0, width - 2);
108 }
109
110 /* A helper function to compute the viewport height. */
viewport_height()111 int viewport_height () const
112 {
113 return std::max (0, height - 2);
114 }
115
116 private:
117
118 /* Location of the cursor. */
119 int cursor_x = 0;
120 int cursor_y = 0;
121
122 /* The name of this window. */
123 std::string m_name;
124
125 /* The underlying Python window object. */
126 gdbpy_ref<> m_window;
127
128 /* The Python wrapper for this object. */
129 gdbpy_ref<gdbpy_tui_window> m_wrapper;
130 };
131
~tui_py_window()132 tui_py_window::~tui_py_window ()
133 {
134 gdbpy_enter enter_py (get_current_arch (), current_language);
135
136 /* This can be null if the user-provided Python construction
137 function failed. */
138 if (m_window != nullptr
139 && PyObject_HasAttrString (m_window.get (), "close"))
140 {
141 gdbpy_ref<> result (PyObject_CallMethod (m_window.get (), "close",
142 nullptr));
143 if (result == nullptr)
144 gdbpy_print_stack ();
145 }
146
147 /* Unlink. */
148 m_wrapper->window = nullptr;
149 /* Explicitly free the Python references. We have to do this
150 manually because we need to hold the GIL while doing so. */
151 m_wrapper.reset (nullptr);
152 m_window.reset (nullptr);
153 }
154
155 void
rerender()156 tui_py_window::rerender ()
157 {
158 gdbpy_enter enter_py (get_current_arch (), current_language);
159
160 if (PyObject_HasAttrString (m_window.get (), "render"))
161 {
162 gdbpy_ref<> result (PyObject_CallMethod (m_window.get (), "render",
163 nullptr));
164 if (result == nullptr)
165 gdbpy_print_stack ();
166 }
167 }
168
169 void
do_scroll_horizontal(int num_to_scroll)170 tui_py_window::do_scroll_horizontal (int num_to_scroll)
171 {
172 gdbpy_enter enter_py (get_current_arch (), current_language);
173
174 if (PyObject_HasAttrString (m_window.get (), "hscroll"))
175 {
176 gdbpy_ref<> result (PyObject_CallMethod (m_window.get(), "hscroll",
177 "i", num_to_scroll, nullptr));
178 if (result == nullptr)
179 gdbpy_print_stack ();
180 }
181 }
182
183 void
do_scroll_vertical(int num_to_scroll)184 tui_py_window::do_scroll_vertical (int num_to_scroll)
185 {
186 gdbpy_enter enter_py (get_current_arch (), current_language);
187
188 if (PyObject_HasAttrString (m_window.get (), "vscroll"))
189 {
190 gdbpy_ref<> result (PyObject_CallMethod (m_window.get (), "vscroll",
191 "i", num_to_scroll, nullptr));
192 if (result == nullptr)
193 gdbpy_print_stack ();
194 }
195 }
196
197 void
output(const char * text)198 tui_py_window::output (const char *text)
199 {
200 int vwidth = viewport_width ();
201
202 while (cursor_y < viewport_height () && *text != '\0')
203 {
204 wmove (handle.get (), cursor_y + 1, cursor_x + 1);
205
206 std::string line = tui_copy_source_line (&text, 0, 0,
207 vwidth - cursor_x, 0);
208 tui_puts (line.c_str (), handle.get ());
209
210 if (*text == '\n')
211 {
212 ++text;
213 ++cursor_y;
214 cursor_x = 0;
215 }
216 else
217 cursor_x = getcurx (handle.get ()) - 1;
218 }
219
220 wrefresh (handle.get ());
221 }
222
223
224
225 /* A callable that is used to create a TUI window. It wraps the
226 user-supplied window constructor. */
227
228 class gdbpy_tui_window_maker
229 {
230 public:
231
gdbpy_tui_window_maker(gdbpy_ref<> && constr)232 explicit gdbpy_tui_window_maker (gdbpy_ref<> &&constr)
233 : m_constr (std::move (constr))
234 {
235 }
236
237 ~gdbpy_tui_window_maker ();
238
gdbpy_tui_window_maker(gdbpy_tui_window_maker && other)239 gdbpy_tui_window_maker (gdbpy_tui_window_maker &&other) noexcept
240 : m_constr (std::move (other.m_constr))
241 {
242 }
243
gdbpy_tui_window_maker(const gdbpy_tui_window_maker & other)244 gdbpy_tui_window_maker (const gdbpy_tui_window_maker &other)
245 {
246 gdbpy_enter enter_py (get_current_arch (), current_language);
247 m_constr = other.m_constr;
248 }
249
250 gdbpy_tui_window_maker &operator= (gdbpy_tui_window_maker &&other)
251 {
252 m_constr = std::move (other.m_constr);
253 return *this;
254 }
255
256 gdbpy_tui_window_maker &operator= (const gdbpy_tui_window_maker &other)
257 {
258 gdbpy_enter enter_py (get_current_arch (), current_language);
259 m_constr = other.m_constr;
260 return *this;
261 }
262
263 tui_win_info *operator() (const char *name);
264
265 private:
266
267 /* A constructor that is called to make a TUI window. */
268 gdbpy_ref<> m_constr;
269 };
270
~gdbpy_tui_window_maker()271 gdbpy_tui_window_maker::~gdbpy_tui_window_maker ()
272 {
273 gdbpy_enter enter_py (get_current_arch (), current_language);
274 m_constr.reset (nullptr);
275 }
276
277 tui_win_info *
operator()278 gdbpy_tui_window_maker::operator() (const char *win_name)
279 {
280 gdbpy_enter enter_py (get_current_arch (), current_language);
281
282 gdbpy_ref<gdbpy_tui_window> wrapper
283 (PyObject_New (gdbpy_tui_window, &gdbpy_tui_window_object_type));
284 if (wrapper == nullptr)
285 {
286 gdbpy_print_stack ();
287 return nullptr;
288 }
289
290 std::unique_ptr<tui_py_window> window
291 (new tui_py_window (win_name, wrapper));
292
293 gdbpy_ref<> user_window
294 (PyObject_CallFunctionObjArgs (m_constr.get (),
295 (PyObject *) wrapper.get (),
296 nullptr));
297 if (user_window == nullptr)
298 {
299 gdbpy_print_stack ();
300 return nullptr;
301 }
302
303 window->set_user_window (std::move (user_window));
304 /* Window is now owned by the TUI. */
305 return window.release ();
306 }
307
308 /* Implement "gdb.register_window_type". */
309
310 PyObject *
gdbpy_register_tui_window(PyObject * self,PyObject * args,PyObject * kw)311 gdbpy_register_tui_window (PyObject *self, PyObject *args, PyObject *kw)
312 {
313 static const char *keywords[] = { "name", "constructor", nullptr };
314
315 const char *name;
316 PyObject *cons_obj;
317
318 if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "sO", keywords,
319 &name, &cons_obj))
320 return nullptr;
321
322 try
323 {
324 gdbpy_tui_window_maker constr (gdbpy_ref<>::new_reference (cons_obj));
325 tui_register_window (name, constr);
326 }
327 catch (const gdb_exception &except)
328 {
329 gdbpy_convert_exception (except);
330 return nullptr;
331 }
332
333 Py_RETURN_NONE;
334 }
335
336
337
338 /* Require that "Window" be a valid window. */
339
340 #define REQUIRE_WINDOW(Window) \
341 do { \
342 if ((Window)->window == nullptr) \
343 return PyErr_Format (PyExc_RuntimeError, \
344 _("TUI window is invalid.")); \
345 } while (0)
346
347 /* Python function which checks the validity of a TUI window
348 object. */
349 static PyObject *
gdbpy_tui_is_valid(PyObject * self,PyObject * args)350 gdbpy_tui_is_valid (PyObject *self, PyObject *args)
351 {
352 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
353
354 if (win->window != nullptr)
355 Py_RETURN_TRUE;
356 Py_RETURN_FALSE;
357 }
358
359 /* Python function that erases the TUI window. */
360 static PyObject *
gdbpy_tui_erase(PyObject * self,PyObject * args)361 gdbpy_tui_erase (PyObject *self, PyObject *args)
362 {
363 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
364
365 REQUIRE_WINDOW (win);
366
367 win->window->erase ();
368
369 Py_RETURN_NONE;
370 }
371
372 /* Python function that writes some text to a TUI window. */
373 static PyObject *
gdbpy_tui_write(PyObject * self,PyObject * args)374 gdbpy_tui_write (PyObject *self, PyObject *args)
375 {
376 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
377 const char *text;
378
379 if (!PyArg_ParseTuple (args, "s", &text))
380 return nullptr;
381
382 REQUIRE_WINDOW (win);
383
384 win->window->output (text);
385
386 Py_RETURN_NONE;
387 }
388
389 /* Return the width of the TUI window. */
390 static PyObject *
gdbpy_tui_width(PyObject * self,void * closure)391 gdbpy_tui_width (PyObject *self, void *closure)
392 {
393 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
394 REQUIRE_WINDOW (win);
395 return PyLong_FromLong (win->window->viewport_width ());
396 }
397
398 /* Return the height of the TUI window. */
399 static PyObject *
gdbpy_tui_height(PyObject * self,void * closure)400 gdbpy_tui_height (PyObject *self, void *closure)
401 {
402 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
403 REQUIRE_WINDOW (win);
404 return PyLong_FromLong (win->window->viewport_height ());
405 }
406
407 /* Return the title of the TUI window. */
408 static PyObject *
gdbpy_tui_title(PyObject * self,void * closure)409 gdbpy_tui_title (PyObject *self, void *closure)
410 {
411 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
412 REQUIRE_WINDOW (win);
413 return host_string_to_python_string (win->window->title.c_str ()).release ();
414 }
415
416 /* Set the title of the TUI window. */
417 static int
gdbpy_tui_set_title(PyObject * self,PyObject * newvalue,void * closure)418 gdbpy_tui_set_title (PyObject *self, PyObject *newvalue, void *closure)
419 {
420 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
421
422 if (win->window == nullptr)
423 {
424 PyErr_Format (PyExc_RuntimeError, _("TUI window is invalid."));
425 return -1;
426 }
427
428 if (win->window == nullptr)
429 {
430 PyErr_Format (PyExc_TypeError, _("Cannot delete \"title\" attribute."));
431 return -1;
432 }
433
434 gdb::unique_xmalloc_ptr<char> value
435 = python_string_to_host_string (newvalue);
436 if (value == nullptr)
437 return -1;
438
439 win->window->title = value.get ();
440 return 0;
441 }
442
443 static gdb_PyGetSetDef tui_object_getset[] =
444 {
445 { "width", gdbpy_tui_width, NULL, "Width of the window.", NULL },
446 { "height", gdbpy_tui_height, NULL, "Height of the window.", NULL },
447 { "title", gdbpy_tui_title, gdbpy_tui_set_title, "Title of the window.",
448 NULL },
449 { NULL } /* Sentinel */
450 };
451
452 static PyMethodDef tui_object_methods[] =
453 {
454 { "is_valid", gdbpy_tui_is_valid, METH_NOARGS,
455 "is_valid () -> Boolean\n\
456 Return true if this TUI window is valid, false if not." },
457 { "erase", gdbpy_tui_erase, METH_NOARGS,
458 "Erase the TUI window." },
459 { "write", (PyCFunction) gdbpy_tui_write, METH_VARARGS,
460 "Append a string to the TUI window." },
461 { NULL } /* Sentinel. */
462 };
463
464 PyTypeObject gdbpy_tui_window_object_type =
465 {
466 PyVarObject_HEAD_INIT (NULL, 0)
467 "gdb.TuiWindow", /*tp_name*/
468 sizeof (gdbpy_tui_window), /*tp_basicsize*/
469 0, /*tp_itemsize*/
470 0, /*tp_dealloc*/
471 0, /*tp_print*/
472 0, /*tp_getattr*/
473 0, /*tp_setattr*/
474 0, /*tp_compare*/
475 0, /*tp_repr*/
476 0, /*tp_as_number*/
477 0, /*tp_as_sequence*/
478 0, /*tp_as_mapping*/
479 0, /*tp_hash */
480 0, /*tp_call*/
481 0, /*tp_str*/
482 0, /*tp_getattro*/
483 0, /*tp_setattro */
484 0, /*tp_as_buffer*/
485 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
486 "GDB TUI window object", /* tp_doc */
487 0, /* tp_traverse */
488 0, /* tp_clear */
489 0, /* tp_richcompare */
490 0, /* tp_weaklistoffset */
491 0, /* tp_iter */
492 0, /* tp_iternext */
493 tui_object_methods, /* tp_methods */
494 0, /* tp_members */
495 tui_object_getset, /* tp_getset */
496 0, /* tp_base */
497 0, /* tp_dict */
498 0, /* tp_descr_get */
499 0, /* tp_descr_set */
500 0, /* tp_dictoffset */
501 0, /* tp_init */
502 0, /* tp_alloc */
503 };
504
505 #endif /* TUI */
506
507 /* Initialize this module. */
508
509 int
gdbpy_initialize_tui()510 gdbpy_initialize_tui ()
511 {
512 #ifdef TUI
513 gdbpy_tui_window_object_type.tp_new = PyType_GenericNew;
514 if (PyType_Ready (&gdbpy_tui_window_object_type) < 0)
515 return -1;
516 #endif /* TUI */
517
518 return 0;
519 }
520