Updated Serbian translations for th bfd, gold and opcodes directories
[binutils-gdb.git] / gdb / python / py-tui.c
blob089f4f877686250d39c5d17d7f71ac3d015ffc56
1 /* TUI windows implemented in Python
3 Copyright (C) 2020-2024 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/>. */
21 #include "defs.h"
22 #include "arch-utils.h"
23 #include "python-internal.h"
24 #include "gdbsupport/intrusive_list.h"
26 #ifdef TUI
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"
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"
41 class tui_py_window;
43 /* A PyObject representing a TUI window. */
45 struct gdbpy_tui_window
47 PyObject_HEAD
49 /* The TUI window, or nullptr if the window has been deleted. */
50 tui_py_window *window;
52 /* Return true if this object is valid. */
53 bool is_valid () const;
56 extern PyTypeObject gdbpy_tui_window_object_type
57 CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("gdbpy_tui_window");
59 /* A TUI window written in Python. */
61 class tui_py_window : public tui_win_info
63 public:
65 tui_py_window (const char *name, gdbpy_ref<gdbpy_tui_window> wrapper)
66 : m_name (name),
67 m_wrapper (std::move (wrapper))
69 m_wrapper->window = this;
72 ~tui_py_window ();
74 DISABLE_COPY_AND_ASSIGN (tui_py_window);
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)
81 m_window = std::move (user_window);
84 const char *name () const override
86 return m_name.c_str ();
89 void rerender () override;
90 void do_scroll_vertical (int num_to_scroll) override;
91 void do_scroll_horizontal (int num_to_scroll) override;
93 void refresh_window () override
95 if (m_inner_window != nullptr)
97 wnoutrefresh (handle.get ());
98 touchwin (m_inner_window.get ());
99 tui_wrefresh (m_inner_window.get ());
101 else
102 tui_win_info::refresh_window ();
105 void resize (int height, int width, int origin_x, int origin_y) override;
107 void click (int mouse_x, int mouse_y, int mouse_button) override;
109 /* Erase and re-box the window. */
110 void erase ()
112 if (is_visible () && m_inner_window != nullptr)
114 werase (m_inner_window.get ());
115 check_and_display_highlight_if_needed ();
119 /* Write STR to the window. FULL_WINDOW is true to erase the window
120 contents beforehand. */
121 void output (const char *str, bool full_window);
123 /* A helper function to compute the viewport width. */
124 int viewport_width () const
126 return std::max (0, width - 2);
129 /* A helper function to compute the viewport height. */
130 int viewport_height () const
132 return std::max (0, height - 2);
135 private:
137 /* The name of this window. */
138 std::string m_name;
140 /* We make our own inner window, so that it is easy to print without
141 overwriting the border. */
142 std::unique_ptr<WINDOW, curses_deleter> m_inner_window;
144 /* The underlying Python window object. */
145 gdbpy_ref<> m_window;
147 /* The Python wrapper for this object. */
148 gdbpy_ref<gdbpy_tui_window> m_wrapper;
151 /* See gdbpy_tui_window declaration above. */
153 bool
154 gdbpy_tui_window::is_valid () const
156 return window != nullptr && tui_active;
159 tui_py_window::~tui_py_window ()
161 gdbpy_enter enter_py;
163 /* This can be null if the user-provided Python construction
164 function failed. */
165 if (m_window != nullptr
166 && PyObject_HasAttrString (m_window.get (), "close"))
168 gdbpy_ref<> result (PyObject_CallMethod (m_window.get (), "close",
169 nullptr));
170 if (result == nullptr)
171 gdbpy_print_stack ();
174 /* Unlink. */
175 m_wrapper->window = nullptr;
176 /* Explicitly free the Python references. We have to do this
177 manually because we need to hold the GIL while doing so. */
178 m_wrapper.reset (nullptr);
179 m_window.reset (nullptr);
182 void
183 tui_py_window::rerender ()
185 tui_win_info::rerender ();
187 gdbpy_enter enter_py;
189 int h = viewport_height ();
190 int w = viewport_width ();
191 if (h == 0 || w == 0)
193 /* The window would be too small, so just remove the
194 contents. */
195 m_inner_window.reset (nullptr);
196 return;
198 m_inner_window.reset (newwin (h, w, y + 1, x + 1));
200 if (PyObject_HasAttrString (m_window.get (), "render"))
202 gdbpy_ref<> result (PyObject_CallMethod (m_window.get (), "render",
203 nullptr));
204 if (result == nullptr)
205 gdbpy_print_stack ();
209 void
210 tui_py_window::do_scroll_horizontal (int num_to_scroll)
212 gdbpy_enter enter_py;
214 if (PyObject_HasAttrString (m_window.get (), "hscroll"))
216 gdbpy_ref<> result (PyObject_CallMethod (m_window.get(), "hscroll",
217 "i", num_to_scroll, nullptr));
218 if (result == nullptr)
219 gdbpy_print_stack ();
223 void
224 tui_py_window::do_scroll_vertical (int num_to_scroll)
226 gdbpy_enter enter_py;
228 if (PyObject_HasAttrString (m_window.get (), "vscroll"))
230 gdbpy_ref<> result (PyObject_CallMethod (m_window.get (), "vscroll",
231 "i", num_to_scroll, nullptr));
232 if (result == nullptr)
233 gdbpy_print_stack ();
237 void
238 tui_py_window::resize (int height_, int width_, int origin_x_, int origin_y_)
240 m_inner_window.reset (nullptr);
242 tui_win_info::resize (height_, width_, origin_x_, origin_y_);
245 void
246 tui_py_window::click (int mouse_x, int mouse_y, int mouse_button)
248 gdbpy_enter enter_py;
250 if (PyObject_HasAttrString (m_window.get (), "click"))
252 gdbpy_ref<> result (PyObject_CallMethod (m_window.get (), "click",
253 "iii", mouse_x, mouse_y,
254 mouse_button));
255 if (result == nullptr)
256 gdbpy_print_stack ();
260 void
261 tui_py_window::output (const char *text, bool full_window)
263 if (m_inner_window != nullptr)
265 if (full_window)
266 werase (m_inner_window.get ());
268 tui_puts (text, m_inner_window.get ());
269 if (full_window)
270 check_and_display_highlight_if_needed ();
271 else
272 tui_wrefresh (m_inner_window.get ());
278 /* A callable that is used to create a TUI window. It wraps the
279 user-supplied window constructor. */
281 class gdbpy_tui_window_maker
282 : public intrusive_list_node<gdbpy_tui_window_maker>
284 public:
286 explicit gdbpy_tui_window_maker (gdbpy_ref<> &&constr)
287 : m_constr (std::move (constr))
289 m_window_maker_list.push_back (*this);
292 ~gdbpy_tui_window_maker ();
294 gdbpy_tui_window_maker (gdbpy_tui_window_maker &&other) noexcept
295 : m_constr (std::move (other.m_constr))
297 m_window_maker_list.push_back (*this);
300 gdbpy_tui_window_maker (const gdbpy_tui_window_maker &other)
302 gdbpy_enter enter_py;
303 m_constr = other.m_constr;
304 m_window_maker_list.push_back (*this);
307 gdbpy_tui_window_maker &operator= (gdbpy_tui_window_maker &&other)
309 m_constr = std::move (other.m_constr);
310 return *this;
313 gdbpy_tui_window_maker &operator= (const gdbpy_tui_window_maker &other)
315 gdbpy_enter enter_py;
316 m_constr = other.m_constr;
317 return *this;
320 tui_win_info *operator() (const char *name);
322 /* Reset the m_constr field of all gdbpy_tui_window_maker objects back to
323 nullptr, this will allow the Python object referenced to be
324 deallocated. This function is intended to be called when GDB is
325 shutting down the Python interpreter to allow all Python objects to be
326 deallocated and cleaned up. */
327 static void
328 invalidate_all ()
330 gdbpy_enter enter_py;
331 for (gdbpy_tui_window_maker &f : m_window_maker_list)
332 f.m_constr.reset (nullptr);
335 private:
337 /* A constructor that is called to make a TUI window. */
338 gdbpy_ref<> m_constr;
340 /* A global list of all gdbpy_tui_window_maker objects. */
341 static intrusive_list<gdbpy_tui_window_maker> m_window_maker_list;
344 /* See comment in class declaration above. */
346 intrusive_list<gdbpy_tui_window_maker>
347 gdbpy_tui_window_maker::m_window_maker_list;
349 gdbpy_tui_window_maker::~gdbpy_tui_window_maker ()
351 /* Remove this gdbpy_tui_window_maker from the global list. */
352 if (is_linked ())
353 m_window_maker_list.erase (m_window_maker_list.iterator_to (*this));
355 if (m_constr != nullptr)
357 gdbpy_enter enter_py;
358 m_constr.reset (nullptr);
362 tui_win_info *
363 gdbpy_tui_window_maker::operator() (const char *win_name)
365 gdbpy_enter enter_py;
367 gdbpy_ref<gdbpy_tui_window> wrapper
368 (PyObject_New (gdbpy_tui_window, &gdbpy_tui_window_object_type));
369 if (wrapper == nullptr)
371 gdbpy_print_stack ();
372 return nullptr;
375 std::unique_ptr<tui_py_window> window
376 (new tui_py_window (win_name, wrapper));
378 /* There's only two ways that m_constr can be reset back to nullptr,
379 first when the parent gdbpy_tui_window_maker object is deleted, in
380 which case it should be impossible to call this method, or second, as
381 a result of a gdbpy_tui_window_maker::invalidate_all call, but this is
382 only called when GDB's Python interpreter is being shut down, after
383 which, this method should not be called. */
384 gdb_assert (m_constr != nullptr);
386 gdbpy_ref<> user_window
387 (PyObject_CallFunctionObjArgs (m_constr.get (),
388 (PyObject *) wrapper.get (),
389 nullptr));
390 if (user_window == nullptr)
392 gdbpy_print_stack ();
393 return nullptr;
396 window->set_user_window (std::move (user_window));
397 /* Window is now owned by the TUI. */
398 return window.release ();
401 /* Implement "gdb.register_window_type". */
403 PyObject *
404 gdbpy_register_tui_window (PyObject *self, PyObject *args, PyObject *kw)
406 static const char *keywords[] = { "name", "constructor", nullptr };
408 const char *name;
409 PyObject *cons_obj;
411 if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "sO", keywords,
412 &name, &cons_obj))
413 return nullptr;
417 gdbpy_tui_window_maker constr (gdbpy_ref<>::new_reference (cons_obj));
418 tui_register_window (name, constr);
420 catch (const gdb_exception &except)
422 gdbpy_convert_exception (except);
423 return nullptr;
426 Py_RETURN_NONE;
431 /* Require that "Window" be a valid window. */
433 #define REQUIRE_WINDOW(Window) \
434 do { \
435 if (!(Window)->is_valid ()) \
436 return PyErr_Format (PyExc_RuntimeError, \
437 _("TUI window is invalid.")); \
438 } while (0)
440 /* Require that "Window" be a valid window. */
442 #define REQUIRE_WINDOW_FOR_SETTER(Window) \
443 do { \
444 if (!(Window)->is_valid ()) \
446 PyErr_Format (PyExc_RuntimeError, \
447 _("TUI window is invalid.")); \
448 return -1; \
450 } while (0)
452 /* Python function which checks the validity of a TUI window
453 object. */
454 static PyObject *
455 gdbpy_tui_is_valid (PyObject *self, PyObject *args)
457 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
459 if (win->is_valid ())
460 Py_RETURN_TRUE;
461 Py_RETURN_FALSE;
464 /* Python function that erases the TUI window. */
465 static PyObject *
466 gdbpy_tui_erase (PyObject *self, PyObject *args)
468 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
470 REQUIRE_WINDOW (win);
472 win->window->erase ();
474 Py_RETURN_NONE;
477 /* Python function that writes some text to a TUI window. */
478 static PyObject *
479 gdbpy_tui_write (PyObject *self, PyObject *args, PyObject *kw)
481 static const char *keywords[] = { "string", "full_window", nullptr };
483 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
484 const char *text;
485 int full_window = 0;
487 if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "s|i", keywords,
488 &text, &full_window))
489 return nullptr;
491 REQUIRE_WINDOW (win);
493 win->window->output (text, full_window);
495 Py_RETURN_NONE;
498 /* Return the width of the TUI window. */
499 static PyObject *
500 gdbpy_tui_width (PyObject *self, void *closure)
502 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
503 REQUIRE_WINDOW (win);
504 gdbpy_ref<> result
505 = gdb_py_object_from_longest (win->window->viewport_width ());
506 return result.release ();
509 /* Return the height of the TUI window. */
510 static PyObject *
511 gdbpy_tui_height (PyObject *self, void *closure)
513 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
514 REQUIRE_WINDOW (win);
515 gdbpy_ref<> result
516 = gdb_py_object_from_longest (win->window->viewport_height ());
517 return result.release ();
520 /* Return the title of the TUI window. */
521 static PyObject *
522 gdbpy_tui_title (PyObject *self, void *closure)
524 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
525 REQUIRE_WINDOW (win);
526 return host_string_to_python_string (win->window->title ().c_str ()).release ();
529 /* Set the title of the TUI window. */
530 static int
531 gdbpy_tui_set_title (PyObject *self, PyObject *newvalue, void *closure)
533 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
535 REQUIRE_WINDOW_FOR_SETTER (win);
537 if (newvalue == nullptr)
539 PyErr_Format (PyExc_TypeError, _("Cannot delete \"title\" attribute."));
540 return -1;
543 gdb::unique_xmalloc_ptr<char> value
544 = python_string_to_host_string (newvalue);
545 if (value == nullptr)
546 return -1;
548 win->window->set_title (value.get ());
549 return 0;
552 static gdb_PyGetSetDef tui_object_getset[] =
554 { "width", gdbpy_tui_width, NULL, "Width of the window.", NULL },
555 { "height", gdbpy_tui_height, NULL, "Height of the window.", NULL },
556 { "title", gdbpy_tui_title, gdbpy_tui_set_title, "Title of the window.",
557 NULL },
558 { NULL } /* Sentinel */
561 static PyMethodDef tui_object_methods[] =
563 { "is_valid", gdbpy_tui_is_valid, METH_NOARGS,
564 "is_valid () -> Boolean\n\
565 Return true if this TUI window is valid, false if not." },
566 { "erase", gdbpy_tui_erase, METH_NOARGS,
567 "Erase the TUI window." },
568 { "write", (PyCFunction) gdbpy_tui_write, METH_VARARGS | METH_KEYWORDS,
569 "Append a string to the TUI window." },
570 { NULL } /* Sentinel. */
573 PyTypeObject gdbpy_tui_window_object_type =
575 PyVarObject_HEAD_INIT (NULL, 0)
576 "gdb.TuiWindow", /*tp_name*/
577 sizeof (gdbpy_tui_window), /*tp_basicsize*/
578 0, /*tp_itemsize*/
579 0, /*tp_dealloc*/
580 0, /*tp_print*/
581 0, /*tp_getattr*/
582 0, /*tp_setattr*/
583 0, /*tp_compare*/
584 0, /*tp_repr*/
585 0, /*tp_as_number*/
586 0, /*tp_as_sequence*/
587 0, /*tp_as_mapping*/
588 0, /*tp_hash */
589 0, /*tp_call*/
590 0, /*tp_str*/
591 0, /*tp_getattro*/
592 0, /*tp_setattro */
593 0, /*tp_as_buffer*/
594 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
595 "GDB TUI window object", /* tp_doc */
596 0, /* tp_traverse */
597 0, /* tp_clear */
598 0, /* tp_richcompare */
599 0, /* tp_weaklistoffset */
600 0, /* tp_iter */
601 0, /* tp_iternext */
602 tui_object_methods, /* tp_methods */
603 0, /* tp_members */
604 tui_object_getset, /* tp_getset */
605 0, /* tp_base */
606 0, /* tp_dict */
607 0, /* tp_descr_get */
608 0, /* tp_descr_set */
609 0, /* tp_dictoffset */
610 0, /* tp_init */
611 0, /* tp_alloc */
614 #endif /* TUI */
616 /* Initialize this module. */
618 static int CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION
619 gdbpy_initialize_tui ()
621 #ifdef TUI
622 gdbpy_tui_window_object_type.tp_new = PyType_GenericNew;
623 if (PyType_Ready (&gdbpy_tui_window_object_type) < 0)
624 return -1;
625 #endif /* TUI */
627 return 0;
630 /* Finalize this module. */
632 static void
633 gdbpy_finalize_tui ()
635 #ifdef TUI
636 gdbpy_tui_window_maker::invalidate_all ();
637 #endif /* TUI */
640 GDBPY_INITIALIZE_FILE (gdbpy_initialize_tui, gdbpy_finalize_tui);