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 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 /* Erase and re-box the window. */ 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. */ 105 int viewport_width () const 106 { 107 return std::max (0, width - 2); 108 } 109 110 /* A helper function to compute the 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 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 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 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 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 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 232 explicit gdbpy_tui_window_maker (gdbpy_ref<> &&constr) 233 : m_constr (std::move (constr)) 234 { 235 } 236 237 ~gdbpy_tui_window_maker (); 238 239 gdbpy_tui_window_maker (gdbpy_tui_window_maker &&other) noexcept 240 : m_constr (std::move (other.m_constr)) 241 { 242 } 243 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 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 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 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