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