1 /* TUI windows implemented in Python
3 Copyright (C) 2020-2021 Free Software Foundation, Inc.
5 This file is part of GDB.
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.
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.
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/>. */
22 #include "arch-utils.h"
23 #include "python-internal.h"
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"
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"
42 /* A PyObject representing a TUI window. */
44 struct gdbpy_tui_window
48 /* The TUI window, or nullptr if the window has been deleted. */
49 tui_py_window *window;
51 /* Return true if this object is valid. */
52 bool is_valid () const;
55 extern PyTypeObject gdbpy_tui_window_object_type
56 CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("gdbpy_tui_window");
58 /* A TUI window written in Python. */
60 class tui_py_window : public tui_win_info
64 tui_py_window (const char *name, gdbpy_ref<gdbpy_tui_window> wrapper)
66 m_wrapper (std::move (wrapper))
68 m_wrapper->window = this;
73 DISABLE_COPY_AND_ASSIGN (tui_py_window);
75 /* Set the "user window" to the indicated reference. The user
76 window is the object returned the by user-defined window
78 void set_user_window (gdbpy_ref<> &&user_window)
80 m_window = std::move (user_window);
83 const char *name () const override
85 return m_name.c_str ();
88 void rerender () override;
89 void do_scroll_vertical (int num_to_scroll) override;
90 void do_scroll_horizontal (int num_to_scroll) override;
92 void refresh_window () override
94 if (m_inner_window != nullptr)
96 wnoutrefresh (handle.get ());
97 touchwin (m_inner_window.get ());
98 tui_wrefresh (m_inner_window.get ());
101 tui_win_info::refresh_window ();
104 /* Erase and re-box the window. */
107 if (is_visible () && m_inner_window != nullptr)
109 werase (m_inner_window.get ());
110 check_and_display_highlight_if_needed ();
114 /* Write STR to the window. FULL_WINDOW is true to erase the window
115 contents beforehand. */
116 void output (const char *str, bool full_window);
118 /* A helper function to compute the viewport width. */
119 int viewport_width () const
121 return std::max (0, width - 2);
124 /* A helper function to compute the viewport height. */
125 int viewport_height () const
127 return std::max (0, height - 2);
132 /* The name of this window. */
135 /* We make our own inner window, so that it is easy to print without
136 overwriting the border. */
137 std::unique_ptr<WINDOW, curses_deleter> m_inner_window;
139 /* The underlying Python window object. */
140 gdbpy_ref<> m_window;
142 /* The Python wrapper for this object. */
143 gdbpy_ref<gdbpy_tui_window> m_wrapper;
146 /* See gdbpy_tui_window declaration above. */
149 gdbpy_tui_window::is_valid () const
151 return window != nullptr && tui_active;
154 tui_py_window::~tui_py_window ()
156 gdbpy_enter enter_py (get_current_arch (), current_language);
158 /* This can be null if the user-provided Python construction
160 if (m_window != nullptr
161 && PyObject_HasAttrString (m_window.get (), "close"))
163 gdbpy_ref<> result (PyObject_CallMethod (m_window.get (), "close",
165 if (result == nullptr)
166 gdbpy_print_stack ();
170 m_wrapper->window = nullptr;
171 /* Explicitly free the Python references. We have to do this
172 manually because we need to hold the GIL while doing so. */
173 m_wrapper.reset (nullptr);
174 m_window.reset (nullptr);
178 tui_py_window::rerender ()
180 tui_win_info::rerender ();
182 gdbpy_enter enter_py (get_current_arch (), current_language);
184 int h = viewport_height ();
185 int w = viewport_width ();
186 if (h == 0 || w == 0)
188 /* The window would be too small, so just remove the
190 m_inner_window.reset (nullptr);
193 m_inner_window.reset (newwin (h, w, y + 1, x + 1));
195 if (PyObject_HasAttrString (m_window.get (), "render"))
197 gdbpy_ref<> result (PyObject_CallMethod (m_window.get (), "render",
199 if (result == nullptr)
200 gdbpy_print_stack ();
205 tui_py_window::do_scroll_horizontal (int num_to_scroll)
207 gdbpy_enter enter_py (get_current_arch (), current_language);
209 if (PyObject_HasAttrString (m_window.get (), "hscroll"))
211 gdbpy_ref<> result (PyObject_CallMethod (m_window.get(), "hscroll",
212 "i", num_to_scroll, nullptr));
213 if (result == nullptr)
214 gdbpy_print_stack ();
219 tui_py_window::do_scroll_vertical (int num_to_scroll)
221 gdbpy_enter enter_py (get_current_arch (), current_language);
223 if (PyObject_HasAttrString (m_window.get (), "vscroll"))
225 gdbpy_ref<> result (PyObject_CallMethod (m_window.get (), "vscroll",
226 "i", num_to_scroll, nullptr));
227 if (result == nullptr)
228 gdbpy_print_stack ();
233 tui_py_window::output (const char *text, bool full_window)
235 if (m_inner_window != nullptr)
238 werase (m_inner_window.get ());
240 tui_puts (text, m_inner_window.get ());
242 check_and_display_highlight_if_needed ();
244 tui_wrefresh (m_inner_window.get ());
250 /* A callable that is used to create a TUI window. It wraps the
251 user-supplied window constructor. */
253 class gdbpy_tui_window_maker
257 explicit gdbpy_tui_window_maker (gdbpy_ref<> &&constr)
258 : m_constr (std::move (constr))
262 ~gdbpy_tui_window_maker ();
264 gdbpy_tui_window_maker (gdbpy_tui_window_maker &&other) noexcept
265 : m_constr (std::move (other.m_constr))
269 gdbpy_tui_window_maker (const gdbpy_tui_window_maker &other)
271 gdbpy_enter enter_py (get_current_arch (), current_language);
272 m_constr = other.m_constr;
275 gdbpy_tui_window_maker &operator= (gdbpy_tui_window_maker &&other)
277 m_constr = std::move (other.m_constr);
281 gdbpy_tui_window_maker &operator= (const gdbpy_tui_window_maker &other)
283 gdbpy_enter enter_py (get_current_arch (), current_language);
284 m_constr = other.m_constr;
288 tui_win_info *operator() (const char *name);
292 /* A constructor that is called to make a TUI window. */
293 gdbpy_ref<> m_constr;
296 gdbpy_tui_window_maker::~gdbpy_tui_window_maker ()
298 gdbpy_enter enter_py (get_current_arch (), current_language);
299 m_constr.reset (nullptr);
303 gdbpy_tui_window_maker::operator() (const char *win_name)
305 gdbpy_enter enter_py (get_current_arch (), current_language);
307 gdbpy_ref<gdbpy_tui_window> wrapper
308 (PyObject_New (gdbpy_tui_window, &gdbpy_tui_window_object_type));
309 if (wrapper == nullptr)
311 gdbpy_print_stack ();
315 std::unique_ptr<tui_py_window> window
316 (new tui_py_window (win_name, wrapper));
318 gdbpy_ref<> user_window
319 (PyObject_CallFunctionObjArgs (m_constr.get (),
320 (PyObject *) wrapper.get (),
322 if (user_window == nullptr)
324 gdbpy_print_stack ();
328 window->set_user_window (std::move (user_window));
329 /* Window is now owned by the TUI. */
330 return window.release ();
333 /* Implement "gdb.register_window_type". */
336 gdbpy_register_tui_window (PyObject *self, PyObject *args, PyObject *kw)
338 static const char *keywords[] = { "name", "constructor", nullptr };
343 if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "sO", keywords,
349 gdbpy_tui_window_maker constr (gdbpy_ref<>::new_reference (cons_obj));
350 tui_register_window (name, constr);
352 catch (const gdb_exception &except)
354 gdbpy_convert_exception (except);
363 /* Require that "Window" be a valid window. */
365 #define REQUIRE_WINDOW(Window) \
367 if (!(Window)->is_valid ()) \
368 return PyErr_Format (PyExc_RuntimeError, \
369 _("TUI window is invalid.")); \
372 /* Require that "Window" be a valid window. */
374 #define REQUIRE_WINDOW_FOR_SETTER(Window) \
376 if (!(Window)->is_valid ()) \
378 PyErr_Format (PyExc_RuntimeError, \
379 _("TUI window is invalid.")); \
384 /* Python function which checks the validity of a TUI window
387 gdbpy_tui_is_valid (PyObject *self, PyObject *args)
389 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
391 if (win->is_valid ())
396 /* Python function that erases the TUI window. */
398 gdbpy_tui_erase (PyObject *self, PyObject *args)
400 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
402 REQUIRE_WINDOW (win);
404 win->window->erase ();
409 /* Python function that writes some text to a TUI window. */
411 gdbpy_tui_write (PyObject *self, PyObject *args)
413 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
417 if (!PyArg_ParseTuple (args, "s|i", &text, &full_window))
420 REQUIRE_WINDOW (win);
422 win->window->output (text, full_window);
427 /* Return the width of the TUI window. */
429 gdbpy_tui_width (PyObject *self, void *closure)
431 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
432 REQUIRE_WINDOW (win);
434 = gdb_py_object_from_longest (win->window->viewport_width ());
435 return result.release ();
438 /* Return the height of the TUI window. */
440 gdbpy_tui_height (PyObject *self, void *closure)
442 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
443 REQUIRE_WINDOW (win);
445 = gdb_py_object_from_longest (win->window->viewport_height ());
446 return result.release ();
449 /* Return the title of the TUI window. */
451 gdbpy_tui_title (PyObject *self, void *closure)
453 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
454 REQUIRE_WINDOW (win);
455 return host_string_to_python_string (win->window->title.c_str ()).release ();
458 /* Set the title of the TUI window. */
460 gdbpy_tui_set_title (PyObject *self, PyObject *newvalue, void *closure)
462 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
464 REQUIRE_WINDOW_FOR_SETTER (win);
466 if (newvalue == nullptr)
468 PyErr_Format (PyExc_TypeError, _("Cannot delete \"title\" attribute."));
472 gdb::unique_xmalloc_ptr<char> value
473 = python_string_to_host_string (newvalue);
474 if (value == nullptr)
477 win->window->title = value.get ();
481 static gdb_PyGetSetDef tui_object_getset[] =
483 { "width", gdbpy_tui_width, NULL, "Width of the window.", NULL },
484 { "height", gdbpy_tui_height, NULL, "Height of the window.", NULL },
485 { "title", gdbpy_tui_title, gdbpy_tui_set_title, "Title of the window.",
487 { NULL } /* Sentinel */
490 static PyMethodDef tui_object_methods[] =
492 { "is_valid", gdbpy_tui_is_valid, METH_NOARGS,
493 "is_valid () -> Boolean\n\
494 Return true if this TUI window is valid, false if not." },
495 { "erase", gdbpy_tui_erase, METH_NOARGS,
496 "Erase the TUI window." },
497 { "write", (PyCFunction) gdbpy_tui_write, METH_VARARGS,
498 "Append a string to the TUI window." },
499 { NULL } /* Sentinel. */
502 PyTypeObject gdbpy_tui_window_object_type =
504 PyVarObject_HEAD_INIT (NULL, 0)
505 "gdb.TuiWindow", /*tp_name*/
506 sizeof (gdbpy_tui_window), /*tp_basicsize*/
515 0, /*tp_as_sequence*/
523 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
524 "GDB TUI window object", /* tp_doc */
527 0, /* tp_richcompare */
528 0, /* tp_weaklistoffset */
531 tui_object_methods, /* tp_methods */
533 tui_object_getset, /* tp_getset */
536 0, /* tp_descr_get */
537 0, /* tp_descr_set */
538 0, /* tp_dictoffset */
545 /* Initialize this module. */
548 gdbpy_initialize_tui ()
551 gdbpy_tui_window_object_type.tp_new = PyType_GenericNew;
552 if (PyType_Ready (&gdbpy_tui_window_object_type) < 0)