GDB (xrefs)
Loading...
Searching...
No Matches
py-tui.c
Go to the documentation of this file.
1/* TUI windows implemented in Python
2
3 Copyright (C) 2020-2023 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#include "gdbsupport/intrusive_list.h"
25
26#ifdef TUI
27
28/* Note that Python's public headers may define HAVE_NCURSES_H, so if
29 we unconditionally include this (outside the #ifdef above), then we
30 can get a compile error when ncurses is not in fact installed. See
31 PR tui/25597; or the upstream Python bug
32 https://bugs.python.org/issue20768. */
33#include "gdb_curses.h"
34
35#include "tui/tui-data.h"
36#include "tui/tui-io.h"
37#include "tui/tui-layout.h"
38#include "tui/tui-wingeneral.h"
39#include "tui/tui-winsource.h"
40
41class tui_py_window;
42
43/* A PyObject representing a TUI window. */
44
45struct gdbpy_tui_window
46{
47 PyObject_HEAD
48
49 /* The TUI window, or nullptr if the window has been deleted. */
50 tui_py_window *window;
51
52 /* Return true if this object is valid. */
53 bool is_valid () const;
54};
55
56extern PyTypeObject gdbpy_tui_window_object_type
57 CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("gdbpy_tui_window");
58
59/* A TUI window written in Python. */
60
61class tui_py_window : public tui_win_info
62{
63public:
64
65 tui_py_window (const char *name, gdbpy_ref<gdbpy_tui_window> wrapper)
66 : m_name (name),
67 m_wrapper (std::move (wrapper))
68 {
69 m_wrapper->window = this;
70 }
71
72 ~tui_py_window ();
73
74 DISABLE_COPY_AND_ASSIGN (tui_py_window);
75
76 /* Set the "user window" to the indicated reference. The user
77 window is the object returned the by user-defined window
78 constructor. */
79 void set_user_window (gdbpy_ref<> &&user_window)
80 {
81 m_window = std::move (user_window);
82 }
83
84 const char *name () const override
85 {
86 return m_name.c_str ();
87 }
88
89 void rerender () override;
90 void do_scroll_vertical (int num_to_scroll) override;
91 void do_scroll_horizontal (int num_to_scroll) override;
92
93 void refresh_window () override
94 {
95 if (m_inner_window != nullptr)
96 {
97 wnoutrefresh (handle.get ());
98 touchwin (m_inner_window.get ());
99 tui_wrefresh (m_inner_window.get ());
100 }
101 else
103 }
104
105 void click (int mouse_x, int mouse_y, int mouse_button) override;
106
107 /* Erase and re-box the window. */
108 void erase ()
109 {
110 if (is_visible () && m_inner_window != nullptr)
111 {
112 werase (m_inner_window.get ());
114 }
115 }
116
117 /* Write STR to the window. FULL_WINDOW is true to erase the window
118 contents beforehand. */
119 void output (const char *str, bool full_window);
120
121 /* A helper function to compute the viewport width. */
122 int viewport_width () const
123 {
124 return std::max (0, width - 2);
125 }
126
127 /* A helper function to compute the viewport height. */
128 int viewport_height () const
129 {
130 return std::max (0, height - 2);
131 }
132
133private:
134
135 /* The name of this window. */
136 std::string m_name;
137
138 /* We make our own inner window, so that it is easy to print without
139 overwriting the border. */
140 std::unique_ptr<WINDOW, curses_deleter> m_inner_window;
141
142 /* The underlying Python window object. */
143 gdbpy_ref<> m_window;
144
145 /* The Python wrapper for this object. */
147};
148
149/* See gdbpy_tui_window declaration above. */
150
151bool
152gdbpy_tui_window::is_valid () const
153{
154 return window != nullptr && tui_active;
155}
156
157tui_py_window::~tui_py_window ()
158{
159 gdbpy_enter enter_py;
160
161 /* This can be null if the user-provided Python construction
162 function failed. */
163 if (m_window != nullptr
164 && PyObject_HasAttrString (m_window.get (), "close"))
165 {
166 gdbpy_ref<> result (PyObject_CallMethod (m_window.get (), "close",
167 nullptr));
168 if (result == nullptr)
170 }
171
172 /* Unlink. */
173 m_wrapper->window = nullptr;
174 /* Explicitly free the Python references. We have to do this
175 manually because we need to hold the GIL while doing so. */
176 m_wrapper.reset (nullptr);
177 m_window.reset (nullptr);
178}
179
180void
181tui_py_window::rerender ()
182{
184
185 gdbpy_enter enter_py;
186
187 int h = viewport_height ();
188 int w = viewport_width ();
189 if (h == 0 || w == 0)
190 {
191 /* The window would be too small, so just remove the
192 contents. */
193 m_inner_window.reset (nullptr);
194 return;
195 }
196 m_inner_window.reset (newwin (h, w, y + 1, x + 1));
197
198 if (PyObject_HasAttrString (m_window.get (), "render"))
199 {
200 gdbpy_ref<> result (PyObject_CallMethod (m_window.get (), "render",
201 nullptr));
202 if (result == nullptr)
204 }
205}
206
207void
208tui_py_window::do_scroll_horizontal (int num_to_scroll)
209{
210 gdbpy_enter enter_py;
211
212 if (PyObject_HasAttrString (m_window.get (), "hscroll"))
213 {
214 gdbpy_ref<> result (PyObject_CallMethod (m_window.get(), "hscroll",
215 "i", num_to_scroll, nullptr));
216 if (result == nullptr)
218 }
219}
220
221void
222tui_py_window::do_scroll_vertical (int num_to_scroll)
223{
224 gdbpy_enter enter_py;
225
226 if (PyObject_HasAttrString (m_window.get (), "vscroll"))
227 {
228 gdbpy_ref<> result (PyObject_CallMethod (m_window.get (), "vscroll",
229 "i", num_to_scroll, nullptr));
230 if (result == nullptr)
232 }
233}
234
235void
236tui_py_window::click (int mouse_x, int mouse_y, int mouse_button)
237{
238 gdbpy_enter enter_py;
239
240 if (PyObject_HasAttrString (m_window.get (), "click"))
241 {
242 gdbpy_ref<> result (PyObject_CallMethod (m_window.get (), "click",
243 "iii", mouse_x, mouse_y,
244 mouse_button));
245 if (result == nullptr)
247 }
248}
249
250void
251tui_py_window::output (const char *text, bool full_window)
252{
253 if (m_inner_window != nullptr)
254 {
255 if (full_window)
256 werase (m_inner_window.get ());
257
258 tui_puts (text, m_inner_window.get ());
259 if (full_window)
260 check_and_display_highlight_if_needed ();
261 else
262 tui_wrefresh (m_inner_window.get ());
263 }
264}
265
266
267
268/* A callable that is used to create a TUI window. It wraps the
269 user-supplied window constructor. */
270
271class gdbpy_tui_window_maker
272 : public intrusive_list_node<gdbpy_tui_window_maker>
273{
274public:
275
276 explicit gdbpy_tui_window_maker (gdbpy_ref<> &&constr)
277 : m_constr (std::move (constr))
278 {
279 m_window_maker_list.push_back (*this);
280 }
281
282 ~gdbpy_tui_window_maker ();
283
284 gdbpy_tui_window_maker (gdbpy_tui_window_maker &&other) noexcept
285 : m_constr (std::move (other.m_constr))
286 {
287 m_window_maker_list.push_back (*this);
288 }
289
290 gdbpy_tui_window_maker (const gdbpy_tui_window_maker &other)
291 {
292 gdbpy_enter enter_py;
293 m_constr = other.m_constr;
294 m_window_maker_list.push_back (*this);
295 }
296
297 gdbpy_tui_window_maker &operator= (gdbpy_tui_window_maker &&other)
298 {
299 m_constr = std::move (other.m_constr);
300 return *this;
301 }
302
303 gdbpy_tui_window_maker &operator= (const gdbpy_tui_window_maker &other)
304 {
305 gdbpy_enter enter_py;
306 m_constr = other.m_constr;
307 return *this;
308 }
309
310 tui_win_info *operator() (const char *name);
311
312 /* Reset the m_constr field of all gdbpy_tui_window_maker objects back to
313 nullptr, this will allow the Python object referenced to be
314 deallocated. This function is intended to be called when GDB is
315 shutting down the Python interpreter to allow all Python objects to be
316 deallocated and cleaned up. */
317 static void
318 invalidate_all ()
319 {
320 gdbpy_enter enter_py;
321 for (gdbpy_tui_window_maker &f : m_window_maker_list)
322 f.m_constr.reset (nullptr);
323 }
324
325private:
326
327 /* A constructor that is called to make a TUI window. */
328 gdbpy_ref<> m_constr;
329
330 /* A global list of all gdbpy_tui_window_maker objects. */
331 static intrusive_list<gdbpy_tui_window_maker> m_window_maker_list;
332};
333
334/* See comment in class declaration above. */
335
336intrusive_list<gdbpy_tui_window_maker>
337 gdbpy_tui_window_maker::m_window_maker_list;
338
339gdbpy_tui_window_maker::~gdbpy_tui_window_maker ()
340{
341 /* Remove this gdbpy_tui_window_maker from the global list. */
342 if (is_linked ())
343 m_window_maker_list.erase (m_window_maker_list.iterator_to (*this));
344
345 if (m_constr != nullptr)
346 {
347 gdbpy_enter enter_py;
348 m_constr.reset (nullptr);
349 }
350}
351
353gdbpy_tui_window_maker::operator() (const char *win_name)
354{
355 gdbpy_enter enter_py;
356
358 (PyObject_New (gdbpy_tui_window, &gdbpy_tui_window_object_type));
359 if (wrapper == nullptr)
360 {
362 return nullptr;
363 }
364
365 std::unique_ptr<tui_py_window> window
366 (new tui_py_window (win_name, wrapper));
367
368 /* There's only two ways that m_constr can be reset back to nullptr,
369 first when the parent gdbpy_tui_window_maker object is deleted, in
370 which case it should be impossible to call this method, or second, as
371 a result of a gdbpy_tui_window_maker::invalidate_all call, but this is
372 only called when GDB's Python interpreter is being shut down, after
373 which, this method should not be called. */
374 gdb_assert (m_constr != nullptr);
375
376 gdbpy_ref<> user_window
377 (PyObject_CallFunctionObjArgs (m_constr.get (),
378 (PyObject *) wrapper.get (),
379 nullptr));
380 if (user_window == nullptr)
381 {
383 return nullptr;
384 }
385
386 window->set_user_window (std::move (user_window));
387 /* Window is now owned by the TUI. */
388 return window.release ();
389}
390
391/* Implement "gdb.register_window_type". */
392
393PyObject *
395{
396 static const char *keywords[] = { "name", "constructor", nullptr };
397
398 const char *name;
399 PyObject *cons_obj;
400
401 if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "sO", keywords,
402 &name, &cons_obj))
403 return nullptr;
404
405 try
406 {
407 gdbpy_tui_window_maker constr (gdbpy_ref<>::new_reference (cons_obj));
408 tui_register_window (name, constr);
409 }
410 catch (const gdb_exception &except)
411 {
413 return nullptr;
414 }
415
416 Py_RETURN_NONE;
417}
418
419
420
421/* Require that "Window" be a valid window. */
422
423#define REQUIRE_WINDOW(Window) \
424 do { \
425 if (!(Window)->is_valid ()) \
426 return PyErr_Format (PyExc_RuntimeError, \
427 _("TUI window is invalid.")); \
428 } while (0)
429
430/* Require that "Window" be a valid window. */
431
432#define REQUIRE_WINDOW_FOR_SETTER(Window) \
433 do { \
434 if (!(Window)->is_valid ()) \
435 { \
436 PyErr_Format (PyExc_RuntimeError, \
437 _("TUI window is invalid.")); \
438 return -1; \
439 } \
440 } while (0)
441
442/* Python function which checks the validity of a TUI window
443 object. */
444static PyObject *
445gdbpy_tui_is_valid (PyObject *self, PyObject *args)
446{
447 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
448
449 if (win->is_valid ())
450 Py_RETURN_TRUE;
451 Py_RETURN_FALSE;
452}
453
454/* Python function that erases the TUI window. */
455static PyObject *
456gdbpy_tui_erase (PyObject *self, PyObject *args)
457{
458 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
459
460 REQUIRE_WINDOW (win);
461
462 win->window->erase ();
463
464 Py_RETURN_NONE;
465}
466
467/* Python function that writes some text to a TUI window. */
468static PyObject *
469gdbpy_tui_write (PyObject *self, PyObject *args)
470{
471 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
472 const char *text;
473 int full_window = 0;
474
475 if (!PyArg_ParseTuple (args, "s|i", &text, &full_window))
476 return nullptr;
477
478 REQUIRE_WINDOW (win);
479
480 win->window->output (text, full_window);
481
482 Py_RETURN_NONE;
483}
484
485/* Return the width of the TUI window. */
486static PyObject *
487gdbpy_tui_width (PyObject *self, void *closure)
488{
489 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
490 REQUIRE_WINDOW (win);
491 gdbpy_ref<> result
492 = gdb_py_object_from_longest (win->window->viewport_width ());
493 return result.release ();
494}
495
496/* Return the height of the TUI window. */
497static PyObject *
498gdbpy_tui_height (PyObject *self, void *closure)
499{
500 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
501 REQUIRE_WINDOW (win);
502 gdbpy_ref<> result
503 = gdb_py_object_from_longest (win->window->viewport_height ());
504 return result.release ();
505}
506
507/* Return the title of the TUI window. */
508static PyObject *
509gdbpy_tui_title (PyObject *self, void *closure)
510{
511 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
512 REQUIRE_WINDOW (win);
513 return host_string_to_python_string (win->window->title ().c_str ()).release ();
514}
515
516/* Set the title of the TUI window. */
517static int
518gdbpy_tui_set_title (PyObject *self, PyObject *newvalue, void *closure)
519{
520 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
521
522 REQUIRE_WINDOW_FOR_SETTER (win);
523
524 if (newvalue == nullptr)
525 {
526 PyErr_Format (PyExc_TypeError, _("Cannot delete \"title\" attribute."));
527 return -1;
528 }
529
530 gdb::unique_xmalloc_ptr<char> value
531 = python_string_to_host_string (newvalue);
532 if (value == nullptr)
533 return -1;
534
535 win->window->set_title (value.get ());
536 return 0;
537}
538
539static gdb_PyGetSetDef tui_object_getset[] =
540{
541 { "width", gdbpy_tui_width, NULL, "Width of the window.", NULL },
542 { "height", gdbpy_tui_height, NULL, "Height of the window.", NULL },
543 { "title", gdbpy_tui_title, gdbpy_tui_set_title, "Title of the window.",
544 NULL },
545 { NULL } /* Sentinel */
546};
547
548static PyMethodDef tui_object_methods[] =
549{
550 { "is_valid", gdbpy_tui_is_valid, METH_NOARGS,
551 "is_valid () -> Boolean\n\
552Return true if this TUI window is valid, false if not." },
553 { "erase", gdbpy_tui_erase, METH_NOARGS,
554 "Erase the TUI window." },
555 { "write", (PyCFunction) gdbpy_tui_write, METH_VARARGS,
556 "Append a string to the TUI window." },
557 { NULL } /* Sentinel. */
558};
559
560PyTypeObject gdbpy_tui_window_object_type =
561{
562 PyVarObject_HEAD_INIT (NULL, 0)
563 "gdb.TuiWindow", /*tp_name*/
564 sizeof (gdbpy_tui_window), /*tp_basicsize*/
565 0, /*tp_itemsize*/
566 0, /*tp_dealloc*/
567 0, /*tp_print*/
568 0, /*tp_getattr*/
569 0, /*tp_setattr*/
570 0, /*tp_compare*/
571 0, /*tp_repr*/
572 0, /*tp_as_number*/
573 0, /*tp_as_sequence*/
574 0, /*tp_as_mapping*/
575 0, /*tp_hash */
576 0, /*tp_call*/
577 0, /*tp_str*/
578 0, /*tp_getattro*/
579 0, /*tp_setattro */
580 0, /*tp_as_buffer*/
581 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
582 "GDB TUI window object", /* tp_doc */
583 0, /* tp_traverse */
584 0, /* tp_clear */
585 0, /* tp_richcompare */
586 0, /* tp_weaklistoffset */
587 0, /* tp_iter */
588 0, /* tp_iternext */
589 tui_object_methods, /* tp_methods */
590 0, /* tp_members */
591 tui_object_getset, /* tp_getset */
592 0, /* tp_base */
593 0, /* tp_dict */
594 0, /* tp_descr_get */
595 0, /* tp_descr_set */
596 0, /* tp_dictoffset */
597 0, /* tp_init */
598 0, /* tp_alloc */
599};
600
601#endif /* TUI */
602
603/* Initialize this module. */
604
607{
608#ifdef TUI
609 gdbpy_tui_window_object_type.tp_new = PyType_GenericNew;
610 if (PyType_Ready (&gdbpy_tui_window_object_type) < 0)
611 return -1;
612#endif /* TUI */
613
614 return 0;
615}
616
617/* Finalize this module. */
618
619static void
621{
622#ifdef TUI
623 gdbpy_tui_window_maker::invalidate_all ();
624#endif /* TUI */
625}
626
const char *const name
void f()
Definition 1.cc:36
Definition aarch64.h:67
gdb::ref_ptr< T, gdbpy_ref_policy< T > > gdbpy_ref
Definition py-ref.h:42
static void gdbpy_finalize_tui()
Definition py-tui.c:620
static int CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION gdbpy_initialize_tui()
Definition py-tui.c:606
gdbpy_ref host_string_to_python_string(const char *str)
Definition py-utils.c:154
gdbpy_ref gdb_py_object_from_longest(LONGEST l)
Definition py-utils.c:282
void gdbpy_convert_exception(const struct gdb_exception &exception)
Definition py-utils.c:217
gdb::unique_xmalloc_ptr< char > python_string_to_host_string(PyObject *obj)
Definition py-utils.c:142
void gdbpy_print_stack(void)
#define PyObject_CallMethod
PyObject * gdbpy_register_tui_window(PyObject *self, PyObject *args, PyObject *kw)
#define CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF(ARG)
#define CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION
#define GDBPY_INITIALIZE_FILE(INIT,...)
static int gdb_PyArg_ParseTupleAndKeywords(PyObject *args, PyObject *kw, const char *format, const char **keywords,...)
void check_and_display_highlight_if_needed()
DISABLE_COPY_AND_ASSIGN(tui_win_info)
virtual void rerender()
Definition tui-data.c:168
virtual void do_scroll_horizontal(int num_to_scroll)=0
virtual const char * name() const =0
virtual void click(int mouse_x, int mouse_y, int mouse_button)
Definition tui-data.h:143
virtual void do_scroll_vertical(int num_to_scroll)=0
virtual void refresh_window()
Definition value.h:130
void tui_puts(const char *string, WINDOW *w)
Definition tui-io.c:459
void tui_register_window(const char *name, window_factory &&factory)
Definition tui-layout.c:403
void tui_wrefresh(WINDOW *win)
bool tui_active
Definition tui.c:73
int PyObject
Definition varobj.c:41
#define nullptr
Definition x86-cpuid.h:28