/* * This is a sample plugin module. * It demonstrates how to create a graph viewer with an aribtrary graph. * * It can be compiled by the following compilers: * * - Borland C++, CBuilder, free C++ * */ #include #include #include #include #include //------------------------------------------------------------------------- struct plugin_ctx_t; struct change_layout_ah_t : public action_handler_t { plugin_ctx_t &plg; change_layout_ah_t(plugin_ctx_t &_plg) : plg(_plg) {} virtual int idaapi activate(action_activation_ctx_t *ctx) override; virtual action_state_t idaapi update(action_update_ctx_t *ctx) override; }; //-------------------------------------------------------------------------- struct plugin_ctx_t : public plugmod_t, public event_listener_t { change_layout_ah_t change_layout_ah = change_layout_ah_t(*this); const action_desc_t change_layout_desc = ACTION_DESC_LITERAL_PLUGMOD( "ugraph:ChangeLayout", "User function", &change_layout_ah, this, NULL, NULL, -1); qstrvec_t graph_text; graph_viewer_t *gv = nullptr; plugin_ctx_t() { hook_event_listener(HT_VIEW, this); } ~plugin_ctx_t() { // listeners are uninstalled automatically // when the owner module is unloaded } virtual bool idaapi run(size_t) override; virtual ssize_t idaapi on_event(ssize_t code, va_list va) override; static ssize_t idaapi gr_callback(void *ud, int code, va_list va); }; //------------------------------------------------------------------------- int idaapi change_layout_ah_t::activate(action_activation_ctx_t *ctx) { plg.gv = (graph_viewer_t *)ctx->widget; mutable_graph_t *g = get_viewer_graph(plg.gv); int code = ask_buttons("Circle", "Tree", "Digraph", 1, "Please select layout type"); node_info_t ni; ni.bg_color = 0x44FF55; ni.text = "Hello from plugin!"; set_node_info(g->gid, 7, ni, NIF_BG_COLOR | NIF_TEXT); g->current_layout = code + 2; g->circle_center = point_t(200, 200); g->circle_radius = 200; g->redo_layout(); refresh_viewer(plg.gv); return 1; } //------------------------------------------------------------------------- action_state_t idaapi change_layout_ah_t::update(action_update_ctx_t *ctx) { if ( ctx->widget == (TWidget *)plg.gv ) return AST_ENABLE_FOR_WIDGET; else return AST_DISABLE_FOR_WIDGET; } //-------------------------------------------------------------------------- static const char *get_node_name(int n) { switch ( n ) { case 0: return COLSTR("This", SCOLOR_MACRO); case 1: return COLSTR("is", SCOLOR_CNAME); case 2: return "a"; case 3: return COLSTR("sample", SCOLOR_DNAME); case 4: return COLSTR("graph", SCOLOR_IMPNAME); case 5: return COLSTR("viewer", SCOLOR_ERROR); case 6: return COLSTR("window!", SCOLOR_DNUM) "\n(with colorful names)"; } return "?"; } //-------------------------------------------------------------------------- ssize_t idaapi plugin_ctx_t::gr_callback(void *ud, int code, va_list va) { plugin_ctx_t &ctx = *(plugin_ctx_t *)ud; ssize_t result = 0; switch ( code ) { case grcode_calculating_layout: // calculating user-defined graph layout // in: mutable_graph_t *g // out: 0-not implemented // 1-graph layout calculated by the plugin msg("calculating graph layout...\n"); break; case grcode_clicked: // a graph has been clicked // in: graph_viewer_t *gv // selection_item_t *current_item // out: 0-ok, 1-ignore click { graph_viewer_t *v = va_arg(va, graph_viewer_t *); qnotused(v); selection_item_t *it = va_arg(va, selection_item_t *); qnotused(it); graph_item_t *m = va_arg(va, graph_item_t *); msg("clicked on "); switch ( m->type ) { case git_none: msg("background\n"); break; case git_edge: msg("edge (%d, %d)\n", m->e.src, m->e.dst); break; case git_node: msg("node %d\n", m->n); break; case git_tool: msg("toolbutton %d\n", m->b); break; case git_text: msg("text (x,y)=(%d,%d)\n", m->p.x, m->p.y); break; case git_elp: msg("edge layout point (%d, %d) #%d\n", m->elp.e.src, m->elp.e.dst, m->elp.pidx); break; } } break; case grcode_dblclicked: // a graph node has been double clicked // in: graph_viewer_t *gv // selection_item_t *current_item // out: 0-ok, 1-ignore click { graph_viewer_t *v = va_arg(va, graph_viewer_t *); selection_item_t *s = va_arg(va, selection_item_t *); msg("%p: dblclicked on ", v); if ( s == NULL ) msg("background\n"); else if ( s->is_node ) msg("node %d\n", s->node); else msg("edge (%d, %d) layout point #%d\n", s->elp.e.src, s->elp.e.dst, s->elp.pidx); } break; case grcode_creating_group: // a group is being created // in: mutable_graph_t *g // intvec_t *nodes // out: 0-ok, 1-forbid group creation { mutable_graph_t *g = va_arg(va, mutable_graph_t *); intvec_t &nodes = *va_arg(va, intvec_t *); msg("%p: creating group", g); for ( intvec_t::iterator p=nodes.begin(); p != nodes.end(); ++p ) msg(" %d", *p); msg("...\n"); } break; case grcode_deleting_group: // a group is being deleted // in: mutable_graph_t *g // int old_group // out: 0-ok, 1-forbid group deletion { mutable_graph_t *g = va_arg(va, mutable_graph_t *); int group = va_argi(va, int); msg("%p: deleting group %d\n", g, group); } break; case grcode_group_visibility: // a group is being collapsed/uncollapsed // in: mutable_graph_t *g // int group // bool expand // out: 0-ok, 1-forbid group modification { mutable_graph_t *g = va_arg(va, mutable_graph_t *); int group = va_argi(va, int); bool expand = va_argi(va, bool); msg("%p: %scollapsing group %d\n", g, expand ? "un" : "", group); } break; case grcode_gotfocus: // a graph viewer got focus // in: graph_viewer_t *gv // out: must return 0 { graph_viewer_t *g = va_arg(va, graph_viewer_t *); msg("%p: got focus\n", g); } break; case grcode_lostfocus: // a graph viewer lost focus // in: graph_viewer_t *gv // out: must return 0 { graph_viewer_t *g = va_arg(va, graph_viewer_t *); msg("%p: lost focus\n", g); } break; case grcode_user_refresh: // refresh user-defined graph nodes and edges // in: mutable_graph_t *g // out: success { mutable_graph_t *g = va_arg(va, mutable_graph_t *); msg("%p: refresh\n", g); // our graph is like this: // 0 -> 1 -> 2 // \-> 3 -> 4 -> 5 -> 6 // ^ / // \-------/ if ( g->empty() ) g->resize(7); g->add_edge(0, 1, NULL); g->add_edge(1, 2, NULL); g->add_edge(1, 3, NULL); g->add_edge(3, 4, NULL); g->add_edge(4, 5, NULL); g->add_edge(5, 3, NULL); g->add_edge(5, 6, NULL); result = true; } break; case grcode_user_gentext: // generate text for user-defined graph nodes // in: mutable_graph_t *g // out: must return 0 { mutable_graph_t *g = va_arg(va, mutable_graph_t *); msg("%p: generate text for graph nodes\n", g); ctx.graph_text.resize(g->size()); for ( node_iterator p=g->begin(); p != g->end(); ++p ) { int n = *p; ctx.graph_text[n] = get_node_name(n); } result = true; } break; case grcode_user_text: // retrieve text for user-defined graph node // in: mutable_graph_t *g // int node // const char **result // bgcolor_t *bg_color (maybe NULL) // out: must return 0, result must be filled // NB: do not use anything calling GDI! { mutable_graph_t *g = va_arg(va, mutable_graph_t *); int node = va_arg(va, int); const char **text = va_arg(va, const char **); bgcolor_t *bgcolor = va_arg(va, bgcolor_t *); *text = ctx.graph_text[node].c_str(); if ( bgcolor != NULL ) *bgcolor = DEFCOLOR; result = true; qnotused(g); } break; case grcode_user_size: // calculate node size for user-defined graph // in: mutable_graph_t *g // int node // int *cx // int *cy // out: 0-did not calculate, ida will use node text size // 1-calculated. ida will add node title to the size msg("calc node size - not implemented\n"); // ida will calculate the node size based on the node text break; case grcode_user_title: // render node title of a user-defined graph // in: mutable_graph_t *g // int node // rect_t *title_rect // int title_bg_color // HDC dc // out: 0-did not render, ida will fill it with title_bg_color // 1-rendered node title // ida will draw the node title itself break; case grcode_user_draw: // render node of a user-defined graph // in: mutable_graph_t *g // int node // rect_t *node_rect // HDC dc // out: 0-not rendered, 1-rendered // NB: draw only on the specified DC and nowhere else! // ida will draw the node text itself break; case grcode_user_hint: // retrieve hint for the user-defined graph // in: mutable_graph_t *g // int mousenode // int mouseedge_src // int mouseedge_dst // char **hint // 'hint' must be allocated by qalloc() or qstrdup() // out: 0-use default hint, 1-use proposed hint { mutable_graph_t *g = va_arg(va, mutable_graph_t *); int mousenode = va_argi(va, int); int mouseedge_src = va_argi(va, int); int mouseedge_dst = va_argi(va, int); char **hint = va_arg(va, char **); char buf[MAXSTR]; buf[0] = '\0'; if ( mousenode != -1 ) qsnprintf(buf, sizeof(buf), "My fancy hint for node %d", mousenode); else if ( mouseedge_src != -1 ) qsnprintf(buf, sizeof(buf), "Hovering on (%d,%d)", mouseedge_src, mouseedge_dst); if ( buf[0] != '\0' ) *hint = qstrdup(buf); result = true; // use our hint qnotused(g); } break; } return result; } //------------------------------------------------------------------------- ssize_t idaapi plugin_ctx_t::on_event(ssize_t code, va_list va) { if ( code == view_close ) { TWidget *view = va_arg(va, TWidget *); if ( view == (TWidget *)gv ) gv = nullptr; } return 0; } //-------------------------------------------------------------------------- static plugmod_t *idaapi init() { if ( !is_idaq() ) return nullptr; return new plugin_ctx_t; } //-------------------------------------------------------------------------- static const char wanted_title[] = "Sample graph"; bool idaapi plugin_ctx_t::run(size_t) { TWidget *widget = find_widget(wanted_title); if ( widget == nullptr ) { // get a unique graph id netnode id; id.create("$ ugraph sample"); gv = create_graph_viewer(wanted_title, id, gr_callback, this, 0); if ( gv != nullptr ) { display_widget(gv, WOPN_DP_TAB); viewer_fit_window(gv); register_action(change_layout_desc); viewer_attach_menu_item(gv, change_layout_desc.name); } } else { close_widget(widget, 0); } return true; } //-------------------------------------------------------------------------- static const char comment[] = "This is a sample graph plugin."; static const char help[] = "A sample graph plugin module\n" "\n" "This module shows you how to create a graph viewer."; //-------------------------------------------------------------------------- // This is the preferred name of the plugin module in the menu system // The preferred name may be overridden in plugins.cfg file static const char wanted_name[] = "Create sample graph view"; // This is the preferred hotkey for the plugin module // The preferred hotkey may be overridden in plugins.cfg file static const char wanted_hotkey[] = ""; //-------------------------------------------------------------------------- // // PLUGIN DESCRIPTION BLOCK // //-------------------------------------------------------------------------- plugin_t PLUGIN = { IDP_INTERFACE_VERSION, PLUGIN_MULTI, // The plugin can work with multiple idbs in parallel init, // initialize nullptr, nullptr, // invoke plugin comment, // long comment about the plugin // it could appear in the status line // or as a hint help, // multiline help about the plugin wanted_name, // the preferred short name of the plugin wanted_hotkey // the preferred hotkey to run the plugin };