1 /* TUI windows implemented in Python
3 Copyright (C) 2020-2022 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 void click (int mouse_x
, int mouse_y
, int mouse_button
) override
;
106 /* Erase and re-box the window. */
109 if (is_visible () && m_inner_window
!= nullptr)
111 werase (m_inner_window
.get ());
112 check_and_display_highlight_if_needed ();
116 /* Write STR to the window. FULL_WINDOW is true to erase the window
117 contents beforehand. */
118 void output (const char *str
, bool full_window
);
120 /* A helper function to compute the viewport width. */
121 int viewport_width () const
123 return std::max (0, width
- 2);
126 /* A helper function to compute the viewport height. */
127 int viewport_height () const
129 return std::max (0, height
- 2);
134 /* The name of this window. */
137 /* We make our own inner window, so that it is easy to print without
138 overwriting the border. */
139 std::unique_ptr
<WINDOW
, curses_deleter
> m_inner_window
;
141 /* The underlying Python window object. */
142 gdbpy_ref
<> m_window
;
144 /* The Python wrapper for this object. */
145 gdbpy_ref
<gdbpy_tui_window
> m_wrapper
;
148 /* See gdbpy_tui_window declaration above. */
151 gdbpy_tui_window::is_valid () const
153 return window
!= nullptr && tui_active
;
156 tui_py_window::~tui_py_window ()
158 gdbpy_enter
enter_py (get_current_arch (), current_language
);
160 /* This can be null if the user-provided Python construction
162 if (m_window
!= nullptr
163 && PyObject_HasAttrString (m_window
.get (), "close"))
165 gdbpy_ref
<> result (PyObject_CallMethod (m_window
.get (), "close",
167 if (result
== nullptr)
168 gdbpy_print_stack ();
172 m_wrapper
->window
= nullptr;
173 /* Explicitly free the Python references. We have to do this
174 manually because we need to hold the GIL while doing so. */
175 m_wrapper
.reset (nullptr);
176 m_window
.reset (nullptr);
180 tui_py_window::rerender ()
182 tui_win_info::rerender ();
184 gdbpy_enter
enter_py (get_current_arch (), current_language
);
186 int h
= viewport_height ();
187 int w
= viewport_width ();
188 if (h
== 0 || w
== 0)
190 /* The window would be too small, so just remove the
192 m_inner_window
.reset (nullptr);
195 m_inner_window
.reset (newwin (h
, w
, y
+ 1, x
+ 1));
197 if (PyObject_HasAttrString (m_window
.get (), "render"))
199 gdbpy_ref
<> result (PyObject_CallMethod (m_window
.get (), "render",
201 if (result
== nullptr)
202 gdbpy_print_stack ();
207 tui_py_window::do_scroll_horizontal (int num_to_scroll
)
209 gdbpy_enter
enter_py (get_current_arch (), current_language
);
211 if (PyObject_HasAttrString (m_window
.get (), "hscroll"))
213 gdbpy_ref
<> result (PyObject_CallMethod (m_window
.get(), "hscroll",
214 "i", num_to_scroll
, nullptr));
215 if (result
== nullptr)
216 gdbpy_print_stack ();
221 tui_py_window::do_scroll_vertical (int num_to_scroll
)
223 gdbpy_enter
enter_py (get_current_arch (), current_language
);
225 if (PyObject_HasAttrString (m_window
.get (), "vscroll"))
227 gdbpy_ref
<> result (PyObject_CallMethod (m_window
.get (), "vscroll",
228 "i", num_to_scroll
, nullptr));
229 if (result
== nullptr)
230 gdbpy_print_stack ();
235 tui_py_window::click (int mouse_x
, int mouse_y
, int mouse_button
)
237 gdbpy_enter
enter_py (get_current_arch (), current_language
);
239 if (PyObject_HasAttrString (m_window
.get (), "click"))
241 gdbpy_ref
<> result (PyObject_CallMethod (m_window
.get (), "click",
242 "iii", mouse_x
, mouse_y
,
244 if (result
== nullptr)
245 gdbpy_print_stack ();
250 tui_py_window::output (const char *text
, bool full_window
)
252 if (m_inner_window
!= nullptr)
255 werase (m_inner_window
.get ());
257 tui_puts (text
, m_inner_window
.get ());
259 check_and_display_highlight_if_needed ();
261 tui_wrefresh (m_inner_window
.get ());
267 /* A callable that is used to create a TUI window. It wraps the
268 user-supplied window constructor. */
270 class gdbpy_tui_window_maker
274 explicit gdbpy_tui_window_maker (gdbpy_ref
<> &&constr
)
275 : m_constr (std::move (constr
))
279 ~gdbpy_tui_window_maker ();
281 gdbpy_tui_window_maker (gdbpy_tui_window_maker
&&other
) noexcept
282 : m_constr (std::move (other
.m_constr
))
286 gdbpy_tui_window_maker (const gdbpy_tui_window_maker
&other
)
288 gdbpy_enter
enter_py (get_current_arch (), current_language
);
289 m_constr
= other
.m_constr
;
292 gdbpy_tui_window_maker
&operator= (gdbpy_tui_window_maker
&&other
)
294 m_constr
= std::move (other
.m_constr
);
298 gdbpy_tui_window_maker
&operator= (const gdbpy_tui_window_maker
&other
)
300 gdbpy_enter
enter_py (get_current_arch (), current_language
);
301 m_constr
= other
.m_constr
;
305 tui_win_info
*operator() (const char *name
);
309 /* A constructor that is called to make a TUI window. */
310 gdbpy_ref
<> m_constr
;
313 gdbpy_tui_window_maker::~gdbpy_tui_window_maker ()
315 gdbpy_enter
enter_py (get_current_arch (), current_language
);
316 m_constr
.reset (nullptr);
320 gdbpy_tui_window_maker::operator() (const char *win_name
)
322 gdbpy_enter
enter_py (get_current_arch (), current_language
);
324 gdbpy_ref
<gdbpy_tui_window
> wrapper
325 (PyObject_New (gdbpy_tui_window
, &gdbpy_tui_window_object_type
));
326 if (wrapper
== nullptr)
328 gdbpy_print_stack ();
332 std::unique_ptr
<tui_py_window
> window
333 (new tui_py_window (win_name
, wrapper
));
335 gdbpy_ref
<> user_window
336 (PyObject_CallFunctionObjArgs (m_constr
.get (),
337 (PyObject
*) wrapper
.get (),
339 if (user_window
== nullptr)
341 gdbpy_print_stack ();
345 window
->set_user_window (std::move (user_window
));
346 /* Window is now owned by the TUI. */
347 return window
.release ();
350 /* Implement "gdb.register_window_type". */
353 gdbpy_register_tui_window (PyObject
*self
, PyObject
*args
, PyObject
*kw
)
355 static const char *keywords
[] = { "name", "constructor", nullptr };
360 if (!gdb_PyArg_ParseTupleAndKeywords (args
, kw
, "sO", keywords
,
366 gdbpy_tui_window_maker
constr (gdbpy_ref
<>::new_reference (cons_obj
));
367 tui_register_window (name
, constr
);
369 catch (const gdb_exception
&except
)
371 gdbpy_convert_exception (except
);
380 /* Require that "Window" be a valid window. */
382 #define REQUIRE_WINDOW(Window) \
384 if (!(Window)->is_valid ()) \
385 return PyErr_Format (PyExc_RuntimeError, \
386 _("TUI window is invalid.")); \
389 /* Require that "Window" be a valid window. */
391 #define REQUIRE_WINDOW_FOR_SETTER(Window) \
393 if (!(Window)->is_valid ()) \
395 PyErr_Format (PyExc_RuntimeError, \
396 _("TUI window is invalid.")); \
401 /* Python function which checks the validity of a TUI window
404 gdbpy_tui_is_valid (PyObject
*self
, PyObject
*args
)
406 gdbpy_tui_window
*win
= (gdbpy_tui_window
*) self
;
408 if (win
->is_valid ())
413 /* Python function that erases the TUI window. */
415 gdbpy_tui_erase (PyObject
*self
, PyObject
*args
)
417 gdbpy_tui_window
*win
= (gdbpy_tui_window
*) self
;
419 REQUIRE_WINDOW (win
);
421 win
->window
->erase ();
426 /* Python function that writes some text to a TUI window. */
428 gdbpy_tui_write (PyObject
*self
, PyObject
*args
)
430 gdbpy_tui_window
*win
= (gdbpy_tui_window
*) self
;
434 if (!PyArg_ParseTuple (args
, "s|i", &text
, &full_window
))
437 REQUIRE_WINDOW (win
);
439 win
->window
->output (text
, full_window
);
444 /* Return the width of the TUI window. */
446 gdbpy_tui_width (PyObject
*self
, void *closure
)
448 gdbpy_tui_window
*win
= (gdbpy_tui_window
*) self
;
449 REQUIRE_WINDOW (win
);
451 = gdb_py_object_from_longest (win
->window
->viewport_width ());
452 return result
.release ();
455 /* Return the height of the TUI window. */
457 gdbpy_tui_height (PyObject
*self
, void *closure
)
459 gdbpy_tui_window
*win
= (gdbpy_tui_window
*) self
;
460 REQUIRE_WINDOW (win
);
462 = gdb_py_object_from_longest (win
->window
->viewport_height ());
463 return result
.release ();
466 /* Return the title of the TUI window. */
468 gdbpy_tui_title (PyObject
*self
, void *closure
)
470 gdbpy_tui_window
*win
= (gdbpy_tui_window
*) self
;
471 REQUIRE_WINDOW (win
);
472 return host_string_to_python_string (win
->window
->title
.c_str ()).release ();
475 /* Set the title of the TUI window. */
477 gdbpy_tui_set_title (PyObject
*self
, PyObject
*newvalue
, void *closure
)
479 gdbpy_tui_window
*win
= (gdbpy_tui_window
*) self
;
481 REQUIRE_WINDOW_FOR_SETTER (win
);
483 if (newvalue
== nullptr)
485 PyErr_Format (PyExc_TypeError
, _("Cannot delete \"title\" attribute."));
489 gdb::unique_xmalloc_ptr
<char> value
490 = python_string_to_host_string (newvalue
);
491 if (value
== nullptr)
494 win
->window
->title
= value
.get ();
498 static gdb_PyGetSetDef tui_object_getset
[] =
500 { "width", gdbpy_tui_width
, NULL
, "Width of the window.", NULL
},
501 { "height", gdbpy_tui_height
, NULL
, "Height of the window.", NULL
},
502 { "title", gdbpy_tui_title
, gdbpy_tui_set_title
, "Title of the window.",
504 { NULL
} /* Sentinel */
507 static PyMethodDef tui_object_methods
[] =
509 { "is_valid", gdbpy_tui_is_valid
, METH_NOARGS
,
510 "is_valid () -> Boolean\n\
511 Return true if this TUI window is valid, false if not." },
512 { "erase", gdbpy_tui_erase
, METH_NOARGS
,
513 "Erase the TUI window." },
514 { "write", (PyCFunction
) gdbpy_tui_write
, METH_VARARGS
,
515 "Append a string to the TUI window." },
516 { NULL
} /* Sentinel. */
519 PyTypeObject gdbpy_tui_window_object_type
=
521 PyVarObject_HEAD_INIT (NULL
, 0)
522 "gdb.TuiWindow", /*tp_name*/
523 sizeof (gdbpy_tui_window
), /*tp_basicsize*/
532 0, /*tp_as_sequence*/
540 Py_TPFLAGS_DEFAULT
| Py_TPFLAGS_BASETYPE
, /*tp_flags*/
541 "GDB TUI window object", /* tp_doc */
544 0, /* tp_richcompare */
545 0, /* tp_weaklistoffset */
548 tui_object_methods
, /* tp_methods */
550 tui_object_getset
, /* tp_getset */
553 0, /* tp_descr_get */
554 0, /* tp_descr_set */
555 0, /* tp_dictoffset */
562 /* Initialize this module. */
565 gdbpy_initialize_tui ()
568 gdbpy_tui_window_object_type
.tp_new
= PyType_GenericNew
;
569 if (PyType_Ready (&gdbpy_tui_window_object_type
) < 0)