Files
2025-03-13 16:17:53 +01:00

295 lines
9.5 KiB
C++

#ifndef __PYWRAPS_CLI__
#define __PYWRAPS_CLI__
//<code(py_kernwin_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 *keydown)(
qstring *line,
int *p_x,
int *p_sellen,
int *vk_key,
int shift);
bool (idaapi *find_completions)(
qstrvec_t *out_completions,
qstrvec_t *out_hints,
qstrvec_t *out_docs,
int *out_match_start,
int *out_match_end,
const char *line,
int x);
};
// 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_find_completions##CBN(qstrvec_t *completions, qstrvec_t *hints, qstrvec_t *docs, int *out_start, int *out_end, const char *line, int x) \
{ \
return py_clis[CBN]->on_find_completions(completions, hints, docs, out_start, out_end, 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 && 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 && PyTuple_Check(result.o);
PyW_ShowCbErr(S_ON_KEYDOWN);
if ( ok )
{
Py_ssize_t sz = PyTuple_Size(result.o);
if ( sz > 0 )
{
borref_t _r(PyTuple_GetItem(result.o, 0));
if ( _r && PyUnicode_Check(_r.o) )
PyUnicode_as_qstring(line, _r.o);
}
#define GET_TUPLE_ENTRY(col, PyThingy, AsThingy, out) \
do \
{ \
if ( sz > col ) \
{ \
borref_t _r(PyTuple_GetItem(result.o, col)); \
if ( _r && PyThingy##_Check(_r.o) ) \
*out = PyThingy##_##AsThingy(_r.o); \
} \
} while ( false )
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 completions
// This callback is optional
bool on_find_completions(
qstrvec_t *out_completions,
qstrvec_t *out_hints,
qstrvec_t *out_docs,
int *out_match_start,
int *out_match_end,
const char *line,
int x)
{
PYW_GIL_GET;
newref_t py_res(
PyObject_CallMethod(
self,
(char *)S_ON_FIND_COMPLETIONS,
"si",
line,
x));
PyW_ShowCbErr(S_ON_FIND_COMPLETIONS);
if ( PyErr_Occurred() != nullptr )
return false;
return idapython_convert_cli_completions(
out_completions, out_hints, out_docs, out_match_start, out_match_end, py_res);
}
// 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] == nullptr )
break;
}
py_cli_t *py_cli = nullptr;
do
{
// No free slots?
if ( cli_idx >= MAX_PY_CLI )
break;
// Create a new instance
py_cli = new py_cli_t();
// 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 == nullptr )
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.keydown = PyObject_HasAttrString(py_obj, S_ON_KEYDOWN) ? py_cli_cbs[cli_idx].keydown : nullptr;
py_cli->cli.find_completions = PyObject_HasAttrString(py_obj, S_ON_FIND_COMPLETIONS) ? py_cli_cbs[cli_idx].find_completions : nullptr;
// 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] == nullptr )
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] = nullptr;
return;
}
};
py_cli_t *py_cli_t::py_clis[MAX_PY_CLI] = { nullptr };
#define DECL_PY_CLI_CB(CBN) { s_execute_line##CBN, s_keydown##CBN, s_find_completions##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_kernwin_cli)>
//--------------------------------------------------------------------------
//<inline(py_kernwin_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_kernwin_cli)>
//---------------------------------------------------------------------------
#endif // __PYWRAPS_CLI__