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