1
0
mirror of https://github.com/Tha14/toxic.git synced 2025-06-20 00:56:35 +02:00

Finalized and documented the Python scripting interface.

This commit is contained in:
jakob
2017-05-16 20:31:23 -04:00
parent 90210daca7
commit b3ed8bc35c
24 changed files with 649 additions and 23 deletions

View File

@ -1,7 +1,7 @@
/* api.c
*
*
* Copyright (C) 2017 Toxic All Rights Reserved.
* Copyright (C) 2017 Jakob Kreuze <jakob@memeware.net>
*
* This file is part of Toxic.
*
@ -20,6 +20,7 @@
*
*/
#include <dirent.h>
#include <stdint.h>
#include <tox/tox.h>
@ -27,7 +28,10 @@
#include "execute.h"
#include "friendlist.h"
#include "line_info.h"
#include "message_queue.h"
#include "misc_tools.h"
#include "python_api.h"
#include "settings.h"
#include "windows.h"
Tox *user_tox;
@ -35,12 +39,13 @@ static WINDOW *cur_window;
static ToxWindow *self_window;
extern FriendsList Friends;
extern struct user_settings *user_settings;
void api_display(const char * const msg)
{
if (msg == NULL)
return;
self_window = get_active_window();
line_info_add(self_window, NULL, NULL, NULL, SYS_MSG, 0, 0, msg);
}
@ -56,6 +61,7 @@ char *api_get_nick(void)
if (name == NULL)
return NULL;
tox_self_get_name(user_tox, name);
name[len] = '\0';
return (char *) name;
}
@ -74,12 +80,45 @@ char *api_get_status_message(void)
return (char *) status;
}
void api_send(const char *msg)
{
if (msg == NULL || self_window->chatwin->cqueue == NULL)
return;
char *name = api_get_nick();
char timefrmt[TIME_STR_SIZE];
get_time_str(timefrmt, sizeof(timefrmt));
self_window = get_active_window();
line_info_add(self_window, timefrmt, name, NULL, OUT_MSG, 0, 0, "%s", msg);
free(name);
cqueue_add(self_window->chatwin->cqueue, msg, strlen(msg), OUT_MSG,
self_window->chatwin->hst->line_end->id + 1);
}
void api_execute(const char *input, int mode)
{
self_window = get_active_window();
execute(cur_window, self_window, user_tox, input, mode);
}
/* TODO: Register command */
int do_plugin_command(int num_args, char (*args)[MAX_STR_SIZE])
{
return do_python_command(num_args, args);
}
int num_registered_handlers(void)
{
return python_num_registered_handlers();
}
int help_max_width(void)
{
return python_help_max_width();
}
void draw_handler_help(WINDOW *win)
{
python_draw_handler_help(win);
}
void cmd_run(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
@ -107,3 +146,30 @@ void cmd_run(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX
run_python(fp, argv[1]);
fclose(fp);
}
void invoke_autoruns(WINDOW *window, ToxWindow *self)
{
struct dirent *dir;
char abspath_buf[PATH_MAX + 1];
size_t path_len;
DIR *d = opendir(user_settings->autorun_path);
FILE *fp;
if (d == NULL)
return;
cur_window = window;
self_window = self;
while ((dir = readdir(d)) != NULL) {
path_len = strlen(dir->d_name);
if (!strcmp(dir->d_name + path_len - 3, ".py")) {
snprintf(abspath_buf, PATH_MAX + 1, "%s%s", user_settings->autorun_path, dir->d_name);
fp = fopen(abspath_buf, "r");
if (fp == NULL)
continue;
run_python(fp, abspath_buf);
fclose(fp);
}
}
closedir(d);
}

View File

@ -1,7 +1,7 @@
/* api.h
*
*
* Copyright (C) 2014 Toxic All Rights Reserved.
* Copyright (C) 2017 Jakob Kreuze <jakob@memeware.net>
*
* This file is part of Toxic.
*
@ -31,6 +31,12 @@ FriendsList api_get_friendslist(void);
char *api_get_nick(void);
TOX_USER_STATUS api_get_status(void);
char *api_get_status_message(void);
void api_send(const char *msg);
void api_execute(const char *input, int mode);
int do_plugin_command(int num_args, char (*args)[MAX_STR_SIZE]);
int num_registered_handlers(void);
int help_max_width(void);
void draw_handler_help(WINDOW *win);
void invoke_autoruns(WINDOW *w, ToxWindow *self);
#endif /* #define API_H */

View File

@ -33,6 +33,7 @@
#include "line_info.h"
#include "misc_tools.h"
#include "notify.h"
#include "api.h"
struct cmd_func {
const char *name;
@ -196,5 +197,10 @@ void execute(WINDOW *w, ToxWindow *self, Tox *m, const char *input, int mode)
if (do_command(w, self, m, num_args, global_commands, args) == 0)
return;
#ifdef PYTHON
if (do_plugin_command(num_args, args) == 0)
return;
#endif
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Invalid command.");
}

View File

@ -26,8 +26,13 @@
#include "toxic.h"
#include "help.h"
#include "misc_tools.h"
#include "api.h"
#ifdef PYTHON
#define HELP_MENU_HEIGHT 10
#else
#define HELP_MENU_HEIGHT 9
#endif /* PYTHON */
#define HELP_MENU_WIDTH 26
void help_init_menu(ToxWindow *self)
@ -95,6 +100,13 @@ static void help_draw_menu(ToxWindow *self)
wattroff(win, A_BOLD | COLOR_PAIR(BLUE));
wprintw(win, "oup commands\n");
#ifdef PYTHON
wattron(win, A_BOLD | COLOR_PAIR(BLUE));
wprintw(win, " p");
wattroff(win, A_BOLD | COLOR_PAIR(BLUE));
wprintw(win, "lugin commands\n");
#endif /* PYTHON */
wattron(win, A_BOLD | COLOR_PAIR(BLUE));
wprintw(win, " f");
wattroff(win, A_BOLD | COLOR_PAIR(BLUE));
@ -286,6 +298,26 @@ static void help_draw_group(ToxWindow *self)
wrefresh(win);
}
#ifdef PYTHON
static void help_draw_plugin(ToxWindow *self)
{
WINDOW *win = self->help->win;
wmove(win, 1, 1);
wattron(win, A_BOLD | COLOR_PAIR(RED));
wprintw(win, "Plugin commands:\n");
wattroff(win, A_BOLD | COLOR_PAIR(RED));
draw_handler_help(win);
help_draw_bottom_menu(win);
box(win, ACS_VLINE, ACS_HLINE);
wrefresh(win);
}
#endif
static void help_draw_contacts(ToxWindow *self)
{
WINDOW *win = self->help->win;
@ -347,6 +379,13 @@ void help_onKey(ToxWindow *self, wint_t key)
self->help->type = HELP_GROUP;
break;
#ifdef PYTHON
case 'p':
help_init_window(self, 4 + num_registered_handlers(), help_max_width());
self->help->type = HELP_PLUGIN;
break;
#endif
case 'f':
help_init_window(self, 10, 80);
self->help->type = HELP_CONTACTS;
@ -392,5 +431,11 @@ void help_onDraw(ToxWindow *self)
case HELP_GROUP:
help_draw_group(self);
break;
#ifdef PYTHON
case HELP_PLUGIN:
help_draw_plugin(self);
break;
#endif /* PYTHON */
}
}

