(root)/
Python-3.12.0/
Modules/
_curses_panel.c
       1  /*
       2   *   Interface to the ncurses panel library
       3   *
       4   * Original version by Thomas Gellekum
       5   */
       6  
       7  /* Release Number */
       8  
       9  static const char PyCursesVersion[] = "2.1";
      10  
      11  /* Includes */
      12  
      13  #include "Python.h"
      14  
      15  #include "py_curses.h"
      16  
      17  #include <panel.h>
      18  
      19  typedef struct {
      20      PyObject *PyCursesError;
      21      PyTypeObject *PyCursesPanel_Type;
      22  } _curses_panel_state;
      23  
      24  static inline _curses_panel_state *
      25  get_curses_panel_state(PyObject *module)
      26  {
      27      void *state = PyModule_GetState(module);
      28      assert(state != NULL);
      29      return (_curses_panel_state *)state;
      30  }
      31  
      32  static int
      33  _curses_panel_clear(PyObject *mod)
      34  {
      35      _curses_panel_state *state = get_curses_panel_state(mod);
      36      Py_CLEAR(state->PyCursesError);
      37      Py_CLEAR(state->PyCursesPanel_Type);
      38      return 0;
      39  }
      40  
      41  static int
      42  _curses_panel_traverse(PyObject *mod, visitproc visit, void *arg)
      43  {
      44      Py_VISIT(Py_TYPE(mod));
      45      _curses_panel_state *state = get_curses_panel_state(mod);
      46      Py_VISIT(state->PyCursesError);
      47      Py_VISIT(state->PyCursesPanel_Type);
      48      return 0;
      49  }
      50  
      51  static void
      52  _curses_panel_free(void *mod)
      53  {
      54      _curses_panel_clear((PyObject *) mod);
      55  }
      56  
      57  /* Utility Functions */
      58  
      59  /*
      60   * Check the return code from a curses function and return None
      61   * or raise an exception as appropriate.
      62   */
      63  
      64  static PyObject *
      65  PyCursesCheckERR(_curses_panel_state *state, int code, const char *fname)
      66  {
      67      if (code != ERR) {
      68          Py_RETURN_NONE;
      69      }
      70      else {
      71          if (fname == NULL) {
      72              PyErr_SetString(state->PyCursesError, catchall_ERR);
      73          }
      74          else {
      75              PyErr_Format(state->PyCursesError, "%s() returned ERR", fname);
      76          }
      77          return NULL;
      78      }
      79  }
      80  
      81  /*****************************************************************************
      82   The Panel Object
      83  ******************************************************************************/
      84  
      85  /* Definition of the panel object and panel type */
      86  
      87  typedef struct {
      88      PyObject_HEAD
      89      PANEL *pan;
      90      PyCursesWindowObject *wo;   /* for reference counts */
      91  } PyCursesPanelObject;
      92  
      93  /* Some helper functions. The problem is that there's always a window
      94     associated with a panel. To ensure that Python's GC doesn't pull
      95     this window from under our feet we need to keep track of references
      96     to the corresponding window object within Python. We can't use
      97     dupwin(oldwin) to keep a copy of the curses WINDOW because the
      98     contents of oldwin is copied only once; code like
      99  
     100     win = newwin(...)
     101     pan = win.panel()
     102     win.addstr(some_string)
     103     pan.window().addstr(other_string)
     104  
     105     will fail. */
     106  
     107  /* We keep a linked list of PyCursesPanelObjects, lop. A list should
     108     suffice, I don't expect more than a handful or at most a few
     109     dozens of panel objects within a typical program. */
     110  typedef struct _list_of_panels {
     111      PyCursesPanelObject *po;
     112      struct _list_of_panels *next;
     113  } list_of_panels;
     114  
     115  /* list anchor */
     116  static list_of_panels *lop;
     117  
     118  /* Insert a new panel object into lop */
     119  static int
     120  insert_lop(PyCursesPanelObject *po)
     121  {
     122      list_of_panels *new;
     123  
     124      if ((new = (list_of_panels *)PyMem_Malloc(sizeof(list_of_panels))) == NULL) {
     125          PyErr_NoMemory();
     126          return -1;
     127      }
     128      new->po = po;
     129      new->next = lop;
     130      lop = new;
     131      return 0;
     132  }
     133  
     134  /* Remove the panel object from lop */
     135  static void
     136  remove_lop(PyCursesPanelObject *po)
     137  {
     138      list_of_panels *temp, *n;
     139  
     140      temp = lop;
     141      if (temp->po == po) {
     142          lop = temp->next;
     143          PyMem_Free(temp);
     144          return;
     145      }
     146      while (temp->next == NULL || temp->next->po != po) {
     147          if (temp->next == NULL) {
     148              PyErr_SetString(PyExc_RuntimeError,
     149                              "remove_lop: can't find Panel Object");
     150              return;
     151          }
     152          temp = temp->next;
     153      }
     154      n = temp->next->next;
     155      PyMem_Free(temp->next);
     156      temp->next = n;
     157      return;
     158  }
     159  
     160  /* Return the panel object that corresponds to pan */
     161  static PyCursesPanelObject *
     162  find_po(PANEL *pan)
     163  {
     164      list_of_panels *temp;
     165      for (temp = lop; temp->po->pan != pan; temp = temp->next)
     166          if (temp->next == NULL) return NULL;    /* not found!? */
     167      return temp->po;
     168  }
     169  
     170  /*[clinic input]
     171  module _curses_panel
     172  class _curses_panel.panel "PyCursesPanelObject *" "&PyCursesPanel_Type"
     173  [clinic start generated code]*/
     174  /*[clinic end generated code: output=da39a3ee5e6b4b0d input=2f4ef263ca850a31]*/
     175  
     176  #include "clinic/_curses_panel.c.h"
     177  
     178  /* ------------- PANEL routines --------------- */
     179  
     180  /*[clinic input]
     181  _curses_panel.panel.bottom
     182  
     183      cls: defining_class
     184  
     185  Push the panel to the bottom of the stack.
     186  [clinic start generated code]*/
     187  
     188  static PyObject *
     189  _curses_panel_panel_bottom_impl(PyCursesPanelObject *self, PyTypeObject *cls)
     190  /*[clinic end generated code: output=8ec7fbbc08554021 input=6b7d2c0578b5a1c4]*/
     191  {
     192      _curses_panel_state *state = PyType_GetModuleState(cls);
     193      return PyCursesCheckERR(state, bottom_panel(self->pan), "bottom");
     194  }
     195  
     196  /*[clinic input]
     197  _curses_panel.panel.hide
     198  
     199      cls: defining_class
     200  
     201  Hide the panel.
     202  
     203  This does not delete the object, it just makes the window on screen invisible.
     204  [clinic start generated code]*/
     205  
     206  static PyObject *
     207  _curses_panel_panel_hide_impl(PyCursesPanelObject *self, PyTypeObject *cls)
     208  /*[clinic end generated code: output=cc6ab7203cdc1450 input=1bfc741f473e6055]*/
     209  {
     210      _curses_panel_state *state = PyType_GetModuleState(cls);
     211      return PyCursesCheckERR(state, hide_panel(self->pan), "hide");
     212  }
     213  
     214  /*[clinic input]
     215  _curses_panel.panel.show
     216  
     217      cls: defining_class
     218  
     219  Display the panel (which might have been hidden).
     220  [clinic start generated code]*/
     221  
     222  static PyObject *
     223  _curses_panel_panel_show_impl(PyCursesPanelObject *self, PyTypeObject *cls)
     224  /*[clinic end generated code: output=dc3421de375f0409 input=8122e80151cb4379]*/
     225  {
     226      _curses_panel_state *state = PyType_GetModuleState(cls);
     227      return PyCursesCheckERR(state, show_panel(self->pan), "show");
     228  }
     229  
     230  /*[clinic input]
     231  _curses_panel.panel.top
     232  
     233      cls: defining_class
     234  
     235  Push panel to the top of the stack.
     236  [clinic start generated code]*/
     237  
     238  static PyObject *
     239  _curses_panel_panel_top_impl(PyCursesPanelObject *self, PyTypeObject *cls)
     240  /*[clinic end generated code: output=10a072e511e873f7 input=1f372d597dda3379]*/
     241  {
     242      _curses_panel_state *state = PyType_GetModuleState(cls);
     243      return PyCursesCheckERR(state, top_panel(self->pan), "top");
     244  }
     245  
     246  /* Allocation and deallocation of Panel Objects */
     247  
     248  static PyObject *
     249  PyCursesPanel_New(_curses_panel_state *state, PANEL *pan,
     250                    PyCursesWindowObject *wo)
     251  {
     252      PyCursesPanelObject *po = PyObject_New(PyCursesPanelObject,
     253                                             state->PyCursesPanel_Type);
     254      if (po == NULL) {
     255          return NULL;
     256      }
     257  
     258      po->pan = pan;
     259      if (insert_lop(po) < 0) {
     260          po->wo = NULL;
     261          Py_DECREF(po);
     262          return NULL;
     263      }
     264      po->wo = (PyCursesWindowObject*)Py_NewRef(wo);
     265      return (PyObject *)po;
     266  }
     267  
     268  static void
     269  PyCursesPanel_Dealloc(PyCursesPanelObject *po)
     270  {
     271      PyObject *tp, *obj;
     272  
     273      tp = (PyObject *) Py_TYPE(po);
     274      obj = (PyObject *) panel_userptr(po->pan);
     275      if (obj) {
     276          (void)set_panel_userptr(po->pan, NULL);
     277          Py_DECREF(obj);
     278      }
     279      (void)del_panel(po->pan);
     280      if (po->wo != NULL) {
     281          Py_DECREF(po->wo);
     282          remove_lop(po);
     283      }
     284      PyObject_Free(po);
     285      Py_DECREF(tp);
     286  }
     287  
     288  /* panel_above(NULL) returns the bottom panel in the stack. To get
     289     this behaviour we use curses.panel.bottom_panel(). */
     290  /*[clinic input]
     291  _curses_panel.panel.above
     292  
     293  Return the panel above the current panel.
     294  [clinic start generated code]*/
     295  
     296  static PyObject *
     297  _curses_panel_panel_above_impl(PyCursesPanelObject *self)
     298  /*[clinic end generated code: output=70ac06d25fd3b4da input=c059994022976788]*/
     299  {
     300      PANEL *pan;
     301      PyCursesPanelObject *po;
     302  
     303      pan = panel_above(self->pan);
     304  
     305      if (pan == NULL) {          /* valid output, it means the calling panel
     306                                     is on top of the stack */
     307          Py_RETURN_NONE;
     308      }
     309      po = find_po(pan);
     310      if (po == NULL) {
     311          PyErr_SetString(PyExc_RuntimeError,
     312                          "panel_above: can't find Panel Object");
     313          return NULL;
     314      }
     315      return Py_NewRef(po);
     316  }
     317  
     318  /* panel_below(NULL) returns the top panel in the stack. To get
     319     this behaviour we use curses.panel.top_panel(). */
     320  /*[clinic input]
     321  _curses_panel.panel.below
     322  
     323  Return the panel below the current panel.
     324  [clinic start generated code]*/
     325  
     326  static PyObject *
     327  _curses_panel_panel_below_impl(PyCursesPanelObject *self)
     328  /*[clinic end generated code: output=282861122e06e3de input=cc08f61936d297c6]*/
     329  {
     330      PANEL *pan;
     331      PyCursesPanelObject *po;
     332  
     333      pan = panel_below(self->pan);
     334  
     335      if (pan == NULL) {          /* valid output, it means the calling panel
     336                                     is on the bottom of the stack */
     337          Py_RETURN_NONE;
     338      }
     339      po = find_po(pan);
     340      if (po == NULL) {
     341          PyErr_SetString(PyExc_RuntimeError,
     342                          "panel_below: can't find Panel Object");
     343          return NULL;
     344      }
     345      return Py_NewRef(po);
     346  }
     347  
     348  /*[clinic input]
     349  _curses_panel.panel.hidden
     350  
     351  Return True if the panel is hidden (not visible), False otherwise.
     352  [clinic start generated code]*/
     353  
     354  static PyObject *
     355  _curses_panel_panel_hidden_impl(PyCursesPanelObject *self)
     356  /*[clinic end generated code: output=66eebd1ab4501a71 input=453d4b4fce25e21a]*/
     357  {
     358      if (panel_hidden(self->pan))
     359          Py_RETURN_TRUE;
     360      else
     361          Py_RETURN_FALSE;
     362  }
     363  
     364  /*[clinic input]
     365  _curses_panel.panel.move
     366  
     367      cls: defining_class
     368      y: int
     369      x: int
     370      /
     371  
     372  Move the panel to the screen coordinates (y, x).
     373  [clinic start generated code]*/
     374  
     375  static PyObject *
     376  _curses_panel_panel_move_impl(PyCursesPanelObject *self, PyTypeObject *cls,
     377                                int y, int x)
     378  /*[clinic end generated code: output=ce546c93e56867da input=60a0e7912ff99849]*/
     379  {
     380      _curses_panel_state *state = PyType_GetModuleState(cls);
     381      return PyCursesCheckERR(state, move_panel(self->pan, y, x), "move_panel");
     382  }
     383  
     384  /*[clinic input]
     385  _curses_panel.panel.window
     386  
     387  Return the window object associated with the panel.
     388  [clinic start generated code]*/
     389  
     390  static PyObject *
     391  _curses_panel_panel_window_impl(PyCursesPanelObject *self)
     392  /*[clinic end generated code: output=5f05940d4106b4cb input=6067353d2c307901]*/
     393  {
     394      return Py_NewRef(self->wo);
     395  }
     396  
     397  /*[clinic input]
     398  _curses_panel.panel.replace
     399  
     400      cls: defining_class
     401      win: object(type="PyCursesWindowObject *", subclass_of="&PyCursesWindow_Type")
     402      /
     403  
     404  Change the window associated with the panel to the window win.
     405  [clinic start generated code]*/
     406  
     407  static PyObject *
     408  _curses_panel_panel_replace_impl(PyCursesPanelObject *self,
     409                                   PyTypeObject *cls,
     410                                   PyCursesWindowObject *win)
     411  /*[clinic end generated code: output=c71f95c212d58ae7 input=dbec7180ece41ff5]*/
     412  {
     413      _curses_panel_state *state = PyType_GetModuleState(cls);
     414  
     415      PyCursesPanelObject *po = find_po(self->pan);
     416      if (po == NULL) {
     417          PyErr_SetString(PyExc_RuntimeError,
     418                          "replace_panel: can't find Panel Object");
     419          return NULL;
     420      }
     421  
     422      int rtn = replace_panel(self->pan, win->win);
     423      if (rtn == ERR) {
     424          PyErr_SetString(state->PyCursesError, "replace_panel() returned ERR");
     425          return NULL;
     426      }
     427      Py_SETREF(po->wo, (PyCursesWindowObject*)Py_NewRef(win));
     428      Py_RETURN_NONE;
     429  }
     430  
     431  /*[clinic input]
     432  _curses_panel.panel.set_userptr
     433  
     434      cls: defining_class
     435      obj: object
     436      /
     437  
     438  Set the panel's user pointer to obj.
     439  [clinic start generated code]*/
     440  
     441  static PyObject *
     442  _curses_panel_panel_set_userptr_impl(PyCursesPanelObject *self,
     443                                       PyTypeObject *cls, PyObject *obj)
     444  /*[clinic end generated code: output=db74f3db07b28080 input=e3fee2ff7b1b8e48]*/
     445  {
     446      PyCursesInitialised;
     447      Py_INCREF(obj);
     448      PyObject *oldobj = (PyObject *) panel_userptr(self->pan);
     449      int rc = set_panel_userptr(self->pan, (void*)obj);
     450      if (rc == ERR) {
     451          /* In case of an ncurses error, decref the new object again */
     452          Py_DECREF(obj);
     453      }
     454      else {
     455          Py_XDECREF(oldobj);
     456      }
     457  
     458      _curses_panel_state *state = PyType_GetModuleState(cls);
     459      return PyCursesCheckERR(state, rc, "set_panel_userptr");
     460  }
     461  
     462  /*[clinic input]
     463  _curses_panel.panel.userptr
     464  
     465      cls: defining_class
     466  
     467  Return the user pointer for the panel.
     468  [clinic start generated code]*/
     469  
     470  static PyObject *
     471  _curses_panel_panel_userptr_impl(PyCursesPanelObject *self,
     472                                   PyTypeObject *cls)
     473  /*[clinic end generated code: output=eea6e6f39ffc0179 input=f22ca4f115e30a80]*/
     474  {
     475      _curses_panel_state *state = PyType_GetModuleState(cls);
     476  
     477      PyCursesInitialised;
     478      PyObject *obj = (PyObject *) panel_userptr(self->pan);
     479      if (obj == NULL) {
     480          PyErr_SetString(state->PyCursesError, "no userptr set");
     481          return NULL;
     482      }
     483  
     484      return Py_NewRef(obj);
     485  }
     486  
     487  
     488  /* Module interface */
     489  
     490  static PyMethodDef PyCursesPanel_Methods[] = {
     491      _CURSES_PANEL_PANEL_ABOVE_METHODDEF
     492      _CURSES_PANEL_PANEL_BELOW_METHODDEF
     493      _CURSES_PANEL_PANEL_BOTTOM_METHODDEF
     494      _CURSES_PANEL_PANEL_HIDDEN_METHODDEF
     495      _CURSES_PANEL_PANEL_HIDE_METHODDEF
     496      _CURSES_PANEL_PANEL_MOVE_METHODDEF
     497      _CURSES_PANEL_PANEL_REPLACE_METHODDEF
     498      _CURSES_PANEL_PANEL_SET_USERPTR_METHODDEF
     499      _CURSES_PANEL_PANEL_SHOW_METHODDEF
     500      _CURSES_PANEL_PANEL_TOP_METHODDEF
     501      _CURSES_PANEL_PANEL_USERPTR_METHODDEF
     502      _CURSES_PANEL_PANEL_WINDOW_METHODDEF
     503      {NULL,              NULL}   /* sentinel */
     504  };
     505  
     506  /* -------------------------------------------------------*/
     507  
     508  static PyType_Slot PyCursesPanel_Type_slots[] = {
     509      {Py_tp_dealloc, PyCursesPanel_Dealloc},
     510      {Py_tp_methods, PyCursesPanel_Methods},
     511      {0, 0},
     512  };
     513  
     514  static PyType_Spec PyCursesPanel_Type_spec = {
     515      .name = "_curses_panel.panel",
     516      .basicsize = sizeof(PyCursesPanelObject),
     517      .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION,
     518      .slots = PyCursesPanel_Type_slots
     519  };
     520  
     521  /* Wrapper for panel_above(NULL). This function returns the bottom
     522     panel of the stack, so it's renamed to bottom_panel().
     523     panel.above() *requires* a panel object in the first place which
     524     may be undesirable. */
     525  /*[clinic input]
     526  _curses_panel.bottom_panel
     527  
     528  Return the bottom panel in the panel stack.
     529  [clinic start generated code]*/
     530  
     531  static PyObject *
     532  _curses_panel_bottom_panel_impl(PyObject *module)
     533  /*[clinic end generated code: output=3aba9f985f4c2bd0 input=634c2a8078b3d7e4]*/
     534  {
     535      PANEL *pan;
     536      PyCursesPanelObject *po;
     537  
     538      PyCursesInitialised;
     539  
     540      pan = panel_above(NULL);
     541  
     542      if (pan == NULL) {          /* valid output, it means
     543                                     there's no panel at all */
     544          Py_RETURN_NONE;
     545      }
     546      po = find_po(pan);
     547      if (po == NULL) {
     548          PyErr_SetString(PyExc_RuntimeError,
     549                          "panel_above: can't find Panel Object");
     550          return NULL;
     551      }
     552      return Py_NewRef(po);
     553  }
     554  
     555  /*[clinic input]
     556  _curses_panel.new_panel
     557  
     558      win: object(type="PyCursesWindowObject *", subclass_of="&PyCursesWindow_Type")
     559      /
     560  
     561  Return a panel object, associating it with the given window win.
     562  [clinic start generated code]*/
     563  
     564  static PyObject *
     565  _curses_panel_new_panel_impl(PyObject *module, PyCursesWindowObject *win)
     566  /*[clinic end generated code: output=45e948e0176a9bd2 input=74d4754e0ebe4800]*/
     567  {
     568      _curses_panel_state *state = get_curses_panel_state(module);
     569  
     570      PANEL *pan = new_panel(win->win);
     571      if (pan == NULL) {
     572          PyErr_SetString(state->PyCursesError, catchall_NULL);
     573          return NULL;
     574      }
     575      return (PyObject *)PyCursesPanel_New(state, pan, win);
     576  }
     577  
     578  
     579  /* Wrapper for panel_below(NULL). This function returns the top panel
     580     of the stack, so it's renamed to top_panel(). panel.below()
     581     *requires* a panel object in the first place which may be
     582     undesirable. */
     583  /*[clinic input]
     584  _curses_panel.top_panel
     585  
     586  Return the top panel in the panel stack.
     587  [clinic start generated code]*/
     588  
     589  static PyObject *
     590  _curses_panel_top_panel_impl(PyObject *module)
     591  /*[clinic end generated code: output=86704988bea8508e input=e62d6278dba39e79]*/
     592  {
     593      PANEL *pan;
     594      PyCursesPanelObject *po;
     595  
     596      PyCursesInitialised;
     597  
     598      pan = panel_below(NULL);
     599  
     600      if (pan == NULL) {          /* valid output, it means
     601                                     there's no panel at all */
     602          Py_RETURN_NONE;
     603      }
     604      po = find_po(pan);
     605      if (po == NULL) {
     606          PyErr_SetString(PyExc_RuntimeError,
     607                          "panel_below: can't find Panel Object");
     608          return NULL;
     609      }
     610      return Py_NewRef(po);
     611  }
     612  
     613  /*[clinic input]
     614  _curses_panel.update_panels
     615  
     616  Updates the virtual screen after changes in the panel stack.
     617  
     618  This does not call curses.doupdate(), so you'll have to do this yourself.
     619  [clinic start generated code]*/
     620  
     621  static PyObject *
     622  _curses_panel_update_panels_impl(PyObject *module)
     623  /*[clinic end generated code: output=2f3b4c2e03d90ded input=5299624c9a708621]*/
     624  {
     625      PyCursesInitialised;
     626      update_panels();
     627      Py_RETURN_NONE;
     628  }
     629  
     630  /* List of functions defined in the module */
     631  
     632  static PyMethodDef PyCurses_methods[] = {
     633      _CURSES_PANEL_BOTTOM_PANEL_METHODDEF
     634      _CURSES_PANEL_NEW_PANEL_METHODDEF
     635      _CURSES_PANEL_TOP_PANEL_METHODDEF
     636      _CURSES_PANEL_UPDATE_PANELS_METHODDEF
     637      {NULL,              NULL}           /* sentinel */
     638  };
     639  
     640  /* Initialization function for the module */
     641  static int
     642  _curses_panel_exec(PyObject *mod)
     643  {
     644      _curses_panel_state *state = get_curses_panel_state(mod);
     645      /* Initialize object type */
     646      state->PyCursesPanel_Type = (PyTypeObject *)PyType_FromModuleAndSpec(
     647          mod, &PyCursesPanel_Type_spec, NULL);
     648      if (state->PyCursesPanel_Type == NULL) {
     649          return -1;
     650      }
     651  
     652      if (PyModule_AddType(mod, state->PyCursesPanel_Type) < 0) {
     653          return -1;
     654      }
     655  
     656      import_curses();
     657      if (PyErr_Occurred()) {
     658          return -1;
     659      }
     660  
     661      /* For exception _curses_panel.error */
     662      state->PyCursesError = PyErr_NewException(
     663          "_curses_panel.error", NULL, NULL);
     664  
     665      if (PyModule_AddObjectRef(mod, "error", state->PyCursesError) < 0) {
     666          return -1;
     667      }
     668  
     669      /* Make the version available */
     670      PyObject *v = PyUnicode_FromString(PyCursesVersion);
     671      if (v == NULL) {
     672          return -1;
     673      }
     674  
     675      PyObject *d = PyModule_GetDict(mod);
     676      if (PyDict_SetItemString(d, "version", v) < 0) {
     677          Py_DECREF(v);
     678          return -1;
     679      }
     680      if (PyDict_SetItemString(d, "__version__", v) < 0) {
     681          Py_DECREF(v);
     682          return -1;
     683      }
     684  
     685      Py_DECREF(v);
     686  
     687      return 0;
     688  }
     689  
     690  static PyModuleDef_Slot _curses_slots[] = {
     691      {Py_mod_exec, _curses_panel_exec},
     692      // XXX gh-103092: fix isolation.
     693      {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
     694      //{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
     695      {0, NULL}
     696  };
     697  
     698  static struct PyModuleDef _curses_panelmodule = {
     699      PyModuleDef_HEAD_INIT,
     700      .m_name = "_curses_panel",
     701      .m_size = sizeof(_curses_panel_state),
     702      .m_methods = PyCurses_methods,
     703      .m_slots = _curses_slots,
     704      .m_traverse = _curses_panel_traverse,
     705      .m_clear = _curses_panel_clear,
     706      .m_free = _curses_panel_free
     707  };
     708  
     709  PyMODINIT_FUNC
     710  PyInit__curses_panel(void)
     711  {
     712      return PyModuleDef_Init(&_curses_panelmodule);
     713  }