mirror of
https://github.com/cemu-project/idapython.git
synced 2024-11-27 19:44:18 +01:00
6729 lines
182 KiB
OpenEdge ABL
6729 lines
182 KiB
OpenEdge ABL
// Ignore the va_list functions
|
|
%ignore AskUsingForm_cv;
|
|
%ignore AskUsingForm_c;
|
|
%ignore OpenForm_cv;
|
|
%ignore OpenForm_c;
|
|
%ignore close_form;
|
|
%ignore vaskstr;
|
|
%ignore strvec_t;
|
|
%ignore load_custom_icon;
|
|
%ignore vasktext;
|
|
%ignore add_menu_item;
|
|
%rename (add_menu_item) py_add_menu_item;
|
|
%ignore del_menu_item;
|
|
%rename (del_menu_item) py_del_menu_item;
|
|
%ignore vwarning;
|
|
|
|
%ignore choose_idasgn;
|
|
%rename (choose_idasgn) py_choose_idasgn;
|
|
|
|
%rename (del_hotkey) py_del_hotkey;
|
|
%rename (add_hotkey) py_add_hotkey;
|
|
|
|
%ignore msg;
|
|
%rename (msg) py_msg;
|
|
|
|
%ignore umsg;
|
|
%rename (umsg) py_umsg;
|
|
|
|
%ignore textctrl_info_t;
|
|
%ignore vinfo;
|
|
%ignore UI_Callback;
|
|
%ignore vnomem;
|
|
%ignore vmsg;
|
|
%ignore show_wait_box_v;
|
|
%ignore askbuttons_cv;
|
|
%ignore askfile_cv;
|
|
%ignore askyn_cv;
|
|
%ignore askyn_v;
|
|
%ignore add_custom_viewer_popup_item;
|
|
%ignore create_custom_viewer;
|
|
%ignore take_database_snapshot;
|
|
%ignore restore_database_snapshot;
|
|
%ignore destroy_custom_viewer;
|
|
%ignore destroy_custom_viewerdestroy_custom_viewer;
|
|
%ignore set_custom_viewer_popup_menu;
|
|
%ignore set_custom_viewer_handler;
|
|
%ignore set_custom_viewer_range;
|
|
%ignore is_idaview;
|
|
%ignore refresh_custom_viewer;
|
|
%ignore set_custom_viewer_handlers;
|
|
%ignore get_viewer_name;
|
|
// Ignore these string functions. There are trivial replacements in Python.
|
|
%ignore addblanks;
|
|
%ignore trim;
|
|
%ignore skipSpaces;
|
|
%ignore stristr;
|
|
%ignore set_nav_colorizer;
|
|
%rename (set_nav_colorizer) py_set_nav_colorizer;
|
|
%rename (call_nav_colorizer) py_call_nav_colorizer;
|
|
|
|
%ignore get_highlighted_identifier;
|
|
%rename (get_highlighted_identifier) py_get_highlighted_identifier;
|
|
|
|
|
|
// CLI
|
|
%ignore cli_t;
|
|
%ignore install_command_interpreter;
|
|
%rename (install_command_interpreter) py_install_command_interpreter;
|
|
%ignore remove_command_interpreter;
|
|
%rename (remove_command_interpreter) py_remove_command_interpreter;
|
|
|
|
%ignore action_desc_t::handler;
|
|
%ignore action_handler_t;
|
|
%ignore register_action;
|
|
%rename (register_action) py_register_action;
|
|
%ignore unregister_action;
|
|
%rename (unregister_action) py_unregister_action;
|
|
%ignore attach_dynamic_action_to_popup;
|
|
%rename (attach_dynamic_action_to_popup) py_attach_dynamic_action_to_popup;
|
|
|
|
%include "typemaps.i"
|
|
|
|
%rename (asktext) py_asktext;
|
|
%rename (str2ea) py_str2ea;
|
|
%rename (str2user) py_str2user;
|
|
%ignore process_ui_action;
|
|
%rename (process_ui_action) py_process_ui_action;
|
|
%ignore execute_sync;
|
|
%ignore exec_request_t;
|
|
%rename (execute_sync) py_execute_sync;
|
|
|
|
%ignore ui_request_t;
|
|
%ignore execute_ui_requests;
|
|
%rename (execute_ui_requests) py_execute_ui_requests;
|
|
|
|
%ignore timer_t;
|
|
%ignore register_timer;
|
|
%rename (register_timer) py_register_timer;
|
|
%ignore unregister_timer;
|
|
%rename (unregister_timer) py_unregister_timer;
|
|
|
|
// Make askaddr(), askseg(), and asklong() return a
|
|
// tuple: (result, value)
|
|
%rename (_asklong) asklong;
|
|
%rename (_askaddr) askaddr;
|
|
%rename (_askseg) askseg;
|
|
|
|
%ignore qvector<disasm_line_t>::operator==;
|
|
%ignore qvector<disasm_line_t>::operator!=;
|
|
%ignore qvector<disasm_line_t>::find;
|
|
%ignore qvector<disasm_line_t>::has;
|
|
%ignore qvector<disasm_line_t>::del;
|
|
%ignore qvector<disasm_line_t>::add_unique;
|
|
|
|
%ignore gen_disasm_text;
|
|
%rename (gen_disasm_text) py_gen_disasm_text;
|
|
|
|
%feature("director") UI_Hooks;
|
|
|
|
//-------------------------------------------------------------------------
|
|
%{
|
|
struct py_action_handler_t : public action_handler_t
|
|
{
|
|
py_action_handler_t(); // No.
|
|
py_action_handler_t(PyObject *_o)
|
|
: pyah(borref_t(_o)), has_activate(false), has_update(false)
|
|
{
|
|
ref_t act(PyW_TryGetAttrString(pyah.o, "activate"));
|
|
if ( act != NULL && PyCallable_Check(act.o) > 0 )
|
|
has_activate = true;
|
|
|
|
ref_t upd(PyW_TryGetAttrString(pyah.o, "update"));
|
|
if ( upd != NULL && PyCallable_Check(upd.o) > 0 )
|
|
has_update = true;
|
|
}
|
|
virtual idaapi ~py_action_handler_t()
|
|
{
|
|
PYW_GIL_GET;
|
|
// NOTE: We need to do the decref _within_ the PYW_GIL_GET scope,
|
|
// and not leave it to the destructor to clean it up, because when
|
|
// ~ref_t() gets called, the GIL will have already been released.
|
|
pyah = ref_t();
|
|
}
|
|
virtual int idaapi activate(action_activation_ctx_t *ctx)
|
|
{
|
|
if ( !has_activate )
|
|
return 0;
|
|
PYW_GIL_GET_AND_REPORT_ERROR;
|
|
newref_t pyctx(SWIG_NewPointerObj(SWIG_as_voidptr(ctx), SWIGTYPE_p_action_activation_ctx_t, 0));
|
|
newref_t pyres(PyObject_CallMethod(pyah.o, (char *)"activate", (char *) "O", pyctx.o));
|
|
return PyErr_Occurred() ? 0 : ((pyres != NULL && PyInt_Check(pyres.o)) ? PyInt_AsLong(pyres.o) : 0);
|
|
}
|
|
virtual action_state_t idaapi update(action_update_ctx_t *ctx)
|
|
{
|
|
if ( !has_update )
|
|
return AST_DISABLE;
|
|
PYW_GIL_GET_AND_REPORT_ERROR;
|
|
newref_t pyctx(SWIG_NewPointerObj(SWIG_as_voidptr(ctx), SWIGTYPE_p_action_update_ctx_t, 0));
|
|
newref_t pyres(PyObject_CallMethod(pyah.o, (char *)"update", (char *) "O", pyctx.o));
|
|
return PyErr_Occurred() ? AST_DISABLE_ALWAYS : ((pyres != NULL && PyInt_Check(pyres.o)) ? action_state_t(PyInt_AsLong(pyres.o)) : AST_DISABLE);
|
|
}
|
|
private:
|
|
ref_t pyah;
|
|
bool has_activate;
|
|
bool has_update;
|
|
};
|
|
|
|
typedef std::map<qstring,action_handler_t*> py_action_handlers_t;
|
|
static py_action_handlers_t py_action_handlers;
|
|
|
|
%}
|
|
|
|
%inline %{
|
|
void refresh_lists(void)
|
|
{
|
|
Py_BEGIN_ALLOW_THREADS;
|
|
callui(ui_list);
|
|
Py_END_ALLOW_THREADS;
|
|
}
|
|
%}
|
|
|
|
# This is for get_cursor()
|
|
%apply int *OUTPUT {int *x, int *y};
|
|
|
|
SWIG_DECLARE_PY_CLINKED_OBJECT(textctrl_info_t)
|
|
|
|
%{
|
|
static void _py_unregister_compiled_form(PyObject *py_form, bool shutdown);
|
|
%}
|
|
|
|
%inline %{
|
|
//<inline(py_kernwin)>
|
|
//------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def register_timer(interval, callback):
|
|
"""
|
|
Register a timer
|
|
|
|
@param interval: Interval in milliseconds
|
|
@param callback: A Python callable that takes no parameters and returns an integer.
|
|
The callback may return:
|
|
-1 : to unregister the timer
|
|
>= 0 : the new or same timer interval
|
|
@return: None or a timer object
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
static PyObject *py_register_timer(int interval, PyObject *py_callback)
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
|
|
if ( py_callback == NULL || !PyCallable_Check(py_callback) )
|
|
Py_RETURN_NONE;
|
|
|
|
// An inner class hosting the callback method
|
|
struct tmr_t
|
|
{
|
|
static int idaapi callback(void *ud)
|
|
{
|
|
PYW_GIL_GET;
|
|
py_timer_ctx_t *ctx = (py_timer_ctx_t *)ud;
|
|
newref_t py_result(PyObject_CallFunctionObjArgs(ctx->pycallback, NULL));
|
|
int ret = py_result == NULL ? -1 : PyLong_AsLong(py_result.o);
|
|
|
|
// Timer has been unregistered?
|
|
if ( ret == -1 )
|
|
{
|
|
// Free the context
|
|
Py_DECREF(ctx->pycallback);
|
|
delete ctx;
|
|
}
|
|
return ret;
|
|
};
|
|
};
|
|
|
|
py_timer_ctx_t *ctx = new py_timer_ctx_t();
|
|
ctx->pycallback = py_callback;
|
|
Py_INCREF(py_callback);
|
|
ctx->timer_id = register_timer(
|
|
interval,
|
|
tmr_t::callback,
|
|
ctx);
|
|
|
|
if ( ctx->timer_id == NULL )
|
|
{
|
|
Py_DECREF(py_callback);
|
|
delete ctx;
|
|
Py_RETURN_NONE;
|
|
}
|
|
return PyCObject_FromVoidPtr(ctx, NULL);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def unregister_timer(timer_obj):
|
|
"""
|
|
Unregister a timer
|
|
|
|
@param timer_obj: a timer object previously returned by a register_timer()
|
|
@return: Boolean
|
|
@note: After the timer has been deleted, the timer_obj will become invalid.
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
static PyObject *py_unregister_timer(PyObject *py_timerctx)
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
|
|
if ( py_timerctx == NULL || !PyCObject_Check(py_timerctx) )
|
|
Py_RETURN_FALSE;
|
|
|
|
py_timer_ctx_t *ctx = (py_timer_ctx_t *) PyCObject_AsVoidPtr(py_timerctx);
|
|
if ( !unregister_timer(ctx->timer_id) )
|
|
Py_RETURN_FALSE;
|
|
|
|
Py_DECREF(ctx->pycallback);
|
|
delete ctx;
|
|
|
|
Py_RETURN_TRUE;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def choose_idasgn():
|
|
"""
|
|
Opens the signature chooser
|
|
|
|
@return: None or the selected signature name
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
static PyObject *py_choose_idasgn()
|
|
{
|
|
char *name = choose_idasgn();
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
if ( name == NULL )
|
|
{
|
|
Py_RETURN_NONE;
|
|
}
|
|
else
|
|
{
|
|
PyObject *py_str = PyString_FromString(name);
|
|
qfree(name);
|
|
return py_str;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def get_highlighted_identifier(flags = 0):
|
|
"""
|
|
Returns the currently highlighted identifier
|
|
|
|
@param flags: reserved (pass 0)
|
|
@return: None or the highlighted identifier
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
static PyObject *py_get_highlighted_identifier(int flags = 0)
|
|
{
|
|
char buf[MAXSTR];
|
|
bool ok = get_highlighted_identifier(buf, sizeof(buf), flags);
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
if ( !ok )
|
|
Py_RETURN_NONE;
|
|
else
|
|
return PyString_FromString(buf);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
static int py_load_custom_icon_fn(const char *filename)
|
|
{
|
|
return load_custom_icon(filename);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
static int py_load_custom_icon_data(PyObject *data, const char *format)
|
|
{
|
|
Py_ssize_t len;
|
|
char *s;
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
if ( PyString_AsStringAndSize(data, &s, &len) == -1 )
|
|
return 0;
|
|
else
|
|
return load_custom_icon(s, len, format);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def free_custom_icon(icon_id):
|
|
"""
|
|
Frees an icon loaded with load_custom_icon()
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
|
|
//-------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def readsel2(view, p0, p1):
|
|
"""
|
|
Read the user selection, and store its information in p0 (from) and p1 (to).
|
|
|
|
This can be used as follows:
|
|
|
|
|
|
>>> p0 = idaapi.twinpos_t()
|
|
p1 = idaapi.twinpos_t()
|
|
view = idaapi.get_current_viewer()
|
|
idaapi.readsel2(view, p0, p1)
|
|
|
|
|
|
At that point, p0 and p1 hold information for the selection.
|
|
But, the 'at' property of p0 and p1 is not properly typed.
|
|
To specialize it, call #place() on it, passing it the view
|
|
they were retrieved from. Like so:
|
|
|
|
|
|
>>> place0 = p0.place(view)
|
|
place1 = p1.place(view)
|
|
|
|
|
|
This will effectively "cast" the place into a specialized type,
|
|
holding proper information, depending on the view type (e.g.,
|
|
disassembly, structures, enums, ...)
|
|
|
|
@param view: The view to retrieve the selection for.
|
|
@param p0: Storage for the "from" part of the selection.
|
|
@param p1: Storage for the "to" part of the selection.
|
|
@return: a bool value indicating success.
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def umsg(text):
|
|
"""
|
|
Prints text into IDA's Output window
|
|
|
|
@param text: text to print
|
|
Can be Unicode, or string in UTF-8 encoding
|
|
@return: number of bytes printed
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
static PyObject* py_umsg(PyObject *o)
|
|
{
|
|
PyObject* utf8 = NULL;
|
|
if ( PyUnicode_Check(o) )
|
|
{
|
|
utf8 = PyUnicode_AsUTF8String(o);
|
|
o = utf8;
|
|
}
|
|
else if ( !PyString_Check(o) )
|
|
{
|
|
PyErr_SetString(PyExc_TypeError, "A unicode or UTF-8 string expected");
|
|
return NULL;
|
|
}
|
|
int rc;
|
|
Py_BEGIN_ALLOW_THREADS;
|
|
rc = umsg("%s", PyString_AsString(o));
|
|
Py_END_ALLOW_THREADS;
|
|
Py_XDECREF(utf8);
|
|
return PyInt_FromLong(rc);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def msg(text):
|
|
"""
|
|
Prints text into IDA's Output window
|
|
|
|
@param text: text to print
|
|
Can be Unicode, or string in local encoding
|
|
@return: number of bytes printed
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
static PyObject* py_msg(PyObject *o)
|
|
{
|
|
if ( PyUnicode_Check(o) )
|
|
return py_umsg(o);
|
|
|
|
if ( !PyString_Check(o) )
|
|
{
|
|
PyErr_SetString(PyExc_TypeError, "A string expected");
|
|
return NULL;
|
|
}
|
|
int rc;
|
|
Py_BEGIN_ALLOW_THREADS;
|
|
rc = msg("%s", PyString_AsString(o));
|
|
Py_END_ALLOW_THREADS;
|
|
return PyInt_FromLong(rc);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def asktext(max_text, defval, prompt):
|
|
"""
|
|
Asks for a long text
|
|
|
|
@param max_text: Maximum text length
|
|
@param defval: The default value
|
|
@param prompt: The prompt value
|
|
@return: None or the entered string
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
PyObject *py_asktext(int max_text, const char *defval, const char *prompt)
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
if ( max_text <= 0 )
|
|
Py_RETURN_NONE;
|
|
|
|
char *buf = new char[max_text];
|
|
if ( buf == NULL )
|
|
Py_RETURN_NONE;
|
|
|
|
PyObject *py_ret;
|
|
if ( asktext(size_t(max_text), buf, defval, "%s", prompt) != NULL )
|
|
{
|
|
py_ret = PyString_FromString(buf);
|
|
}
|
|
else
|
|
{
|
|
py_ret = Py_None;
|
|
Py_INCREF(py_ret);
|
|
}
|
|
delete [] buf;
|
|
return py_ret;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def str2ea(addr):
|
|
"""
|
|
Converts a string express to EA. The expression evaluator may be called as well.
|
|
|
|
@return: BADADDR or address value
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
ea_t py_str2ea(const char *str, ea_t screenEA = BADADDR)
|
|
{
|
|
ea_t ea;
|
|
bool ok = str2ea(str, &ea, screenEA);
|
|
return ok ? ea : BADADDR;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def str2user(str):
|
|
"""
|
|
Insert C-style escape characters to string
|
|
|
|
@return: new string with escape characters inserted
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
PyObject *py_str2user(const char *str)
|
|
{
|
|
qstring qstr(str);
|
|
qstring retstr;
|
|
qstr2user(&retstr, qstr);
|
|
return PyString_FromString(retstr.c_str());
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def process_ui_action(name):
|
|
"""
|
|
Invokes an IDA UI action by name
|
|
|
|
@param name: action name
|
|
@return: Boolean
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
static bool py_process_ui_action(const char *name, int flags = 0)
|
|
{
|
|
return process_ui_action(name, flags, NULL);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def del_menu_item(menu_ctx):
|
|
"""Deprecated. Use detach_menu_item()/unregister_action() instead."""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
static bool py_del_menu_item(PyObject *py_ctx)
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
if ( !PyCObject_Check(py_ctx) )
|
|
return false;
|
|
|
|
py_add_del_menu_item_ctx *ctx = (py_add_del_menu_item_ctx *)PyCObject_AsVoidPtr(py_ctx);
|
|
|
|
bool ok = del_menu_item(ctx->menupath.c_str());
|
|
if ( ok )
|
|
{
|
|
Py_DECREF(ctx->cb_data);
|
|
delete ctx;
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def del_hotkey(ctx):
|
|
"""
|
|
Deletes a previously registered function hotkey
|
|
|
|
@param ctx: Hotkey context previously returned by add_hotkey()
|
|
|
|
@return: Boolean.
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
bool py_del_hotkey(PyObject *pyctx)
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
if ( !PyCObject_Check(pyctx) )
|
|
return false;
|
|
|
|
py_idchotkey_ctx_t *ctx = (py_idchotkey_ctx_t *) PyCObject_AsVoidPtr(pyctx);
|
|
if ( !del_idc_hotkey(ctx->hotkey.c_str()) )
|
|
return false;
|
|
|
|
Py_DECREF(ctx->pyfunc);
|
|
delete ctx;
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def add_hotkey(hotkey, pyfunc):
|
|
"""
|
|
Associates a function call with a hotkey.
|
|
Callable pyfunc will be called each time the hotkey is pressed
|
|
|
|
@param hotkey: The hotkey
|
|
@param pyfunc: Callable
|
|
|
|
@return: Context object on success or None on failure.
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
PyObject *py_add_hotkey(const char *hotkey, PyObject *pyfunc)
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
// Make sure a callable was passed
|
|
if ( !PyCallable_Check(pyfunc) )
|
|
return NULL;
|
|
|
|
// Form the function name
|
|
qstring idc_func_name;
|
|
idc_func_name.sprnt("py_hotkeycb_%p", pyfunc);
|
|
|
|
// Can add the hotkey?
|
|
if ( add_idc_hotkey(hotkey, idc_func_name.c_str()) == IDCHK_OK ) do
|
|
{
|
|
// Generate global variable name
|
|
qstring idc_gvarname;
|
|
idc_gvarname.sprnt("_g_pyhotkey_ref_%p", pyfunc);
|
|
|
|
// Now add the global variable
|
|
idc_value_t *gvar = add_idc_gvar(idc_gvarname.c_str());
|
|
if ( gvar == NULL )
|
|
break;
|
|
|
|
// The function body will call a registered IDC function that
|
|
// will take a global variable that wraps a PyCallable as a pvoid
|
|
qstring idc_func;
|
|
idc_func.sprnt("static %s() { %s(%s); }",
|
|
idc_func_name.c_str(),
|
|
S_PYINVOKE0,
|
|
idc_gvarname.c_str());
|
|
|
|
// Compile the IDC condition
|
|
char errbuf[MAXSTR];
|
|
if ( !CompileLineEx(idc_func.c_str(), errbuf, sizeof(errbuf)) )
|
|
break;
|
|
|
|
// Create new context
|
|
// Define context
|
|
py_idchotkey_ctx_t *ctx = new py_idchotkey_ctx_t();
|
|
|
|
// Remember the hotkey
|
|
ctx->hotkey = hotkey;
|
|
|
|
// Take reference to the callable
|
|
ctx->pyfunc = pyfunc;
|
|
Py_INCREF(pyfunc);
|
|
|
|
// Bind IDC variable w/ the PyCallable
|
|
gvar->set_pvoid(pyfunc);
|
|
|
|
// Return the context
|
|
return PyCObject_FromVoidPtr(ctx, NULL);
|
|
} while (false);
|
|
|
|
// Cleanup
|
|
del_idc_hotkey(hotkey);
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def add_menu_item(menupath, name, hotkey, flags, callback, args):
|
|
"""Deprecated. Use register_action()/attach_menu_item() instead."""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
bool idaapi py_menu_item_callback(void *userdata);
|
|
static PyObject *py_add_menu_item(
|
|
const char *menupath,
|
|
const char *name,
|
|
const char *hotkey,
|
|
int flags,
|
|
PyObject *pyfunc,
|
|
PyObject *args)
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
bool no_args;
|
|
|
|
// No slash in the menu path?
|
|
const char *p = strrchr(menupath, '/');
|
|
if ( p == NULL )
|
|
Py_RETURN_NONE;
|
|
|
|
if ( args == Py_None )
|
|
{
|
|
no_args = true;
|
|
args = PyTuple_New(0);
|
|
if ( args == NULL )
|
|
return NULL;
|
|
}
|
|
else if ( !PyTuple_Check(args) )
|
|
{
|
|
PyErr_SetString(PyExc_TypeError, "args must be a tuple or None");
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
no_args = false;
|
|
}
|
|
|
|
// Form a tuple holding the function to be called and its arguments
|
|
PyObject *cb_data = Py_BuildValue("(OO)", pyfunc, args);
|
|
|
|
// If we created an empty tuple, then we must free it
|
|
if ( no_args )
|
|
Py_DECREF(args);
|
|
|
|
// Add the menu item
|
|
bool b = add_menu_item(menupath, name, hotkey, flags, py_menu_item_callback, (void *)cb_data);
|
|
|
|
if ( !b )
|
|
{
|
|
Py_XDECREF(cb_data);
|
|
Py_RETURN_NONE;
|
|
}
|
|
// Create a context (for the delete_menu_item())
|
|
py_add_del_menu_item_ctx *ctx = new py_add_del_menu_item_ctx();
|
|
|
|
// Form the complete menu path
|
|
ctx->menupath.append(menupath, p - menupath + 1);
|
|
ctx->menupath.append(name);
|
|
// Save callback data
|
|
ctx->cb_data = cb_data;
|
|
|
|
// Return context to user
|
|
return PyCObject_FromVoidPtr(ctx, NULL);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
|
|
MFF_FAST = 0x0000
|
|
"""execute code as soon as possible
|
|
this mode is ok call ui related functions
|
|
that do not query the database."""
|
|
|
|
MFF_READ = 0x0001
|
|
"""execute code only when ida is idle and it is safe to query the database.
|
|
this mode is recommended only for code that does not modify the database.
|
|
(nb: ida may be in the middle of executing another user request, for example it may be waiting for him to enter values into a modal dialog box)"""
|
|
|
|
MFF_WRITE = 0x0002
|
|
"""execute code only when ida is idle and it is safe to modify the database. in particular, this flag will suspend execution if there is
|
|
a modal dialog box on the screen this mode can be used to call any ida api function. MFF_WRITE implies MFF_READ"""
|
|
|
|
MFF_NOWAIT = 0x0004
|
|
"""Do not wait for the request to be executed.
|
|
he caller should ensure that the request is not
|
|
destroyed until the execution completes.
|
|
if not, the request will be ignored.
|
|
the return code of execute_sync() is meaningless
|
|
in this case.
|
|
This flag can be used to delay the code execution
|
|
until the next UI loop run even from the main thread"""
|
|
|
|
def execute_sync(callable, reqf):
|
|
"""
|
|
Executes a function in the context of the main thread.
|
|
If the current thread not the main thread, then the call is queued and
|
|
executed afterwards.
|
|
|
|
@note: The Python version of execute_sync() cannot be called from a different thread
|
|
for the time being.
|
|
@param callable: A python callable object
|
|
@param reqf: one of MFF_ flags
|
|
@return: -1 or the return value of the callable
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
//------------------------------------------------------------------------
|
|
static int py_execute_sync(PyObject *py_callable, int reqf)
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
int rc = -1;
|
|
// Callable?
|
|
if ( PyCallable_Check(py_callable) )
|
|
{
|
|
struct py_exec_request_t : exec_request_t
|
|
{
|
|
ref_t py_callable;
|
|
virtual int idaapi execute()
|
|
{
|
|
PYW_GIL_GET;
|
|
newref_t py_result(PyObject_CallFunctionObjArgs(py_callable.o, NULL));
|
|
int ret = py_result == NULL || !PyInt_Check(py_result.o)
|
|
? -1
|
|
: PyInt_AsLong(py_result.o);
|
|
// if the requesting thread decided not to wait for the request to
|
|
// complete, we have to self-destroy, nobody else will do it
|
|
if ( (code & MFF_NOWAIT) != 0 )
|
|
delete this;
|
|
return ret;
|
|
}
|
|
py_exec_request_t(PyObject *pyc)
|
|
{
|
|
// No need to GIL-ensure here, since this is created
|
|
// within the py_execute_sync() scope.
|
|
py_callable = borref_t(pyc);
|
|
}
|
|
virtual ~py_exec_request_t()
|
|
{
|
|
// Need to GIL-ensure here, since this might be called
|
|
// from the main thread.
|
|
PYW_GIL_GET;
|
|
py_callable = ref_t(); // Release callable
|
|
}
|
|
};
|
|
py_exec_request_t *req = new py_exec_request_t(py_callable);
|
|
|
|
// Release GIL before executing, or if this is running in the
|
|
// non-main thread, this will wait on the req.sem, while the main
|
|
// thread might be waiting for the GIL to be available.
|
|
Py_BEGIN_ALLOW_THREADS;
|
|
rc = execute_sync(*req, reqf);
|
|
Py_END_ALLOW_THREADS;
|
|
// destroy the request once it is finished. exception: NOWAIT requests
|
|
// will be handled in the future, so do not destroy them yet!
|
|
if ( (reqf & MFF_NOWAIT) == 0 )
|
|
delete req;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
|
|
def execute_ui_requests(callable_list):
|
|
"""
|
|
Inserts a list of callables into the UI message processing queue.
|
|
When the UI is ready it will call one callable.
|
|
A callable can request to be called more than once if it returns True.
|
|
|
|
@param callable_list: A list of python callable objects.
|
|
@note: A callable should return True if it wants to be called more than once.
|
|
@return: Boolean. False if the list contains a non callabale item
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
static bool py_execute_ui_requests(PyObject *py_list)
|
|
{
|
|
struct py_ui_request_t: public ui_request_t
|
|
{
|
|
private:
|
|
ref_vec_t py_callables;
|
|
size_t py_callable_idx;
|
|
|
|
static int idaapi s_py_list_walk_cb(
|
|
const ref_t &py_item,
|
|
Py_ssize_t index,
|
|
void *ud)
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
// Not callable? Terminate iteration
|
|
if ( !PyCallable_Check(py_item.o) )
|
|
return CIP_FAILED;
|
|
|
|
// Append this callable and increment its reference
|
|
py_ui_request_t *_this = (py_ui_request_t *)ud;
|
|
_this->py_callables.push_back(py_item);
|
|
return CIP_OK;
|
|
}
|
|
public:
|
|
py_ui_request_t(): py_callable_idx(0)
|
|
{
|
|
}
|
|
|
|
virtual bool idaapi run()
|
|
{
|
|
PYW_GIL_GET;
|
|
|
|
// Get callable
|
|
ref_t py_callable = py_callables.at(py_callable_idx);
|
|
bool reschedule;
|
|
newref_t py_result(PyObject_CallFunctionObjArgs(py_callable.o, NULL));
|
|
reschedule = py_result != NULL && PyObject_IsTrue(py_result.o);
|
|
|
|
// No rescheduling? Then advance to the next callable
|
|
if ( !reschedule )
|
|
++py_callable_idx;
|
|
|
|
// Reschedule this C callback only if there are more callables
|
|
return py_callable_idx < py_callables.size();
|
|
}
|
|
|
|
// Walk the list and extract all callables
|
|
bool init(PyObject *py_list)
|
|
{
|
|
Py_ssize_t count = pyvar_walk_list(
|
|
py_list,
|
|
s_py_list_walk_cb,
|
|
this);
|
|
return count > 0;
|
|
}
|
|
|
|
virtual idaapi ~py_ui_request_t()
|
|
{
|
|
py_callables.clear();
|
|
}
|
|
};
|
|
|
|
py_ui_request_t *req = new py_ui_request_t();
|
|
if ( !req->init(py_list) )
|
|
{
|
|
delete req;
|
|
return false;
|
|
}
|
|
execute_ui_requests(req, NULL);
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def set_dock_pos(src, dest, orient, left = 0, top = 0, right = 0, bottom = 0):
|
|
"""
|
|
Sets the dock orientation of a window relatively to another window.
|
|
|
|
@param src: Source docking control
|
|
@param dest: Destination docking control
|
|
@param orient: One of DOR_XXXX constants
|
|
@param left, top, right, bottom: These parameter if DOR_FLOATING is used, or if you want to specify the width of docked windows
|
|
@return: Boolean
|
|
|
|
Example:
|
|
set_dock_pos('Structures', 'Enums', DOR_RIGHT) <- docks the Structures window to the right of Enums window
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def is_idaq():
|
|
"""
|
|
Returns True or False depending if IDAPython is hosted by IDAQ
|
|
"""
|
|
#</pydoc>
|
|
*/
|
|
|
|
//---------------------------------------------------------------------------
|
|
// UI hooks
|
|
//---------------------------------------------------------------------------
|
|
int idaapi UI_Callback(void *ud, int notification_code, va_list va);
|
|
/*
|
|
#<pydoc>
|
|
class UI_Hooks(object):
|
|
def hook(self):
|
|
"""
|
|
Creates an UI hook
|
|
|
|
@return: Boolean true on success
|
|
"""
|
|
pass
|
|
|
|
def unhook(self):
|
|
"""
|
|
Removes the UI hook
|
|
@return: Boolean true on success
|
|
"""
|
|
pass
|
|
|
|
def preprocess(self, name):
|
|
"""
|
|
IDA ui is about to handle a user command
|
|
|
|
@param name: ui command name
|
|
(these names can be looked up in ida[tg]ui.cfg)
|
|
@return: 0-ok, nonzero - a plugin has handled the command
|
|
"""
|
|
pass
|
|
|
|
def postprocess(self):
|
|
"""
|
|
An ida ui command has been handled
|
|
|
|
@return: Ignored
|
|
"""
|
|
pass
|
|
|
|
def saving(self):
|
|
"""
|
|
The kernel is saving the database.
|
|
|
|
@return: Ignored
|
|
"""
|
|
pass
|
|
|
|
def saved(self):
|
|
"""
|
|
The kernel has saved the database.
|
|
|
|
@return: Ignored
|
|
"""
|
|
pass
|
|
|
|
def get_ea_hint(self, ea):
|
|
"""
|
|
The UI wants to display a simple hint for an address in the navigation band
|
|
|
|
@param ea: The address
|
|
@return: String with the hint or None
|
|
"""
|
|
pass
|
|
|
|
def updating_actions(self, ctx):
|
|
"""
|
|
The UI is about to batch-update some actions.
|
|
|
|
@param ctx: The action_update_ctx_t instance
|
|
@return: Ignored
|
|
"""
|
|
pass
|
|
|
|
def updated_actions(self):
|
|
"""
|
|
The UI is done updating actions.
|
|
|
|
@return: Ignored
|
|
"""
|
|
pass
|
|
|
|
def populating_tform_popup(self, form, popup):
|
|
"""
|
|
The UI is populating the TForm's popup menu.
|
|
Now is a good time to call idaapi.attach_action_to_popup()
|
|
|
|
@param form: The form
|
|
@param popup: The popup menu.
|
|
@return: Ignored
|
|
"""
|
|
pass
|
|
|
|
def finish_populating_tform_popup(self, form, popup):
|
|
"""
|
|
The UI is about to be done populating the TForm's popup menu.
|
|
Now is a good time to call idaapi.attach_action_to_popup()
|
|
|
|
@param form: The form
|
|
@param popup: The popup menu.
|
|
@return: Ignored
|
|
"""
|
|
pass
|
|
|
|
def term(self):
|
|
"""
|
|
IDA is terminated and the database is already closed.
|
|
The UI may close its windows in this callback.
|
|
"""
|
|
# if the user forgot to call unhook, do it for him
|
|
self.unhook()
|
|
|
|
def __term__(self):
|
|
self.term()
|
|
|
|
#</pydoc>
|
|
*/
|
|
class UI_Hooks
|
|
{
|
|
public:
|
|
virtual ~UI_Hooks()
|
|
{
|
|
unhook();
|
|
}
|
|
|
|
bool hook()
|
|
{
|
|
return hook_to_notification_point(HT_UI, UI_Callback, this);
|
|
}
|
|
|
|
bool unhook()
|
|
{
|
|
return unhook_from_notification_point(HT_UI, UI_Callback, this);
|
|
}
|
|
|
|
virtual int preprocess(const char * /*name*/)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
virtual void postprocess()
|
|
{
|
|
}
|
|
|
|
virtual void saving()
|
|
{
|
|
}
|
|
|
|
virtual void saved()
|
|
{
|
|
}
|
|
|
|
virtual void term()
|
|
{
|
|
}
|
|
|
|
virtual PyObject *get_ea_hint(ea_t /*ea*/)
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
Py_RETURN_NONE;
|
|
};
|
|
|
|
virtual void current_tform_changed(TForm * /*form*/, TForm * /*previous_form*/)
|
|
{
|
|
}
|
|
|
|
virtual void updating_actions(action_update_ctx_t *ctx)
|
|
{
|
|
}
|
|
|
|
virtual void updated_actions()
|
|
{
|
|
}
|
|
|
|
virtual void populating_tform_popup(TForm * /*form*/, TPopupMenu * /*popup*/)
|
|
{
|
|
}
|
|
|
|
virtual void finish_populating_tform_popup(TForm * /*form*/, TPopupMenu * /*popup*/)
|
|
{
|
|
}
|
|
};
|
|
|
|
//-------------------------------------------------------------------------
|
|
bool py_register_action(action_desc_t *desc)
|
|
{
|
|
bool ok = register_action(*desc);
|
|
if ( ok )
|
|
{
|
|
// Success. We are managing this handler from now on,
|
|
// and must prevent it from being destroyed.
|
|
py_action_handlers[desc->name] = desc->handler;
|
|
// Let's set this to NULL, so when the wrapping Python action_desc_t
|
|
// instance is deleted, it doesn't try to delete the handler (See
|
|
// kernwin.i's action_desc_t::~action_desc_t()).
|
|
desc->handler = NULL;
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
bool py_unregister_action(const char *name)
|
|
{
|
|
bool ok = unregister_action(name);
|
|
if ( ok )
|
|
{
|
|
py_action_handler_t *handler =
|
|
(py_action_handler_t *) py_action_handlers[name];
|
|
delete handler;
|
|
py_action_handlers.erase(name);
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
bool py_attach_dynamic_action_to_popup(
|
|
TForm *form,
|
|
TPopupMenu *popup_handle,
|
|
action_desc_t *desc,
|
|
const char *popuppath = NULL,
|
|
int flags = 0)
|
|
{
|
|
bool ok = attach_dynamic_action_to_popup(
|
|
form, popup_handle, *desc, popuppath, flags);
|
|
if ( ok )
|
|
// Set the handler to null, so the desc won't destroy
|
|
// it, as it noticed ownership was taken by IDA.
|
|
// In addition, we don't need to register into the
|
|
// 'py_action_handlers', because IDA will destroy the
|
|
// handler as soon as the popup menu is closed.
|
|
desc->handler = NULL;
|
|
return ok;
|
|
}
|
|
|
|
// This is similar to a twinline_t, with improved memory management:
|
|
// twinline_t has a dummy destructor, that performs no cleanup.
|
|
struct disasm_line_t
|
|
{
|
|
disasm_line_t() : at(NULL) {}
|
|
~disasm_line_t() { qfree(at); }
|
|
disasm_line_t(const disasm_line_t &other) { *this = other; }
|
|
disasm_line_t &operator=(const disasm_line_t &other)
|
|
{
|
|
qfree(at);
|
|
at = other.at == NULL ? NULL : other.at->clone();
|
|
return *this;
|
|
}
|
|
place_t *at;
|
|
qstring line;
|
|
color_t prefix_color;
|
|
bgcolor_t bg_color;
|
|
bool is_default;
|
|
};
|
|
DECLARE_TYPE_AS_MOVABLE(disasm_line_t);
|
|
typedef qvector<disasm_line_t> disasm_text_t;
|
|
|
|
//-------------------------------------------------------------------------
|
|
void py_gen_disasm_text(ea_t ea1, ea_t ea2, disasm_text_t &text, bool truncate_lines)
|
|
{
|
|
text_t _text;
|
|
gen_disasm_text(ea1, ea2, _text, truncate_lines);
|
|
for ( size_t i = 0, n = _text.size(); i < n; ++i )
|
|
{
|
|
const twinline_t &tl = _text[i];
|
|
disasm_line_t &dl = text.push_back();
|
|
dl.at = tl.at; // Transfer ownership
|
|
dl.line.inject(tl.line); // Transfer ownership
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Although 'TCustomControl*' and 'TForm*' instances can both be used
|
|
// for attach_action_to_popup() at a binary-level, IDAPython SWIG bindings
|
|
// require that a 'TForm *' wrapper be passed to wrap_attach_action_to_popup().
|
|
// Thus, we provide another attach_action_to_popup() version, that
|
|
// accepts a 'TCustomControl' as first argument.
|
|
//
|
|
// Since user-created GraphViewer are created like so:
|
|
// +-------- PluginForm ----------+
|
|
// |+----- TCustomControl -------+|
|
|
// || ||
|
|
// || ||
|
|
// || ||
|
|
// || ||
|
|
// || ||
|
|
// |+----------------------------+|
|
|
// +------------------------------+
|
|
// The user cannot use GetTForm(), and use that to attach
|
|
// an action to, because that'll attach the action to the PluginForm
|
|
// instance.
|
|
// Instead, the user must use GetTCustomControl(), and call
|
|
// this function below with it.
|
|
bool attach_action_to_popup(
|
|
TCustomControl *tcc,
|
|
TPopupMenu *popup_handle,
|
|
const char *name,
|
|
const char *popuppath = NULL,
|
|
int flags = 0)
|
|
{
|
|
return attach_action_to_popup((TForm*) tcc, popup_handle, name, popuppath, flags);
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def set_nav_colorizer(callback):
|
|
"""
|
|
Set a new colorizer for the navigation band.
|
|
|
|
The 'callback' is a function of 2 arguments:
|
|
- ea (the EA to colorize for)
|
|
- nbytes (the number of bytes at that EA)
|
|
and must return a 'long' value.
|
|
|
|
The previous colorizer is returned, allowing
|
|
the new 'callback' to use 'call_nav_colorizer'
|
|
with it.
|
|
|
|
Note that the previous colorizer is returned
|
|
only the first time set_nav_colorizer() is called:
|
|
due to the way the colorizers API is defined in C,
|
|
it is impossible to chain more than 2 colorizers
|
|
in IDAPython: the original, IDA-provided colorizer,
|
|
and a user-provided one.
|
|
|
|
Example: colorizer inverting the color provided by the IDA colorizer:
|
|
def my_colorizer(ea, nbytes):
|
|
global ida_colorizer
|
|
orig = idaapi.call_nav_colorizer(ida_colorizer, ea, nbytes)
|
|
return long(~orig)
|
|
|
|
ida_colorizer = idaapi.set_nav_colorizer(my_colorizer)
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
nav_colorizer_t *py_set_nav_colorizer(PyObject *new_py_colorizer)
|
|
{
|
|
static ref_t py_colorizer;
|
|
struct ida_local lambda_t
|
|
{
|
|
static uint32 idaapi call_py_colorizer(ea_t ea, asize_t nbytes)
|
|
{
|
|
PYW_GIL_GET;
|
|
|
|
if ( py_colorizer == NULL ) // Shouldn't happen.
|
|
return 0;
|
|
newref_t pyres = PyObject_CallFunction(
|
|
py_colorizer.o, "KK",
|
|
(unsigned long long) ea,
|
|
(unsigned long long) nbytes);
|
|
PyW_ShowCbErr("nav_colorizer");
|
|
if ( pyres.o == NULL )
|
|
return 0;
|
|
if ( !PyLong_Check(pyres.o) )
|
|
{
|
|
static bool warned = false;
|
|
if ( !warned )
|
|
{
|
|
msg("WARNING: set_nav_colorizer() callback must return a 'long'.\n");
|
|
warned = true;
|
|
}
|
|
return 0;
|
|
}
|
|
return PyLong_AsLong(pyres.o);
|
|
}
|
|
};
|
|
|
|
bool need_install = py_colorizer == NULL;
|
|
py_colorizer = borref_t(new_py_colorizer);
|
|
return need_install
|
|
? set_nav_colorizer(lambda_t::call_py_colorizer)
|
|
: NULL;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
/*
|
|
#<pydoc>
|
|
def call_nav_colorizer(colorizer, ea, nbytes):
|
|
"""
|
|
To be used with the IDA-provided colorizer, that is
|
|
returned as result of the first call to set_nav_colorizer().
|
|
|
|
This is a trivial trampoline, so that SWIG can generate a
|
|
wrapper that will do the types checking.
|
|
"""
|
|
pass
|
|
#</pydoc>
|
|
*/
|
|
uint32 py_call_nav_colorizer(
|
|
nav_colorizer_t *col,
|
|
ea_t ea,
|
|
asize_t nbytes)
|
|
{
|
|
return col(ea, nbytes);
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
uint32 idaapi choose_sizer(void *self)
|
|
{
|
|
PYW_GIL_GET;
|
|
newref_t pyres(PyObject_CallMethod((PyObject *)self, "sizer", ""));
|
|
return PyInt_AsLong(pyres.o);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
char *idaapi choose_getl(void *self, uint32 n, char *buf)
|
|
{
|
|
PYW_GIL_GET;
|
|
newref_t pyres(
|
|
PyObject_CallMethod(
|
|
(PyObject *)self,
|
|
"getl",
|
|
"l",
|
|
n));
|
|
|
|
const char *res;
|
|
if (pyres == NULL || (res = PyString_AsString(pyres.o)) == NULL )
|
|
qstrncpy(buf, "<Empty>", MAXSTR);
|
|
else
|
|
qstrncpy(buf, res, MAXSTR);
|
|
return buf;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
void idaapi choose_enter(void *self, uint32 n)
|
|
{
|
|
PYW_GIL_GET;
|
|
newref_t res(PyObject_CallMethod((PyObject *)self, "enter", "l", n));
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
uint32 choose_choose(
|
|
void *self,
|
|
int flags,
|
|
int x0,int y0,
|
|
int x1,int y1,
|
|
int width,
|
|
int deflt,
|
|
int icon)
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
newref_t pytitle(PyObject_GetAttrString((PyObject *)self, "title"));
|
|
const char *title = pytitle != NULL ? PyString_AsString(pytitle.o) : "Choose";
|
|
|
|
int r = choose(
|
|
flags,
|
|
x0, y0,
|
|
x1, y1,
|
|
self,
|
|
width,
|
|
choose_sizer,
|
|
choose_getl,
|
|
title,
|
|
icon,
|
|
deflt,
|
|
NULL, /* del */
|
|
NULL, /* inst */
|
|
NULL, /* update */
|
|
NULL, /* edit */
|
|
choose_enter,
|
|
NULL, /* destroy */
|
|
NULL, /* popup_names */
|
|
NULL);/* get_icon */
|
|
|
|
return r;
|
|
}
|
|
|
|
|
|
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);
|
|
|
|
|
|
#define DECLARE_FORM_ACTIONS form_actions_t *fa = (form_actions_t *)p_fa;
|
|
|
|
//---------------------------------------------------------------------------
|
|
static bool textctrl_info_t_assign(PyObject *self, PyObject *other)
|
|
{
|
|
textctrl_info_t *lhs = textctrl_info_t_get_clink(self);
|
|
textctrl_info_t *rhs = textctrl_info_t_get_clink(other);
|
|
if (lhs == NULL || rhs == NULL)
|
|
return false;
|
|
|
|
*lhs = *rhs;
|
|
return true;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
static bool textctrl_info_t_set_text(PyObject *self, const char *s)
|
|
{
|
|
textctrl_info_t *ti = (textctrl_info_t *)pyobj_get_clink(self);
|
|
if ( ti == NULL )
|
|
return false;
|
|
ti->text = s;
|
|
return true;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
static const char *textctrl_info_t_get_text(PyObject *self)
|
|
{
|
|
textctrl_info_t *ti = (textctrl_info_t *)pyobj_get_clink(self);
|
|
return ti == NULL ? "" : ti->text.c_str();
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
static bool textctrl_info_t_set_flags(PyObject *self, unsigned int flags)
|
|
{
|
|
textctrl_info_t *ti = (textctrl_info_t *)pyobj_get_clink(self);
|
|
if ( ti == NULL )
|
|
return false;
|
|
ti->flags = flags;
|
|
return true;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
static unsigned int textctrl_info_t_get_flags(
|
|
PyObject *self,
|
|
unsigned int flags)
|
|
{
|
|
textctrl_info_t *ti = (textctrl_info_t *)pyobj_get_clink(self);
|
|
return ti == NULL ? 0 : ti->flags;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
static bool textctrl_info_t_set_tabsize(
|
|
PyObject *self,
|
|
unsigned int tabsize)
|
|
{
|
|
textctrl_info_t *ti = (textctrl_info_t *)pyobj_get_clink(self);
|
|
if ( ti == NULL )
|
|
return false;
|
|
ti->tabsize = tabsize;
|
|
return true;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
static unsigned int textctrl_info_t_get_tabsize(
|
|
PyObject *self,
|
|
unsigned int tabsize)
|
|
{
|
|
textctrl_info_t *ti = (textctrl_info_t *)pyobj_get_clink(self);
|
|
return ti == NULL ? 0 : ti->tabsize;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
static bool formchgcbfa_enable_field(size_t p_fa, int fid, bool enable)
|
|
{
|
|
DECLARE_FORM_ACTIONS;
|
|
return fa->enable_field(fid, enable);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
static bool formchgcbfa_show_field(size_t p_fa, int fid, bool show)
|
|
{
|
|
DECLARE_FORM_ACTIONS;
|
|
return fa->show_field(fid, show);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
static bool formchgcbfa_move_field(
|
|
size_t p_fa,
|
|
int fid,
|
|
int x,
|
|
int y,
|
|
int w,
|
|
int h)
|
|
{
|
|
DECLARE_FORM_ACTIONS;
|
|
return fa->move_field(fid, x, y, w, h);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
static int formchgcbfa_get_focused_field(size_t p_fa)
|
|
{
|
|
DECLARE_FORM_ACTIONS;
|
|
return fa->get_focused_field();
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
static bool formchgcbfa_set_focused_field(size_t p_fa, int fid)
|
|
{
|
|
DECLARE_FORM_ACTIONS;
|
|
return fa->set_focused_field(fid);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
static void formchgcbfa_refresh_field(size_t p_fa, int fid)
|
|
{
|
|
DECLARE_FORM_ACTIONS;
|
|
return fa->refresh_field(fid);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
static void formchgcbfa_close(size_t p_fa, int close_normally)
|
|
{
|
|
DECLARE_FORM_ACTIONS;
|
|
fa->close(close_normally);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
static PyObject *formchgcbfa_get_field_value(
|
|
size_t p_fa,
|
|
int fid,
|
|
int ft,
|
|
size_t sz)
|
|
{
|
|
DECLARE_FORM_ACTIONS;
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
switch ( ft )
|
|
{
|
|
// dropdown list
|
|
case 8:
|
|
{
|
|
// Readonly? Then return the selected index
|
|
if ( sz == 1 )
|
|
{
|
|
int sel_idx;
|
|
if ( fa->get_combobox_value(fid, &sel_idx) )
|
|
return PyLong_FromLong(sel_idx);
|
|
}
|
|
// Not readonly? Then return the qstring
|
|
else
|
|
{
|
|
qstring val;
|
|
if ( fa->get_combobox_value(fid, &val) )
|
|
return PyString_FromString(val.c_str());
|
|
}
|
|
break;
|
|
}
|
|
// multilinetext - tuple representing textctrl_info_t
|
|
case 7:
|
|
{
|
|
textctrl_info_t ti;
|
|
if ( fa->get_text_value(fid, &ti) )
|
|
return Py_BuildValue("(sII)", ti.text.c_str(), ti.flags, ti.tabsize);
|
|
break;
|
|
}
|
|
// button - uint32
|
|
case 4:
|
|
{
|
|
uval_t val;
|
|
if ( fa->get_unsigned_value(fid, &val) )
|
|
return PyLong_FromUnsignedLong(val);
|
|
break;
|
|
}
|
|
// ushort
|
|
case 2:
|
|
{
|
|
ushort val;
|
|
if ( fa->_get_field_value(fid, &val) )
|
|
return PyLong_FromUnsignedLong(val);
|
|
break;
|
|
}
|
|
// string label
|
|
case 1:
|
|
{
|
|
char val[MAXSTR];
|
|
if ( fa->get_ascii_value(fid, val, sizeof(val)) )
|
|
return PyString_FromString(val);
|
|
break;
|
|
}
|
|
// string input
|
|
case 3:
|
|
{
|
|
qstring val;
|
|
val.resize(sz + 1);
|
|
if ( fa->get_ascii_value(fid, val.begin(), val.size()) )
|
|
return PyString_FromString(val.begin());
|
|
break;
|
|
}
|
|
case 5:
|
|
{
|
|
intvec_t intvec;
|
|
// Returned as 1-base
|
|
if (fa->get_chooser_value(fid, &intvec))
|
|
{
|
|
// Make 0-based
|
|
for ( intvec_t::iterator it=intvec.begin(); it != intvec.end(); ++it)
|
|
(*it)--;
|
|
ref_t l(PyW_IntVecToPyList(intvec));
|
|
l.incref();
|
|
return l.o;
|
|
}
|
|
break;
|
|
}
|
|
// Numeric control
|
|
case 6:
|
|
{
|
|
union
|
|
{
|
|
sel_t sel;
|
|
sval_t sval;
|
|
uval_t uval;
|
|
ulonglong ull;
|
|
} u;
|
|
switch ( sz )
|
|
{
|
|
case 'S': // sel_t
|
|
{
|
|
if ( fa->get_segment_value(fid, &u.sel) )
|
|
return Py_BuildValue(PY_FMT64, u.sel);
|
|
break;
|
|
}
|
|
// sval_t
|
|
case 'n':
|
|
case 'D':
|
|
case 'O':
|
|
case 'Y':
|
|
case 'H':
|
|
{
|
|
if ( fa->get_signed_value(fid, &u.sval) )
|
|
return Py_BuildValue(PY_SFMT64, u.sval);
|
|
break;
|
|
}
|
|
case 'L': // uint64
|
|
case 'l': // int64
|
|
{
|
|
if ( fa->_get_field_value(fid, &u.ull) )
|
|
return Py_BuildValue("K", u.ull);
|
|
break;
|
|
}
|
|
case 'N':
|
|
case 'M': // uval_t
|
|
{
|
|
if ( fa->get_unsigned_value(fid, &u.uval) )
|
|
return Py_BuildValue(PY_FMT64, u.uval);
|
|
break;
|
|
}
|
|
case '$': // ea_t
|
|
{
|
|
if ( fa->get_ea_value(fid, &u.uval) )
|
|
return Py_BuildValue(PY_FMT64, u.uval);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
static bool formchgcbfa_set_field_value(
|
|
size_t p_fa,
|
|
int fid,
|
|
int ft,
|
|
PyObject *py_val)
|
|
{
|
|
DECLARE_FORM_ACTIONS;
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
|
|
switch ( ft )
|
|
{
|
|
// dropdown list
|
|
case 8:
|
|
{
|
|
// Editable dropdown list
|
|
if ( PyString_Check(py_val) )
|
|
{
|
|
qstring val(PyString_AsString(py_val));
|
|
return fa->set_combobox_value(fid, &val);
|
|
}
|
|
// Readonly dropdown list
|
|
else
|
|
{
|
|
int sel_idx = PyLong_AsLong(py_val);
|
|
return fa->set_combobox_value(fid, &sel_idx);
|
|
}
|
|
break;
|
|
}
|
|
// multilinetext - textctrl_info_t
|
|
case 7:
|
|
{
|
|
textctrl_info_t *ti = (textctrl_info_t *)pyobj_get_clink(py_val);
|
|
return ti == NULL ? false : fa->set_text_value(fid, ti);
|
|
}
|
|
// button - uint32
|
|
case 4:
|
|
{
|
|
uval_t val = PyLong_AsUnsignedLong(py_val);
|
|
return fa->set_unsigned_value(fid, &val);
|
|
}
|
|
// ushort
|
|
case 2:
|
|
{
|
|
ushort val = PyLong_AsUnsignedLong(py_val) & 0xffff;
|
|
return fa->_set_field_value(fid, &val);
|
|
}
|
|
// strings
|
|
case 3:
|
|
case 1:
|
|
return fa->set_ascii_value(fid, PyString_AsString(py_val));
|
|
// intvec_t
|
|
case 5:
|
|
{
|
|
intvec_t intvec;
|
|
// Passed as 0-based
|
|
if ( !PyW_PyListToIntVec(py_val, intvec) )
|
|
break;
|
|
|
|
// Make 1-based
|
|
for ( intvec_t::iterator it=intvec.begin(); it != intvec.end(); ++it)
|
|
(*it)++;
|
|
|
|
return fa->set_chooser_value(fid, &intvec);
|
|
}
|
|
// Numeric
|
|
case 6:
|
|
{
|
|
uint64 num;
|
|
if ( PyW_GetNumber(py_val, &num) )
|
|
return fa->_set_field_value(fid, &num);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#undef DECLARE_FORM_ACTIONS
|
|
|
|
static size_t py_get_AskUsingForm()
|
|
{
|
|
// Return a pointer to the function. Note that, although
|
|
// the C implementation of AskUsingForm_cv will do some
|
|
// Qt/txt widgets generation, the Python's ctypes
|
|
// implementation through which the call well go will first
|
|
// unblock other threads. No need to do it ourselves.
|
|
return (size_t)AskUsingForm_c;
|
|
}
|
|
|
|
static size_t py_get_OpenForm()
|
|
{
|
|
// See comments above.
|
|
return (size_t)OpenForm_c;
|
|
}
|
|
|
|
static qvector<ref_t> py_compiled_form_vec;
|
|
static void py_register_compiled_form(PyObject *py_form)
|
|
{
|
|
ref_t ref = borref_t(py_form);
|
|
if ( !py_compiled_form_vec.has(ref) )
|
|
py_compiled_form_vec.push_back(ref);
|
|
}
|
|
|
|
static void py_unregister_compiled_form(PyObject *py_form)
|
|
{
|
|
ref_t ref = borref_t(py_form);
|
|
if ( py_compiled_form_vec.has(ref) )
|
|
py_compiled_form_vec.del(ref);
|
|
}
|
|
|
|
//</inline(py_kernwin)>
|
|
%}
|
|
|
|
%{
|
|
//<code(py_kernwin)>
|
|
//---------------------------------------------------------------------------
|
|
|
|
//---------------------------------------------------------------------------
|
|
int idaapi UI_Callback(void *ud, int notification_code, va_list va)
|
|
{
|
|
// This hook gets called from the kernel. Ensure we hold the GIL.
|
|
PYW_GIL_GET;
|
|
UI_Hooks *proxy = (UI_Hooks *)ud;
|
|
int ret = 0;
|
|
try
|
|
{
|
|
switch (notification_code)
|
|
{
|
|
case ui_preprocess:
|
|
{
|
|
const char *name = va_arg(va, const char *);
|
|
return proxy->preprocess(name);
|
|
}
|
|
|
|
case ui_postprocess:
|
|
proxy->postprocess();
|
|
break;
|
|
|
|
case ui_saving:
|
|
proxy->saving();
|
|
break;
|
|
|
|
case ui_saved:
|
|
proxy->saved();
|
|
break;
|
|
|
|
case ui_term:
|
|
proxy->term();
|
|
break;
|
|
|
|
case ui_get_ea_hint:
|
|
{
|
|
ea_t ea = va_arg(va, ea_t);
|
|
char *buf = va_arg(va, char *);
|
|
size_t sz = va_arg(va, size_t);
|
|
char *_buf;
|
|
Py_ssize_t _len;
|
|
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
PyObject *py_str = proxy->get_ea_hint(ea);
|
|
if ( py_str != NULL
|
|
&& PyString_Check(py_str)
|
|
&& PyString_AsStringAndSize(py_str, &_buf, &_len) != - 1 )
|
|
{
|
|
qstrncpy(buf, _buf, qmin(_len, sz));
|
|
ret = 1;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case ui_current_tform_changed:
|
|
{
|
|
TForm *form = va_arg(va, TForm *);
|
|
TForm *prev_form = va_arg(va, TForm *);
|
|
proxy->current_tform_changed(form, prev_form);
|
|
}
|
|
break;
|
|
|
|
case ui_updating_actions:
|
|
{
|
|
action_update_ctx_t *ctx = va_arg(va, action_update_ctx_t *);
|
|
proxy->updating_actions(ctx);
|
|
}
|
|
break;
|
|
|
|
|
|
case ui_updated_actions:
|
|
{
|
|
proxy->updated_actions();
|
|
}
|
|
break;
|
|
|
|
case ui_populating_tform_popup:
|
|
{
|
|
TForm *form = va_arg(va, TForm *);
|
|
TPopupMenu *popup = va_arg(va, TPopupMenu *);
|
|
proxy->populating_tform_popup(form, popup);
|
|
}
|
|
break;
|
|
|
|
case ui_finish_populating_tform_popup:
|
|
{
|
|
TForm *form = va_arg(va, TForm *);
|
|
TPopupMenu *popup = va_arg(va, TPopupMenu *);
|
|
proxy->finish_populating_tform_popup(form, popup);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
catch (Swig::DirectorException &e)
|
|
{
|
|
msg("Exception in UI Hook function: %s\n", e.getMessage());
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
if ( PyErr_Occurred() )
|
|
PyErr_Print();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
bool idaapi py_menu_item_callback(void *userdata)
|
|
{
|
|
PYW_GIL_GET;
|
|
|
|
// userdata is a tuple of ( func, args )
|
|
// func and args are borrowed references from userdata
|
|
PyObject *func = PyTuple_GET_ITEM(userdata, 0);
|
|
PyObject *args = PyTuple_GET_ITEM(userdata, 1);
|
|
|
|
// Call the python function
|
|
newref_t result(PyEval_CallObject(func, args));
|
|
|
|
// We cannot raise an exception in the callback, just print it.
|
|
if ( result == NULL )
|
|
{
|
|
PyErr_Print();
|
|
return false;
|
|
}
|
|
|
|
return PyObject_IsTrue(result.o) != 0;
|
|
}
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------
|
|
// 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<PyObject *, py_choose2_t *> 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<POPUP_NAMES_COUNT; i++ )
|
|
qfree((void *)popup_names[i]);
|
|
|
|
delete [] popup_names;
|
|
popup_names = NULL;
|
|
}
|
|
|
|
void install_hooks(bool install)
|
|
{
|
|
if ( install )
|
|
{
|
|
if ( (flags & CH_ATTRS) != 0 )
|
|
{
|
|
if ( !hook_to_notification_point(HT_UI, ui_cb, this) )
|
|
flags &= ~CH_ATTRS;
|
|
else
|
|
ui_cb_hooked = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( (flags & CH_ATTRS) != 0 )
|
|
{
|
|
unhook_from_notification_point(HT_UI, ui_cb, this);
|
|
ui_cb_hooked = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void on_get_line(int lineno, char * const *line_arr)
|
|
{
|
|
// Called from s_getl, which itself can be called from the kernel. Ensure GIL
|
|
PYW_GIL_GET;
|
|
|
|
// Get headers?
|
|
if ( lineno == 0 )
|
|
{
|
|
// Copy the pre-parsed columns
|
|
for ( size_t i=0; i < cols.size(); i++ )
|
|
qstrncpy(line_arr[i], cols[i].c_str(), MAXSTR);
|
|
return;
|
|
}
|
|
|
|
// Clear buffer
|
|
int ncols = int(cols.size());
|
|
for ( int i=ncols-1; 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; i<ncols; i++ )
|
|
{
|
|
// get list item: [name, width]
|
|
borref_t list(PyList_GetItem(cols_attr.o, i));
|
|
borref_t v(PyList_GetItem(list.o, 0));
|
|
|
|
// Extract string
|
|
const char *str = v == NULL ? "" : PyString_AsString(v.o);
|
|
cols.push_back(str);
|
|
|
|
// Extract width
|
|
int width;
|
|
borref_t v2(PyList_GetItem(list.o, 1));
|
|
// No width? Guess width from column title
|
|
if ( v2 == NULL )
|
|
width = strlen(str);
|
|
else
|
|
width = PyInt_AsLong(v2.o);
|
|
widths.push_back(width);
|
|
}
|
|
|
|
// Get *deflt
|
|
int deflt = -1;
|
|
ref_t deflt_attr(PyW_TryGetAttrString(self, "deflt"));
|
|
if ( deflt_attr != NULL )
|
|
deflt = PyInt_AsLong(deflt_attr.o);
|
|
|
|
// Get *icon
|
|
int icon = -1;
|
|
ref_t icon_attr(PyW_TryGetAttrString(self, "icon"));
|
|
if ( icon_attr != NULL )
|
|
icon = PyInt_AsLong(icon_attr.o);
|
|
|
|
// Get *x1,y1,x2,y2
|
|
int pts[4];
|
|
static const char *pt_attrs[qnumber(pts)] = {"x1", "y1", "x2", "y2"};
|
|
for ( size_t i=0; i < qnumber(pts); i++ )
|
|
{
|
|
ref_t pt_attr(PyW_TryGetAttrString(self, pt_attrs[i]));
|
|
if ( pt_attr == NULL )
|
|
pts[i] = -1;
|
|
else
|
|
pts[i] = PyInt_AsLong(pt_attr.o);
|
|
}
|
|
|
|
// Check what callbacks we have
|
|
static const struct
|
|
{
|
|
const char *name;
|
|
unsigned int have; // 0 = mandatory callback
|
|
} callbacks[] =
|
|
{
|
|
{S_ON_GET_SIZE, 0},
|
|
{S_ON_GET_LINE, 0},
|
|
{S_ON_CLOSE, 0},
|
|
{S_ON_EDIT_LINE, CHOOSE2_HAVE_EDIT},
|
|
{S_ON_INSERT_LINE, CHOOSE2_HAVE_INS},
|
|
{S_ON_DELETE_LINE, CHOOSE2_HAVE_DEL},
|
|
{S_ON_REFRESH, CHOOSE2_HAVE_UPDATE}, // update()
|
|
{S_ON_SELECT_LINE, CHOOSE2_HAVE_ENTER}, // enter()
|
|
{S_ON_COMMAND, CHOOSE2_HAVE_COMMAND},
|
|
{S_ON_GET_LINE_ATTR, CHOOSE2_HAVE_GETATTR},
|
|
{S_ON_GET_ICON, CHOOSE2_HAVE_GETICON},
|
|
{S_ON_SELECTION_CHANGE, CHOOSE2_HAVE_SELECT},
|
|
{S_ON_REFRESHED, CHOOSE2_HAVE_REFRESHED},
|
|
};
|
|
cb_flags = 0;
|
|
for ( int i=0; i<qnumber(callbacks); i++ )
|
|
{
|
|
ref_t cb_attr(PyW_TryGetAttrString(self, callbacks[i].name));
|
|
bool have_cb = cb_attr != NULL && PyCallable_Check(cb_attr.o) != 0;
|
|
if ( have_cb )
|
|
{
|
|
cb_flags |= callbacks[i].have;
|
|
}
|
|
else
|
|
{
|
|
// Mandatory field?
|
|
if ( callbacks[i].have == 0 )
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// Get *popup names
|
|
// An array of 4 strings: ("Insert", "Delete", "Edit", "Refresh"
|
|
ref_t pn_attr(PyW_TryGetAttrString(self, S_POPUP_NAMES));
|
|
if ( (pn_attr != NULL)
|
|
&& PyList_Check(pn_attr.o)
|
|
&& PyList_Size(pn_attr.o) == POPUP_NAMES_COUNT )
|
|
{
|
|
popup_names = new const char *[POPUP_NAMES_COUNT];
|
|
for ( int i=0; i<POPUP_NAMES_COUNT; i++ )
|
|
{
|
|
const char *str = PyString_AsString(PyList_GetItem(pn_attr.o, i));
|
|
popup_names[i] = qstrdup(str);
|
|
}
|
|
}
|
|
|
|
// Adjust flags (if needed)
|
|
if ( (cb_flags & CHOOSE2_HAVE_GETATTR) != 0 )
|
|
flags |= CH_ATTRS;
|
|
|
|
// Increase object reference
|
|
Py_INCREF(self);
|
|
this->self = 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();
|
|
}
|
|
|
|
|
|
void free_compiled_form_instances(void)
|
|
{
|
|
while ( !py_compiled_form_vec.empty() )
|
|
{
|
|
const ref_t &ref = py_compiled_form_vec[0];
|
|
qstring title;
|
|
if ( !PyW_GetStringAttr(ref.o, "title", &title) )
|
|
title = "<unknown title>";
|
|
msg("WARNING: Form \"%s\" was not Free()d. Force-freeing.\n", title.c_str());
|
|
// Will call 'py_unregister_compiled_form()', and thus trim the vector down.
|
|
newref_t unused(PyObject_CallMethod(ref.o, (char *)"Free", "()"));
|
|
}
|
|
}
|
|
//</code(py_kernwin)>
|
|
|
|
%}
|
|
|
|
%{
|
|
//<code(py_cli)>
|
|
//--------------------------------------------------------------------------
|
|
#define MAX_PY_CLI 12
|
|
|
|
// Callbacks table
|
|
// This structure was devised because the cli callbacks have no user-data parameter
|
|
struct py_cli_cbs_t
|
|
{
|
|
bool (idaapi *execute_line)(const char *line);
|
|
bool (idaapi *complete_line)(
|
|
qstring *completion,
|
|
const char *prefix,
|
|
int n,
|
|
const char *line,
|
|
int x);
|
|
bool (idaapi *keydown)(
|
|
qstring *line,
|
|
int *p_x,
|
|
int *p_sellen,
|
|
int *vk_key,
|
|
int shift);
|
|
};
|
|
|
|
// CLI Python wrapper class
|
|
class py_cli_t
|
|
{
|
|
private:
|
|
//--------------------------------------------------------------------------
|
|
cli_t cli;
|
|
PyObject *self;
|
|
qstring cli_sname, cli_lname, cli_hint;
|
|
|
|
//--------------------------------------------------------------------------
|
|
static py_cli_t *py_clis[MAX_PY_CLI];
|
|
static const py_cli_cbs_t py_cli_cbs[MAX_PY_CLI];
|
|
//--------------------------------------------------------------------------
|
|
#define IMPL_PY_CLI_CB(CBN) \
|
|
static bool idaapi s_keydown##CBN(qstring *line, int *p_x, int *p_sellen, int *vk_key, int shift) \
|
|
{ \
|
|
return py_clis[CBN]->on_keydown(line, p_x, p_sellen, vk_key, shift); \
|
|
} \
|
|
static bool idaapi s_execute_line##CBN(const char *line) \
|
|
{ \
|
|
return py_clis[CBN]->on_execute_line(line); \
|
|
} \
|
|
static bool idaapi s_complete_line##CBN(qstring *completion, const char *prefix, int n, const char *line, int x) \
|
|
{ \
|
|
return py_clis[CBN]->on_complete_line(completion, prefix, n, line, x); \
|
|
}
|
|
|
|
IMPL_PY_CLI_CB(0); IMPL_PY_CLI_CB(1); IMPL_PY_CLI_CB(2); IMPL_PY_CLI_CB(3);
|
|
IMPL_PY_CLI_CB(4); IMPL_PY_CLI_CB(5); IMPL_PY_CLI_CB(6); IMPL_PY_CLI_CB(7);
|
|
IMPL_PY_CLI_CB(8); IMPL_PY_CLI_CB(9); IMPL_PY_CLI_CB(10); IMPL_PY_CLI_CB(11);
|
|
#undef IMPL_PY_CLI_CB
|
|
|
|
//--------------------------------------------------------------------------
|
|
// callback: the user pressed Enter
|
|
// CLI is free to execute the line immediately or ask for more lines
|
|
// Returns: true-executed line, false-ask for more lines
|
|
bool on_execute_line(const char *line)
|
|
{
|
|
PYW_GIL_GET;
|
|
newref_t result(
|
|
PyObject_CallMethod(
|
|
self,
|
|
(char *)S_ON_EXECUTE_LINE,
|
|
"s",
|
|
line));
|
|
PyW_ShowCbErr(S_ON_EXECUTE_LINE);
|
|
return result != NULL && PyObject_IsTrue(result.o);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// callback: a keyboard key has been pressed
|
|
// This is a generic callback and the CLI is free to do whatever
|
|
// it wants.
|
|
// line - current input line (in/out argument)
|
|
// p_x - pointer to current x coordinate of the cursor (in/out)
|
|
// p_sellen - pointer to current selection length (usually 0)
|
|
// p_vk_key - pointer to virtual key code (in/out)
|
|
// if the key has been handled, it should be reset to 0 by CLI
|
|
// shift - shift state
|
|
// Returns: true-modified input line or x coordinate or selection length
|
|
// This callback is optional
|
|
bool on_keydown(
|
|
qstring *line,
|
|
int *p_x,
|
|
int *p_sellen,
|
|
int *vk_key,
|
|
int shift)
|
|
{
|
|
PYW_GIL_GET;
|
|
newref_t result(
|
|
PyObject_CallMethod(
|
|
self,
|
|
(char *)S_ON_KEYDOWN,
|
|
"siiHi",
|
|
line->c_str(),
|
|
*p_x,
|
|
*p_sellen,
|
|
*vk_key,
|
|
shift));
|
|
|
|
bool ok = result != NULL && PyTuple_Check(result.o);
|
|
|
|
PyW_ShowCbErr(S_ON_KEYDOWN);
|
|
|
|
if ( ok )
|
|
{
|
|
Py_ssize_t sz = PyTuple_Size(result.o);
|
|
PyObject *item;
|
|
|
|
#define GET_TUPLE_ENTRY(col, PyThingy, AsThingy, out) \
|
|
do \
|
|
{ \
|
|
if ( sz > col ) \
|
|
{ \
|
|
borref_t _r(PyTuple_GetItem(result.o, col)); \
|
|
if ( _r != NULL && PyThingy##_Check(_r.o) ) \
|
|
*out = PyThingy##_##AsThingy(_r.o); \
|
|
} \
|
|
} while ( false )
|
|
|
|
GET_TUPLE_ENTRY(0, PyString, AsString, line);
|
|
GET_TUPLE_ENTRY(1, PyInt, AsLong, p_x);
|
|
GET_TUPLE_ENTRY(2, PyInt, AsLong, p_sellen);
|
|
GET_TUPLE_ENTRY(3, PyInt, AsLong, vk_key);
|
|
*vk_key &= 0xffff;
|
|
#undef GET_TUPLE_ENTRY
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
// callback: the user pressed Tab
|
|
// Find a completion number N for prefix PREFIX
|
|
// LINE is given as context information. X is the index where PREFIX starts in LINE
|
|
// New prefix should be stored in PREFIX.
|
|
// Returns: true if generated a new completion
|
|
// This callback is optional
|
|
bool on_complete_line(
|
|
qstring *completion,
|
|
const char *prefix,
|
|
int n,
|
|
const char *line,
|
|
int x)
|
|
{
|
|
PYW_GIL_GET;
|
|
newref_t result(
|
|
PyObject_CallMethod(
|
|
self,
|
|
(char *)S_ON_COMPLETE_LINE,
|
|
"sisi",
|
|
prefix,
|
|
n,
|
|
line,
|
|
x));
|
|
|
|
bool ok = result != NULL && PyString_Check(result.o);
|
|
PyW_ShowCbErr(S_ON_COMPLETE_LINE);
|
|
if ( ok )
|
|
*completion = PyString_AsString(result.o);
|
|
return ok;
|
|
}
|
|
|
|
// Private ctor (use bind())
|
|
py_cli_t()
|
|
{
|
|
}
|
|
|
|
public:
|
|
//---------------------------------------------------------------------------
|
|
static int bind(PyObject *py_obj)
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
|
|
int cli_idx;
|
|
// Find an empty slot
|
|
for ( cli_idx = 0; cli_idx < MAX_PY_CLI; ++cli_idx )
|
|
{
|
|
if ( py_clis[cli_idx] == NULL )
|
|
break;
|
|
}
|
|
py_cli_t *py_cli = NULL;
|
|
do
|
|
{
|
|
// No free slots?
|
|
if ( cli_idx >= MAX_PY_CLI )
|
|
break;
|
|
|
|
// Create a new instance
|
|
py_cli = new py_cli_t();
|
|
PyObject *attr;
|
|
|
|
// Start populating the 'cli' member
|
|
py_cli->cli.size = sizeof(cli_t);
|
|
|
|
// Store 'flags'
|
|
{
|
|
ref_t flags_attr(PyW_TryGetAttrString(py_obj, S_FLAGS));
|
|
if ( flags_attr == NULL )
|
|
py_cli->cli.flags = 0;
|
|
else
|
|
py_cli->cli.flags = PyLong_AsLong(flags_attr.o);
|
|
}
|
|
|
|
// Store 'sname'
|
|
if ( !PyW_GetStringAttr(py_obj, "sname", &py_cli->cli_sname) )
|
|
break;
|
|
py_cli->cli.sname = py_cli->cli_sname.c_str();
|
|
|
|
// Store 'lname'
|
|
if ( !PyW_GetStringAttr(py_obj, "lname", &py_cli->cli_lname) )
|
|
break;
|
|
py_cli->cli.lname = py_cli->cli_lname.c_str();
|
|
|
|
// Store 'hint'
|
|
if ( !PyW_GetStringAttr(py_obj, "hint", &py_cli->cli_hint) )
|
|
break;
|
|
py_cli->cli.hint = py_cli->cli_hint.c_str();
|
|
|
|
// Store callbacks
|
|
if ( !PyObject_HasAttrString(py_obj, S_ON_EXECUTE_LINE) )
|
|
break;
|
|
py_cli->cli.execute_line = py_cli_cbs[cli_idx].execute_line;
|
|
|
|
py_cli->cli.complete_line = PyObject_HasAttrString(py_obj, S_ON_COMPLETE_LINE) ? py_cli_cbs[cli_idx].complete_line : NULL;
|
|
py_cli->cli.keydown = PyObject_HasAttrString(py_obj, S_ON_KEYDOWN) ? py_cli_cbs[cli_idx].keydown : NULL;
|
|
|
|
// install CLI
|
|
install_command_interpreter(&py_cli->cli);
|
|
|
|
// Take reference to this object
|
|
py_cli->self = py_obj;
|
|
Py_INCREF(py_obj);
|
|
|
|
// Save the instance
|
|
py_clis[cli_idx] = py_cli;
|
|
|
|
return cli_idx;
|
|
} while (false);
|
|
|
|
delete py_cli;
|
|
return -1;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
static void unbind(int cli_idx)
|
|
{
|
|
// Out of bounds or not set?
|
|
if ( cli_idx < 0 || cli_idx >= MAX_PY_CLI || py_clis[cli_idx] == NULL )
|
|
return;
|
|
|
|
py_cli_t *py_cli = py_clis[cli_idx];
|
|
remove_command_interpreter(&py_cli->cli);
|
|
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
Py_DECREF(py_cli->self);
|
|
delete py_cli;
|
|
}
|
|
|
|
py_clis[cli_idx] = NULL;
|
|
|
|
return;
|
|
}
|
|
};
|
|
py_cli_t *py_cli_t::py_clis[MAX_PY_CLI] = {NULL};
|
|
#define DECL_PY_CLI_CB(CBN) { s_execute_line##CBN, s_complete_line##CBN, s_keydown##CBN }
|
|
const py_cli_cbs_t py_cli_t::py_cli_cbs[MAX_PY_CLI] =
|
|
{
|
|
DECL_PY_CLI_CB(0), DECL_PY_CLI_CB(1), DECL_PY_CLI_CB(2), DECL_PY_CLI_CB(3),
|
|
DECL_PY_CLI_CB(4), DECL_PY_CLI_CB(5), DECL_PY_CLI_CB(6), DECL_PY_CLI_CB(7),
|
|
DECL_PY_CLI_CB(8), DECL_PY_CLI_CB(9), DECL_PY_CLI_CB(10), DECL_PY_CLI_CB(11)
|
|
};
|
|
#undef DECL_PY_CLI_CB
|
|
//</code(py_cli)>
|
|
|
|
//<code(py_plgform)>
|
|
//---------------------------------------------------------------------------
|
|
class plgform_t
|
|
{
|
|
private:
|
|
ref_t py_obj;
|
|
TForm *form;
|
|
|
|
static int idaapi s_callback(void *ud, int notification_code, va_list va)
|
|
{
|
|
// This hook gets called from the kernel. Ensure we hold the GIL.
|
|
PYW_GIL_GET;
|
|
|
|
plgform_t *_this = (plgform_t *)ud;
|
|
if ( notification_code == ui_tform_visible )
|
|
{
|
|
TForm *form = va_arg(va, TForm *);
|
|
if ( form == _this->form )
|
|
{
|
|
// Qt: QWidget*
|
|
// G: HWND
|
|
// We wrap and pass as a CObject in the hope that a Python UI framework
|
|
// can unwrap a CObject and get the hwnd/widget back
|
|
newref_t py_result(
|
|
PyObject_CallMethod(
|
|
_this->py_obj.o,
|
|
(char *)S_ON_CREATE, "O",
|
|
PyCObject_FromVoidPtr(form, NULL)));
|
|
PyW_ShowCbErr(S_ON_CREATE);
|
|
}
|
|
}
|
|
else if ( notification_code == ui_tform_invisible )
|
|
{
|
|
TForm *form = va_arg(va, TForm *);
|
|
if ( form == _this->form )
|
|
{
|
|
{
|
|
newref_t py_result(
|
|
PyObject_CallMethod(
|
|
_this->py_obj.o,
|
|
(char *)S_ON_CLOSE, "O",
|
|
PyCObject_FromVoidPtr(form, NULL)));
|
|
PyW_ShowCbErr(S_ON_CLOSE);
|
|
}
|
|
_this->unhook();
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void unhook()
|
|
{
|
|
unhook_from_notification_point(HT_UI, s_callback, this);
|
|
form = NULL;
|
|
|
|
// Call DECREF at last, since it may trigger __del__
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
py_obj = ref_t();
|
|
}
|
|
|
|
public:
|
|
plgform_t(): form(NULL)
|
|
{
|
|
}
|
|
|
|
bool show(
|
|
PyObject *obj,
|
|
const char *caption,
|
|
int options)
|
|
{
|
|
// Already displayed?
|
|
TForm *f = find_tform(caption);
|
|
if ( f != NULL )
|
|
{
|
|
// Our form?
|
|
if ( f == form )
|
|
{
|
|
// Switch to it
|
|
switchto_tform(form, true);
|
|
return true;
|
|
}
|
|
// Fail to create
|
|
return false;
|
|
}
|
|
|
|
// Create a form
|
|
form = create_tform(caption, NULL);
|
|
if ( form == NULL )
|
|
return false;
|
|
|
|
if ( !hook_to_notification_point(HT_UI, s_callback, this) )
|
|
{
|
|
form = NULL;
|
|
return false;
|
|
}
|
|
|
|
py_obj = borref_t(obj);
|
|
|
|
if ( is_idaq() )
|
|
options |= FORM_QWIDGET;
|
|
|
|
this->form = form;
|
|
open_tform(form, options);
|
|
return true;
|
|
}
|
|
|
|
void close(int options = 0)
|
|
{
|
|
if ( form != NULL )
|
|
close_tform(form, options);
|
|
}
|
|
|
|
static PyObject *create()
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
return PyCObject_FromVoidPtr(new plgform_t(), destroy);
|
|
}
|
|
|
|
static void destroy(void *obj)
|
|
{
|
|
delete (plgform_t *)obj;
|
|
}
|
|
};
|
|
//</code(py_plgform)>
|
|
%}
|
|
|
|
%inline %{
|
|
//<inline(py_plgform)>
|
|
//---------------------------------------------------------------------------
|
|
#define DECL_PLGFORM PYW_GIL_CHECK_LOCKED_SCOPE(); plgform_t *plgform = (plgform_t *) PyCObject_AsVoidPtr(py_link);
|
|
static PyObject *plgform_new()
|
|
{
|
|
return plgform_t::create();
|
|
}
|
|
|
|
static bool plgform_show(
|
|
PyObject *py_link,
|
|
PyObject *py_obj,
|
|
const char *caption,
|
|
int options = FORM_TAB|FORM_MENU|FORM_RESTORE)
|
|
{
|
|
DECL_PLGFORM;
|
|
return plgform->show(py_obj, caption, options);
|
|
}
|
|
|
|
static void plgform_close(
|
|
PyObject *py_link,
|
|
int options)
|
|
{
|
|
DECL_PLGFORM;
|
|
plgform->close(options);
|
|
}
|
|
#undef DECL_PLGFORM
|
|
//</inline(py_plgform)>
|
|
%}
|
|
|
|
%{
|
|
//<code(py_custviewer)>
|
|
//---------------------------------------------------------------------------
|
|
// Base class for all custviewer place_t providers
|
|
class custviewer_data_t
|
|
{
|
|
public:
|
|
virtual void *get_ud() = 0;
|
|
virtual place_t *get_min() = 0;
|
|
virtual place_t *get_max() = 0;
|
|
};
|
|
|
|
//---------------------------------------------------------------------------
|
|
class cvdata_simpleline_t: public custviewer_data_t
|
|
{
|
|
private:
|
|
strvec_t lines;
|
|
simpleline_place_t pl_min, pl_max;
|
|
public:
|
|
|
|
void *get_ud()
|
|
{
|
|
return &lines;
|
|
}
|
|
|
|
place_t *get_min()
|
|
{
|
|
return &pl_min;
|
|
}
|
|
|
|
place_t *get_max()
|
|
{
|
|
return &pl_max;
|
|
}
|
|
|
|
strvec_t &get_lines()
|
|
{
|
|
return lines;
|
|
}
|
|
|
|
void set_minmax(size_t start=0, size_t end=size_t(-1))
|
|
{
|
|
if ( start == 0 && end == size_t(-1) )
|
|
{
|
|
end = lines.size();
|
|
pl_min.n = 0;
|
|
pl_max.n = end == 0 ? 0 : end - 1;
|
|
}
|
|
else
|
|
{
|
|
pl_min.n = start;
|
|
pl_max.n = end;
|
|
}
|
|
}
|
|
|
|
bool set_line(size_t nline, simpleline_t &sl)
|
|
{
|
|
if ( nline >= lines.size() )
|
|
return false;
|
|
lines[nline] = sl;
|
|
return true;
|
|
}
|
|
|
|
bool del_line(size_t nline)
|
|
{
|
|
if ( nline >= lines.size() )
|
|
return false;
|
|
lines.erase(lines.begin()+nline);
|
|
return true;
|
|
}
|
|
|
|
void add_line(simpleline_t &line)
|
|
{
|
|
lines.push_back(line);
|
|
}
|
|
|
|
void add_line(const char *str)
|
|
{
|
|
lines.push_back(simpleline_t(str));
|
|
}
|
|
|
|
bool insert_line(size_t nline, simpleline_t &line)
|
|
{
|
|
if ( nline >= lines.size() )
|
|
return false;
|
|
lines.insert(lines.begin()+nline, line);
|
|
return true;
|
|
}
|
|
|
|
bool patch_line(size_t nline, size_t offs, int value)
|
|
{
|
|
if ( nline >= lines.size() )
|
|
return false;
|
|
qstring &L = lines[nline].line;
|
|
L[offs] = (uchar) value & 0xFF;
|
|
return true;
|
|
}
|
|
|
|
const size_t to_lineno(place_t *pl) const
|
|
{
|
|
return ((simpleline_place_t *)pl)->n;
|
|
}
|
|
|
|
bool curline(place_t *pl, size_t *n)
|
|
{
|
|
if ( pl == NULL )
|
|
return false;
|
|
|
|
*n = to_lineno(pl);
|
|
return true;
|
|
}
|
|
|
|
simpleline_t *get_line(size_t nline)
|
|
{
|
|
return nline >= lines.size() ? NULL : &lines[nline];
|
|
}
|
|
|
|
simpleline_t *get_line(place_t *pl)
|
|
{
|
|
return pl == NULL ? NULL : get_line(((simpleline_place_t *)pl)->n);
|
|
}
|
|
|
|
const size_t count() const
|
|
{
|
|
return lines.size();
|
|
}
|
|
|
|
void clear_lines()
|
|
{
|
|
lines.clear();
|
|
set_minmax();
|
|
}
|
|
};
|
|
|
|
//---------------------------------------------------------------------------
|
|
// FIXME: This should inherit py_view_base.hpp's py_customidamemo_t,
|
|
// just like py_graph.hpp's py_graph_t does.
|
|
// There should be a way to "merge" the two mechanisms; they are similar.
|
|
class customviewer_t
|
|
{
|
|
protected:
|
|
qstring _title;
|
|
TForm *_form;
|
|
TCustomControl *_cv;
|
|
custviewer_data_t *_data;
|
|
int _features;
|
|
enum
|
|
{
|
|
HAVE_HINT = 0x0001,
|
|
HAVE_KEYDOWN = 0x0002,
|
|
HAVE_POPUP = 0x0004,
|
|
HAVE_DBLCLICK = 0x0008,
|
|
HAVE_CURPOS = 0x0010,
|
|
HAVE_CLICK = 0x0020,
|
|
HAVE_CLOSE = 0x0040
|
|
};
|
|
private:
|
|
struct cvw_popupctx_t
|
|
{
|
|
size_t menu_id;
|
|
customviewer_t *cv;
|
|
cvw_popupctx_t(): menu_id(0), cv(NULL) { }
|
|
cvw_popupctx_t(size_t mid, customviewer_t *v): menu_id(mid), cv(v) { }
|
|
};
|
|
typedef std::map<unsigned int, cvw_popupctx_t> cvw_popupmap_t;
|
|
static cvw_popupmap_t _global_popup_map;
|
|
static size_t _global_popup_id;
|
|
qstring _curline;
|
|
intvec_t _installed_popups;
|
|
|
|
static bool idaapi s_popup_menu_cb(void *ud)
|
|
{
|
|
size_t mid = (size_t)ud;
|
|
cvw_popupmap_t::iterator it = _global_popup_map.find(mid);
|
|
if ( it == _global_popup_map.end() )
|
|
return false;
|
|
|
|
PYW_GIL_GET;
|
|
return it->second.cv->on_popup_menu(it->second.menu_id);
|
|
}
|
|
|
|
static bool idaapi s_cv_keydown(
|
|
TCustomControl * /*cv*/,
|
|
int vk_key,
|
|
int shift,
|
|
void *ud)
|
|
{
|
|
PYW_GIL_GET;
|
|
customviewer_t *_this = (customviewer_t *)ud;
|
|
return _this->on_keydown(vk_key, shift);
|
|
}
|
|
|
|
// The popup menu is being constructed
|
|
static void idaapi s_cv_popup(TCustomControl * /*cv*/, void *ud)
|
|
{
|
|
PYW_GIL_GET;
|
|
customviewer_t *_this = (customviewer_t *)ud;
|
|
_this->on_popup();
|
|
}
|
|
|
|
// The user clicked
|
|
static bool idaapi s_cv_click(TCustomControl * /*cv*/, int shift, void *ud)
|
|
{
|
|
PYW_GIL_GET;
|
|
customviewer_t *_this = (customviewer_t *)ud;
|
|
return _this->on_click(shift);
|
|
}
|
|
|
|
// The user double clicked
|
|
static bool idaapi s_cv_dblclick(TCustomControl * /*cv*/, int shift, void *ud)
|
|
{
|
|
PYW_GIL_GET;
|
|
customviewer_t *_this = (customviewer_t *)ud;
|
|
return _this->on_dblclick(shift);
|
|
}
|
|
|
|
// Cursor position has been changed
|
|
static void idaapi s_cv_curpos(TCustomControl * /*cv*/, void *ud)
|
|
{
|
|
PYW_GIL_GET;
|
|
customviewer_t *_this = (customviewer_t *)ud;
|
|
_this->on_curpos_changed();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
static int idaapi s_ui_cb(void *ud, int code, va_list va)
|
|
{
|
|
// This hook gets called from the kernel. Ensure we hold the GIL.
|
|
PYW_GIL_GET;
|
|
customviewer_t *_this = (customviewer_t *)ud;
|
|
switch ( code )
|
|
{
|
|
case ui_get_custom_viewer_hint:
|
|
{
|
|
TCustomControl *viewer = va_arg(va, TCustomControl *);
|
|
place_t *place = va_arg(va, place_t *);
|
|
int *important_lines = va_arg(va, int *);
|
|
qstring &hint = *va_arg(va, qstring *);
|
|
if ( (_this->_features & HAVE_HINT) == 0 || place == NULL || _this->_cv != viewer )
|
|
return 0;
|
|
else
|
|
return _this->on_hint(place, important_lines, hint) ? 1 : 0;
|
|
}
|
|
|
|
case ui_tform_invisible:
|
|
{
|
|
TForm *form = va_arg(va, TForm *);
|
|
if ( _this->_form != form )
|
|
break;
|
|
}
|
|
// fallthrough...
|
|
case ui_term:
|
|
unhook_from_notification_point(HT_UI, s_ui_cb, _this);
|
|
_this->on_close();
|
|
_this->on_post_close();
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void on_post_close()
|
|
{
|
|
init_vars();
|
|
clear_popup_menu();
|
|
}
|
|
|
|
public:
|
|
|
|
inline TForm *get_tform() { return _form; }
|
|
inline TCustomControl *get_tcustom_control() { return _cv; }
|
|
|
|
//
|
|
// All the overridable callbacks
|
|
//
|
|
|
|
// OnClick
|
|
virtual bool on_click(int /*shift*/) { return false; }
|
|
|
|
// OnDblClick
|
|
virtual bool on_dblclick(int /*shift*/) { return false; }
|
|
|
|
// OnCurorPositionChanged
|
|
virtual void on_curpos_changed() { }
|
|
|
|
// OnHostFormClose
|
|
virtual void on_close() { }
|
|
|
|
// OnKeyDown
|
|
virtual bool on_keydown(int /*vk_key*/, int /*shift*/) { return false; }
|
|
|
|
// OnPopupShow
|
|
virtual bool on_popup() { return false; }
|
|
|
|
// OnHint
|
|
virtual bool on_hint(place_t * /*place*/, int * /*important_lines*/, qstring &/*hint*/) { return false; }
|
|
|
|
// OnPopupMenuClick
|
|
virtual bool on_popup_menu(size_t menu_id) { return false; }
|
|
|
|
void init_vars()
|
|
{
|
|
_data = NULL;
|
|
_features = 0;
|
|
_curline.clear();
|
|
_cv = NULL;
|
|
_form = NULL;
|
|
}
|
|
|
|
customviewer_t()
|
|
{
|
|
init_vars();
|
|
}
|
|
|
|
~customviewer_t()
|
|
{
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
void close()
|
|
{
|
|
if ( _form != NULL )
|
|
close_tform(_form, FORM_SAVE | FORM_CLOSE_LATER);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool set_range(
|
|
const place_t *minplace = NULL,
|
|
const place_t *maxplace = NULL)
|
|
{
|
|
if ( _cv == NULL )
|
|
return false;
|
|
|
|
set_custom_viewer_range(
|
|
_cv,
|
|
minplace == NULL ? _data->get_min() : minplace,
|
|
maxplace == NULL ? _data->get_max() : maxplace);
|
|
return true;
|
|
}
|
|
|
|
place_t *get_place(
|
|
bool mouse = false,
|
|
int *x = 0,
|
|
int *y = 0)
|
|
{
|
|
return _cv == NULL ? NULL : get_custom_viewer_place(_cv, mouse, x, y);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool refresh()
|
|
{
|
|
if ( _cv == NULL )
|
|
return false;
|
|
|
|
refresh_custom_viewer(_cv);
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool refresh_current()
|
|
{
|
|
int x, y;
|
|
place_t *pl = get_place(false, &x, &y);
|
|
if ( pl == NULL )
|
|
return false;
|
|
|
|
return jumpto(pl, x, y);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool get_current_word(bool mouse, qstring &word)
|
|
{
|
|
// query the cursor position
|
|
int x, y;
|
|
if ( get_place(mouse, &x, &y) == NULL )
|
|
return false;
|
|
|
|
// query the line at the cursor
|
|
const char *line = get_current_line(mouse, true);
|
|
if ( line == NULL )
|
|
return false;
|
|
|
|
if ( x >= (int)strlen(line) )
|
|
return false;
|
|
|
|
// find the beginning of the word
|
|
const char *ptr = line + x;
|
|
while ( ptr > line && !qisspace(ptr[-1]) )
|
|
ptr--;
|
|
|
|
// find the end of the word
|
|
const char *begin = ptr;
|
|
ptr = line + x;
|
|
while ( !qisspace(*ptr) && *ptr != '\0' )
|
|
ptr++;
|
|
|
|
word.qclear();
|
|
word.append(begin, ptr-begin);
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
const char *get_current_line(bool mouse, bool notags)
|
|
{
|
|
const char *r = get_custom_viewer_curline(_cv, mouse);
|
|
if ( r == NULL || !notags )
|
|
return r;
|
|
|
|
size_t sz = strlen(r);
|
|
if ( sz == 0 )
|
|
return r;
|
|
|
|
_curline.resize(sz + 5, '\0');
|
|
tag_remove(r, &_curline[0], sz + 1);
|
|
return _curline.c_str();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool is_focused()
|
|
{
|
|
return get_current_viewer() == _cv;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool jumpto(place_t *place, int x, int y)
|
|
{
|
|
return ::jumpto(_cv, place, x, y);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
void clear_popup_menu()
|
|
{
|
|
if ( _cv != NULL )
|
|
set_custom_viewer_popup_menu(_cv, NULL);
|
|
|
|
for (intvec_t::iterator it=_installed_popups.begin(), it_end=_installed_popups.end();
|
|
it != it_end;
|
|
++it)
|
|
{
|
|
_global_popup_map.erase(*it);
|
|
}
|
|
_installed_popups.clear();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
size_t add_popup_menu(
|
|
const char *title,
|
|
const char *hotkey)
|
|
{
|
|
size_t menu_id = _global_popup_id + 1;
|
|
|
|
// Overlap / already exists?
|
|
if (_cv == NULL || // No custviewer?
|
|
// Overlap?
|
|
menu_id == 0 ||
|
|
// Already exists?
|
|
_global_popup_map.find(menu_id) != _global_popup_map.end())
|
|
{
|
|
return 0;
|
|
}
|
|
add_custom_viewer_popup_item(_cv, title, hotkey, s_popup_menu_cb, (void *)menu_id);
|
|
|
|
// Save global association
|
|
_global_popup_map[menu_id] = cvw_popupctx_t(menu_id, this);
|
|
_global_popup_id = menu_id;
|
|
|
|
// Remember what menu IDs are set with this form
|
|
_installed_popups.push_back(menu_id);
|
|
return menu_id;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool create(const char *title, int features, custviewer_data_t *data)
|
|
{
|
|
// Already created? (in the instance)
|
|
if ( _form != NULL )
|
|
return true;
|
|
|
|
// Already created? (in IDA windows list)
|
|
HWND hwnd(NULL);
|
|
TForm *form = create_tform(title, &hwnd);
|
|
if ( hwnd == NULL )
|
|
return false;
|
|
|
|
_title = title;
|
|
_data = data;
|
|
_form = form;
|
|
_features = features;
|
|
|
|
// Create the viewer
|
|
_cv = create_custom_viewer(
|
|
title,
|
|
(TWinControl *)_form,
|
|
_data->get_min(),
|
|
_data->get_max(),
|
|
_data->get_min(),
|
|
0,
|
|
_data->get_ud());
|
|
|
|
// Set user-data
|
|
set_custom_viewer_handler(_cv, CVH_USERDATA, (void *)this);
|
|
|
|
//
|
|
// Set other optional callbacks
|
|
//
|
|
if ( (features & HAVE_KEYDOWN) != 0 )
|
|
set_custom_viewer_handler(_cv, CVH_KEYDOWN, (void *)s_cv_keydown);
|
|
|
|
if ( (features & HAVE_POPUP) != 0 )
|
|
set_custom_viewer_handler(_cv, CVH_POPUP, (void *)s_cv_popup);
|
|
|
|
if ( (features & HAVE_DBLCLICK) != 0 )
|
|
set_custom_viewer_handler(_cv, CVH_DBLCLICK, (void *)s_cv_dblclick);
|
|
|
|
if ( (features & HAVE_CURPOS) != 0 )
|
|
set_custom_viewer_handler(_cv, CVH_CURPOS, (void *)s_cv_curpos);
|
|
|
|
if ( (features & HAVE_CLICK) != 0 )
|
|
set_custom_viewer_handler(_cv, CVH_CLICK, (void *)s_cv_click);
|
|
|
|
// Hook to UI notifications (for TForm close event)
|
|
hook_to_notification_point(HT_UI, s_ui_cb, this);
|
|
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool show()
|
|
{
|
|
// Closed already?
|
|
if ( _form == NULL )
|
|
return false;
|
|
|
|
open_tform(_form, FORM_TAB|FORM_MENU|FORM_RESTORE|FORM_QWIDGET);
|
|
return true;
|
|
}
|
|
};
|
|
|
|
customviewer_t::cvw_popupmap_t customviewer_t::_global_popup_map;
|
|
size_t customviewer_t::_global_popup_id = 0;
|
|
//---------------------------------------------------------------------------
|
|
class py_simplecustview_t: public customviewer_t
|
|
{
|
|
private:
|
|
cvdata_simpleline_t data;
|
|
PyObject *py_self, *py_this, *py_last_link;
|
|
int features;
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Convert a tuple (String, [color, [bgcolor]]) to a simpleline_t
|
|
static bool py_to_simpleline(PyObject *py, simpleline_t &sl)
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
|
|
if ( PyString_Check(py) )
|
|
{
|
|
sl.line = PyString_AsString(py);
|
|
return true;
|
|
}
|
|
Py_ssize_t sz;
|
|
if ( !PyTuple_Check(py) || (sz = PyTuple_Size(py)) <= 0 )
|
|
return false;
|
|
|
|
PyObject *py_val = PyTuple_GetItem(py, 0);
|
|
if ( !PyString_Check(py_val) )
|
|
return false;
|
|
|
|
sl.line = PyString_AsString(py_val);
|
|
|
|
if ( (sz > 1) && (py_val = PyTuple_GetItem(py, 1)) && PyLong_Check(py_val) )
|
|
sl.color = color_t(PyLong_AsUnsignedLong(py_val));
|
|
|
|
if ( (sz > 2) && (py_val = PyTuple_GetItem(py, 2)) && PyLong_Check(py_val) )
|
|
sl.bgcolor = PyLong_AsUnsignedLong(py_val);
|
|
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// Callbacks
|
|
//
|
|
virtual bool on_click(int shift)
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
newref_t py_result(PyObject_CallMethod(py_self, (char *)S_ON_CLICK, "i", shift));
|
|
PyW_ShowCbErr(S_ON_CLICK);
|
|
return py_result != NULL && PyObject_IsTrue(py_result.o);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// OnDblClick
|
|
virtual bool on_dblclick(int shift)
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
newref_t py_result(PyObject_CallMethod(py_self, (char *)S_ON_DBL_CLICK, "i", shift));
|
|
PyW_ShowCbErr(S_ON_DBL_CLICK);
|
|
return py_result != NULL && PyObject_IsTrue(py_result.o);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// OnCurorPositionChanged
|
|
virtual void on_curpos_changed()
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
newref_t py_result(PyObject_CallMethod(py_self, (char *)S_ON_CURSOR_POS_CHANGED, NULL));
|
|
PyW_ShowCbErr(S_ON_CURSOR_POS_CHANGED);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// OnHostFormClose
|
|
virtual void on_close()
|
|
{
|
|
// Call the close method if it is there and the object is still bound
|
|
if ( (features & HAVE_CLOSE) != 0 && py_self != NULL )
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
newref_t py_result(PyObject_CallMethod(py_self, (char *)S_ON_CLOSE, NULL));
|
|
PyW_ShowCbErr(S_ON_CLOSE);
|
|
|
|
// Cleanup
|
|
Py_DECREF(py_self);
|
|
py_self = NULL;
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// OnKeyDown
|
|
virtual bool on_keydown(int vk_key, int shift)
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
newref_t py_result(
|
|
PyObject_CallMethod(
|
|
py_self,
|
|
(char *)S_ON_KEYDOWN,
|
|
"ii",
|
|
vk_key,
|
|
shift));
|
|
|
|
PyW_ShowCbErr(S_ON_KEYDOWN);
|
|
return py_result != NULL && PyObject_IsTrue(py_result.o);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// OnPopupShow
|
|
virtual bool on_popup()
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
newref_t py_result(
|
|
PyObject_CallMethod(
|
|
py_self,
|
|
(char *)S_ON_POPUP,
|
|
NULL));
|
|
PyW_ShowCbErr(S_ON_POPUP);
|
|
return py_result != NULL && PyObject_IsTrue(py_result.o);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// OnHint
|
|
virtual bool on_hint(place_t *place, int *important_lines, qstring &hint)
|
|
{
|
|
size_t ln = data.to_lineno(place);
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
newref_t py_result(
|
|
PyObject_CallMethod(
|
|
py_self,
|
|
(char *)S_ON_HINT,
|
|
PY_FMT64,
|
|
pyul_t(ln)));
|
|
|
|
PyW_ShowCbErr(S_ON_HINT);
|
|
bool ok = py_result != NULL && PyTuple_Check(py_result.o) && PyTuple_Size(py_result.o) == 2;
|
|
if ( ok )
|
|
{
|
|
if ( important_lines != NULL )
|
|
*important_lines = PyInt_AsLong(PyTuple_GetItem(py_result.o, 0));
|
|
hint = PyString_AsString(PyTuple_GetItem(py_result.o, 1));
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// OnPopupMenuClick
|
|
virtual bool on_popup_menu(size_t menu_id)
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
newref_t py_result(
|
|
PyObject_CallMethod(
|
|
py_self,
|
|
(char *)S_ON_POPUP_MENU,
|
|
PY_FMT64,
|
|
pyul_t(menu_id)));
|
|
PyW_ShowCbErr(S_ON_POPUP_MENU);
|
|
return py_result != NULL && PyObject_IsTrue(py_result.o);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
void refresh_range()
|
|
{
|
|
data.set_minmax();
|
|
set_range();
|
|
}
|
|
|
|
public:
|
|
py_simplecustview_t()
|
|
{
|
|
py_this = py_self = py_last_link = NULL;
|
|
}
|
|
~py_simplecustview_t()
|
|
{
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Edits an existing line
|
|
bool edit_line(size_t nline, PyObject *py_sl)
|
|
{
|
|
simpleline_t sl;
|
|
if ( !py_to_simpleline(py_sl, sl) )
|
|
return false;
|
|
|
|
return data.set_line(nline, sl);
|
|
}
|
|
|
|
// Low level: patches a line string directly
|
|
bool patch_line(size_t nline, size_t offs, int value)
|
|
{
|
|
return data.patch_line(nline, offs, value);
|
|
}
|
|
|
|
// Insert a line
|
|
bool insert_line(size_t nline, PyObject *py_sl)
|
|
{
|
|
simpleline_t sl;
|
|
if ( !py_to_simpleline(py_sl, sl) )
|
|
return false;
|
|
return data.insert_line(nline, sl);
|
|
}
|
|
|
|
// Adds a line tuple
|
|
bool add_line(PyObject *py_sl)
|
|
{
|
|
simpleline_t sl;
|
|
if ( !py_to_simpleline(py_sl, sl) )
|
|
return false;
|
|
data.add_line(sl);
|
|
refresh_range();
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool del_line(size_t nline)
|
|
{
|
|
bool ok = data.del_line(nline);
|
|
if ( ok )
|
|
refresh_range();
|
|
return ok;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Gets the position and returns a tuple (lineno, x, y)
|
|
PyObject *get_pos(bool mouse)
|
|
{
|
|
place_t *pl;
|
|
int x, y;
|
|
pl = get_place(mouse, &x, &y);
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
if ( pl == NULL )
|
|
Py_RETURN_NONE;
|
|
return Py_BuildValue("(" PY_FMT64 "ii)", pyul_t(data.to_lineno(pl)), x, y);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Returns the line tuple
|
|
PyObject *get_line(size_t nline)
|
|
{
|
|
simpleline_t *r = data.get_line(nline);
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
if ( r == NULL )
|
|
Py_RETURN_NONE;
|
|
return Py_BuildValue("(sII)", r->line.c_str(), (unsigned int)r->color, (unsigned int)r->bgcolor);
|
|
}
|
|
|
|
// Returns the count of lines
|
|
const size_t count() const
|
|
{
|
|
return data.count();
|
|
}
|
|
|
|
// Clears lines
|
|
void clear()
|
|
{
|
|
data.clear_lines();
|
|
refresh_range();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool jumpto(size_t ln, int x, int y)
|
|
{
|
|
simpleline_place_t l(ln);
|
|
return customviewer_t::jumpto(&l, x, y);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Initializes and links the Python object to this class
|
|
bool init(PyObject *py_link, const char *title)
|
|
{
|
|
// Already created?
|
|
if ( _form != NULL )
|
|
return true;
|
|
|
|
// Probe callbacks
|
|
features = 0;
|
|
static struct
|
|
{
|
|
const char *cb_name;
|
|
int feature;
|
|
} const cbtable[] =
|
|
{
|
|
{S_ON_CLICK, HAVE_CLICK},
|
|
{S_ON_CLOSE, HAVE_CLOSE},
|
|
{S_ON_HINT, HAVE_HINT},
|
|
{S_ON_KEYDOWN, HAVE_KEYDOWN},
|
|
{S_ON_POPUP, HAVE_POPUP},
|
|
{S_ON_DBL_CLICK, HAVE_DBLCLICK},
|
|
{S_ON_CURSOR_POS_CHANGED, HAVE_CURPOS}
|
|
};
|
|
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
for ( size_t i=0; i<qnumber(cbtable); i++ )
|
|
{
|
|
if ( PyObject_HasAttrString(py_link, cbtable[i].cb_name) )
|
|
features |= cbtable[i].feature;
|
|
}
|
|
|
|
if ( !create(title, features, &data) )
|
|
return false;
|
|
|
|
// Hold a reference to this object
|
|
py_last_link = py_self = py_link;
|
|
Py_INCREF(py_self);
|
|
|
|
// Return a reference to the C++ instance (only once)
|
|
if ( py_this == NULL )
|
|
py_this = PyCObject_FromVoidPtr(this, NULL);
|
|
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool show()
|
|
{
|
|
// Form was closed, but object already linked?
|
|
if ( _form == NULL && py_last_link != NULL )
|
|
{
|
|
// Re-create the view (with same previous parameters)
|
|
if ( !init(py_last_link, _title.c_str()) )
|
|
return false;
|
|
}
|
|
return customviewer_t::show();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool get_selection(size_t *x1, size_t *y1, size_t *x2, size_t *y2)
|
|
{
|
|
if ( _cv == NULL )
|
|
return false;
|
|
|
|
twinpos_t p1, p2;
|
|
if ( !::readsel2(_cv, &p1, &p2) )
|
|
return false;
|
|
|
|
if ( y1 != NULL )
|
|
*y1 = data.to_lineno(p1.at);
|
|
if ( y2 != NULL )
|
|
*y2 = data.to_lineno(p2.at);
|
|
if ( x1 != NULL )
|
|
*x1 = size_t(p1.x);
|
|
if ( x2 != NULL )
|
|
*x2 = p2.x;
|
|
return true;
|
|
}
|
|
|
|
PyObject *py_get_selection()
|
|
{
|
|
size_t x1, y1, x2, y2;
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
if ( !get_selection(&x1, &y1, &x2, &y2) )
|
|
Py_RETURN_NONE;
|
|
return Py_BuildValue("(" PY_FMT64 PY_FMT64 PY_FMT64 PY_FMT64 ")", pyul_t(x1), pyul_t(y1), pyul_t(x2), pyul_t(y2));
|
|
}
|
|
|
|
static py_simplecustview_t *get_this(PyObject *py_this)
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
return PyCObject_Check(py_this) ? (py_simplecustview_t *) PyCObject_AsVoidPtr(py_this) : NULL;
|
|
}
|
|
|
|
PyObject *get_pythis()
|
|
{
|
|
return py_this;
|
|
}
|
|
};
|
|
|
|
//</code(py_custviewer)>
|
|
%}
|
|
|
|
%inline %{
|
|
//<inline(py_cli)>
|
|
static int py_install_command_interpreter(PyObject *py_obj)
|
|
{
|
|
return py_cli_t::bind(py_obj);
|
|
}
|
|
|
|
static void py_remove_command_interpreter(int cli_idx)
|
|
{
|
|
py_cli_t::unbind(cli_idx);
|
|
}
|
|
//</inline(py_cli)>
|
|
|
|
//<inline(py_custviewer)>
|
|
//
|
|
// Pywraps Simple Custom Viewer functions
|
|
//
|
|
PyObject *pyscv_init(PyObject *py_link, const char *title)
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
py_simplecustview_t *_this = new py_simplecustview_t();
|
|
bool ok = _this->init(py_link, title);
|
|
if ( !ok )
|
|
{
|
|
delete _this;
|
|
Py_RETURN_NONE;
|
|
}
|
|
return _this->get_pythis();
|
|
}
|
|
#define DECL_THIS py_simplecustview_t *_this = py_simplecustview_t::get_this(py_this)
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool pyscv_refresh(PyObject *py_this)
|
|
{
|
|
DECL_THIS;
|
|
if ( _this == NULL )
|
|
return false;
|
|
return _this->refresh();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool pyscv_delete(PyObject *py_this)
|
|
{
|
|
DECL_THIS;
|
|
if ( _this == NULL )
|
|
return false;
|
|
_this->close();
|
|
delete _this;
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool pyscv_refresh_current(PyObject *py_this)
|
|
{
|
|
DECL_THIS;
|
|
if ( _this == NULL )
|
|
return false;
|
|
return _this->refresh_current();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
PyObject *pyscv_get_current_line(PyObject *py_this, bool mouse, bool notags)
|
|
{
|
|
DECL_THIS;
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
const char *line;
|
|
if ( _this == NULL || (line = _this->get_current_line(mouse, notags)) == NULL )
|
|
Py_RETURN_NONE;
|
|
return PyString_FromString(line);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool pyscv_is_focused(PyObject *py_this)
|
|
{
|
|
DECL_THIS;
|
|
if ( _this == NULL )
|
|
return false;
|
|
return _this->is_focused();
|
|
}
|
|
|
|
void pyscv_clear_popup_menu(PyObject *py_this)
|
|
{
|
|
DECL_THIS;
|
|
if ( _this != NULL )
|
|
_this->clear_popup_menu();
|
|
}
|
|
|
|
size_t pyscv_add_popup_menu(PyObject *py_this, const char *title, const char *hotkey)
|
|
{
|
|
DECL_THIS;
|
|
return _this == NULL ? 0 : _this->add_popup_menu(title, hotkey);
|
|
}
|
|
|
|
size_t pyscv_count(PyObject *py_this)
|
|
{
|
|
DECL_THIS;
|
|
return _this == NULL ? 0 : _this->count();
|
|
}
|
|
|
|
bool pyscv_show(PyObject *py_this)
|
|
{
|
|
DECL_THIS;
|
|
return _this == NULL ? false : _this->show();
|
|
}
|
|
|
|
void pyscv_close(PyObject *py_this)
|
|
{
|
|
DECL_THIS;
|
|
if ( _this != NULL )
|
|
_this->close();
|
|
}
|
|
|
|
bool pyscv_jumpto(PyObject *py_this, size_t ln, int x, int y)
|
|
{
|
|
DECL_THIS;
|
|
if ( _this == NULL )
|
|
return false;
|
|
return _this->jumpto(ln, x, y);
|
|
}
|
|
|
|
// Returns the line tuple
|
|
PyObject *pyscv_get_line(PyObject *py_this, size_t nline)
|
|
{
|
|
DECL_THIS;
|
|
if ( _this == NULL )
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
Py_RETURN_NONE;
|
|
}
|
|
return _this->get_line(nline);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Gets the position and returns a tuple (lineno, x, y)
|
|
PyObject *pyscv_get_pos(PyObject *py_this, bool mouse)
|
|
{
|
|
DECL_THIS;
|
|
if ( _this == NULL )
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
Py_RETURN_NONE;
|
|
}
|
|
return _this->get_pos(mouse);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
PyObject *pyscv_clear_lines(PyObject *py_this)
|
|
{
|
|
DECL_THIS;
|
|
if ( _this != NULL )
|
|
_this->clear();
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Adds a line tuple
|
|
bool pyscv_add_line(PyObject *py_this, PyObject *py_sl)
|
|
{
|
|
DECL_THIS;
|
|
return _this == NULL ? false : _this->add_line(py_sl);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool pyscv_insert_line(PyObject *py_this, size_t nline, PyObject *py_sl)
|
|
{
|
|
DECL_THIS;
|
|
return _this == NULL ? false : _this->insert_line(nline, py_sl);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool pyscv_patch_line(PyObject *py_this, size_t nline, size_t offs, int value)
|
|
{
|
|
DECL_THIS;
|
|
return _this == NULL ? false : _this->patch_line(nline, offs, value);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool pyscv_del_line(PyObject *py_this, size_t nline)
|
|
{
|
|
DECL_THIS;
|
|
return _this == NULL ? false : _this->del_line(nline);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
PyObject *pyscv_get_selection(PyObject *py_this)
|
|
{
|
|
DECL_THIS;
|
|
if ( _this == NULL )
|
|
{
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
Py_RETURN_NONE;
|
|
}
|
|
return _this->py_get_selection();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
PyObject *pyscv_get_current_word(PyObject *py_this, bool mouse)
|
|
{
|
|
DECL_THIS;
|
|
PYW_GIL_CHECK_LOCKED_SCOPE();
|
|
if ( _this != NULL )
|
|
{
|
|
qstring word;
|
|
if ( _this->get_current_word(mouse, word) )
|
|
return PyString_FromString(word.c_str());
|
|
}
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Edits an existing line
|
|
bool pyscv_edit_line(PyObject *py_this, size_t nline, PyObject *py_sl)
|
|
{
|
|
DECL_THIS;
|
|
return _this == NULL ? false : _this->edit_line(nline, py_sl);
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
TForm *pyscv_get_tform(PyObject *py_this)
|
|
{
|
|
DECL_THIS;
|
|
return _this == NULL ? NULL : _this->get_tform();
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
TCustomControl *pyscv_get_tcustom_control(PyObject *py_this)
|
|
{
|
|
DECL_THIS;
|
|
return _this == NULL ? NULL : _this->get_tcustom_control();
|
|
}
|
|
|
|
|
|
#undef DECL_THIS
|
|
//</inline(py_custviewer)>
|
|
%}
|
|
|
|
%include "kernwin.hpp"
|
|
|
|
%template(disasm_text_t) qvector<disasm_line_t>;
|
|
|
|
%extend action_desc_t {
|
|
action_desc_t(
|
|
const char *name,
|
|
const char *label,
|
|
PyObject *handler,
|
|
const char *shortcut = NULL,
|
|
const char *tooltip = NULL,
|
|
int icon = -1)
|
|
{
|
|
action_desc_t *ad = new action_desc_t();
|
|
#define DUPSTR(Prop) ad->Prop = Prop == NULL ? NULL : qstrdup(Prop)
|
|
DUPSTR(name);
|
|
DUPSTR(label);
|
|
DUPSTR(shortcut);
|
|
DUPSTR(tooltip);
|
|
#undef DUPSTR
|
|
ad->icon = icon;
|
|
ad->handler = new py_action_handler_t(handler);
|
|
ad->owner = &PLUGIN;
|
|
return ad;
|
|
}
|
|
|
|
~action_desc_t()
|
|
{
|
|
if ( $self->handler != NULL ) // Ownership not taken?
|
|
delete $self->handler;
|
|
#define FREESTR(Prop) qfree((char *) $self->Prop)
|
|
FREESTR(name);
|
|
FREESTR(label);
|
|
FREESTR(shortcut);
|
|
FREESTR(tooltip);
|
|
#undef FREESTR
|
|
delete $self;
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
uint32 choose_choose(PyObject *self,
|
|
int flags,
|
|
int x0,int y0,
|
|
int x1,int y1,
|
|
int width,
|
|
int deflt,
|
|
int icon);
|
|
|
|
%extend place_t {
|
|
static idaplace_t *as_idaplace_t(place_t *p) { return (idaplace_t *) p; }
|
|
static enumplace_t *as_enumplace_t(place_t *p) { return (enumplace_t *) p; }
|
|
static structplace_t *as_structplace_t(place_t *p) { return (structplace_t *) p; }
|
|
static simpleline_place_t *as_simpleline_place_t(place_t *p) { return (simpleline_place_t *) p; }
|
|
}
|
|
|
|
%extend twinpos_t {
|
|
|
|
%pythoncode {
|
|
def place_as_idaplace_t(self):
|
|
return place_t.as_idaplace_t(self.at)
|
|
def place_as_enumplace_t(self):
|
|
return place_t.as_enumplace_t(self.at)
|
|
def place_as_structplace_t(self):
|
|
return place_t.as_structplace_t(self.at)
|
|
def place_as_simpleline_place_t(self):
|
|
return place_t.as_simpleline_place_t(self.at)
|
|
|
|
def place(self, view):
|
|
ptype = get_viewer_place_type(view)
|
|
if ptype == TCCPT_IDAPLACE:
|
|
return self.place_as_idaplace_t()
|
|
elif ptype == TCCPT_ENUMPLACE:
|
|
return self.place_as_enumplace_t()
|
|
elif ptype == TCCPT_STRUCTPLACE:
|
|
return self.place_as_structplace_t()
|
|
elif ptype == TCCPT_SIMPLELINE_PLACE:
|
|
return self.place_as_simpleline_place_t()
|
|
else:
|
|
return self.at
|
|
}
|
|
}
|
|
|
|
%pythoncode %{
|
|
|
|
#<pycode(py_kernwin)>
|
|
DP_LEFT = 0x0001
|
|
DP_TOP = 0x0002
|
|
DP_RIGHT = 0x0004
|
|
DP_BOTTOM = 0x0008
|
|
DP_INSIDE = 0x0010
|
|
# if not before, then it is after
|
|
# (use DP_INSIDE | DP_BEFORE to insert a tab before a given tab)
|
|
# this flag alone cannot be used to determine orientation
|
|
DP_BEFORE = 0x0020
|
|
# used with combination of other flags
|
|
DP_TAB = 0x0040
|
|
DP_FLOATING = 0x0080
|
|
|
|
# ----------------------------------------------------------------------
|
|
def load_custom_icon(file_name=None, data=None, format=None):
|
|
"""
|
|
Loads a custom icon and returns an identifier that can be used with other APIs
|
|
|
|
If file_name is passed then the other two arguments are ignored.
|
|
|
|
@param file_name: The icon file name
|
|
@param data: The icon data
|
|
@param format: The icon data format
|
|
|
|
@return: Icon id or 0 on failure.
|
|
Use free_custom_icon() to free it
|
|
"""
|
|
if file_name is not None:
|
|
return _idaapi.py_load_custom_icon_fn(file_name)
|
|
elif not (data is None and format is None):
|
|
return _idaapi.py_load_custom_icon_data(data, format)
|
|
else:
|
|
return 0
|
|
|
|
# ----------------------------------------------------------------------
|
|
def asklong(defval, format):
|
|
res, val = _idaapi._asklong(defval, format)
|
|
|
|
if res == 1:
|
|
return val
|
|
else:
|
|
return None
|
|
|
|
# ----------------------------------------------------------------------
|
|
def askaddr(defval, format):
|
|
res, ea = _idaapi._askaddr(defval, format)
|
|
|
|
if res == 1:
|
|
return ea
|
|
else:
|
|
return None
|
|
|
|
# ----------------------------------------------------------------------
|
|
def askseg(defval, format):
|
|
res, sel = _idaapi._askseg(defval, format)
|
|
|
|
if res == 1:
|
|
return sel
|
|
else:
|
|
return None
|
|
|
|
# ----------------------------------------------------------------------
|
|
class action_handler_t:
|
|
def __init__(self):
|
|
pass
|
|
|
|
def activate(self, ctx):
|
|
return 0
|
|
|
|
def update(self, ctx):
|
|
pass
|
|
|
|
|
|
|
|
class Choose2(object):
|
|
"""
|
|
Choose2 wrapper class.
|
|
|
|
Some constants are defined in this class. Please refer to kernwin.hpp for more information.
|
|
"""
|
|
|
|
CH_MODAL = 0x01
|
|
"""Modal chooser"""
|
|
|
|
CH_MULTI = 0x02
|
|
"""Allow multi selection"""
|
|
|
|
CH_MULTI_EDIT = 0x04
|
|
CH_NOBTNS = 0x08
|
|
CH_ATTRS = 0x10
|
|
CH_NOIDB = 0x20
|
|
"""use the chooser even without an open database, same as x0=-2"""
|
|
CH_UTF8 = 0x40
|
|
"""string encoding is utf-8"""
|
|
|
|
CH_BUILTIN_MASK = 0xF80000
|
|
|
|
# column flags (are specified in the widths array)
|
|
CHCOL_PLAIN = 0x00000000
|
|
CHCOL_PATH = 0x00010000
|
|
CHCOL_HEX = 0x00020000
|
|
CHCOL_DEC = 0x00030000
|
|
CHCOL_FORMAT = 0x00070000
|
|
|
|
|
|
def __init__(self, title, cols, flags=0, popup_names=None,
|
|
icon=-1, x1=-1, y1=-1, x2=-1, y2=-1, deflt=-1,
|
|
embedded=False, width=None, height=None):
|
|
"""
|
|
Constructs a chooser window.
|
|
@param title: The chooser title
|
|
@param cols: a list of colums; each list item is a list of two items
|
|
example: [ ["Address", 10 | Choose2.CHCOL_HEX], ["Name", 30 | Choose2.CHCOL_PLAIN] ]
|
|
@param flags: One of CH_XXXX constants
|
|
@param deflt: Default starting item
|
|
@param popup_names: list of new captions to replace this list ["Insert", "Delete", "Edit", "Refresh"]
|
|
@param icon: Icon index (the icon should exist in ida resources or an index to a custom loaded icon)
|
|
@param x1, y1, x2, y2: The default location
|
|
@param embedded: Create as embedded chooser
|
|
@param width: Embedded chooser width
|
|
@param height: Embedded chooser height
|
|
"""
|
|
self.title = title
|
|
self.flags = flags
|
|
self.cols = cols
|
|
self.deflt = deflt
|
|
self.popup_names = popup_names
|
|
self.icon = icon
|
|
self.x1 = x1
|
|
self.y1 = y1
|
|
self.x2 = x2
|
|
self.y2 = y2
|
|
self.embedded = embedded
|
|
if embedded:
|
|
self.x1 = width
|
|
self.y1 = height
|
|
|
|
|
|
def Embedded(self):
|
|
"""
|
|
Creates an embedded chooser (as opposed to Show())
|
|
@return: Returns 1 on success
|
|
"""
|
|
return _idaapi.choose2_create(self, True)
|
|
|
|
|
|
def GetEmbSelection(self):
|
|
"""
|
|
Returns the selection associated with an embedded chooser
|
|
|
|
@return:
|
|
- None if chooser is not embedded
|
|
- A list with selection indices (0-based)
|
|
"""
|
|
return _idaapi.choose2_get_embedded_selection(self)
|
|
|
|
|
|
def Show(self, modal=False):
|
|
"""
|
|
Activates or creates a chooser window
|
|
@param modal: Display as modal dialog
|
|
@return: For modal choosers it will return the selected item index (0-based)
|
|
"""
|
|
if modal:
|
|
self.flags |= Choose2.CH_MODAL
|
|
|
|
# Disable the timeout
|
|
old = _idaapi.set_script_timeout(0)
|
|
n = _idaapi.choose2_create(self, False)
|
|
_idaapi.set_script_timeout(old)
|
|
|
|
# Delete the modal chooser instance
|
|
self.Close()
|
|
|
|
return n
|
|
else:
|
|
self.flags &= ~Choose2.CH_MODAL
|
|
return _idaapi.choose2_create(self, False)
|
|
|
|
|
|
def Activate(self):
|
|
"""Activates a visible chooser"""
|
|
return _idaapi.choose2_activate(self)
|
|
|
|
|
|
def Refresh(self):
|
|
"""Causes the refresh callback to trigger"""
|
|
return _idaapi.choose2_refresh(self)
|
|
|
|
|
|
def Close(self):
|
|
"""Closes the chooser"""
|
|
return _idaapi.choose2_close(self)
|
|
|
|
|
|
def AddCommand(self,
|
|
caption,
|
|
flags = _idaapi.CHOOSER_POPUP_MENU,
|
|
menu_index = -1,
|
|
icon = -1,
|
|
emb=None):
|
|
"""
|
|
Deprecated: Use
|
|
- register_action()
|
|
- attach_action_to_menu()
|
|
- attach_action_to_popup()
|
|
"""
|
|
# Use the 'emb' as a sentinel. It will be passed the correct value from the EmbeddedChooserControl
|
|
if self.embedded and ((emb is None) or (emb != 2002)):
|
|
raise RuntimeError("Please add a command through EmbeddedChooserControl.AddCommand()")
|
|
return _idaapi.choose2_add_command(self, caption, flags, menu_index, icon)
|
|
|
|
#
|
|
# Implement these methods in the subclass:
|
|
#
|
|
#<pydoc>
|
|
# def OnClose(self):
|
|
# """
|
|
# Called when the window is being closed.
|
|
# This callback is mandatory.
|
|
# @return: nothing
|
|
# """
|
|
# pass
|
|
#
|
|
# def OnGetLine(self, n):
|
|
# """Called when the chooser window requires lines.
|
|
# This callback is mandatory.
|
|
# @param n: Line number (0-based)
|
|
# @return: The user should return a list with ncols elements.
|
|
# example: a list [col1, col2, col3, ...] describing the n-th line
|
|
# """
|
|
# return ["col1 val", "col2 val"]
|
|
#
|
|
# def OnGetSize(self):
|
|
# """Returns the element count.
|
|
# This callback is mandatory.
|
|
# @return: Number of elements
|
|
# """
|
|
# return len(self.the_list)
|
|
#
|
|
# def OnEditLine(self, n):
|
|
# """
|
|
# Called when an item is being edited.
|
|
# @param n: Line number (0-based)
|
|
# @return: Nothing
|
|
# """
|
|
# pass
|
|
#
|
|
# def OnInsertLine(self):
|
|
# """
|
|
# Called when 'Insert' is selected either via the hotkey or popup menu.
|
|
# @return: Nothing
|
|
# """
|
|
# pass
|
|
#
|
|
# def OnSelectLine(self, n):
|
|
# """
|
|
# Called when a line is selected and then Ok or double click was pressed
|
|
# @param n: Line number (0-based)
|
|
# """
|
|
# pass
|
|
#
|
|
# def OnSelectionChange(self, sel_list):
|
|
# """
|
|
# Called when the selection changes
|
|
# @param sel_list: A list of selected item indices
|
|
# """
|
|
# pass
|
|
#
|
|
# def OnDeleteLine(self, n):
|
|
# """
|
|
# Called when a line is about to be deleted
|
|
# @param n: Line number (0-based)
|
|
# """
|
|
# return self.n
|
|
#
|
|
# def OnRefresh(self, n):
|
|
# """
|
|
# Triggered when the 'Refresh' is called from the popup menu item.
|
|
#
|
|
# @param n: The currently selected line (0-based) at the time of the refresh call
|
|
# @return: Return the number of elements
|
|
# """
|
|
# return self.n
|
|
#
|
|
# def OnRefreshed(self):
|
|
# """
|
|
# Triggered when a refresh happens (for example due to column sorting)
|
|
# @param n: Line number (0-based)
|
|
# @return: Return the number of elements
|
|
# """
|
|
# return self.n
|
|
#
|
|
# def OnCommand(self, n, cmd_id):
|
|
# """Return int ; check add_chooser_command()"""
|
|
# return 0
|
|
#
|
|
# def OnGetIcon(self, n):
|
|
# """
|
|
# Return icon number for a given item (or -1 if no icon is avail)
|
|
# @param n: Line number (0-based)
|
|
# """
|
|
# return -1
|
|
#
|
|
# def OnGetLineAttr(self, n):
|
|
# """
|
|
# Return list [bgcolor, flags=CHITEM_XXXX] or None; check chooser_item_attrs_t
|
|
# @param n: Line number (0-based)
|
|
# """
|
|
# return [0x0, CHITEM_BOLD]
|
|
#</pydoc>
|
|
|
|
|
|
#ICON WARNING|QUESTION|INFO|NONE
|
|
#AUTOHIDE NONE|DATABASE|REGISTRY|SESSION
|
|
#HIDECANCEL
|
|
#BUTTON YES|NO|CANCEL "Value|NONE"
|
|
#STARTITEM {id:ItemName}
|
|
#HELP / ENDHELP
|
|
try:
|
|
import types
|
|
from ctypes import *
|
|
# On Windows, we use stdcall
|
|
|
|
# Callback for buttons
|
|
# typedef void (idaapi *formcb_t)(TView *fields[], int code);
|
|
|
|
_FORMCB_T = WINFUNCTYPE(None, c_void_p, c_int)
|
|
|
|
# Callback for form change
|
|
# typedef int (idaapi *formchgcb_t)(int field_id, form_actions_t &fa);
|
|
_FORMCHGCB_T = WINFUNCTYPE(c_int, c_int, c_void_p)
|
|
except:
|
|
try:
|
|
_FORMCB_T = CFUNCTYPE(None, c_void_p, c_int)
|
|
_FORMCHGCB_T = CFUNCTYPE(c_int, c_int, c_void_p)
|
|
except:
|
|
_FORMCHGCB_T = _FORMCB_T = None
|
|
|
|
|
|
# -----------------------------------------------------------------------
|
|
# textctrl_info_t clinked object
|
|
class textctrl_info_t(py_clinked_object_t):
|
|
"""Class representing textctrl_info_t"""
|
|
|
|
# Some constants
|
|
TXTF_AUTOINDENT = 0x0001
|
|
"""Auto-indent on new line"""
|
|
TXTF_ACCEPTTABS = 0x0002
|
|
"""Tab key inserts 'tabsize' spaces"""
|
|
TXTF_READONLY = 0x0004
|
|
"""Text cannot be edited (but can be selected and copied)"""
|
|
TXTF_SELECTED = 0x0008
|
|
"""Shows the field with its text selected"""
|
|
TXTF_MODIFIED = 0x0010
|
|
"""Gets/sets the modified status"""
|
|
TXTF_FIXEDFONT = 0x0020
|
|
"""The control uses IDA's fixed font"""
|
|
|
|
def __init__(self, text="", flags=0, tabsize=0):
|
|
py_clinked_object_t.__init__(self)
|
|
if text:
|
|
self.text = text
|
|
if flags:
|
|
self.flags = flags
|
|
if tabsize:
|
|
self.tabsize = tabsize
|
|
|
|
def _create_clink(self):
|
|
return _idaapi.textctrl_info_t_create()
|
|
|
|
def _del_clink(self, lnk):
|
|
return _idaapi.textctrl_info_t_destroy(lnk)
|
|
|
|
def _get_clink_ptr(self):
|
|
return _idaapi.textctrl_info_t_get_clink_ptr(self)
|
|
|
|
def assign(self, other):
|
|
"""Copies the contents of 'other' to 'self'"""
|
|
return _idaapi.textctrl_info_t_assign(self, other)
|
|
|
|
def __set_text(self, s):
|
|
"""Sets the text value"""
|
|
return _idaapi.textctrl_info_t_set_text(self, s)
|
|
|
|
def __get_text(self):
|
|
"""Sets the text value"""
|
|
return _idaapi.textctrl_info_t_get_text(self)
|
|
|
|
def __set_flags__(self, flags):
|
|
"""Sets the flags value"""
|
|
return _idaapi.textctrl_info_t_set_flags(self, flags)
|
|
|
|
def __get_flags__(self):
|
|
"""Returns the flags value"""
|
|
return _idaapi.textctrl_info_t_get_flags(self)
|
|
|
|
def __set_tabsize__(self, tabsize):
|
|
"""Sets the tabsize value"""
|
|
return _idaapi.textctrl_info_t_set_tabsize(self, tabsize)
|
|
|
|
def __get_tabsize__(self):
|
|
"""Returns the tabsize value"""
|
|
return _idaapi.textctrl_info_t_get_tabsize(self)
|
|
|
|
value = property(__get_text, __set_text)
|
|
"""Alias for the text property"""
|
|
text = property(__get_text, __set_text)
|
|
"""Text value"""
|
|
flags = property(__get_flags__, __set_flags__)
|
|
"""Flags value"""
|
|
tabsize = property(__get_tabsize__, __set_tabsize__)
|
|
|
|
# -----------------------------------------------------------------------
|
|
class Form(object):
|
|
|
|
FT_ASCII = 'A'
|
|
"""Ascii string - char *"""
|
|
FT_SEG = 'S'
|
|
"""Segment - sel_t *"""
|
|
FT_HEX = 'N'
|
|
"""Hex number - uval_t *"""
|
|
FT_SHEX = 'n'
|
|
"""Signed hex number - sval_t *"""
|
|
FT_COLOR = 'K'
|
|
"""Color button - bgcolor_t *"""
|
|
FT_ADDR = '$'
|
|
"""Address - ea_t *"""
|
|
FT_UINT64 = 'L'
|
|
"""default base uint64 - uint64"""
|
|
FT_INT64 = 'l'
|
|
"""default base int64 - int64"""
|
|
FT_RAWHEX = 'M'
|
|
"""Hex number, no 0x prefix - uval_t *"""
|
|
FT_FILE = 'f'
|
|
"""File browse - char * at least QMAXPATH"""
|
|
FT_DEC = 'D'
|
|
"""Decimal number - sval_t *"""
|
|
FT_OCT = 'O'
|
|
"""Octal number, C notation - sval_t *"""
|
|
FT_BIN = 'Y'
|
|
"""Binary number, 0b prefix - sval_t *"""
|
|
FT_CHAR = 'H'
|
|
"""Char value -- sval_t *"""
|
|
FT_IDENT = 'I'
|
|
"""Identifier - char * at least MAXNAMELEN"""
|
|
FT_BUTTON = 'B'
|
|
"""Button - def handler(code)"""
|
|
FT_DIR = 'F'
|
|
"""Path to directory - char * at least QMAXPATH"""
|
|
FT_TYPE = 'T'
|
|
"""Type declaration - char * at least MAXSTR"""
|
|
_FT_USHORT = '_US'
|
|
"""Unsigned short"""
|
|
FT_FORMCHG = '%/'
|
|
"""Form change callback - formchgcb_t"""
|
|
FT_ECHOOSER = 'E'
|
|
"""Embedded chooser - idaapi.Choose2"""
|
|
FT_MULTI_LINE_TEXT = 't'
|
|
"""Multi text control - textctrl_info_t"""
|
|
FT_DROPDOWN_LIST = 'b'
|
|
"""Dropdown list control - Form.DropdownControl"""
|
|
|
|
FT_CHKGRP = 'C'
|
|
FT_CHKGRP2= 'c'
|
|
FT_RADGRP = 'R'
|
|
FT_RADGRP2= 'r'
|
|
|
|
@staticmethod
|
|
def fieldtype_to_ctype(tp, i64 = False):
|
|
"""
|
|
Factory method returning a ctype class corresponding to the field type string
|
|
"""
|
|
if tp in (Form.FT_SEG, Form.FT_HEX, Form.FT_RAWHEX, Form.FT_ADDR):
|
|
return c_ulonglong if i64 else c_ulong
|
|
elif tp in (Form.FT_SHEX, Form.FT_DEC, Form.FT_OCT, Form.FT_BIN, Form.FT_CHAR):
|
|
return c_longlong if i64 else c_long
|
|
elif tp == Form.FT_UINT64:
|
|
return c_ulonglong
|
|
elif tp == Form.FT_INT64:
|
|
return c_longlong
|
|
elif tp == Form.FT_COLOR:
|
|
return c_ulong
|
|
elif tp == Form._FT_USHORT:
|
|
return c_ushort
|
|
elif tp in (Form.FT_FORMCHG, Form.FT_ECHOOSER):
|
|
return c_void_p
|
|
else:
|
|
return None
|
|
|
|
|
|
#
|
|
# Generic argument helper classes
|
|
#
|
|
class NumericArgument(object):
|
|
"""
|
|
Argument representing various integer arguments (ushort, uint32, uint64, etc...)
|
|
@param tp: One of Form.FT_XXX
|
|
"""
|
|
DefI64 = False
|
|
def __init__(self, tp, value):
|
|
cls = Form.fieldtype_to_ctype(tp, self.DefI64)
|
|
if cls is None:
|
|
raise TypeError("Invalid numeric field type: %s" % tp)
|
|
# Get a pointer type to the ctype type
|
|
self.arg = pointer(cls(value))
|
|
|
|
def __set_value(self, v):
|
|
self.arg.contents.value = v
|
|
value = property(lambda self: self.arg.contents.value, __set_value)
|
|
|
|
|
|
class StringArgument(object):
|
|
"""
|
|
Argument representing a character buffer
|
|
"""
|
|
def __init__(self, size=None, value=None):
|
|
if size is None:
|
|
raise SyntaxError("The string size must be passed")
|
|
|
|
if value is None:
|
|
self.arg = create_string_buffer(size)
|
|
else:
|
|
self.arg = create_string_buffer(value, size)
|
|
self.size = size
|
|
|
|
def __set_value(self, v):
|
|
self.arg.value = v
|
|
value = property(lambda self: self.arg.value, __set_value)
|
|
|
|
|
|
#
|
|
# Base control class
|
|
#
|
|
class Control(object):
|
|
def __init__(self):
|
|
self.id = 0
|
|
"""Automatically assigned control ID"""
|
|
|
|
self.arg = None
|
|
"""Control argument value. This could be one element or a list/tuple (for multiple args per control)"""
|
|
|
|
self.form = None
|
|
"""Reference to the parent form. It is filled by Form.Add()"""
|
|
|
|
|
|
def get_tag(self):
|
|
"""
|
|
Control tag character. One of Form.FT_XXXX.
|
|
The form class will expand the {} notation and replace them with the tags
|
|
"""
|
|
pass
|
|
|
|
def get_arg(self):
|
|
"""
|
|
Control returns the parameter to be pushed on the stack
|
|
(Of AskUsingForm())
|
|
"""
|
|
return self.arg
|
|
|
|
def free(self):
|
|
"""
|
|
Free the control
|
|
"""
|
|
# Release the parent form reference
|
|
self.form = None
|
|
|
|
|
|
#
|
|
# Label controls
|
|
#
|
|
class LabelControl(Control):
|
|
"""
|
|
Base class for static label control
|
|
"""
|
|
def __init__(self, tp):
|
|
Form.Control.__init__(self)
|
|
self.tp = tp
|
|
|
|
def get_tag(self):
|
|
return '%%%d%s' % (self.id, self.tp)
|
|
|
|
|
|
class StringLabel(LabelControl):
|
|
"""
|
|
String label control
|
|
"""
|
|
def __init__(self, value, tp=None, sz=1024):
|
|
"""
|
|
Type field can be one of:
|
|
A - ascii string
|
|
T - type declaration
|
|
I - ident
|
|
F - folder
|
|
f - file
|
|
X - command
|
|
"""
|
|
if tp is None:
|
|
tp = Form.FT_ASCII
|
|
Form.LabelControl.__init__(self, tp)
|
|
self.size = sz
|
|
self.arg = create_string_buffer(value, sz)
|
|
|
|
|
|
class NumericLabel(LabelControl, NumericArgument):
|
|
"""
|
|
Numeric label control
|
|
"""
|
|
def __init__(self, value, tp=None):
|
|
if tp is None:
|
|
tp = Form.FT_HEX
|
|
Form.LabelControl.__init__(self, tp)
|
|
Form.NumericArgument.__init__(self, tp, value)
|
|
|
|
|
|
#
|
|
# Group controls
|
|
#
|
|
class GroupItemControl(Control):
|
|
"""
|
|
Base class for group control items
|
|
"""
|
|
def __init__(self, tag, parent):
|
|
Form.Control.__init__(self)
|
|
self.tag = tag
|
|
self.parent = parent
|
|
# Item position (filled when form is compiled)
|
|
self.pos = 0
|
|
|
|
def assign_pos(self):
|
|
self.pos = self.parent.next_child_pos()
|
|
|
|
def get_tag(self):
|
|
return "%s%d" % (self.tag, self.id)
|
|
|
|
|
|
class ChkGroupItemControl(GroupItemControl):
|
|
"""
|
|
Checkbox group item control
|
|
"""
|
|
def __init__(self, tag, parent):
|
|
Form.GroupItemControl.__init__(self, tag, parent)
|
|
|
|
def __get_value(self):
|
|
return (self.parent.value & (1 << self.pos)) != 0
|
|
|
|
def __set_value(self, v):
|
|
pv = self.parent.value
|
|
if v:
|
|
pv = pv | (1 << self.pos)
|
|
else:
|
|
pv = pv & ~(1 << self.pos)
|
|
|
|
self.parent.value = pv
|
|
|
|
checked = property(__get_value, __set_value)
|
|
"""Get/Sets checkbox item check status"""
|
|
|
|
|
|
class RadGroupItemControl(GroupItemControl):
|
|
"""
|
|
Radiobox group item control
|
|
"""
|
|
def __init__(self, tag, parent):
|
|
Form.GroupItemControl.__init__(self, tag, parent)
|
|
|
|
def __get_value(self):
|
|
return self.parent.value == self.pos
|
|
|
|
def __set_value(self, v):
|
|
self.parent.value = self.pos
|
|
|
|
selected = property(__get_value, __set_value)
|
|
"""Get/Sets radiobox item selection status"""
|
|
|
|
|
|
class GroupControl(Control, NumericArgument):
|
|
"""
|
|
Base class for group controls
|
|
"""
|
|
def __init__(self, children_names, tag, value=0):
|
|
Form.Control.__init__(self)
|
|
self.children_names = children_names
|
|
self.tag = tag
|
|
self._reset()
|
|
Form.NumericArgument.__init__(self, Form._FT_USHORT, value)
|
|
|
|
def _reset(self):
|
|
self.childpos = 0
|
|
|
|
def next_child_pos(self):
|
|
v = self.childpos
|
|
self.childpos += 1
|
|
return v
|
|
|
|
def get_tag(self):
|
|
return "%d" % self.id
|
|
|
|
|
|
class ChkGroupControl(GroupControl):
|
|
"""
|
|
Checkbox group control class.
|
|
It holds a set of checkbox controls
|
|
"""
|
|
ItemClass = None
|
|
"""
|
|
Group control item factory class instance
|
|
We need this because later we won't be treating ChkGroupControl or RadGroupControl
|
|
individually, instead we will be working with GroupControl in general.
|
|
"""
|
|
def __init__(self, children_names, value=0, secondary=False):
|
|
# Assign group item factory class
|
|
if Form.ChkGroupControl.ItemClass is None:
|
|
Form.ChkGroupControl.ItemClass = Form.ChkGroupItemControl
|
|
|
|
Form.GroupControl.__init__(
|
|
self,
|
|
children_names,
|
|
Form.FT_CHKGRP2 if secondary else Form.FT_CHKGRP,
|
|
value)
|
|
|
|
|
|
class RadGroupControl(GroupControl):
|
|
"""
|
|
Radiobox group control class.
|
|
It holds a set of radiobox controls
|
|
"""
|
|
ItemClass = None
|
|
def __init__(self, children_names, value=0, secondary=False):
|
|
"""
|
|
Creates a radiogroup control.
|
|
@param children_names: A tuple containing group item names
|
|
@param value: Initial selected radio item
|
|
@param secondory: Allows rendering one the same line as the previous group control.
|
|
Use this if you have another group control on the same line.
|
|
"""
|
|
# Assign group item factory class
|
|
if Form.RadGroupControl.ItemClass is None:
|
|
Form.RadGroupControl.ItemClass = Form.RadGroupItemControl
|
|
|
|
Form.GroupControl.__init__(
|
|
self,
|
|
children_names,
|
|
Form.FT_RADGRP2 if secondary else Form.FT_RADGRP,
|
|
value)
|
|
|
|
|
|
#
|
|
# Input controls
|
|
#
|
|
class InputControl(Control):
|
|
"""
|
|
Generic form input control.
|
|
It could be numeric control, string control, directory/file browsing, etc...
|
|
"""
|
|
def __init__(self, tp, width, swidth, hlp = None):
|
|
"""
|
|
@param width: Display width
|
|
@param swidth: String width
|
|
"""
|
|
Form.Control.__init__(self)
|
|
self.tp = tp
|
|
self.width = width
|
|
self.switdh = swidth
|
|
self.hlp = hlp
|
|
|
|
def get_tag(self):
|
|
return "%s%d:%s:%s:%s" % (
|
|
self.tp, self.id,
|
|
self.width,
|
|
self.switdh,
|
|
":" if self.hlp is None else self.hlp)
|
|
|
|
|
|
class NumericInput(InputControl, NumericArgument):
|
|
"""
|
|
A composite class serving as a base numeric input control class
|
|
"""
|
|
def __init__(self, tp=None, value=0, width=50, swidth=10, hlp=None):
|
|
if tp is None:
|
|
tp = Form.FT_HEX
|
|
Form.InputControl.__init__(self, tp, width, swidth, hlp)
|
|
Form.NumericArgument.__init__(self, self.tp, value)
|
|
|
|
|
|
class ColorInput(NumericInput):
|
|
"""
|
|
Color button input control
|
|
"""
|
|
def __init__(self, value = 0):
|
|
"""
|
|
@param value: Initial color value in RGB
|
|
"""
|
|
Form.NumericInput.__init__(self, tp=Form.FT_COLOR, value=value)
|
|
|
|
|
|
class StringInput(InputControl, StringArgument):
|
|
"""
|
|
Base string input control class.
|
|
This class also constructs a StringArgument
|
|
"""
|
|
def __init__(self,
|
|
tp=None,
|
|
width=1024,
|
|
swidth=40,
|
|
hlp=None,
|
|
value=None,
|
|
size=None):
|
|
"""
|
|
@param width: String size. But in some cases it has special meaning. For example in FileInput control.
|
|
If you want to define the string buffer size then pass the 'size' argument
|
|
@param swidth: Control width
|
|
@param value: Initial value
|
|
@param size: String size
|
|
"""
|
|
if tp is None:
|
|
tp = Form.FT_ASCII
|
|
if not size:
|
|
size = width
|
|
Form.InputControl.__init__(self, tp, width, swidth, hlp)
|
|
Form.StringArgument.__init__(self, size=size, value=value)
|
|
|
|
|
|
class FileInput(StringInput):
|
|
"""
|
|
File Open/Save input control
|
|
"""
|
|
def __init__(self,
|
|
width=512,
|
|
swidth=80,
|
|
save=False, open=False,
|
|
hlp=None, value=None):
|
|
|
|
if save == open:
|
|
raise ValueError("Invalid mode. Choose either open or save")
|
|
if width < 512:
|
|
raise ValueError("Invalid width. Must be greater than 512.")
|
|
|
|
# The width field is overloaded in this control and is used
|
|
# to denote the type of the FileInput dialog (save or load)
|
|
# On the other hand it is passed as is to the StringArgument part
|
|
Form.StringInput.__init__(
|
|
self,
|
|
tp=Form.FT_FILE,
|
|
width="1" if save else "0",
|
|
swidth=swidth,
|
|
hlp=hlp,
|
|
size=width,
|
|
value=value)
|
|
|
|
|
|
class DirInput(StringInput):
|
|
"""
|
|
Directory browsing control
|
|
"""
|
|
def __init__(self,
|
|
width=512,
|
|
swidth=80,
|
|
hlp=None,
|
|
value=None):
|
|
|
|
if width < 512:
|
|
raise ValueError("Invalid width. Must be greater than 512.")
|
|
|
|
Form.StringInput.__init__(
|
|
self,
|
|
tp=Form.FT_DIR,
|
|
width=width,
|
|
swidth=swidth,
|
|
hlp=hlp,
|
|
size=width,
|
|
value=value)
|
|
|
|
|
|
class ButtonInput(InputControl):
|
|
"""
|
|
Button control.
|
|
A handler along with a 'code' (numeric value) can be associated with the button.
|
|
This way one handler can handle many buttons based on the button code (or in other terms id or tag)
|
|
"""
|
|
def __init__(self, handler, code="", swidth="", hlp=None):
|
|
"""
|
|
@param handler: Button handler. A callback taking one argument which is the code.
|
|
@param code: A code associated with the button and that is later passed to the handler.
|
|
"""
|
|
Form.InputControl.__init__(
|
|
self,
|
|
Form.FT_BUTTON,
|
|
code,
|
|
swidth,
|
|
hlp)
|
|
self.arg = _FORMCB_T(lambda view, code, h=handler: h(code))
|
|
|
|
|
|
class FormChangeCb(Control):
|
|
"""
|
|
Form change handler.
|
|
This can be thought of like a dialog procedure.
|
|
Everytime a form action occurs, this handler will be called along with the control id.
|
|
The programmer can then call various form actions accordingly:
|
|
- EnableField
|
|
- ShowField
|
|
- MoveField
|
|
- GetFieldValue
|
|
- etc...
|
|
|
|
Special control IDs: -1 (The form is initialized) and -2 (Ok has been clicked)
|
|
|
|
"""
|
|
def __init__(self, handler):
|
|
"""
|
|
Constructs the handler.
|
|
@param handler: The handler (preferrably a member function of a class derived from the Form class).
|
|
"""
|
|
Form.Control.__init__(self)
|
|
|
|
# Save the handler
|
|
self.handler = handler
|
|
|
|
# Create a callback stub
|
|
# We use this mechanism to create an intermediate step
|
|
# where we can create an 'fa' adapter for use by Python
|
|
self.arg = _FORMCHGCB_T(self.helper_cb)
|
|
|
|
def helper_cb(self, fid, p_fa):
|
|
# Remember the pointer to the forms_action in the parent form
|
|
self.form.p_fa = p_fa
|
|
|
|
# Call user's handler
|
|
r = self.handler(fid)
|
|
return 0 if r is None else r
|
|
|
|
def get_tag(self):
|
|
return Form.FT_FORMCHG
|
|
|
|
def free(self):
|
|
Form.Control.free(self)
|
|
# Remove reference to the handler
|
|
# (Normally the handler is a member function in the parent form)
|
|
self.handler = None
|
|
|
|
|
|
class EmbeddedChooserControl(InputControl):
|
|
"""
|
|
Embedded chooser control.
|
|
This control links to a Chooser2 control created with the 'embedded=True'
|
|
"""
|
|
def __init__(self,
|
|
chooser=None,
|
|
swidth=40,
|
|
hlp=None):
|
|
"""
|
|
Embedded chooser control
|
|
|
|
@param chooser: A chooser2 instance (must be constructed with 'embedded=True')
|
|
"""
|
|
|
|
# !! Make sure a chooser instance is passed !!
|
|
if chooser is None or not isinstance(chooser, Choose2):
|
|
raise ValueError("Invalid chooser passed.")
|
|
|
|
# Create an embedded chooser structure from the Choose2 instance
|
|
if chooser.Embedded() != 1:
|
|
raise ValueError("Failed to create embedded chooser instance.")
|
|
|
|
# Construct input control
|
|
Form.InputControl.__init__(self, Form.FT_ECHOOSER, "", swidth)
|
|
|
|
# Get a pointer to the chooser_info_t and the selection vector
|
|
# (These two parameters are the needed arguments for the AskUsingForm())
|
|
emb, sel = _idaapi.choose2_get_embedded(chooser)
|
|
|
|
# Get a pointer to a c_void_p constructed from an address
|
|
p_embedded = pointer(c_void_p.from_address(emb))
|
|
p_sel = pointer(c_void_p.from_address(sel))
|
|
|
|
# - Create the embedded chooser info on control creation
|
|
# - Do not free the embeded chooser because after we get the args
|
|
# via Compile() the user can still call Execute() which relies
|
|
# on the already computed args
|
|
self.arg = (p_embedded, p_sel)
|
|
|
|
# Save chooser instance
|
|
self.chooser = chooser
|
|
|
|
# Add a bogus 'size' attribute
|
|
self.size = 0
|
|
|
|
|
|
value = property(lambda self: self.chooser)
|
|
"""Returns the embedded chooser instance"""
|
|
|
|
|
|
def AddCommand(self,
|
|
caption,
|
|
flags = _idaapi.CHOOSER_POPUP_MENU,
|
|
menu_index = -1,
|
|
icon = -1):
|
|
"""
|
|
Adds a new embedded chooser command
|
|
Save the returned value and later use it in the OnCommand handler
|
|
|
|
@return: Returns a negative value on failure or the command index
|
|
"""
|
|
if not self.form.title:
|
|
raise ValueError("Form title is not set!")
|
|
|
|
# Encode all information for the AddCommand() in the 'caption' parameter
|
|
caption = "%s:%d:%s" % (self.form.title, self.id, caption)
|
|
return self.chooser.AddCommand(caption, flags=flags, menu_index=menu_index, icon=icon, emb=2002)
|
|
|
|
|
|
def free(self):
|
|
"""
|
|
Frees the embedded chooser data
|
|
"""
|
|
self.chooser.Close()
|
|
self.chooser = None
|
|
Form.Control.free(self)
|
|
|
|
|
|
class DropdownListControl(InputControl, _qstrvec_t):
|
|
"""
|
|
Dropdown control
|
|
This control allows manipulating a dropdown control
|
|
"""
|
|
def __init__(self, items=[], readonly=True, selval=0, width=50, swidth=50, hlp = None):
|
|
"""
|
|
@param items: A string list of items used to prepopulate the control
|
|
@param readonly: Specifies whether the dropdown list is editable or not
|
|
@param selval: The preselected item index (when readonly) or text value (when editable)
|
|
@param width: the control width (n/a if the dropdown list is readonly)
|
|
@param swidth: string width
|
|
"""
|
|
|
|
# Ignore the width if readonly was set
|
|
if readonly:
|
|
width = 0
|
|
|
|
# Init the input control base class
|
|
Form.InputControl.__init__(
|
|
self,
|
|
Form.FT_DROPDOWN_LIST,
|
|
width,
|
|
swidth,
|
|
hlp)
|
|
|
|
# Init the associated qstrvec
|
|
_qstrvec_t.__init__(self, items)
|
|
|
|
# Remember if readonly or not
|
|
self.readonly = readonly
|
|
|
|
if readonly:
|
|
# Create a C integer and remember it
|
|
self.__selval = c_int(selval)
|
|
val_addr = addressof(self.__selval)
|
|
else:
|
|
# Create an strvec with one qstring
|
|
self.__selval = _qstrvec_t([selval])
|
|
# Get address of the first element
|
|
val_addr = self.__selval.addressof(0)
|
|
|
|
# Two arguments:
|
|
# - argument #1: a pointer to the qstrvec containing the items
|
|
# - argument #2: an integer to hold the selection
|
|
# or
|
|
# #2: a qstring to hold the dropdown text control value
|
|
self.arg = (
|
|
pointer(c_void_p.from_address(self.clink_ptr)),
|
|
pointer(c_void_p.from_address(val_addr))
|
|
)
|
|
|
|
|
|
def __set_selval(self, val):
|
|
if self.readonly:
|
|
self.__selval.value = val
|
|
else:
|
|
self.__selval[0] = val
|
|
|
|
def __get_selval(self):
|
|
# Return the selection index
|
|
# or the entered text value
|
|
return self.__selval.value if self.readonly else self.__selval[0]
|
|
|
|
value = property(__get_selval, __set_selval)
|
|
selval = property(__get_selval, __set_selval)
|
|
"""
|
|
Read/write the selection value.
|
|
The value is used as an item index in readonly mode or text value in editable mode
|
|
This value can be used only after the form has been closed.
|
|
"""
|
|
|
|
def free(self):
|
|
self._free()
|
|
|
|
|
|
def set_items(self, items):
|
|
"""Sets the dropdown list items"""
|
|
self.from_list(items)
|
|
|
|
|
|
class MultiLineTextControl(InputControl, textctrl_info_t):
|
|
"""
|
|
Multi line text control.
|
|
This class inherits from textctrl_info_t. Thus the attributes are also inherited
|
|
This control allows manipulating a multilinetext control
|
|
"""
|
|
def __init__(self, text="", flags=0, tabsize=0, width=50, swidth=50, hlp = None):
|
|
"""
|
|
@param text: Initial text value
|
|
@param flags: One of textctrl_info_t.TXTF_.... values
|
|
@param tabsize: Tab size
|
|
@param width: Display width
|
|
@param swidth: String width
|
|
"""
|
|
# Init the input control base class
|
|
Form.InputControl.__init__(self, Form.FT_MULTI_LINE_TEXT, width, swidth, hlp)
|
|
|
|
# Init the associated textctrl_info base class
|
|
textctrl_info_t.__init__(self, text=text, flags=flags, tabsize=tabsize)
|
|
|
|
# Get the argument as a pointer from the embedded ti
|
|
self.arg = pointer(c_void_p.from_address(self.clink_ptr))
|
|
|
|
|
|
def free(self):
|
|
self._free()
|
|
|
|
|
|
#
|
|
# Form class
|
|
#
|
|
def __init__(self, form, controls):
|
|
"""
|
|
Contruct a Form class.
|
|
This class wraps around AskUsingForm() or OpenForm() and provides an easier / alternative syntax for describing forms.
|
|
The form control names are wrapped inside the opening and closing curly braces and the control themselves are
|
|
defined and instantiated via various form controls (subclasses of Form).
|
|
|
|
@param form: The form string
|
|
@param controls: A dictionary containing the control name as a _key_ and control object as _value_
|
|
"""
|
|
self._reset()
|
|
self.form = form
|
|
"""Form string"""
|
|
self.controls = controls
|
|
"""Dictionary of controls"""
|
|
self.__args = None
|
|
|
|
self.title = None
|
|
"""The Form title. It will be filled when the form is compiled"""
|
|
|
|
self.modal = True
|
|
"""By default, forms are modal"""
|
|
|
|
self.openform_flags = 0
|
|
"""
|
|
If non-modal, these flags will be passed to OpenForm.
|
|
This is an OR'ed combination of the PluginForm.FORM_* values.
|
|
"""
|
|
|
|
|
|
def Free(self):
|
|
"""
|
|
Frees all resources associated with a compiled form.
|
|
Make sure you call this function when you finish using the form.
|
|
"""
|
|
|
|
# Free all the controls
|
|
for ctrl in self.__controls.values():
|
|
ctrl.free()
|
|
|
|
# Reset the controls
|
|
# (Note that we are not removing the form control attributes, no need)
|
|
self._reset()
|
|
|
|
# Unregister, so we don't try and free it again at closing-time.
|
|
_idaapi.py_unregister_compiled_form(self)
|
|
|
|
|
|
def _reset(self):
|
|
"""
|
|
Resets the Form class state variables
|
|
"""
|
|
self.__controls = {}
|
|
self.__ctrl_id = 1
|
|
|
|
|
|
def __getitem__(self, name):
|
|
"""Returns a control object by name"""
|
|
return self.__controls[name]
|
|
|
|
|
|
def Add(self, name, ctrl, mkattr = True):
|
|
"""
|
|
Low level function. Prefer AddControls() to this function.
|
|
This function adds one control to the form.
|
|
|
|
@param name: Control name
|
|
@param ctrl: Control object
|
|
@param mkattr: Create control name / control object as a form attribute
|
|
"""
|
|
# Assign a unique ID
|
|
ctrl.id = self.__ctrl_id
|
|
self.__ctrl_id += 1
|
|
|
|
# Create attribute with control name
|
|
if mkattr:
|
|
setattr(self, name, ctrl)
|
|
|
|
# Remember the control
|
|
self.__controls[name] = ctrl
|
|
|
|
# Link the form to the control via its form attribute
|
|
ctrl.form = self
|
|
|
|
# Is it a group? Add each child
|
|
if isinstance(ctrl, Form.GroupControl):
|
|
self._AddGroup(ctrl, mkattr)
|
|
|
|
|
|
def FindControlById(self, id):
|
|
"""
|
|
Finds a control instance given its id
|
|
"""
|
|
for ctrl in self.__controls.values():
|
|
if ctrl.id == id:
|
|
return ctrl
|
|
return None
|
|
|
|
|
|
@staticmethod
|
|
def _ParseFormTitle(form):
|
|
"""
|
|
Parses the form's title from the form text
|
|
"""
|
|
help_state = 0
|
|
for i, line in enumerate(form.split("\n")):
|
|
if line.startswith("STARTITEM ") or line.startswith("BUTTON "):
|
|
continue
|
|
# Skip "HELP" and remember state
|
|
elif help_state == 0 and line == "HELP":
|
|
help_state = 1 # Mark inside HELP
|
|
continue
|
|
elif help_state == 1 and line == "ENDHELP":
|
|
help_state = 2 # Mark end of HELP
|
|
continue
|
|
return line.strip()
|
|
|
|
return None
|
|
|
|
|
|
def _AddGroup(self, Group, mkattr=True):
|
|
"""
|
|
Internal function.
|
|
This function expands the group item names and creates individual group item controls
|
|
|
|
@param Group: The group class (checkbox or radio group class)
|
|
"""
|
|
|
|
# Create group item controls for each child
|
|
for child_name in Group.children_names:
|
|
self.Add(
|
|
child_name,
|
|
# Use the class factory
|
|
Group.ItemClass(Group.tag, Group),
|
|
mkattr)
|
|
|
|
|
|
def AddControls(self, controls, mkattr=True):
|
|
"""
|
|
Adds controls from a dictionary.
|
|
The dictionary key is the control name and the value is a Form.Control object
|
|
@param controls: The control dictionary
|
|
"""
|
|
for name, ctrl in controls.items():
|
|
# Add the control
|
|
self.Add(name, ctrl, mkattr)
|
|
|
|
|
|
def CompileEx(self, form):
|
|
"""
|
|
Low level function.
|
|
Compiles (parses the form syntax and adds the control) the form string and
|
|
returns the argument list to be passed the argument list to AskUsingForm().
|
|
|
|
The form controls are wrapped inside curly braces: {ControlName}.
|
|
|
|
A special operator can be used to return the ID of a given control by its name: {id:ControlName}.
|
|
This is useful when you use the STARTITEM form keyword to set the initially focused control.
|
|
|
|
@param form: Compiles the form and returns the arguments needed to be passed to AskUsingForm()
|
|
"""
|
|
# First argument is the form string
|
|
args = [None]
|
|
|
|
# Second argument, if form is not modal, is the set of flags
|
|
if not self.modal:
|
|
args.append(self.openform_flags | 0x80) # Add FORM_QWIDGET
|
|
|
|
ctrlcnt = 1
|
|
|
|
# Reset all group control internal flags
|
|
for ctrl in self.__controls.values():
|
|
if isinstance(ctrl, Form.GroupControl):
|
|
ctrl._reset()
|
|
|
|
p = 0
|
|
while True:
|
|
i1 = form.find("{", p)
|
|
# No more items?
|
|
if i1 == -1:
|
|
break
|
|
|
|
# Check if escaped
|
|
if (i1 != 0) and form[i1-1] == "\\":
|
|
# Remove escape sequence and restart search
|
|
form = form[:i1-1] + form[i1:]
|
|
|
|
# Skip current marker
|
|
p = i1
|
|
|
|
# Continue search
|
|
continue
|
|
|
|
i2 = form.find("}", i1)
|
|
if i2 == -1:
|
|
raise SyntaxError("No matching closing brace '}'")
|
|
|
|
# Parse control name
|
|
ctrlname = form[i1+1:i2]
|
|
if not ctrlname:
|
|
raise ValueError("Control %d has an invalid name!" % ctrlcnt)
|
|
|
|
# Is it the IDOF operator?
|
|
if ctrlname.startswith("id:"):
|
|
idfunc = True
|
|
# Take actual ctrlname
|
|
ctrlname = ctrlname[3:]
|
|
else:
|
|
idfunc = False
|
|
|
|
# Find the control
|
|
ctrl = self.__controls.get(ctrlname, None)
|
|
if ctrl is None:
|
|
raise ValueError("No matching control '%s'" % ctrlname)
|
|
|
|
# Replace control name by tag
|
|
if idfunc:
|
|
tag = str(ctrl.id)
|
|
else:
|
|
tag = ctrl.get_tag()
|
|
taglen = len(tag)
|
|
form = form[:i1] + tag + form[i2+1:]
|
|
|
|
# Set new position
|
|
p = i1 + taglen
|
|
|
|
# Was it an IDOF() ? No need to push parameters
|
|
# Just ID substitution is fine
|
|
if idfunc:
|
|
continue
|
|
|
|
|
|
# For GroupItem controls, there are no individual arguments
|
|
# The argument is assigned for the group itself
|
|
if isinstance(ctrl, Form.GroupItemControl):
|
|
# GroupItem controls will have their position dynamically set
|
|
ctrl.assign_pos()
|
|
else:
|
|
# Push argument(s)
|
|
# (Some controls need more than one argument)
|
|
arg = ctrl.get_arg()
|
|
if isinstance(arg, (types.ListType, types.TupleType)):
|
|
# Push all args
|
|
args.extend(arg)
|
|
else:
|
|
# Push one arg
|
|
args.append(arg)
|
|
|
|
ctrlcnt += 1
|
|
|
|
# If no FormChangeCb instance was passed, and thus there's no '%/'
|
|
# in the resulting form string, let's provide a minimal one, so that
|
|
# we will retrieve 'p_fa', and thus actions that rely on it will work.
|
|
if form.find(Form.FT_FORMCHG) < 0:
|
|
form = form + Form.FT_FORMCHG
|
|
fccb = Form.FormChangeCb(lambda *args: 1)
|
|
self.Add("___dummyfchgcb", fccb)
|
|
# Regardless of the actual position of '%/' in the form
|
|
# string, a formchange callback _must_ be right after
|
|
# the form string.
|
|
if self.modal:
|
|
inspos = 1
|
|
else:
|
|
inspos = 2
|
|
args.insert(inspos, fccb.get_arg())
|
|
|
|
# Patch in the final form string
|
|
args[0] = form
|
|
|
|
self.title = self._ParseFormTitle(form)
|
|
return args
|
|
|
|
|
|
def Compile(self):
|
|
"""
|
|
Compiles a form and returns the form object (self) and the argument list.
|
|
The form object will contain object names corresponding to the form elements
|
|
|
|
@return: It will raise an exception on failure. Otherwise the return value is ignored
|
|
"""
|
|
|
|
# Reset controls
|
|
self._reset()
|
|
|
|
# Insert controls
|
|
self.AddControls(self.controls)
|
|
|
|
# Compile form and get args
|
|
self.__args = self.CompileEx(self.form)
|
|
|
|
# Register this form, to make sure it will be freed at closing-time.
|
|
_idaapi.py_register_compiled_form(self)
|
|
|
|
return (self, self.__args)
|
|
|
|
|
|
def Compiled(self):
|
|
"""
|
|
Checks if the form has already been compiled
|
|
|
|
@return: Boolean
|
|
"""
|
|
return self.__args is not None
|
|
|
|
|
|
def _ChkCompiled(self):
|
|
if not self.Compiled():
|
|
raise SyntaxError("Form is not compiled")
|
|
|
|
|
|
def Execute(self):
|
|
"""
|
|
Displays a modal dialog containing the compiled form.
|
|
@return: 1 - ok ; 0 - cancel
|
|
"""
|
|
self._ChkCompiled()
|
|
if not self.modal:
|
|
raise SyntaxError("Form is not modal. Open() should be instead")
|
|
|
|
return AskUsingForm(*self.__args)
|
|
|
|
|
|
def Open(self):
|
|
"""
|
|
Opens a widget containing the compiled form.
|
|
"""
|
|
self._ChkCompiled()
|
|
if self.modal:
|
|
raise SyntaxError("Form is modal. Execute() should be instead")
|
|
|
|
OpenForm(*self.__args)
|
|
|
|
|
|
def EnableField(self, ctrl, enable):
|
|
"""
|
|
Enable or disable an input field
|
|
@return: False - no such control
|
|
"""
|
|
return _idaapi.formchgcbfa_enable_field(self.p_fa, ctrl.id, enable)
|
|
|
|
|
|
def ShowField(self, ctrl, show):
|
|
"""
|
|
Show or hide an input field
|
|
@return: False - no such control
|
|
"""
|
|
return _idaapi.formchgcbfa_show_field(self.p_fa, ctrl.id, show)
|
|
|
|
|
|
def MoveField(self, ctrl, x, y, w, h):
|
|
"""
|
|
Move/resize an input field
|
|
|
|
@return: False - no such fiel
|
|
"""
|
|
return _idaapi.formchgcbfa_move_field(self.p_fa, ctrl.id, x, y, w, h)
|
|
|
|
|
|
def GetFocusedField(self):
|
|
"""
|
|
Get currently focused input field.
|
|
@return: None if no field is selected otherwise the control ID
|
|
"""
|
|
id = _idaapi.formchgcbfa_get_focused_field(self.p_fa)
|
|
return self.FindControlById(id)
|
|
|
|
|
|
def SetFocusedField(self, ctrl):
|
|
"""
|
|
Set currently focused input field
|
|
@return: False - no such control
|
|
"""
|
|
return _idaapi.formchgcbfa_set_focused_field(self.p_fa, ctrl.id)
|
|
|
|
|
|
def RefreshField(self, ctrl):
|
|
"""
|
|
Refresh a field
|
|
@return: False - no such control
|
|
"""
|
|
return _idaapi.formchgcbfa_refresh_field(self.p_fa, ctrl.id)
|
|
|
|
|
|
def Close(self, close_normally):
|
|
"""
|
|
Close the form
|
|
@param close_normally:
|
|
1: form is closed normally as if the user pressed Enter
|
|
0: form is closed abnormally as if the user pressed Esc
|
|
@return: None
|
|
"""
|
|
return _idaapi.formchgcbfa_close(self.p_fa, close_normally)
|
|
|
|
|
|
def GetControlValue(self, ctrl):
|
|
"""
|
|
Returns the control's value depending on its type
|
|
@param ctrl: Form control instance
|
|
@return:
|
|
- color button, radio controls: integer
|
|
- file/dir input, string input and string label: string
|
|
- embedded chooser control (0-based indices of selected items): integer list
|
|
- for multilinetext control: textctrl_info_t
|
|
- dropdown list controls: string (when editable) or index (when readonly)
|
|
- None: on failure
|
|
"""
|
|
tid, sz = self.ControlToFieldTypeIdAndSize(ctrl)
|
|
r = _idaapi.formchgcbfa_get_field_value(
|
|
self.p_fa,
|
|
ctrl.id,
|
|
tid,
|
|
sz)
|
|
# Multilinetext? Unpack the tuple into a new textctrl_info_t instance
|
|
if r is not None and tid == 7:
|
|
return textctrl_info_t(text=r[0], flags=r[1], tabsize=r[2])
|
|
else:
|
|
return r
|
|
|
|
|
|
def SetControlValue(self, ctrl, value):
|
|
"""
|
|
Set the control's value depending on its type
|
|
@param ctrl: Form control instance
|
|
@param value:
|
|
- embedded chooser: a 0-base indices list to select embedded chooser items
|
|
- multilinetext: a textctrl_info_t
|
|
- dropdown list: an integer designating the selection index if readonly
|
|
a string designating the edit control value if not readonly
|
|
@return: Boolean true on success
|
|
"""
|
|
tid, _ = self.ControlToFieldTypeIdAndSize(ctrl)
|
|
return _idaapi.formchgcbfa_set_field_value(
|
|
self.p_fa,
|
|
ctrl.id,
|
|
tid,
|
|
value)
|
|
|
|
|
|
@staticmethod
|
|
def ControlToFieldTypeIdAndSize(ctrl):
|
|
"""
|
|
Converts a control object to a tuple containing the field id
|
|
and the associated buffer size
|
|
"""
|
|
# Input control depend on the associated buffer size (supplied by the user)
|
|
|
|
# Make sure you check instances types taking into account inheritance
|
|
if isinstance(ctrl, Form.DropdownListControl):
|
|
return (8, 1 if ctrl.readonly else 0)
|
|
elif isinstance(ctrl, Form.MultiLineTextControl):
|
|
return (7, 0)
|
|
elif isinstance(ctrl, Form.EmbeddedChooserControl):
|
|
return (5, 0)
|
|
# Group items or controls
|
|
elif isinstance(ctrl, (Form.GroupItemControl, Form.GroupControl)):
|
|
return (2, 0)
|
|
elif isinstance(ctrl, Form.StringLabel):
|
|
return (3, min(_idaapi.MAXSTR, ctrl.size))
|
|
elif isinstance(ctrl, Form.ColorInput):
|
|
return (4, 0)
|
|
elif isinstance(ctrl, Form.NumericInput):
|
|
# Pass the numeric control type
|
|
return (6, ord(ctrl.tp[0]))
|
|
elif isinstance(ctrl, Form.InputControl):
|
|
return (1, ctrl.size)
|
|
else:
|
|
raise NotImplementedError, "Not yet implemented"
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Instantiate AskUsingForm function pointer
|
|
try:
|
|
import ctypes
|
|
# Setup the numeric argument size
|
|
Form.NumericArgument.DefI64 = _idaapi.BADADDR == 0xFFFFFFFFFFFFFFFFL
|
|
AskUsingForm__ = ctypes.CFUNCTYPE(ctypes.c_long)(_idaapi.py_get_AskUsingForm())
|
|
OpenForm__ = ctypes.CFUNCTYPE(ctypes.c_long)(_idaapi.py_get_OpenForm())
|
|
except:
|
|
def AskUsingForm__(*args):
|
|
warning("AskUsingForm() needs ctypes library in order to work")
|
|
return 0
|
|
def OpenForm__(*args):
|
|
warning("OpenForm() needs ctypes library in order to work")
|
|
|
|
|
|
def AskUsingForm(*args):
|
|
"""
|
|
Calls AskUsingForm()
|
|
@param: Compiled Arguments obtain through the Form.Compile() function
|
|
@return: 1 = ok, 0 = cancel
|
|
"""
|
|
old = set_script_timeout(0)
|
|
r = AskUsingForm__(*args)
|
|
set_script_timeout(old)
|
|
return r
|
|
|
|
def OpenForm(*args):
|
|
"""
|
|
Calls OpenForm()
|
|
@param: Compiled Arguments obtain through the Form.Compile() function
|
|
"""
|
|
old = set_script_timeout(0)
|
|
r = OpenForm__(*args)
|
|
set_script_timeout(old)
|
|
|
|
|
|
#</pycode(py_kernwin)>
|
|
|
|
#<pycode(py_plgform)>
|
|
class PluginForm(object):
|
|
"""
|
|
PluginForm class.
|
|
|
|
This form can be used to host additional controls. Please check the PyQt example.
|
|
"""
|
|
|
|
FORM_MDI = 0x01
|
|
"""start by default as MDI (obsolete)"""
|
|
FORM_TAB = 0x02
|
|
"""attached by default to a tab"""
|
|
FORM_RESTORE = 0x04
|
|
"""restore state from desktop config"""
|
|
FORM_ONTOP = 0x08
|
|
"""form should be "ontop"""
|
|
FORM_MENU = 0x10
|
|
"""form must be listed in the windows menu (automatically set for all plugins)"""
|
|
FORM_CENTERED = 0x20
|
|
"""form will be centered on the screen"""
|
|
FORM_PERSIST = 0x40
|
|
"""form will persist until explicitly closed with Close()"""
|
|
|
|
|
|
def __init__(self):
|
|
"""
|
|
"""
|
|
self.__clink__ = _idaapi.plgform_new()
|
|
|
|
|
|
|
|
def Show(self, caption, options = 0):
|
|
"""
|
|
Creates the form if not was not created or brings to front if it was already created
|
|
|
|
@param caption: The form caption
|
|
@param options: One of PluginForm.FORM_ constants
|
|
"""
|
|
options |= PluginForm.FORM_TAB|PluginForm.FORM_MENU|PluginForm.FORM_RESTORE
|
|
return _idaapi.plgform_show(self.__clink__, self, caption, options)
|
|
|
|
|
|
@staticmethod
|
|
def FormToPyQtWidget(form, ctx = sys.modules['__main__']):
|
|
"""
|
|
Use this method to convert a TForm* to a QWidget to be used by PyQt
|
|
|
|
@param ctx: Context. Reference to a module that already imported SIP and QtGui modules
|
|
"""
|
|
return ctx.sip.wrapinstance(ctx.sip.voidptr(form).__int__(), ctx.QtGui.QWidget)
|
|
|
|
|
|
@staticmethod
|
|
def FormToPySideWidget(form, ctx = sys.modules['__main__']):
|
|
"""
|
|
Use this method to convert a TForm* to a QWidget to be used by PySide
|
|
|
|
@param ctx: Context. Reference to a module that already imported QtGui module
|
|
"""
|
|
if form is None:
|
|
return None
|
|
if type(form).__name__ == "SwigPyObject":
|
|
# Since 'form' is a SwigPyObject, we first need to convert it to a PyCObject.
|
|
# However, there's no easy way of doing it, so we'll use a rather brutal approach:
|
|
# converting the SwigPyObject to a 'long' (will go through 'SwigPyObject_long',
|
|
# that will return the pointer's value as a long), and then convert that value
|
|
# back to a pointer into a PyCObject.
|
|
ptr_l = long(form)
|
|
from ctypes import pythonapi, c_void_p, py_object
|
|
pythonapi.PyCObject_FromVoidPtr.restype = py_object
|
|
pythonapi.PyCObject_AsVoidPtr.argtypes = [c_void_p, c_void_p]
|
|
form = pythonapi.PyCObject_FromVoidPtr(ptr_l, 0)
|
|
return ctx.QtGui.QWidget.FromCObject(form)
|
|
|
|
|
|
def OnCreate(self, form):
|
|
"""
|
|
This event is called when the plugin form is created.
|
|
The programmer should populate the form when this event is triggered.
|
|
|
|
@return: None
|
|
"""
|
|
pass
|
|
|
|
|
|
def OnClose(self, form):
|
|
"""
|
|
Called when the plugin form is closed
|
|
|
|
@return: None
|
|
"""
|
|
pass
|
|
|
|
|
|
def Close(self, options):
|
|
"""
|
|
Closes the form.
|
|
|
|
@param options: Close options (FORM_SAVE, FORM_NO_CONTEXT, ...)
|
|
|
|
@return: None
|
|
"""
|
|
return _idaapi.plgform_close(self.__clink__, options)
|
|
|
|
FORM_SAVE = 0x1
|
|
"""Save state in desktop config"""
|
|
|
|
FORM_NO_CONTEXT = 0x2
|
|
"""Don't change the current context (useful for toolbars)"""
|
|
|
|
FORM_DONT_SAVE_SIZE = 0x4
|
|
"""Don't save size of the window"""
|
|
|
|
FORM_CLOSE_LATER = 0x8
|
|
"""This flag should be used when Close() is called from an event handler"""
|
|
#</pycode(py_plgform)>
|
|
|
|
class Choose:
|
|
"""
|
|
Choose - class for choose() with callbacks
|
|
"""
|
|
def __init__(self, list, title, flags=0, deflt=1, icon=37):
|
|
self.list = list
|
|
self.title = title
|
|
|
|
self.flags = flags
|
|
self.x0 = -1
|
|
self.x1 = -1
|
|
self.y0 = -1
|
|
self.y1 = -1
|
|
|
|
self.width = -1
|
|
self.deflt = deflt
|
|
self.icon = icon
|
|
|
|
# HACK: Add a circular reference for non-modal choosers. This prevents the GC
|
|
# from collecting the class object the callbacks need. Unfortunately this means
|
|
# that the class will never be collected, unless refhack is set to None explicitly.
|
|
if (flags & Choose2.CH_MODAL) == 0:
|
|
self.refhack = self
|
|
|
|
def sizer(self):
|
|
"""
|
|
Callback: sizer - returns the length of the list
|
|
"""
|
|
return len(self.list)
|
|
|
|
def getl(self, n):
|
|
"""
|
|
Callback: getl - get one item from the list
|
|
"""
|
|
if n == 0:
|
|
return self.title
|
|
if n <= self.sizer():
|
|
return str(self.list[n-1])
|
|
else:
|
|
return "<Empty>"
|
|
|
|
|
|
def ins(self):
|
|
pass
|
|
|
|
|
|
def update(self, n):
|
|
pass
|
|
|
|
|
|
def edit(self, n):
|
|
pass
|
|
|
|
|
|
def enter(self, n):
|
|
print "enter(%d) called" % n
|
|
|
|
|
|
def destroy(self):
|
|
pass
|
|
|
|
|
|
def get_icon(self, n):
|
|
pass
|
|
|
|
|
|
def choose(self):
|
|
"""
|
|
choose - Display the choose dialogue
|
|
"""
|
|
old = set_script_timeout(0)
|
|
n = _idaapi.choose_choose(
|
|
self,
|
|
self.flags,
|
|
self.x0,
|
|
self.y0,
|
|
self.x1,
|
|
self.y1,
|
|
self.width,
|
|
self.deflt,
|
|
self.icon)
|
|
set_script_timeout(old)
|
|
return n
|
|
%}
|
|
|
|
%pythoncode %{
|
|
#<pycode(py_cli)>
|
|
class cli_t(pyidc_opaque_object_t):
|
|
"""
|
|
cli_t wrapper class.
|
|
|
|
This class allows you to implement your own command line interface handlers.
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.__cli_idx = -1
|
|
self.__clink__ = None
|
|
|
|
|
|
def register(self, flags = 0, sname = None, lname = None, hint = None):
|
|
"""
|
|
Registers the CLI.
|
|
|
|
@param flags: Feature bits. No bits are defined yet, must be 0
|
|
@param sname: Short name (displayed on the button)
|
|
@param lname: Long name (displayed in the menu)
|
|
@param hint: Hint for the input line
|
|
|
|
@return Boolean: True-Success, False-Failed
|
|
"""
|
|
|
|
# Already registered?
|
|
if self.__cli_idx >= 0:
|
|
return True
|
|
|
|
if sname is not None: self.sname = sname
|
|
if lname is not None: self.lname = lname
|
|
if hint is not None: self.hint = hint
|
|
|
|
# Register
|
|
self.__cli_idx = _idaapi.install_command_interpreter(self)
|
|
return False if self.__cli_idx < 0 else True
|
|
|
|
|
|
def unregister(self):
|
|
"""
|
|
Unregisters the CLI (if it was registered)
|
|
"""
|
|
if self.__cli_idx < 0:
|
|
return False
|
|
|
|
_idaapi.remove_command_interpreter(self.__cli_idx)
|
|
self.__cli_idx = -1
|
|
return True
|
|
|
|
|
|
def __del__(self):
|
|
self.unregister()
|
|
|
|
#
|
|
# Implement these methods in the subclass:
|
|
#
|
|
#<pydoc>
|
|
# def OnExecuteLine(self, line):
|
|
# """
|
|
# The user pressed Enter. The CLI is free to execute the line immediately or ask for more lines.
|
|
#
|
|
# This callback is mandatory.
|
|
#
|
|
# @param line: typed line(s)
|
|
# @return Boolean: True-executed line, False-ask for more lines
|
|
# """
|
|
# return True
|
|
#
|
|
# def OnKeydown(self, line, x, sellen, vkey, shift):
|
|
# """
|
|
# A keyboard key has been pressed
|
|
# This is a generic callback and the CLI is free to do whatever it wants.
|
|
#
|
|
# This callback is optional.
|
|
#
|
|
# @param line: current input line
|
|
# @param x: current x coordinate of the cursor
|
|
# @param sellen: current selection length (usually 0)
|
|
# @param vkey: virtual key code. if the key has been handled, it should be returned as zero
|
|
# @param shift: shift state
|
|
#
|
|
# @return:
|
|
# None - Nothing was changed
|
|
# tuple(line, x, sellen, vkey): if either of the input line or the x coordinate or the selection length has been modified.
|
|
# It is possible to return a tuple with None elements to preserve old values. Example: tuple(new_line, None, None, None) or tuple(new_line)
|
|
# """
|
|
# return None
|
|
#
|
|
# def OnCompleteLine(self, prefix, n, line, prefix_start):
|
|
# """
|
|
# The user pressed Tab. Find a completion number N for prefix PREFIX
|
|
#
|
|
# This callback is optional.
|
|
#
|
|
# @param prefix: Line prefix at prefix_start (string)
|
|
# @param n: completion number (int)
|
|
# @param line: the current line (string)
|
|
# @param prefix_start: the index where PREFIX starts in LINE (int)
|
|
#
|
|
# @return: None if no completion could be generated otherwise a String with the completion suggestion
|
|
# """
|
|
# return None
|
|
#</pydoc>
|
|
|
|
#</pycode(py_cli)>
|
|
#<pycode(py_custviewer)>
|
|
class simplecustviewer_t(object):
|
|
"""The base class for implementing simple custom viewers"""
|
|
def __init__(self):
|
|
self.__this = None
|
|
|
|
def __del__(self):
|
|
"""Destructor. It also frees the associated C++ object"""
|
|
try:
|
|
_idaapi.pyscv_delete(self.__this)
|
|
except:
|
|
pass
|
|
|
|
@staticmethod
|
|
def __make_sl_arg(line, fgcolor=None, bgcolor=None):
|
|
return line if (fgcolor is None and bgcolor is None) else (line, fgcolor, bgcolor)
|
|
|
|
def Create(self, title):
|
|
"""
|
|
Creates the custom view. This should be the first method called after instantiation
|
|
|
|
@param title: The title of the view
|
|
@return: Boolean whether it succeeds or fails. It may fail if a window with the same title is already open.
|
|
In this case better close existing windows
|
|
"""
|
|
self.title = title
|
|
self.__this = _idaapi.pyscv_init(self, title)
|
|
return True if self.__this else False
|
|
|
|
def Close(self):
|
|
"""
|
|
Destroys the view.
|
|
One has to call Create() afterwards.
|
|
Show() can be called and it will call Create() internally.
|
|
@return: Boolean
|
|
"""
|
|
return _idaapi.pyscv_close(self.__this)
|
|
|
|
def Show(self):
|
|
"""
|
|
Shows an already created view. It the view was close, then it will call Create() for you
|
|
@return: Boolean
|
|
"""
|
|
return _idaapi.pyscv_show(self.__this)
|
|
|
|
def Refresh(self):
|
|
return _idaapi.pyscv_refresh(self.__this)
|
|
|
|
def RefreshCurrent(self):
|
|
"""Refreshes the current line only"""
|
|
return _idaapi.pyscv_refresh_current(self.__this)
|
|
|
|
def Count(self):
|
|
"""Returns the number of lines in the view"""
|
|
return _idaapi.pyscv_count(self.__this)
|
|
|
|
def GetSelection(self):
|
|
"""
|
|
Returns the selected area or None
|
|
@return:
|
|
- tuple(x1, y1, x2, y2)
|
|
- None if no selection
|
|
"""
|
|
return _idaapi.pyscv_get_selection(self.__this)
|
|
|
|
def ClearLines(self):
|
|
"""Clears all the lines"""
|
|
_idaapi.pyscv_clear_lines(self.__this)
|
|
|
|
def AddLine(self, line, fgcolor=None, bgcolor=None):
|
|
"""
|
|
Adds a colored line to the view
|
|
@return: Boolean
|
|
"""
|
|
return _idaapi.pyscv_add_line(self.__this, self.__make_sl_arg(line, fgcolor, bgcolor))
|
|
|
|
def InsertLine(self, lineno, line, fgcolor=None, bgcolor=None):
|
|
"""
|
|
Inserts a line in the given position
|
|
@return: Boolean
|
|
"""
|
|
return _idaapi.pyscv_insert_line(self.__this, lineno, self.__make_sl_arg(line, fgcolor, bgcolor))
|
|
|
|
def EditLine(self, lineno, line, fgcolor=None, bgcolor=None):
|
|
"""
|
|
Edits an existing line.
|
|
@return: Boolean
|
|
"""
|
|
return _idaapi.pyscv_edit_line(self.__this, lineno, self.__make_sl_arg(line, fgcolor, bgcolor))
|
|
|
|
def PatchLine(self, lineno, offs, value):
|
|
"""Patches an existing line character at the given offset. This is a low level function. You must know what you're doing"""
|
|
return _idaapi.pyscv_patch_line(self.__this, lineno, offs, value)
|
|
|
|
def DelLine(self, lineno):
|
|
"""
|
|
Deletes an existing line
|
|
@return: Boolean
|
|
"""
|
|
return _idaapi.pyscv_del_line(self.__this, lineno)
|
|
|
|
def GetLine(self, lineno):
|
|
"""
|
|
Returns a line
|
|
@param lineno: The line number
|
|
@return:
|
|
Returns a tuple (colored_line, fgcolor, bgcolor) or None
|
|
"""
|
|
return _idaapi.pyscv_get_line(self.__this, lineno)
|
|
|
|
def GetCurrentWord(self, mouse = 0):
|
|
"""
|
|
Returns the current word
|
|
@param mouse: Use mouse position or cursor position
|
|
@return: None if failed or a String containing the current word at mouse or cursor
|
|
"""
|
|
return _idaapi.pyscv_get_current_word(self.__this, mouse)
|
|
|
|
def GetCurrentLine(self, mouse = 0, notags = 0):
|
|
"""
|
|
Returns the current line.
|
|
@param mouse: Current line at mouse pos
|
|
@param notags: If True then tag_remove() will be called before returning the line
|
|
@return: Returns the current line (colored or uncolored) or None on failure
|
|
"""
|
|
return _idaapi.pyscv_get_current_line(self.__this, mouse, notags)
|
|
|
|
def GetPos(self, mouse = 0):
|
|
"""
|
|
Returns the current cursor or mouse position.
|
|
@param mouse: return mouse position
|
|
@return: Returns a tuple (lineno, x, y)
|
|
"""
|
|
return _idaapi.pyscv_get_pos(self.__this, mouse)
|
|
|
|
def GetLineNo(self, mouse = 0):
|
|
"""Calls GetPos() and returns the current line number or -1 on failure"""
|
|
r = self.GetPos(mouse)
|
|
return -1 if not r else r[0]
|
|
|
|
def Jump(self, lineno, x=0, y=0):
|
|
return _idaapi.pyscv_jumpto(self.__this, lineno, x, y)
|
|
|
|
def AddPopupMenu(self, title, hotkey=""):
|
|
"""
|
|
Adds a popup menu item
|
|
@param title: The name of the menu item
|
|
@param hotkey: Hotkey of the item or just empty
|
|
@return: Returns the
|
|
"""
|
|
return _idaapi.pyscv_add_popup_menu(self.__this, title, hotkey)
|
|
|
|
def ClearPopupMenu(self):
|
|
"""
|
|
Clears all previously installed popup menu items.
|
|
Use this function if you're generating menu items on the fly (in the OnPopup() callback),
|
|
and before adding new items
|
|
"""
|
|
_idaapi.pyscv_clear_popup_menu(self.__this)
|
|
|
|
def IsFocused(self):
|
|
"""Returns True if the current view is the focused view"""
|
|
return _idaapi.pyscv_is_focused(self.__this)
|
|
|
|
def GetTForm(self):
|
|
"""
|
|
Return the TForm hosting this view.
|
|
|
|
@return: The TForm that hosts this view, or None.
|
|
"""
|
|
return _idaapi.pyscv_get_tform(self.__this)
|
|
|
|
def GetTCustomControl(self):
|
|
"""
|
|
Return the TCustomControl underlying this view.
|
|
|
|
@return: The TCustomControl underlying this view, or None.
|
|
"""
|
|
return _idaapi.pyscv_get_tcustom_control(self.__this)
|
|
|
|
|
|
|
|
# Here are all the supported events
|
|
#<pydoc>
|
|
# def OnClick(self, shift):
|
|
# """
|
|
# User clicked in the view
|
|
# @param shift: Shift flag
|
|
# @return: Boolean. True if you handled the event
|
|
# """
|
|
# print "OnClick, shift=%d" % shift
|
|
# return True
|
|
#
|
|
# def OnDblClick(self, shift):
|
|
# """
|
|
# User dbl-clicked in the view
|
|
# @param shift: Shift flag
|
|
# @return: Boolean. True if you handled the event
|
|
# """
|
|
# print "OnDblClick, shift=%d" % shift
|
|
# return True
|
|
#
|
|
# def OnCursorPosChanged(self):
|
|
# """
|
|
# Cursor position changed.
|
|
# @return: Nothing
|
|
# """
|
|
# print "OnCurposChanged"
|
|
#
|
|
# def OnClose(self):
|
|
# """
|
|
# The view is closing. Use this event to cleanup.
|
|
# @return: Nothing
|
|
# """
|
|
# print "OnClose"
|
|
#
|
|
# def OnKeydown(self, vkey, shift):
|
|
# """
|
|
# User pressed a key
|
|
# @param vkey: Virtual key code
|
|
# @param shift: Shift flag
|
|
# @return: Boolean. True if you handled the event
|
|
# """
|
|
# print "OnKeydown, vk=%d shift=%d" % (vkey, shift)
|
|
# return False
|
|
#
|
|
# def OnPopup(self):
|
|
# """
|
|
# Context menu popup is about to be shown. Create items dynamically if you wish
|
|
# @return: Boolean. True if you handled the event
|
|
# """
|
|
# print "OnPopup"
|
|
#
|
|
# def OnHint(self, lineno):
|
|
# """
|
|
# Hint requested for the given line number.
|
|
# @param lineno: The line number (zero based)
|
|
# @return:
|
|
# - tuple(number of important lines, hint string)
|
|
# - None: if no hint available
|
|
# """
|
|
# return (1, "OnHint, line=%d" % lineno)
|
|
#
|
|
# def OnPopupMenu(self, menu_id):
|
|
# """
|
|
# A context (or popup) menu item was executed.
|
|
# @param menu_id: ID previously registered with add_popup_menu()
|
|
# @return: Boolean
|
|
# """
|
|
# print "OnPopupMenu, menu_id=" % menu_id
|
|
# return True
|
|
#</pydoc>
|
|
#</pycode(py_custviewer)>
|
|
%}
|