Plugin writers, either using the C SDK or IDAPython, who would like to add actions/commands to IDA UI in order to augment its capabilities.
Depending on what type of context you were in, various APIs were available to you:
Want to add a main menu item?
add_menu_item(const char *menupath, const char *name, const char *hotkey, int flags, menu_item_callback_t *callback, void *ud); del_menu_item(const char *menupath)
Want to add an action to a list view? (or, as we call them: ‘choosers’)
add_chooser_command(const char *chooser_caption, const char *cmd_caption, chooser_cb_t *chooser_cb, int menu_index=-1, int icon=-1, int flags=0); add_chooser_command(const char *chooser_caption, const char *cmd_caption, chooser_cb_t *chooser_cb, const char *hotkey, int menu_index=-1, int icon=-1, int flags=0);
…or through populating callbacks:
struct chooser_info_t { // ...snipped... // function is called every time before // context menu is shown to fill user_cmds prepare_popup_cmds_t *prepare_popup_cmds; };
Want to add/manage the items that show up in the context menu for a custom code viewer you have created? (that does not work for the ‘core’ viewers like ‘IDA View-A’!)
set_custom_viewer_popup_menu(TCustomControl *custom_viewer, TPopupMenu *menu); add_custom_viewer_popup_item(TCustomControl *custom_viewer, const char *title, const char *hotkey, menu_item_callback_t *cb, void *ud);
Want to add/manage the items that show up in the context menu for a custom graph viewer you have created? (again, doesn’t work with ‘core’ viewers)
viewer_add_menu_item(graph_viewer_t *gv, const char *title, menu_item_callback_t *callback, void *ud, const char *hotkey, int flags);
Want to add an item to the Output window’s context menu?
add_output_popup(char *n, menu_item_callback_t *c, void *u);
Note that not all of those functions take the same set of parameters, or even the same type for seemingly-related ones (e.g., chooser_cb_t *chooser_cb
VS menu_item_callback_t *callback
)
It is also not possible to specify some attributes of the commands in some cases. E.g.,
add_menu_item()
doesn’t allow to specify an iconadd_output_popup()
doesn’t accept a shortcutAnd it’s just plain impossible to do some things. E.g.,
add_output_popup()
.These inconsistencies/lack of features were making writing extensions difficult, as the wealth of APIs was somewhat confusing and it was easy to hit the limitations.
In addition, there was no really good way of enabling/disabling your actions, or changing their properties after adding them.
Thus, we decided a refactoring of those APIs was in order.
Borrowing some concepts from Qt itself (upon which IDA’s graphical interface relies) we have decided to redesign the actions API in a hopefully more streamlined manner.
What follows is a series of short examples, using the IDAPython bindings for actions. Note that the IDAPython API is purposely similar to the C SDK API.
# 1) Create the handler class class MyHandler(idaapi.action_handler_t): def __init__(self): idaapi.action_handler_t.__init__(self) # Say hello when invoked. def activate(self, ctx): print "Hello!" return 1 # This action is always available. def update(self, ctx): return idaapi.AST_ENABLE_ALWAYS # 2) Describe the action action_desc = idaapi.action_desc_t( 'my:action', # The action name. This acts like an ID and must be unique 'Say hello!', # The action text. MyHandler(), # The action handler. 'Ctrl+H', # Optional: the action shortcut 'Says hello', # Optional: the action tooltip (available in menus/toolbar) 199) # Optional: the action icon (shows when in menus/toolbars) # 3) Register the action idaapi.register_action(action_desc)
From there on, the action is created and, although it has not yet been attached to menus, toolbars or context menus, it can already be invoked by pressing Ctrl+H:
This is slightly more code than before (and in this case, it is explicitely verbose), but regardless of the context/widget in which you want to use that action, it will always be the same API.
Note: In this example, we used icon number 199, which corresponds to a stock icon. It is perfectly possible to define your own icons by using the API function load_custom_icon()
. See kernwin.hpp
for more information.
idaapi.attach_action_to_menu( 'Edit/Other/Manual instruction...', # The relative path of where to add the action 'my:action', # The action ID (see above) idaapi.SETMENU_APP) # We want to append the action after the 'Manual instruction...'
idaapi.attach_action_to_toolbar( "AnalysisToolBar", # The toolbar name 'my:action') # The action ID
There are two ways to add actions to a widget’s context menu:
To permanently attach an action to a widget’s context menu:
# Create a widget, or retrieve a pointer to it. form = idaapi.get_current_tform() idaapi.attach_action_to_popup(form, None, "my:action", None)
Notice that:
get_current_tform()
, to retrieve the widget that currently has the keyboard focus.None
as second parameter to attach_action_to_popup()
. That means we want to attach the action to that widget permanently.Pros:
Cons:
To attach an action to a context menu when it is being created, you need to listen to a UI notification, and use the same attach_action_to_popup
function:
class Hooks(idaapi.UI_Hooks): def populating_tform_popup(self, form, popup): # You can attach here. pass def finish_populating_tform_popup(self, form, popup): # Or here, after the popup is done being populated by its owner. # We will attach our action to the context menu # for the 'Functions window' widget. # The action will be be inserted in a submenu of # the context menu, named 'Others'. if idaapi.get_tform_type(form) == idaapi.BWN_FUNCS: idaapi.attach_action_to_popup(form, popup, "my:action", "Others/") hooks = Hooks() hooks.hook()
Notice that:
BWN_FUNCS
. Many other BWN_*
values are available; see kernwin.hpp
for a list of those!get_tform_title(form)
.Pros:
Cons:
update
callbackAs stated above, the update callback is responsible not only for possibly updating some of the actions properties (See the update_action_*
functions in kernwin.hpp
), but also for stating whether the action is currently available or not, and when update
should be queried again.
Per contract, update
should return one of the following values (again, from kernwin.hpp
):
AST_ENABLE_ALWAYS // enable action and do not call action_handler_t::update() anymore AST_ENABLE_FOR_IDB // enable action for the current idb. Call action_handler_t::update() when a database is opened/closed AST_ENABLE_FOR_FORM // enable action for the current form. Call action_handler_t::update() when a form gets/loses focus AST_ENABLE // enable action - call action_handler_t::update() when anything changes AST_DISABLE_ALWAYS // disable action and do not call action_handler_t::action() anymore AST_DISABLE_FOR_IDB // analog of ::AST_ENABLE_FOR_IDB AST_DISABLE_FOR_FORM // analog of ::AST_ENABLE_FOR_FORM AST_DISABLE // analog of ::AST_ENABLE
Thus, not only does update
tell IDA whether the action is available or not, but it also tells IDA the “time span” during which the action will be (un)available.
For example, returning AST_ENABLE_FOR_FORM
will tell IDA: “this action is available for the current widget; don’t call update
again until the user switches to another widget.”
The AST_*
values really represent various levels of “granularity”, where
AST_ENABLE_ALWAYS
is the coarser level: the action will be available all the time, whatever happens.AST_ENABLE
is the finest level: whenever the user moves the cursor, switches to another form, triggers another action, …, update
will be called again.In most cases, you will want your action to be registered in IDA, and be available through menus, shortcuts, etc…
But there can be “one-shot” situations where you really want to add an action to a context menu only once, and you really don’t want to go through the trouble of registering & deregistering the action.
For those “one-shot” situations, although they are rare, we have created a helper:
class Hooks(idaapi.UI_Hooks): def finish_populating_tform_popup(self, form, popup): tft = idaapi.get_tform_type(form) if tft == idaapi.BWN_EXPORTS: # Define a silly handler. class MyHandler(idaapi.action_handler_t): def activate(self, ctx): print "Hello from exports" def update(self, ctx): return idaapi.AST_ENABLE_ALWAYS # Note the 'None' as action name (1st parameter). # That's because the action will be deleted immediately # after the context menu is hidden anyway, so there's # really no need giving it a valid ID. desc = idaapi.action_desc_t(None, 'My dynamic action', MyHandler()) idaapi.attach_dynamic_action_to_popup(form, popup, desc, None) hooks = Hooks() hooks.hook()
The main difference When using attach_dynamic_action_to_popup
is that the action will be available only during the time the context menu is displayed. Once the context menu is hidden, the action will be unregistered & destroyed.
detach_action_from_menu
detach_action_from_toolbar
unregister_action
All the previous API is still supported. We have fairly good test coverage of those, and therefore are quite confident the backward compatibility is working.
If your plugin suddenly stops working properly in 6.7, that’s a bug. Should that happen, please let us know about it and we’ll do our best to address it.