#ifndef __PY_CHOOSE2__ #define __PY_CHOOSE2__ // //------------------------------------------------------------------------ // Some defines #define POPUP_NAMES_COUNT 4 #define MAX_CHOOSER_MENU_COMMANDS 20 #define thisobj ((py_choose2_t *) obj) #define thisdecl py_choose2_t *_this = thisobj #define MENU_COMMAND_CB(id) \ static uint32 idaapi s_menu_command_##id(void *obj, uint32 n) \ { \ return thisobj->on_command(id, int(n)); \ } //------------------------------------------------------------------------ // Helper functions class py_choose2_t; typedef std::map pychoose2_to_choose2_map_t; static pychoose2_to_choose2_map_t choosers; py_choose2_t *choose2_find_instance(PyObject *self) { pychoose2_to_choose2_map_t::iterator it = choosers.find(self); return it == choosers.end() ? NULL : it->second; } void choose2_add_instance(PyObject *self, py_choose2_t *c2) { choosers[self] = c2; } void choose2_del_instance(PyObject *self) { pychoose2_to_choose2_map_t::iterator it = choosers.find(self); if ( it != choosers.end() ) choosers.erase(it); } //------------------------------------------------------------------------ class py_choose2_t { private: enum { CHOOSE2_HAVE_DEL = 0x0001, CHOOSE2_HAVE_INS = 0x0002, CHOOSE2_HAVE_UPDATE = 0x0004, CHOOSE2_HAVE_EDIT = 0x0008, CHOOSE2_HAVE_ENTER = 0x0010, CHOOSE2_HAVE_GETICON = 0x0020, CHOOSE2_HAVE_GETATTR = 0x0040, CHOOSE2_HAVE_COMMAND = 0x0080, CHOOSE2_HAVE_ONCLOSE = 0x0100, CHOOSE2_HAVE_SELECT = 0x0200, CHOOSE2_HAVE_REFRESHED = 0x0400, }; // Chooser flags int flags; // Callback flags (to tell which callback exists and which not) // One of CHOOSE2_HAVE_xxxx unsigned int cb_flags; chooser_info_t *embedded; intvec_t embedded_sel; // Menu callback index (in the menu_cbs array) int menu_cb_idx; // Chooser title qstring title; // Column widths intvec_t widths; // Python object link PyObject *self; // Chooser columns qstrvec_t cols; const char **popup_names; bool ui_cb_hooked; // The number of declarations should follow the MAX_CHOOSER_MENU_COMMANDS value MENU_COMMAND_CB(0) MENU_COMMAND_CB(1) MENU_COMMAND_CB(2) MENU_COMMAND_CB(3) MENU_COMMAND_CB(4) MENU_COMMAND_CB(5) MENU_COMMAND_CB(6) MENU_COMMAND_CB(7) MENU_COMMAND_CB(8) MENU_COMMAND_CB(9) MENU_COMMAND_CB(10) MENU_COMMAND_CB(11) MENU_COMMAND_CB(12) MENU_COMMAND_CB(13) MENU_COMMAND_CB(14) MENU_COMMAND_CB(15) MENU_COMMAND_CB(16) MENU_COMMAND_CB(17) MENU_COMMAND_CB(18) MENU_COMMAND_CB(19) static chooser_cb_t *menu_cbs[MAX_CHOOSER_MENU_COMMANDS]; //------------------------------------------------------------------------ // Static methods to dispatch to member functions //------------------------------------------------------------------------ static int idaapi ui_cb(void *obj, int notification_code, va_list va) { // This hook gets called from the kernel. Ensure we hold the GIL. PYW_GIL_GET; // UI callback to handle chooser items with attributes if ( notification_code != ui_get_chooser_item_attrs ) return 0; // Pass events that belong to our chooser only void *chooser_obj = va_arg(va, void *); if ( obj != chooser_obj ) return 0; int n = int(va_arg(va, uint32)); chooser_item_attrs_t *attr = va_arg(va, chooser_item_attrs_t *); thisobj->on_get_line_attr(n, attr); return 1; } static void idaapi s_select(void *obj, const intvec_t &sel) { thisobj->on_select(sel); } static void idaapi s_refreshed(void *obj) { thisobj->on_refreshed(); } static uint32 idaapi s_sizer(void *obj) { return (uint32)thisobj->on_get_size(); } static void idaapi s_getl(void *obj, uint32 n, char * const *arrptr) { thisobj->on_get_line(int(n), arrptr); } static uint32 idaapi s_del(void *obj, uint32 n) { return uint32(thisobj->on_delete_line(int(n))); } static void idaapi s_ins(void *obj) { thisobj->on_insert_line(); } static uint32 idaapi s_update(void *obj, uint32 n) { return uint32(thisobj->on_refresh(int(n))); } static void idaapi s_edit(void *obj, uint32 n) { thisobj->on_edit_line(int(n)); } static void idaapi s_enter(void * obj, uint32 n) { thisobj->on_enter(int(n)); } static int idaapi s_get_icon(void *obj, uint32 n) { return thisobj->on_get_icon(int(n)); } static void idaapi s_destroy(void *obj) { thisobj->on_close(); } //------------------------------------------------------------------------ // Member functions corresponding to each chooser2() callback //------------------------------------------------------------------------ void clear_popup_names() { if ( popup_names == NULL ) return; for ( int i=0; i=0; i-- ) line_arr[i][0] = '\0'; // Call Python PYW_GIL_CHECK_LOCKED_SCOPE(); pycall_res_t list(PyObject_CallMethod(self, (char *)S_ON_GET_LINE, "i", lineno - 1)); if ( list.result == NULL ) return; // Go over the List returned by Python and convert to C strings for ( int i=ncols-1; i>=0; i-- ) { borref_t item(PyList_GetItem(list.result.o, Py_ssize_t(i))); if ( item == NULL ) continue; const char *str = PyString_AsString(item.o); if ( str != NULL ) qstrncpy(line_arr[i], str, MAXSTR); } } size_t on_get_size() { PYW_GIL_GET; pycall_res_t pyres(PyObject_CallMethod(self, (char *)S_ON_GET_SIZE, NULL)); if ( pyres.result == NULL ) return 0; return PyInt_AsLong(pyres.result.o); } void on_refreshed() { PYW_GIL_GET; pycall_res_t pyres(PyObject_CallMethod(self, (char *)S_ON_REFRESHED, NULL)); } void on_select(const intvec_t &intvec) { PYW_GIL_GET; ref_t py_list(PyW_IntVecToPyList(intvec)); pycall_res_t pyres(PyObject_CallMethod(self, (char *)S_ON_SELECTION_CHANGE, "O", py_list.o)); } void on_close() { PYW_GIL_GET; pycall_res_t pyres(PyObject_CallMethod(self, (char *)S_ON_CLOSE, NULL)); // Delete this instance if none modal and not embedded if ( !is_modal() && get_embedded() == NULL ) delete this; } int on_delete_line(int lineno) { PYW_GIL_GET; pycall_res_t pyres( PyObject_CallMethod( self, (char *)S_ON_DELETE_LINE, "i", IS_CHOOSER_EVENT(lineno) ? lineno : lineno-1)); return pyres.result == NULL ? 1 : PyInt_AsLong(pyres.result.o); } int on_refresh(int lineno) { PYW_GIL_GET; pycall_res_t pyres( PyObject_CallMethod( self, (char *)S_ON_REFRESH, "i", lineno - 1)); return pyres.result == NULL ? lineno : PyInt_AsLong(pyres.result.o) + 1; } void on_insert_line() { PYW_GIL_GET; pycall_res_t pyres(PyObject_CallMethod(self, (char *)S_ON_INSERT_LINE, NULL)); } void on_enter(int lineno) { PYW_GIL_GET; pycall_res_t pyres( PyObject_CallMethod( self, (char *)S_ON_SELECT_LINE, "i", lineno - 1)); } void on_edit_line(int lineno) { PYW_GIL_GET; pycall_res_t pyres( PyObject_CallMethod( self, (char *)S_ON_EDIT_LINE, "i", lineno - 1)); } int on_command(int cmd_id, int lineno) { PYW_GIL_GET; pycall_res_t pyres( PyObject_CallMethod( self, (char *)S_ON_COMMAND, "ii", lineno - 1, cmd_id)); return pyres.result == NULL ? lineno : PyInt_AsLong(pyres.result.o); } int on_get_icon(int lineno) { PYW_GIL_GET; pycall_res_t pyres( PyObject_CallMethod( self, (char *)S_ON_GET_ICON, "i", lineno - 1)); return PyInt_AsLong(pyres.result.o); } void on_get_line_attr(int lineno, chooser_item_attrs_t *attr) { PYW_GIL_GET; pycall_res_t pyres(PyObject_CallMethod(self, (char *)S_ON_GET_LINE_ATTR, "i", lineno - 1)); if ( pyres.result != NULL ) { if ( PyList_Check(pyres.result.o) ) { PyObject *item; if ( (item = PyList_GetItem(pyres.result.o, 0)) != NULL ) attr->color = PyInt_AsLong(item); if ( (item = PyList_GetItem(pyres.result.o, 1)) != NULL ) attr->flags = PyInt_AsLong(item); } } } bool split_chooser_caption(qstring *out_title, qstring *out_caption, const char *caption) const { if ( get_embedded() != NULL ) { // For embedded chooser, the "caption" will be overloaded to encode // the AskUsingForm's title, caption and embedded chooser id // Title:EmbeddedChooserID:Caption char title_buf[MAXSTR]; const char *ptitle; static const char delimiter[] = ":"; char temp[MAXSTR]; qstrncpy(temp, caption, sizeof(temp)); char *ctx; char *p = qstrtok(temp, delimiter, &ctx); if ( p == NULL ) return false; // Copy the title char title_str[MAXSTR]; qstrncpy(title_str, p, sizeof(title_str)); // Copy the echooser ID p = qstrtok(NULL, delimiter, &ctx); if ( p == NULL ) return false; char id_str[10]; qstrncpy(id_str, p, sizeof(id_str)); // Form the new title of the form: "AskUsingFormTitle:EchooserId" qsnprintf(title_buf, sizeof(title_buf), "%s:%s", title_str, id_str); // Adjust the title *out_title = title_buf; // Adjust the caption p = qstrtok(NULL, delimiter, &ctx); *out_caption = caption + (p - temp); } else { *out_title = title; *out_caption = caption; } return true; } // This must be called at the end of create(), when many dependencies // have been computed (title, widths, popup_names, [cb_]flags, ...) void fill_chooser_info( chooser_info_t *out, int deflt, int desired_width, int desired_height, int icon) { memset(out, 0, sizeof(*out)); out->obj = this; out->cb = sizeof(*out); out->title = title.c_str(); out->columns = widths.size(); out->deflt = deflt; out->flags = flags; out->width = desired_width; out->height = desired_height; out->icon = icon; out->popup_names = popup_names; out->widths = widths.begin(); out->destroyer = s_destroy; out->getl = s_getl; out->sizer = s_sizer; out->del = (cb_flags & CHOOSE2_HAVE_DEL) != 0 ? s_del : NULL; out->edit = (cb_flags & CHOOSE2_HAVE_EDIT) != 0 ? s_edit : NULL; out->enter = (cb_flags & CHOOSE2_HAVE_ENTER) != 0 ? s_enter : NULL; out->get_icon = (cb_flags & CHOOSE2_HAVE_GETICON) != 0 ? s_get_icon : NULL; out->ins = (cb_flags & CHOOSE2_HAVE_INS) != 0 ? s_ins : NULL; out->update = (cb_flags & CHOOSE2_HAVE_UPDATE) != 0 ? s_update : NULL; out->get_attrs = NULL; out->initializer = NULL; // Fill callbacks that are only present in idaq if ( is_idaq() ) { out->select = (cb_flags & CHOOSE2_HAVE_SELECT) != 0 ? s_select : NULL; out->refresh = (cb_flags & CHOOSE2_HAVE_REFRESHED)!= 0 ? s_refreshed : NULL; } else { out->select = NULL; out->refresh = NULL; } } public: //------------------------------------------------------------------------ // Public methods //------------------------------------------------------------------------ py_choose2_t(): flags(0), cb_flags(0), embedded(NULL), menu_cb_idx(0), self(NULL), popup_names(NULL), ui_cb_hooked(false) { } ~py_choose2_t() { // Remove from list choose2_del_instance(self); // Uninstall hooks install_hooks(false); delete embedded; Py_XDECREF(self); clear_popup_names(); } static py_choose2_t *find_chooser(const char *title) { return (py_choose2_t *) get_chooser_obj(title); } void close() { // Will trigger on_close() close_chooser(title.c_str()); } bool activate() { TForm *frm = find_tform(title.c_str()); if ( frm == NULL ) return false; switchto_tform(frm, true); return true; } int add_command( const char *_caption, int flags=0, int menu_index=-1, int icon=-1) { if ( menu_cb_idx >= MAX_CHOOSER_MENU_COMMANDS ) return -1; qstring title, caption; if ( !split_chooser_caption(&title, &caption, _caption) || !add_chooser_command( title.c_str(), caption.c_str(), menu_cbs[menu_cb_idx], menu_index, icon, flags) ) return -1; return menu_cb_idx++; } // Create a chooser. // If it detects the "embedded" attribute, then it will create a chooser_info_t structure // Otherwise the chooser window is created and displayed int create(PyObject *self) { PYW_GIL_CHECK_LOCKED_SCOPE(); // Get flags ref_t flags_attr(PyW_TryGetAttrString(self, S_FLAGS)); if ( flags_attr == NULL ) return -1; flags = PyInt_Check(flags_attr.o) != 0 ? PyInt_AsLong(flags_attr.o) : 0; // Get the title if ( !PyW_GetStringAttr(self, S_TITLE, &title) ) return -1; // Get columns ref_t cols_attr(PyW_TryGetAttrString(self, "cols")); if ( cols_attr == NULL ) return -1; // Get col count int ncols = int(PyList_Size(cols_attr.o)); // Get cols caption and widthes cols.qclear(); for ( int i=0; iself = self; // Hook to notification point (to handle chooser item attributes) install_hooks(true); // Check if *embedded ref_t emb_attr(PyW_TryGetAttrString(self, S_EMBEDDED)); int rc; if ( emb_attr != NULL && PyObject_IsTrue(emb_attr.o) == 1 ) { // Create an embedded chooser structure embedded = new chooser_info_t(); fill_chooser_info(embedded, deflt, pts[0], pts[1], icon); rc = 1; // success } else { chooser_info_t ci; fill_chooser_info(&ci, deflt, -1, -1, icon); rc = choose3(&ci); clear_popup_names(); if ( is_modal() ) --rc; // modal chooser return the index of the selected item } return rc; } inline PyObject *get_self() { return self; } void refresh() { refresh_chooser(title.c_str()); } bool is_modal() { return (flags & CH_MODAL) != 0; } intvec_t *get_sel_vec() { return &embedded_sel; } chooser_info_t *get_embedded() const { return embedded; } }; //------------------------------------------------------------------------ // Initialize the callback pointers #define DECL_MENU_COMMAND_CB(id) s_menu_command_##id chooser_cb_t *py_choose2_t::menu_cbs[MAX_CHOOSER_MENU_COMMANDS] = { DECL_MENU_COMMAND_CB(0), DECL_MENU_COMMAND_CB(1), DECL_MENU_COMMAND_CB(2), DECL_MENU_COMMAND_CB(3), DECL_MENU_COMMAND_CB(4), DECL_MENU_COMMAND_CB(5), DECL_MENU_COMMAND_CB(6), DECL_MENU_COMMAND_CB(7), DECL_MENU_COMMAND_CB(8), DECL_MENU_COMMAND_CB(9), DECL_MENU_COMMAND_CB(10), DECL_MENU_COMMAND_CB(11), DECL_MENU_COMMAND_CB(12), DECL_MENU_COMMAND_CB(13), DECL_MENU_COMMAND_CB(14), DECL_MENU_COMMAND_CB(15), DECL_MENU_COMMAND_CB(16), DECL_MENU_COMMAND_CB(17), DECL_MENU_COMMAND_CB(18), DECL_MENU_COMMAND_CB(19) }; #undef DECL_MENU_COMMAND_CB #undef POPUP_NAMES_COUNT #undef MAX_CHOOSER_MENU_COMMANDS #undef thisobj #undef thisdecl #undef MENU_COMMAND_CB //------------------------------------------------------------------------ int choose2_create(PyObject *self, bool embedded) { py_choose2_t *c2; c2 = choose2_find_instance(self); if ( c2 != NULL ) { if ( !embedded ) c2->activate(); return 1; } c2 = new py_choose2_t(); choose2_add_instance(self, c2); int r = c2->create(self); // Non embedded chooser? Return immediately if ( !embedded ) return r; // Embedded chooser was not created? if ( c2->get_embedded() == NULL || r != 1 ) { delete c2; r = 0; } return r; } //------------------------------------------------------------------------ void choose2_close(PyObject *self) { py_choose2_t *c2 = choose2_find_instance(self); if ( c2 == NULL ) return; // Modal or embedded chooser? if ( c2->get_embedded() != NULL || c2->is_modal() ) { // Then simply delete the instance delete c2; } else { // Close the chooser. // In turn this will lead to the deletion of the object c2->close(); } } //------------------------------------------------------------------------ void choose2_refresh(PyObject *self) { py_choose2_t *c2 = choose2_find_instance(self); if ( c2 != NULL ) c2->refresh(); } //------------------------------------------------------------------------ void choose2_activate(PyObject *self) { py_choose2_t *c2 = choose2_find_instance(self); if ( c2 != NULL ) c2->activate(); } //------------------------------------------------------------------------ PyObject *choose2_get_embedded_selection(PyObject *self) { PYW_GIL_CHECK_LOCKED_SCOPE(); py_choose2_t *c2 = choose2_find_instance(self); chooser_info_t *embedded; if ( c2 == NULL || (embedded = c2->get_embedded()) == NULL ) Py_RETURN_NONE; // Returned as 1-based intvec_t &intvec = *c2->get_sel_vec(); // Make 0-based for ( intvec_t::iterator it=intvec.begin(); it != intvec.end(); ++it) (*it)--; ref_t ret(PyW_IntVecToPyList(intvec)); ret.incref(); return ret.o; } //------------------------------------------------------------------------ // Return the C instances as 64bit numbers PyObject *choose2_get_embedded(PyObject *self) { PYW_GIL_CHECK_LOCKED_SCOPE(); py_choose2_t *c2 = choose2_find_instance(self); chooser_info_t *embedded; if ( c2 == NULL || (embedded = c2->get_embedded()) == NULL ) Py_RETURN_NONE; else return Py_BuildValue("(KK)", PY_ULONG_LONG(embedded), PY_ULONG_LONG(c2->get_sel_vec())); } //------------------------------------------------------------------------ int choose2_add_command( PyObject *self, const char *caption, int flags=0, int menu_index=-1, int icon=-1) { py_choose2_t *c2 = choose2_find_instance(self); return c2 == NULL ? -2 : c2->add_command(caption, flags, menu_index, icon); } //------------------------------------------------------------------------ PyObject *choose2_find(const char *title) { py_choose2_t *c2 = py_choose2_t::find_chooser(title); return c2 == NULL ? NULL : c2->get_self(); } // //--------------------------------------------------------------------------- // PyObject *choose2_find(const char *title); int choose2_add_command(PyObject *self, const char *caption, int flags, int menu_index, int icon); void choose2_refresh(PyObject *self); void choose2_close(PyObject *self); int choose2_create(PyObject *self, bool embedded); void choose2_activate(PyObject *self); PyObject *choose2_get_embedded(PyObject *self); PyObject *choose2_get_embedded_selection(PyObject *self); // //--------------------------------------------------------------------------- // Testing functions. They belong to PyWraps and won't be copied to IDAPython //--------------------------------------------------------------------------- static void NT_CDECL choose2_test_embedded(chooser_info_t *embedded) { msg("cb=%d -> looks %valid\n", embedded->cb, embedded->cb == sizeof(chooser_info_t) ? "" : "in"); } static size_t choose2_get_test_embedded() { return (size_t)choose2_test_embedded; } #endif // __PY_CHOOSE2__