mirror of
https://github.com/ZoiteChat/zoitechat.git
synced 2026-11-08 17:05:38 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
074417591a | ||
|
|
2355ffc5d0 |
No files matched your search
@@ -0,0 +1,24 @@
|
||||
# Optional plugin API additions (discussion #144)
|
||||
|
||||
Existing hooks, callbacks, return values, context lookup, strings and menu behaviour
|
||||
remain unchanged. The C plugin function table gains four fixed append-only slots;
|
||||
unused slots are NULL. Older binaries use the unchanged prefix. A plugin must check
|
||||
the documented get_info() feature key before accessing a new slot on an older host.
|
||||
New Python modules are installed by Meson and the existing Windows copy/installer
|
||||
pipeline. Only modules present in the installation are exported; imports of
|
||||
zoitechat, hexchat and xchat retain their compatibility aliases.
|
||||
|
||||
All APIs run on the client main thread unless explicitly documented otherwise.
|
||||
New reserved flags must be zero. These changes do not make old API calls thread-safe.
|
||||
|
||||
Nested hook dispatch defers deleted-hook reclamation until its outermost return,
|
||||
so new observers can safely emit events and unhook themselves or other hooks.
|
||||
|
||||
The optional asyncio module requires Python 3.7 or newer; older supported
|
||||
interpreters skip that module and retain the existing scripting API.
|
||||
|
||||
Python reload drops the script/API modules while retaining the embedding module
|
||||
so callback and lib.ph pointer types keep the same CFFI identity.
|
||||
|
||||
Server-hook insertion matches both raw and attrs hooks so documented priorities
|
||||
are honoured for mixed registrations, including new filtered attrs callbacks.
|
||||
@@ -0,0 +1,21 @@
|
||||
# Post-display print observers
|
||||
|
||||
Python: `hook_print_after(name, callback, userdata=None, priority=PRI_NORM)`.
|
||||
The callback is `(word, word_eol, userdata, attrs)`, matching hook_print_attrs().
|
||||
It runs after display_event() has handled the text, so prnt() follows the original
|
||||
text. Return values are ignored. Events eaten by a pre-hook or hidden by client
|
||||
preferences do not invoke these observers. This is post-display notification,
|
||||
not a notification that the entire incoming IRC command has finished updating state.
|
||||
|
||||
C: `zoitechat_hook_print_after(ph, name, priority, flags, callback, userdata)`;
|
||||
flags must be zero. Check `get_info("api_hook_print_after")` for "1" before
|
||||
accessing the appended Windows function-table slot. Existing pre-hook timing is
|
||||
unchanged. Unhook/unload use existing lifecycle functions. Recursive emission
|
||||
skips an already-running observer to prevent it immediately invoking itself.
|
||||
|
||||
```python
|
||||
def after(word, word_eol, userdata, attrs):
|
||||
zoitechat.prnt('This follows the original channel message')
|
||||
|
||||
zoitechat.hook_print_after('Channel Message', after)
|
||||
```
|
||||
@@ -383,3 +383,24 @@ def find_context(server=None, channel=None):
|
||||
return None
|
||||
|
||||
return Context(ctx)
|
||||
|
||||
|
||||
# Optional additive APIs live in separate modules so builds can select features.
|
||||
def __load_additive_features():
|
||||
import importlib
|
||||
import os
|
||||
|
||||
for suffix in ('fd', 'events', 'async', 'names', 'contexts',
|
||||
'post', 'filters', 'caps'):
|
||||
if suffix == 'async' and sys.version_info < (3, 7):
|
||||
continue
|
||||
name = '_zoitechat_' + suffix
|
||||
if not os.path.isfile(os.path.join(os.path.dirname(__file__), name + '.py')):
|
||||
continue
|
||||
module = importlib.import_module(name)
|
||||
for symbol in module.__all__:
|
||||
globals()[symbol] = getattr(module, symbol)
|
||||
__all__.append(symbol)
|
||||
|
||||
|
||||
__load_additive_features()
|
||||
@@ -0,0 +1,21 @@
|
||||
"""Opt-in post-display print observers."""
|
||||
import _zoitechat as api
|
||||
from _zoitechat_embedded import ffi, lib
|
||||
|
||||
__all__ = ['hook_print_after']
|
||||
|
||||
|
||||
def hook_print_after(name, callback, userdata=None, priority=api.PRI_NORM):
|
||||
"""callback(word, word_eol, userdata, attrs); its return value is ignored."""
|
||||
if not callable(callback):
|
||||
raise TypeError('callback must be callable')
|
||||
plugin = api.__get_current_plugin()
|
||||
hook = plugin.add_hook(callback, userdata)
|
||||
handle = lib.zoitechat_hook_print_after(lib.ph, name.encode(), priority, 0,
|
||||
lib._on_print_attrs_hook, hook.handle)
|
||||
if handle == ffi.NULL:
|
||||
hook.is_unload = True
|
||||
plugin.remove_hook(id(hook))
|
||||
raise RuntimeError('unable to install post-display hook')
|
||||
hook.zoitechat_hook = handle
|
||||
return id(hook)
|
||||
@@ -27,7 +27,11 @@ python3_source = custom_target('python-bindings',
|
||||
command: [find_program('generate_plugin.py'), '@INPUT@', '@OUTPUT@']
|
||||
)
|
||||
|
||||
install_data(['_zoitechat.py', 'zoitechat.py', 'xchat.py', 'hexchat.py'],
|
||||
# Include any selected additive API modules alongside the compatibility modules.
|
||||
python_api_files = run_command(python3, '-c',
|
||||
'import glob,sys; print("\\n".join(sorted(glob.glob(sys.argv[1] + "/_zoitechat*.py"))))',
|
||||
meson.current_source_dir(), check: true).stdout().strip().split('\n')
|
||||
install_data(python_api_files + ['zoitechat.py', 'xchat.py', 'hexchat.py'],
|
||||
install_dir: join_paths(get_option('libdir'), 'zoitechat/python')
|
||||
)
|
||||
|
||||
|
||||
@@ -590,7 +590,12 @@ def _on_plugin_deinit():
|
||||
sys.stderr = sys.__stderr__
|
||||
pydoc.help = pydoc.Helper()
|
||||
|
||||
for mod in ('_zoitechat', 'zoitechat', 'xchat', '_zoitechat_embedded'):
|
||||
# CFFI callbacks keep the embedding module's ffi/lib identity for the
|
||||
# process lifetime; deleting it creates incompatible types on reload.
|
||||
for mod in ('_zoitechat', 'zoitechat', 'hexchat', 'xchat',
|
||||
'_zoitechat_fd', '_zoitechat_events', '_zoitechat_async',
|
||||
'_zoitechat_names', '_zoitechat_contexts', '_zoitechat_post',
|
||||
'_zoitechat_filters', '_zoitechat_caps'):
|
||||
try:
|
||||
del sys.modules[mod]
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
<None Include="xchat.py" />
|
||||
<None Include="hexchat.py" />
|
||||
<None Include="_zoitechat.py" />
|
||||
<None Include="_zoitechat_*.py" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="$(IntDir)python.c" />
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
<None Include="xchat.py" />
|
||||
<None Include="hexchat.py" />
|
||||
<None Include="_zoitechat.py" />
|
||||
<None Include="_zoitechat_*.py" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="$(IntDir)python.c" />
|
||||
|
||||
+49
-3
@@ -143,7 +143,8 @@ enum
|
||||
HOOK_PRINT_ATTRS = 1 << 4, /* same as above, with attributes */
|
||||
HOOK_TIMER = 1 << 5, /* timeouts */
|
||||
HOOK_FD = 1 << 6, /* sockets & fds */
|
||||
HOOK_DELETED = 1 << 7 /* marked for deletion */
|
||||
HOOK_DELETED = 1 << 7, /* marked for deletion */
|
||||
HOOK_PRINT_AFTER = 1 << 8 /* post-display observers */
|
||||
};
|
||||
|
||||
enum
|
||||
@@ -176,6 +177,7 @@ enum
|
||||
|
||||
GSList *plugin_list = NULL; /* export for plugingui.c */
|
||||
static GSList *hook_list = NULL;
|
||||
static unsigned int plugin_dispatch_depth;
|
||||
|
||||
extern const struct prefs vars[]; /* cfgfiles.c */
|
||||
|
||||
@@ -243,7 +245,7 @@ plugin_list_add (zoitechat_context *ctx, char *filename, const char *name,
|
||||
{
|
||||
zoitechat_plugin *pl;
|
||||
|
||||
pl = g_new (zoitechat_plugin, 1);
|
||||
pl = g_new0 (zoitechat_plugin, 1);
|
||||
pl->handle = handle;
|
||||
pl->filename = filename;
|
||||
pl->context = ctx;
|
||||
@@ -350,6 +352,7 @@ plugin_add (session *sess, char *filename, void *handle, void *init_func,
|
||||
pl->zoitechat_emit_print_attrs = zoitechat_emit_print_attrs;
|
||||
pl->zoitechat_event_attrs_create = zoitechat_event_attrs_create;
|
||||
pl->zoitechat_event_attrs_free = zoitechat_event_attrs_free;
|
||||
pl->zoitechat_hook_print_after = zoitechat_hook_print_after;
|
||||
|
||||
/* run zoitechat_plugin_init, if it returns 0, close the plugin */
|
||||
if (((zoitechat_init_func *)init_func) (pl, &pl->name, &pl->desc, &pl->version, arg) == 0)
|
||||
@@ -610,6 +613,7 @@ plugin_hook_run (session *sess, char *name, char *word[], char *word_eol[],
|
||||
zoitechat_hook *hook;
|
||||
int ret, eat = 0;
|
||||
|
||||
plugin_dispatch_depth++;
|
||||
list = hook_list;
|
||||
while (1)
|
||||
{
|
||||
@@ -619,6 +623,13 @@ plugin_hook_run (session *sess, char *name, char *word[], char *word_eol[],
|
||||
|
||||
hook = list->data;
|
||||
next = list->next;
|
||||
if (type == HOOK_PRINT_AFTER && hook->tag)
|
||||
{
|
||||
list = next;
|
||||
continue;
|
||||
}
|
||||
if (type == HOOK_PRINT_AFTER)
|
||||
hook->tag = 1;
|
||||
hook->pl->context = sess;
|
||||
|
||||
/* run the plugin's callback function */
|
||||
@@ -627,6 +638,7 @@ plugin_hook_run (session *sess, char *name, char *word[], char *word_eol[],
|
||||
case HOOK_COMMAND:
|
||||
ret = ((zoitechat_cmd_cb *)hook->callback) (word, word_eol, hook->userdata);
|
||||
break;
|
||||
case HOOK_PRINT_AFTER:
|
||||
case HOOK_PRINT_ATTRS:
|
||||
ret = ((zoitechat_print_attrs_cb *)hook->callback) (word, attrs, hook->userdata);
|
||||
break;
|
||||
@@ -641,6 +653,15 @@ plugin_hook_run (session *sess, char *name, char *word[], char *word_eol[],
|
||||
break;
|
||||
}
|
||||
|
||||
if (type == HOOK_PRINT_AFTER)
|
||||
{
|
||||
hook->tag = 0;
|
||||
if (!is_session (sess))
|
||||
goto xit;
|
||||
list = next;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((ret & ZOITECHAT_EAT_ZOITECHAT) && (ret & ZOITECHAT_EAT_PLUGIN))
|
||||
{
|
||||
eat = 1;
|
||||
@@ -655,6 +676,10 @@ plugin_hook_run (session *sess, char *name, char *word[], char *word_eol[],
|
||||
}
|
||||
|
||||
xit:
|
||||
/* Nested callbacks can unhook entries still used by an outer dispatch.
|
||||
* Keep both hook objects and list links alive until the outermost return. */
|
||||
if (--plugin_dispatch_depth != 0)
|
||||
return eat;
|
||||
/* really remove deleted hooks now */
|
||||
list = hook_list;
|
||||
while (list)
|
||||
@@ -719,6 +744,15 @@ plugin_emit_print (session *sess, char *word[], time_t server_time)
|
||||
HOOK_PRINT | HOOK_PRINT_ATTRS);
|
||||
}
|
||||
|
||||
void
|
||||
plugin_emit_print_after (session *sess, char *word[], time_t server_time)
|
||||
{
|
||||
zoitechat_event_attrs attrs;
|
||||
attrs.server_time_utc = server_time;
|
||||
if (is_session (sess))
|
||||
plugin_hook_run (sess, word[0], word, NULL, &attrs, HOOK_PRINT_AFTER);
|
||||
}
|
||||
|
||||
int
|
||||
plugin_emit_dummy_print (session *sess, char *name)
|
||||
{
|
||||
@@ -806,7 +840,7 @@ plugin_insert_hook (zoitechat_hook *new_hook)
|
||||
break;
|
||||
case HOOK_SERVER:
|
||||
case HOOK_SERVER_ATTRS:
|
||||
new_hook_type = HOOK_SERVER | HOOK_PRINT_ATTRS;
|
||||
new_hook_type = HOOK_SERVER | HOOK_SERVER_ATTRS;
|
||||
break;
|
||||
default:
|
||||
new_hook_type = new_hook->type;
|
||||
@@ -1022,6 +1056,15 @@ zoitechat_unhook (zoitechat_plugin *ph, zoitechat_hook *hook)
|
||||
return hook->userdata;
|
||||
}
|
||||
|
||||
zoitechat_hook *
|
||||
zoitechat_hook_print_after (zoitechat_plugin *ph, const char *name, int pri,
|
||||
int flags, zoitechat_print_attrs_cb *callback, void *userdata)
|
||||
{
|
||||
if (flags != 0 || !name || !callback)
|
||||
return NULL;
|
||||
return plugin_add_hook (ph, HOOK_PRINT_AFTER, pri, name, NULL, callback, 0, userdata);
|
||||
}
|
||||
|
||||
zoitechat_hook *
|
||||
zoitechat_hook_command (zoitechat_plugin *ph, const char *name, int pri,
|
||||
zoitechat_cmd_cb *callb, const char *help_text, void *userdata)
|
||||
@@ -1181,6 +1224,9 @@ zoitechat_get_info (zoitechat_plugin *ph, const char *id)
|
||||
return text_find_format_string (e);
|
||||
}
|
||||
|
||||
if (strcmp (id, "api_hook_print_after") == 0)
|
||||
return "1";
|
||||
|
||||
hash = str_hash (id);
|
||||
/* do the session independant ones first */
|
||||
switch (hash)
|
||||
|
||||
@@ -150,6 +150,15 @@ struct _zoitechat_plugin
|
||||
void (*zoitechat_event_attrs_free) (zoitechat_plugin *ph,
|
||||
zoitechat_event_attrs *attrs);
|
||||
|
||||
/* Additive API slots: append only; old binary offsets are unchanged. */
|
||||
zoitechat_hook *(*zoitechat_hook_print_after) (zoitechat_plugin *, const char *, int,
|
||||
int, int (*)(char *[], zoitechat_event_attrs *, void *), void *);
|
||||
zoitechat_hook *(*zoitechat_hook_server_filtered) (zoitechat_plugin *, const char *, int,
|
||||
int, int, const char *, int (*)(char *[], char *[], zoitechat_event_attrs *, void *), void *);
|
||||
zoitechat_hook *(*zoitechat_hook_print_filtered) (zoitechat_plugin *, const char *, int,
|
||||
int, int, const char *, int (*)(char *[], zoitechat_event_attrs *, void *), void *);
|
||||
zoitechat_hook *(*zoitechat_register_capability) (zoitechat_plugin *, const char *, int, int, void *);
|
||||
|
||||
/* PRIVATE FIELDS! */
|
||||
void *handle; /* from dlopen */
|
||||
char *filename; /* loaded from */
|
||||
@@ -174,6 +183,7 @@ int plugin_emit_command (session *sess, char *name, char *word[], char *word_eol
|
||||
int plugin_emit_server (session *sess, char *name, char *word[], char *word_eol[],
|
||||
time_t server_time);
|
||||
int plugin_emit_print (session *sess, char *word[], time_t server_time);
|
||||
void plugin_emit_print_after (session *sess, char *word[], time_t server_time);
|
||||
int plugin_emit_dummy_print (session *sess, char *name);
|
||||
int plugin_emit_keypress (session *sess, unsigned int state, unsigned int keyval, gunichar key);
|
||||
GList* plugin_command_list(GList *tmp_list);
|
||||
|
||||
@@ -2320,6 +2320,7 @@ text_emit_with_message (int index, session *sess, char *a, char *b, char *c, cha
|
||||
if (!prefs.hex_away_omit_alerts || !sess->server->is_away)
|
||||
sound_play_event (index);
|
||||
display_event (sess, index, word, stripcolor_args, timestamp, message);
|
||||
plugin_emit_print_after (sess, word, timestamp);
|
||||
}
|
||||
|
||||
char *
|
||||
|
||||
@@ -193,10 +193,25 @@ struct _zoitechat_plugin
|
||||
zoitechat_event_attrs *(*zoitechat_event_attrs_create) (zoitechat_plugin *ph);
|
||||
void (*zoitechat_event_attrs_free) (zoitechat_plugin *ph,
|
||||
zoitechat_event_attrs *attrs);
|
||||
/* Additive API slots: append only; old binary offsets are unchanged. */
|
||||
zoitechat_hook *(*zoitechat_hook_print_after) (zoitechat_plugin *, const char *, int,
|
||||
int, int (*)(char *[], zoitechat_event_attrs *, void *), void *);
|
||||
zoitechat_hook *(*zoitechat_hook_server_filtered) (zoitechat_plugin *, const char *, int,
|
||||
int, int, const char *, int (*)(char *[], char *[], zoitechat_event_attrs *, void *), void *);
|
||||
zoitechat_hook *(*zoitechat_hook_print_filtered) (zoitechat_plugin *, const char *, int,
|
||||
int, int, const char *, int (*)(char *[], zoitechat_event_attrs *, void *), void *);
|
||||
zoitechat_hook *(*zoitechat_register_capability) (zoitechat_plugin *, const char *, int, int, void *);
|
||||
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
/* api_hook_print_after=1: observer returns are ignored; flags must be zero. */
|
||||
zoitechat_hook *zoitechat_hook_print_after (zoitechat_plugin *ph,
|
||||
const char *name, int pri, int flags,
|
||||
int (*callback) (char *word[], zoitechat_event_attrs *attrs, void *userdata),
|
||||
void *userdata);
|
||||
|
||||
zoitechat_hook *
|
||||
zoitechat_hook_command (zoitechat_plugin *ph,
|
||||
const char *name,
|
||||
@@ -416,6 +431,7 @@ zoitechat_pluginpref_list (zoitechat_plugin *ph,
|
||||
#define zoitechat_hook_server ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_server)
|
||||
#define zoitechat_hook_server_attrs ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_server_attrs)
|
||||
#define zoitechat_hook_print ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_print)
|
||||
#define zoitechat_hook_print_after ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_print_after)
|
||||
#define zoitechat_hook_print_attrs ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_print_attrs)
|
||||
#define zoitechat_hook_timer ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_timer)
|
||||
#define zoitechat_hook_fd ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_fd)
|
||||
|
||||
@@ -90,6 +90,7 @@
|
||||
<MSWindowsTheme Include="$(DepsRoot)\share\themes\MS-Windows\**\*" />
|
||||
<HicolorIcons Include="$(DepsRoot)\share\icons\hicolor\index.theme" />
|
||||
<HicolorIcons Include="$(DepsRoot)\share\icons\hicolor\**\*.*" />
|
||||
<PythonAdditiveApis Include="..\..\plugins\python\_zoitechat_*.py" />
|
||||
</ItemGroup>
|
||||
|
||||
<Copy SourceFiles="@(None)" DestinationFolder="$(ZoiteChatRel)" />
|
||||
@@ -115,6 +116,7 @@
|
||||
<Copy SourceFiles="..\..\plugins\python\hexchat.py" DestinationFolder="$(ZoiteChatRel)\python" Condition="'$(Python3Enabled)'=='true' or '$(Python38Enabled)'=='true'" />
|
||||
<Copy SourceFiles="..\..\plugins\python\zoitechat.py" DestinationFolder="$(ZoiteChatRel)\python" Condition="'$(Python3Enabled)'=='true' or '$(Python38Enabled)'=='true'" />
|
||||
<Copy SourceFiles="..\..\plugins\python\_zoitechat.py" DestinationFolder="$(ZoiteChatRel)\python" Condition="'$(Python3Enabled)'=='true' or '$(Python38Enabled)'=='true'" />
|
||||
<Copy SourceFiles="@(PythonAdditiveApis)" DestinationFolder="$(ZoiteChatRel)\python" Condition="('$(Python3Enabled)'=='true' or '$(Python38Enabled)'=='true') and '@(PythonAdditiveApis)' != ''" />
|
||||
|
||||
<WriteLinesToFile File="$(ZoiteChatRel)portable-mode" Lines="2" Overwrite="true" />
|
||||
|
||||
|
||||
Reference in new issue
Block a user