View File

@ -33,6 +33,9 @@ typedef enum {
HELP_GROUP,
HELP_KEYS,
HELP_CONTACTS,
#ifdef PYTHON
HELP_PLUGIN,
#endif
} HELP_TYPES;
void help_onDraw(ToxWindow *self);

View File

@ -1,7 +1,7 @@
/* python_api.c
*
*
* Copyright (C) 2017 Toxic All Rights Reserved.
* Copyright (C) 2017 Jakob Kreuze <jakob@memeware.net>
*
* This file is part of Toxic.
*
@ -24,7 +24,14 @@
#include "api.h"
extern Tox *user_tox;
extern Tox *user_tox;
struct python_registered_func {
char *name;
char *help;
PyObject *callback;
struct python_registered_func *next;
} python_commands = {0};
static PyObject *python_api_display(PyObject *self, PyObject *args)
{
@ -74,6 +81,35 @@ static PyObject *python_api_get_status_message(PyObject *self, PyObject *args)
return ret;
}
static PyObject *python_api_get_all_friends(PyObject *self, PyObject *args)
{
size_t i, ii;
FriendsList friends;
PyObject *cur, *ret;
char pubkey_buf[TOX_PUBLIC_KEY_SIZE * 2 + 1];
if (!PyArg_ParseTuple(args, ""))
return NULL;
friends = api_get_friendslist();
ret = PyList_New(0);
for (i = 0; i < friends.num_friends; i++) {
for (ii = 0; ii < TOX_PUBLIC_KEY_SIZE; ii++)
snprintf(pubkey_buf + ii * 2, 3, "%02X", friends.list[i].pub_key[ii] & 0xff);
pubkey_buf[TOX_PUBLIC_KEY_SIZE * 2] = '\0';
cur = Py_BuildValue("(s,s)", friends.list[i].name, pubkey_buf);
PyList_Append(ret, cur);
}
return ret;
}
static PyObject *python_api_send(PyObject *self, PyObject *args)
{
const char *msg;
if (!PyArg_ParseTuple(args, "s", &msg))
return NULL;
api_send(msg);
return Py_None;
}
static PyObject *python_api_execute(PyObject *self, PyObject *args)
{
int mode;
@ -84,19 +120,70 @@ static PyObject *python_api_execute(PyObject *self, PyObject *args)
return Py_None;
}
static PyObject *python_api_register(PyObject *self, PyObject *args)
{
struct python_registered_func *cur;
size_t command_len, help_len;
const char *command, *help;
PyObject *callback;
if (!PyArg_ParseTuple(args, "ssO:register_command", &command, &help, &callback))
return NULL;
if (!PyCallable_Check(callback)) {
PyErr_SetString(PyExc_TypeError, "Parameter must be callable");
return NULL;
}
if (command[0] != '/') {
PyErr_SetString(PyExc_TypeError, "Command must be prefixed with a '/'");
return NULL;
}
for (cur = &python_commands; ; cur = cur->next) {
if (cur->name != NULL && !strcmp(command, cur->name)) {
Py_XDECREF(cur->callback);
Py_XINCREF(callback);
cur->callback = callback;
break;
}
if (cur->next == NULL) {
Py_XINCREF(callback);
cur->next = malloc(sizeof(struct python_registered_func));
if (cur->next == NULL)
return PyErr_NoMemory();
command_len = strlen(command);
cur->next->name = malloc(command_len + 1);
if (cur->next->name == NULL)
return PyErr_NoMemory();
strncpy(cur->next->name, command, command_len + 1);
help_len = strlen(help);
cur->next->help = malloc(help_len + 1);
if (cur->next->help == NULL)
return PyErr_NoMemory();
strncpy(cur->next->help, help, help_len + 1);
cur->next->callback = callback;
cur->next->next = NULL;
break;
}
}
Py_INCREF(Py_None);
return Py_None;
}
static PyMethodDef ToxicApiMethods[] = {
{"display", python_api_display, METH_VARARGS, "Display a message to the primary prompt"},
{"get_nick", python_api_get_nick, METH_VARARGS, "Return the user's current nickname"},
{"display", python_api_display, METH_VARARGS, "Display a message to the current prompt"},
{"get_nick", python_api_get_nick, METH_VARARGS, "Return the user's current nickname"},
{"get_status", python_api_get_status, METH_VARARGS, "Returns the user's current status"},
{"get_status_message", python_api_get_status_message, METH_VARARGS, "Return the user's current status message"},
{"execute", python_api_execute, METH_VARARGS, "Execute a command like `/nick`"},
{NULL, NULL, 0, NULL},
{"get_all_friends", python_api_get_all_friends, METH_VARARGS, "Return all of the user's friends"},
{"send", python_api_send, METH_VARARGS, "Send the message to the current user"},
{"execute", python_api_execute, METH_VARARGS, "Execute a command like `/nick`"},
{"register", python_api_register, METH_VARARGS, "Register a command like `/nick` to a Python function"},
{NULL, NULL, 0, NULL},
};
static struct PyModuleDef toxic_api_module = {
PyModuleDef_HEAD_INIT,
"toxic_api",
NULL, /* TODO: Module documentation. */
-1, /* TODO: Assumption that no per-interpreter state is maintained. */
NULL,
-1,
ToxicApiMethods
};
@ -107,6 +194,15 @@ PyMODINIT_FUNC PyInit_toxic_api(void)
void terminate_python(void)
{
struct python_registered_func *cur, *old;
if (python_commands.name != NULL)
free(python_commands.name);
for (cur = python_commands.next; cur != NULL;) {
old = cur;
cur = cur->next;
free(old->name);
free(old);
}
Py_FinalizeEx();
}
@ -124,3 +220,59 @@ void run_python(FILE *fp, char *path)
{
PyRun_SimpleFile(fp, path);
}
int do_python_command(int num_args, char (*args)[MAX_STR_SIZE])
{
int i;
PyObject *callback_args, *args_strings;
struct python_registered_func *cur;
for (cur = &python_commands; cur != NULL; cur = cur->next) {
if (cur->name == NULL)
continue;
if (!strcmp(args[0], cur->name)) {
args_strings = PyList_New(0);
for (i = 1; i < num_args; i++)
PyList_Append(args_strings, Py_BuildValue("s", args[i]));
callback_args = PyTuple_Pack(1, args_strings);
if (PyObject_CallObject(cur->callback, callback_args) == NULL)
api_display("Exception raised in callback function");
return 0;
}
}
return 1;
}
int python_num_registered_handlers(void)
{
int n = 0;
struct python_registered_func *cur;
for (cur = &python_commands; cur != NULL; cur = cur->next) {
if (cur->name != NULL)
n++;
}
return n;
}
int python_help_max_width(void)
{
size_t tmp;
int max = 0;
struct python_registered_func *cur;
for (cur = &python_commands; cur != NULL; cur = cur->next) {
if (cur->name != NULL) {
tmp = strlen(cur->help);
max = tmp > max ? tmp : max;
}
}
max = max > 50 ? 50 : max;
return 37 + max;
}
void python_draw_handler_help(WINDOW *win)
{
struct python_registered_func *cur;
for (cur = &python_commands; cur != NULL; cur = cur->next) {
if (cur->name != NULL)
wprintw(win, " %-29s: %.50s\n", cur->name, cur->help);
}
}

