xref: /netbsd-src/external/gpl3/gdb.old/dist/gdb/python/py-tui.c (revision 6881a4007f077b54e5f51159c52b9b25f57deb0d)
1 /* TUI windows implemented in Python
2 
3    Copyright (C) 2020-2023 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   /* Return true if this object is valid.  */
52   bool is_valid () const;
53 };
54 
55 extern PyTypeObject gdbpy_tui_window_object_type
56     CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("gdbpy_tui_window");
57 
58 /* A TUI window written in Python.  */
59 
60 class tui_py_window : public tui_win_info
61 {
62 public:
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 
92   void refresh_window () override
93   {
94     if (m_inner_window != nullptr)
95       {
96 	wnoutrefresh (handle.get ());
97 	touchwin (m_inner_window.get ());
98 	tui_wrefresh (m_inner_window.get ());
99       }
100     else
101       tui_win_info::refresh_window ();
102   }
103 
104   void click (int mouse_x, int mouse_y, int mouse_button) override;
105 
106   /* Erase and re-box the window.  */
107   void erase ()
108   {
109     if (is_visible () && m_inner_window != nullptr)
110       {
111 	werase (m_inner_window.get ());
112 	check_and_display_highlight_if_needed ();
113       }
114   }
115 
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);
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 
132 private:
133 
134   /* The name of this window.  */
135   std::string m_name;
136 
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 
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 
148 /* See gdbpy_tui_window declaration above.  */
149 
150 bool
151 gdbpy_tui_window::is_valid () const
152 {
153   return window != nullptr && tui_active;
154 }
155 
156 tui_py_window::~tui_py_window ()
157 {
158   gdbpy_enter enter_py;
159 
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"))
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 
179 void
180 tui_py_window::rerender ()
181 {
182   tui_win_info::rerender ();
183 
184   gdbpy_enter enter_py;
185 
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 
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 
206 void
207 tui_py_window::do_scroll_horizontal (int num_to_scroll)
208 {
209   gdbpy_enter enter_py;
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 
220 void
221 tui_py_window::do_scroll_vertical (int num_to_scroll)
222 {
223   gdbpy_enter enter_py;
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 
234 void
235 tui_py_window::click (int mouse_x, int mouse_y, int mouse_button)
236 {
237   gdbpy_enter enter_py;
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 
249 void
250 tui_py_window::output (const char *text, bool full_window)
251 {
252   if (m_inner_window != nullptr)
253     {
254       if (full_window)
255 	werase (m_inner_window.get ());
256 
257       tui_puts (text, m_inner_window.get ());
258       if (full_window)
259 	check_and_display_highlight_if_needed ();
260       else
261 	tui_wrefresh (m_inner_window.get ());
262     }
263 }
264 
265 
266 
267 /* A callable that is used to create a TUI window.  It wraps the
268    user-supplied window constructor.  */
269 
270 class gdbpy_tui_window_maker
271 {
272 public:
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 
281   gdbpy_tui_window_maker (gdbpy_tui_window_maker &&other) noexcept
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;
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;
301     m_constr = other.m_constr;
302     return *this;
303   }
304 
305   tui_win_info *operator() (const char *name);
306 
307 private:
308 
309   /* A constructor that is called to make a TUI window.  */
310   gdbpy_ref<> m_constr;
311 };
312 
313 gdbpy_tui_window_maker::~gdbpy_tui_window_maker ()
314 {
315   gdbpy_enter enter_py;
316   m_constr.reset (nullptr);
317 }
318 
319 tui_win_info *
320 gdbpy_tui_window_maker::operator() (const char *win_name)
321 {
322   gdbpy_enter enter_py;
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 
352 PyObject *
353 gdbpy_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 
379 
380 /* Require that "Window" be a valid window.  */
381 
382 #define REQUIRE_WINDOW(Window)					\
383     do {							\
384       if (!(Window)->is_valid ())				\
385 	return PyErr_Format (PyExc_RuntimeError,		\
386 			     _("TUI window is invalid."));	\
387     } while (0)
388 
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 
401 /* Python function which checks the validity of a TUI window
402    object.  */
403 static PyObject *
404 gdbpy_tui_is_valid (PyObject *self, PyObject *args)
405 {
406   gdbpy_tui_window *win = (gdbpy_tui_window *) self;
407 
408   if (win->is_valid ())
409     Py_RETURN_TRUE;
410   Py_RETURN_FALSE;
411 }
412 
413 /* Python function that erases the TUI window.  */
414 static PyObject *
415 gdbpy_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.  */
427 static PyObject *
428 gdbpy_tui_write (PyObject *self, PyObject *args)
429 {
430   gdbpy_tui_window *win = (gdbpy_tui_window *) self;
431   const char *text;
432   int full_window = 0;
433 
434   if (!PyArg_ParseTuple (args, "s|i", &text, &full_window))
435     return nullptr;
436 
437   REQUIRE_WINDOW (win);
438 
439   win->window->output (text, full_window);
440 
441   Py_RETURN_NONE;
442 }
443 
444 /* Return the width of the TUI window.  */
445 static PyObject *
446 gdbpy_tui_width (PyObject *self, void *closure)
447 {
448   gdbpy_tui_window *win = (gdbpy_tui_window *) self;
449   REQUIRE_WINDOW (win);
450   gdbpy_ref<> result
451     = gdb_py_object_from_longest (win->window->viewport_width ());
452   return result.release ();
453 }
454 
455 /* Return the height of the TUI window.  */
456 static PyObject *
457 gdbpy_tui_height (PyObject *self, void *closure)
458 {
459   gdbpy_tui_window *win = (gdbpy_tui_window *) self;
460   REQUIRE_WINDOW (win);
461   gdbpy_ref<> result
462     = gdb_py_object_from_longest (win->window->viewport_height ());
463   return result.release ();
464 }
465 
466 /* Return the title of the TUI window.  */
467 static PyObject *
468 gdbpy_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.  */
476 static int
477 gdbpy_tui_set_title (PyObject *self, PyObject *newvalue, void *closure)
478 {
479   gdbpy_tui_window *win = (gdbpy_tui_window *) self;
480 
481   REQUIRE_WINDOW_FOR_SETTER (win);
482 
483   if (newvalue == nullptr)
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 
494   win->window->title = value.get ();
495   return 0;
496 }
497 
498 static 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 
507 static PyMethodDef tui_object_methods[] =
508 {
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.  */
517 };
518 
519 PyTypeObject 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 
564 int
565 gdbpy_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 }
575