]> git.ipfire.org Git - thirdparty/binutils-gdb.git/blame - gdb/python/py-tui.c
Automatic Copyright Year update after running gdb/copyright.py
[thirdparty/binutils-gdb.git] / gdb / python / py-tui.c
CommitLineData
01b1af32
TT
1/* TUI windows implemented in Python
2
4a94e368 3 Copyright (C) 2020-2022 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
21#include "defs.h"
22#include "arch-utils.h"
23#include "python-internal.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"
39
40class tui_py_window;
41
42/* A PyObject representing a TUI window. */
43
44struct gdbpy_tui_window
45{
46 PyObject_HEAD
47
48 /* The TUI window, or nullptr if the window has been deleted. */
49 tui_py_window *window;
29db1eb3
AB
50
51 /* Return true if this object is valid. */
52 bool is_valid () const;
01b1af32
TT
53};
54
55extern PyTypeObject gdbpy_tui_window_object_type
56 CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("gdbpy_tui_window");
57
58/* A TUI window written in Python. */
59
60class tui_py_window : public tui_win_info
61{
62public:
63
64 tui_py_window (const char *name, gdbpy_ref<gdbpy_tui_window> wrapper)
65 : m_name (name),
66 m_wrapper (std::move (wrapper))
67 {
68 m_wrapper->window = this;
69 }
70
71 ~tui_py_window ();
72
73 DISABLE_COPY_AND_ASSIGN (tui_py_window);
74
75 /* Set the "user window" to the indicated reference. The user
76 window is the object returned the by user-defined window
77 constructor. */
78 void set_user_window (gdbpy_ref<> &&user_window)
79 {
80 m_window = std::move (user_window);
81 }
82
83 const char *name () const override
84 {
85 return m_name.c_str ();
86 }
87
88 void rerender () override;
89 void do_scroll_vertical (int num_to_scroll) override;
90 void do_scroll_horizontal (int num_to_scroll) override;
91
149830c1
TT
92 void refresh_window () override
93 {
149830c1
TT
94 if (m_inner_window != nullptr)
95 {
a56889ae 96 wnoutrefresh (handle.get ());
149830c1
TT
97 touchwin (m_inner_window.get ());
98 tui_wrefresh (m_inner_window.get ());
99 }
a56889ae
HD
100 else
101 tui_win_info::refresh_window ();
149830c1
TT
102 }
103
a5375566
HD
104 void click (int mouse_x, int mouse_y, int mouse_button) override;
105
01b1af32
TT
106 /* Erase and re-box the window. */
107 void erase ()
108 {
149830c1 109 if (is_visible () && m_inner_window != nullptr)
01b1af32 110 {
149830c1 111 werase (m_inner_window.get ());
01b1af32 112 check_and_display_highlight_if_needed ();
01b1af32
TT
113 }
114 }
115
bdef5723
HD
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);
01b1af32
TT
119
120 /* A helper function to compute the viewport width. */
121 int viewport_width () const
122 {
123 return std::max (0, width - 2);
124 }
125
126 /* A helper function to compute the viewport height. */
127 int viewport_height () const
128 {
129 return std::max (0, height - 2);
130 }
131
132private:
133
01b1af32
TT
134 /* The name of this window. */
135 std::string m_name;
136
149830c1
TT
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;
140
01b1af32
TT
141 /* The underlying Python window object. */
142 gdbpy_ref<> m_window;
143
144 /* The Python wrapper for this object. */
145 gdbpy_ref<gdbpy_tui_window> m_wrapper;
146};
147
29db1eb3
AB
148/* See gdbpy_tui_window declaration above. */
149
150bool
151gdbpy_tui_window::is_valid () const
152{
153 return window != nullptr && tui_active;
154}
155
01b1af32
TT
156tui_py_window::~tui_py_window ()
157{
158 gdbpy_enter enter_py (get_current_arch (), current_language);
159
d2d1ea20
TT
160 /* This can be null if the user-provided Python construction
161 function failed. */
162 if (m_window != nullptr
163 && PyObject_HasAttrString (m_window.get (), "close"))
01b1af32
TT
164 {
165 gdbpy_ref<> result (PyObject_CallMethod (m_window.get (), "close",
166 nullptr));
167 if (result == nullptr)
168 gdbpy_print_stack ();
169 }
170
171 /* Unlink. */
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);
177}
178
179void
180tui_py_window::rerender ()
181{
149830c1
TT
182 tui_win_info::rerender ();
183
01b1af32
TT
184 gdbpy_enter enter_py (get_current_arch (), current_language);
185
149830c1
TT
186 int h = viewport_height ();
187 int w = viewport_width ();
188 if (h == 0 || w == 0)
189 {
190 /* The window would be too small, so just remove the
191 contents. */
192 m_inner_window.reset (nullptr);
193 return;
194 }
195 m_inner_window.reset (newwin (h, w, y + 1, x + 1));
196
01b1af32
TT
197 if (PyObject_HasAttrString (m_window.get (), "render"))
198 {
199 gdbpy_ref<> result (PyObject_CallMethod (m_window.get (), "render",
200 nullptr));
201 if (result == nullptr)
202 gdbpy_print_stack ();
203 }
204}
205
206void
207tui_py_window::do_scroll_horizontal (int num_to_scroll)
208{
209 gdbpy_enter enter_py (get_current_arch (), current_language);
210
211 if (PyObject_HasAttrString (m_window.get (), "hscroll"))
212 {
213 gdbpy_ref<> result (PyObject_CallMethod (m_window.get(), "hscroll",
214 "i", num_to_scroll, nullptr));
215 if (result == nullptr)
216 gdbpy_print_stack ();
217 }
218}
219
220void
221tui_py_window::do_scroll_vertical (int num_to_scroll)
222{
223 gdbpy_enter enter_py (get_current_arch (), current_language);
224
225 if (PyObject_HasAttrString (m_window.get (), "vscroll"))
226 {
227 gdbpy_ref<> result (PyObject_CallMethod (m_window.get (), "vscroll",
228 "i", num_to_scroll, nullptr));
229 if (result == nullptr)
230 gdbpy_print_stack ();
231 }
232}
233
a5375566
HD
234void
235tui_py_window::click (int mouse_x, int mouse_y, int mouse_button)
236{
237 gdbpy_enter enter_py (get_current_arch (), current_language);
238
239 if (PyObject_HasAttrString (m_window.get (), "click"))
240 {
241 gdbpy_ref<> result (PyObject_CallMethod (m_window.get (), "click",
242 "iii", mouse_x, mouse_y,
243 mouse_button));
244 if (result == nullptr)
245 gdbpy_print_stack ();
246 }
247}
248
01b1af32 249void
bdef5723 250tui_py_window::output (const char *text, bool full_window)
01b1af32 251{
149830c1 252 if (m_inner_window != nullptr)
01b1af32 253 {
bdef5723
HD
254 if (full_window)
255 werase (m_inner_window.get ());
256
149830c1 257 tui_puts (text, m_inner_window.get ());
bdef5723
HD
258 if (full_window)
259 check_and_display_highlight_if_needed ();
260 else
261 tui_wrefresh (m_inner_window.get ());
01b1af32 262 }
01b1af32
TT
263}
264
265\f
266
267/* A callable that is used to create a TUI window. It wraps the
268 user-supplied window constructor. */
269
270class gdbpy_tui_window_maker
271{
272public:
273
274 explicit gdbpy_tui_window_maker (gdbpy_ref<> &&constr)
275 : m_constr (std::move (constr))
276 {
277 }
278
279 ~gdbpy_tui_window_maker ();
280
0fa7617d 281 gdbpy_tui_window_maker (gdbpy_tui_window_maker &&other) noexcept
01b1af32
TT
282 : m_constr (std::move (other.m_constr))
283 {
284 }
285
286 gdbpy_tui_window_maker (const gdbpy_tui_window_maker &other)
287 {
288 gdbpy_enter enter_py (get_current_arch (), current_language);
289 m_constr = other.m_constr;
290 }
291
292 gdbpy_tui_window_maker &operator= (gdbpy_tui_window_maker &&other)
293 {
294 m_constr = std::move (other.m_constr);
295 return *this;
296 }
297
298 gdbpy_tui_window_maker &operator= (const gdbpy_tui_window_maker &other)
299 {
300 gdbpy_enter enter_py (get_current_arch (), current_language);
301 m_constr = other.m_constr;
302 return *this;
303 }
304
305 tui_win_info *operator() (const char *name);
306
307private:
308
309 /* A constructor that is called to make a TUI window. */
310 gdbpy_ref<> m_constr;
311};
312
313gdbpy_tui_window_maker::~gdbpy_tui_window_maker ()
314{
315 gdbpy_enter enter_py (get_current_arch (), current_language);
316 m_constr.reset (nullptr);
317}
318
319tui_win_info *
320gdbpy_tui_window_maker::operator() (const char *win_name)
321{
322 gdbpy_enter enter_py (get_current_arch (), current_language);
323
324 gdbpy_ref<gdbpy_tui_window> wrapper
325 (PyObject_New (gdbpy_tui_window, &gdbpy_tui_window_object_type));
326 if (wrapper == nullptr)
327 {
328 gdbpy_print_stack ();
329 return nullptr;
330 }
331
332 std::unique_ptr<tui_py_window> window
333 (new tui_py_window (win_name, wrapper));
334
335 gdbpy_ref<> user_window
336 (PyObject_CallFunctionObjArgs (m_constr.get (),
337 (PyObject *) wrapper.get (),
338 nullptr));
339 if (user_window == nullptr)
340 {
341 gdbpy_print_stack ();
342 return nullptr;
343 }
344
345 window->set_user_window (std::move (user_window));
346 /* Window is now owned by the TUI. */
347 return window.release ();
348}
349
350/* Implement "gdb.register_window_type". */
351
352PyObject *
353gdbpy_register_tui_window (PyObject *self, PyObject *args, PyObject *kw)
354{
355 static const char *keywords[] = { "name", "constructor", nullptr };
356
357 const char *name;
358 PyObject *cons_obj;
359
360 if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "sO", keywords,
361 &name, &cons_obj))
362 return nullptr;
363
364 try
365 {
366 gdbpy_tui_window_maker constr (gdbpy_ref<>::new_reference (cons_obj));
367 tui_register_window (name, constr);
368 }
369 catch (const gdb_exception &except)
370 {
371 gdbpy_convert_exception (except);
372 return nullptr;
373 }
374
375 Py_RETURN_NONE;
376}
377
378\f
379
380/* Require that "Window" be a valid window. */
381
382#define REQUIRE_WINDOW(Window) \
383 do { \
29db1eb3 384 if (!(Window)->is_valid ()) \
dda83cd7
SM
385 return PyErr_Format (PyExc_RuntimeError, \
386 _("TUI window is invalid.")); \
01b1af32
TT
387 } while (0)
388
29db1eb3
AB
389/* Require that "Window" be a valid window. */
390
391#define REQUIRE_WINDOW_FOR_SETTER(Window) \
392 do { \
393 if (!(Window)->is_valid ()) \
394 { \
395 PyErr_Format (PyExc_RuntimeError, \
396 _("TUI window is invalid.")); \
397 return -1; \
398 } \
399 } while (0)
400
01b1af32
TT
401/* Python function which checks the validity of a TUI window
402 object. */
403static PyObject *
404gdbpy_tui_is_valid (PyObject *self, PyObject *args)
405{
406 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
407
29db1eb3 408 if (win->is_valid ())
01b1af32
TT
409 Py_RETURN_TRUE;
410 Py_RETURN_FALSE;
411}
412
413/* Python function that erases the TUI window. */
414static PyObject *
415gdbpy_tui_erase (PyObject *self, PyObject *args)
416{
417 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
418
419 REQUIRE_WINDOW (win);
420
421 win->window->erase ();
422
423 Py_RETURN_NONE;
424}
425
426/* Python function that writes some text to a TUI window. */
427static PyObject *
428gdbpy_tui_write (PyObject *self, PyObject *args)
429{
430 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
431 const char *text;
bdef5723 432 int full_window = 0;
01b1af32 433
bdef5723 434 if (!PyArg_ParseTuple (args, "s|i", &text, &full_window))
01b1af32
TT
435 return nullptr;
436
437 REQUIRE_WINDOW (win);
438
bdef5723 439 win->window->output (text, full_window);
01b1af32
TT
440
441 Py_RETURN_NONE;
442}
443
444/* Return the width of the TUI window. */
445static PyObject *
446gdbpy_tui_width (PyObject *self, void *closure)
447{
448 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
449 REQUIRE_WINDOW (win);
062534d4
TT
450 gdbpy_ref<> result
451 = gdb_py_object_from_longest (win->window->viewport_width ());
452 return result.release ();
01b1af32
TT
453}
454
455/* Return the height of the TUI window. */
456static PyObject *
457gdbpy_tui_height (PyObject *self, void *closure)
458{
459 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
460 REQUIRE_WINDOW (win);
062534d4
TT
461 gdbpy_ref<> result
462 = gdb_py_object_from_longest (win->window->viewport_height ());
463 return result.release ();
01b1af32
TT
464}
465
466/* Return the title of the TUI window. */
467static PyObject *
468gdbpy_tui_title (PyObject *self, void *closure)
469{
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 ();
473}
474
475/* Set the title of the TUI window. */
476static int
477gdbpy_tui_set_title (PyObject *self, PyObject *newvalue, void *closure)
478{
479 gdbpy_tui_window *win = (gdbpy_tui_window *) self;
480
29db1eb3 481 REQUIRE_WINDOW_FOR_SETTER (win);
01b1af32 482
e0c23e11 483 if (newvalue == nullptr)
01b1af32
TT
484 {
485 PyErr_Format (PyExc_TypeError, _("Cannot delete \"title\" attribute."));
486 return -1;
487 }
488
489 gdb::unique_xmalloc_ptr<char> value
490 = python_string_to_host_string (newvalue);
491 if (value == nullptr)
492 return -1;
493
940dace9 494 win->window->title = value.get ();
01b1af32
TT
495 return 0;
496}
497
498static gdb_PyGetSetDef tui_object_getset[] =
499{
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.",
503 NULL },
504 { NULL } /* Sentinel */
505};
506
507static PyMethodDef tui_object_methods[] =
508{
509 { "is_valid", gdbpy_tui_is_valid, METH_NOARGS,
510 "is_valid () -> Boolean\n\
511Return 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. */
517};
518
519PyTypeObject gdbpy_tui_window_object_type =
520{
521 PyVarObject_HEAD_INIT (NULL, 0)
522 "gdb.TuiWindow", /*tp_name*/
523 sizeof (gdbpy_tui_window), /*tp_basicsize*/
524 0, /*tp_itemsize*/
525 0, /*tp_dealloc*/
526 0, /*tp_print*/
527 0, /*tp_getattr*/
528 0, /*tp_setattr*/
529 0, /*tp_compare*/
530 0, /*tp_repr*/
531 0, /*tp_as_number*/
532 0, /*tp_as_sequence*/
533 0, /*tp_as_mapping*/
534 0, /*tp_hash */
535 0, /*tp_call*/
536 0, /*tp_str*/
537 0, /*tp_getattro*/
538 0, /*tp_setattro */
539 0, /*tp_as_buffer*/
540 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
541 "GDB TUI window object", /* tp_doc */
542 0, /* tp_traverse */
543 0, /* tp_clear */
544 0, /* tp_richcompare */
545 0, /* tp_weaklistoffset */
546 0, /* tp_iter */
547 0, /* tp_iternext */
548 tui_object_methods, /* tp_methods */
549 0, /* tp_members */
550 tui_object_getset, /* tp_getset */
551 0, /* tp_base */
552 0, /* tp_dict */
553 0, /* tp_descr_get */
554 0, /* tp_descr_set */
555 0, /* tp_dictoffset */
556 0, /* tp_init */
557 0, /* tp_alloc */
558};
559
560#endif /* TUI */
561
562/* Initialize this module. */
563
564int
565gdbpy_initialize_tui ()
566{
567#ifdef TUI
568 gdbpy_tui_window_object_type.tp_new = PyType_GenericNew;
569 if (PyType_Ready (&gdbpy_tui_window_object_type) < 0)
570 return -1;
571#endif /* TUI */
572
573 return 0;
574}