View File

@ -1,7 +1,7 @@
/* python_api.h
*
*
* Copyright (C) 2017 Toxic All Rights Reserved.
* Copyright (C) 2017 Jakob Kreuze <jakob@memeware.net>
*
* This file is part of Toxic.
*
@ -29,5 +29,9 @@ PyMODINIT_FUNC PyInit_toxic_api(void);
void terminate_python(void);
void init_python(Tox *m);
void run_python(FILE *fp, char *path);
int do_python_command(int num_args, char (*args)[MAX_STR_SIZE]);
int python_num_registered_handlers(void);
int python_help_max_width(void);
void python_draw_handler_help(WINDOW *win);
#endif /* #define PYTHON_API_H */

View File

@ -179,12 +179,14 @@ static const struct tox_strings {
const char *download_path;
const char *chatlogs_path;
const char *avatar_path;
const char *autorun_path;
const char *password_eval;
} tox_strings = {
"tox",
"download_path",
"chatlogs_path",
"avatar_path",
"autorun_path",
"password_eval",
};
@ -418,6 +420,18 @@ int settings_load(struct user_settings *s, const char *patharg)
s->avatar_path[0] = '\0';
}
#ifdef PYTHON
if ( config_setting_lookup_string(setting, tox_strings.autorun_path, &str) ) {
snprintf(s->autorun_path, sizeof(s->autorun_path), "%s", str);
int len = strlen(str);
if (len >= sizeof(s->autorun_path) - 2)
s->autorun_path[0] = '\0';
else if (s->autorun_path[len - 1] != '/')
strcat(&s->autorun_path[len - 1], "/");
}
#endif
if ( config_setting_lookup_string(setting, tox_strings.password_eval, &str) ) {
snprintf(s->password_eval, sizeof(s->password_eval), "%s", str);
int len = strlen(str);

View File

@ -63,6 +63,7 @@ struct user_settings {
char download_path[PATH_MAX];
char chatlogs_path[PATH_MAX];
char avatar_path[PATH_MAX];
char autorun_path[PATH_MAX];
char password_eval[PASSWORD_EVAL_MAX];
int key_next_tab;

View File

@ -76,6 +76,7 @@ ToxAV *av;
#endif /* AUDIO */
#ifdef PYTHON
#include "api.h"
#include "python_api.h"
#endif
@ -1222,6 +1223,7 @@ int main(int argc, char **argv)
#ifdef PYTHON
init_python(m);
invoke_autoruns(prompt->chatwin->history, prompt);
#endif /* PYTHON */

View File

@ -584,6 +584,12 @@ ToxWindow *get_window_ptr(int i)
return toxwin;
}
/* returns a pointer to the currently open ToxWindow. */
ToxWindow *get_active_window(void)
{
return active_window;
}
void force_refresh(WINDOW *w)
{
wclear(w);

View File

@ -259,6 +259,7 @@ void kill_all_windows(Tox *m); /* should only be called on shutdown */
void on_window_resize(void);
void force_refresh(WINDOW *w);
ToxWindow *get_window_ptr(int i);
ToxWindow *get_active_window(void);
/* refresh inactive windows to prevent scrolling bugs.
call at least once per second */