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