commit 6d3be469d82c5ab99ea95953e3cca98620844570 Author: CentOS Sources Date: Tue Nov 2 16:53:17 2021 -0400 import hexchat-2.14.3-17.el9 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ee339c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +SOURCES/hexchat-2.14.3.tar.xz diff --git a/.hexchat.metadata b/.hexchat.metadata new file mode 100644 index 0000000..a713581 --- /dev/null +++ b/.hexchat.metadata @@ -0,0 +1 @@ +a219796d70023b675e5ea24af9f22beb9e855abb SOURCES/hexchat-2.14.3.tar.xz diff --git a/SOURCES/090fd29acf4af0d8e13fcf2861b14a356db72641.patch b/SOURCES/090fd29acf4af0d8e13fcf2861b14a356db72641.patch new file mode 100644 index 0000000..f5bbc41 --- /dev/null +++ b/SOURCES/090fd29acf4af0d8e13fcf2861b14a356db72641.patch @@ -0,0 +1,25 @@ +From 090fd29acf4af0d8e13fcf2861b14a356db72641 Mon Sep 17 00:00:00 2001 +From: Sbgodin +Date: Sun, 7 Mar 2021 12:51:45 +0000 +Subject: [PATCH] python: Fix exception with list_pluginpref() + +__decode cannot work (with Python3) because prefs_str has no attribute 'decode'. + +Related to https://github.com/hexchat/hexchat/issues/2531 +--- + plugins/python/_hexchat.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/plugins/python/_hexchat.py b/plugins/python/_hexchat.py +index 567b34934..5e4b0c65f 100644 +--- a/plugins/python/_hexchat.py ++++ b/plugins/python/_hexchat.py +@@ -319,7 +319,7 @@ def del_pluginpref(name): + def list_pluginpref(): + prefs_str = ffi.new('char[4096]') + if lib.hexchat_pluginpref_list(lib.ph, prefs_str) == 1: +- return __decode(prefs_str).split(',') ++ return __decode(ffi.string(prefs_str)).split(',') + + return [] + diff --git a/SOURCES/163608d7fd861c2c4911a38f45be484c88626bdc.patch b/SOURCES/163608d7fd861c2c4911a38f45be484c88626bdc.patch new file mode 100644 index 0000000..871eabc --- /dev/null +++ b/SOURCES/163608d7fd861c2c4911a38f45be484c88626bdc.patch @@ -0,0 +1,48 @@ +From 163608d7fd861c2c4911a38f45be484c88626bdc Mon Sep 17 00:00:00 2001 +From: John Levon +Date: Mon, 7 Sep 2020 17:53:31 +0100 +Subject: [PATCH] Use pango_font_metrics_get_height() to calculate font height + (#2500) + +--- + src/fe-gtk/xtext.c | 18 ++++++++++++++++-- + 1 file changed, 16 insertions(+), 2 deletions(-) + +diff --git a/src/fe-gtk/xtext.c b/src/fe-gtk/xtext.c +index fac0c4e6d..418bb4da5 100644 +--- a/src/fe-gtk/xtext.c ++++ b/src/fe-gtk/xtext.c +@@ -283,8 +283,24 @@ backend_font_open (GtkXText *xtext, char *name) + metrics = pango_context_get_metrics (context, xtext->font->font, lang); + xtext->font->ascent = pango_font_metrics_get_ascent (metrics) / PANGO_SCALE; + xtext->font->descent = pango_font_metrics_get_descent (metrics) / PANGO_SCALE; ++ ++ /* ++ * In later versions of pango, a font's height should be calculated like ++ * this to account for line gap; a typical symptom of not doing so is ++ * cutting off the underscore on some fonts. ++ */ ++#if PANGO_VERSION_CHECK(1, 44, 0) ++ xtext->fontsize = pango_font_metrics_get_height (metrics) / PANGO_SCALE + 1; ++ ++ if (xtext->fontsize == 0) ++ xtext->fontsize = xtext->font->ascent + xtext->font->descent; ++#else ++ xtext->fontsize = xtext->font->ascent + xtext->font->descent; ++#endif ++ + pango_font_metrics_unref (metrics); + } ++ + static int + backend_get_text_width_emph (GtkXText *xtext, guchar *str, int len, int emphasis) + { +@@ -3479,8 +3495,6 @@ gtk_xtext_set_font (GtkXText *xtext, char *name) + if (xtext->font == NULL) + return FALSE; + +- xtext->fontsize = xtext->font->ascent + xtext->font->descent; +- + { + char *time_str; + int stamp_size = xtext_get_stamp_str (time(0), &time_str); diff --git a/SOURCES/5deb69591992d4fede9090b60d3dc847612a4d60.patch b/SOURCES/5deb69591992d4fede9090b60d3dc847612a4d60.patch new file mode 100644 index 0000000..fdfc74f --- /dev/null +++ b/SOURCES/5deb69591992d4fede9090b60d3dc847612a4d60.patch @@ -0,0 +1,28 @@ +From 5deb69591992d4fede9090b60d3dc847612a4d60 Mon Sep 17 00:00:00 2001 +From: Patrick Griffis +Date: Wed, 11 Mar 2020 11:07:56 -0700 +Subject: [PATCH] build: Better support building against python 3.8+ + +Closes #2441 +--- + plugins/python/meson.build | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/plugins/python/meson.build b/plugins/python/meson.build +index 2ad5128e5..eb762134a 100644 +--- a/plugins/python/meson.build ++++ b/plugins/python/meson.build +@@ -1,6 +1,12 @@ + python_opt = get_option('with-python') + if python_opt.startswith('python3') +- python_dep = dependency(python_opt, version: '>= 3.3') ++ # Python 3.8 introduced a new -embed variant ++ if not python_opt.endswith('-embed') ++ python_dep = dependency(python_opt + '-embed', version: '>= 3.3', required: false) ++ endif ++ if not python_dep.found() ++ python_dep = dependency(python_opt, version: '>= 3.3') ++ endif + else + python_dep = dependency(python_opt, version: '>= 2.7') + endif diff --git a/SOURCES/hexchat-Avoid-direct-use-of-libproxy.patch b/SOURCES/hexchat-Avoid-direct-use-of-libproxy.patch new file mode 100644 index 0000000..0957f30 --- /dev/null +++ b/SOURCES/hexchat-Avoid-direct-use-of-libproxy.patch @@ -0,0 +1,199 @@ +From 54321bae1feaf0845d75f67245c95795651277f5 Mon Sep 17 00:00:00 2001 +From: Michael Catanzaro +Date: Mon, 12 Jul 2021 08:38:02 -0500 +Subject: [PATCH] Avoid direct use of libproxy + +Since hexchat already depends on GLib, it's better to use GProxyResolver +instead. This might use libproxy, or not, as appropriate. + +P.S. This removes a memory safety issue because proxy_list is allocated +using malloc(), not g_malloc(), and therefore using g_strfreev() is +incorrect. The proper way to free the proxy list returned by libproxy +is to use px_proxy_factory_free_proxies() (but nobody does that because +it was added in libproxy 0.4.16, which is somewhat recent). +--- + meson.build | 1 - + meson_options.txt | 3 --- + src/common/hexchat.c | 16 ---------------- + src/common/meson.build | 4 ---- + src/common/server.c | 26 ++++++++++++++++---------- + src/fe-gtk/setup.c | 2 -- + 6 files changed, 16 insertions(+), 36 deletions(-) + +diff --git a/meson.build b/meson.build +index 18baf26e126e..381e3fd87ac6 100644 +--- a/meson.build ++++ b/meson.build +@@ -33,7 +33,6 @@ config_h.set10('ENABLE_NLS', true) + + # Optional features + config_h.set('USE_OPENSSL', get_option('with-ssl')) +-config_h.set('USE_LIBPROXY', get_option('with-libproxy')) + config_h.set('USE_LIBCANBERRA', get_option('with-libcanberra')) + config_h.set('USE_DBUS', get_option('with-dbus')) + config_h.set('USE_PLUGIN', get_option('with-plugin')) +diff --git a/meson_options.txt b/meson_options.txt +index 100a5ee72ad7..ad03d6bc58ef 100644 +--- a/meson_options.txt ++++ b/meson_options.txt +@@ -13,9 +13,6 @@ option('with-plugin', type: 'boolean', + option('with-dbus', type: 'boolean', + description: 'Support used for single-instance and scripting interface, Unix only' + ) +-option('with-libproxy', type: 'boolean', +- description: 'Support for getting proxy information, Unix only' +-) + option('with-libnotify', type: 'boolean', + description: 'Support for freedesktop notifications, Unix only' + ) +diff --git a/src/common/hexchat.c b/src/common/hexchat.c +index e9a9a7fc9fcd..a98fcafba2f7 100644 +--- a/src/common/hexchat.c ++++ b/src/common/hexchat.c +@@ -57,10 +57,6 @@ + #include /* for g_type_init() */ + #endif + +-#ifdef USE_LIBPROXY +-#include +-#endif +- + GSList *popup_list = 0; + GSList *button_list = 0; + GSList *dlgbutton_list = 0; +@@ -111,10 +107,6 @@ struct session *current_tab; + struct session *current_sess = 0; + struct hexchatprefs prefs; + +-#ifdef USE_LIBPROXY +-pxProxyFactory *libproxy_factory; +-#endif +- + /* + * Update the priority queue of the "interesting sessions" + * (sess_list_by_lastact). +@@ -1101,10 +1093,6 @@ main (int argc, char *argv[]) + hexchat_remote (); + #endif + +-#ifdef USE_LIBPROXY +- libproxy_factory = px_proxy_factory_new (); +-#endif +- + #ifdef WIN32 + coinit_result = CoInitializeEx (NULL, COINIT_APARTMENTTHREADED); + if (SUCCEEDED (coinit_result)) +@@ -1147,10 +1135,6 @@ main (int argc, char *argv[]) + } + #endif + +-#ifdef USE_LIBPROXY +- px_proxy_factory_free (libproxy_factory); +-#endif +- + #ifdef WIN32 + WSACleanup (); + #endif +diff --git a/src/common/meson.build b/src/common/meson.build +index bbb646459522..5885ec9e47c1 100644 +--- a/src/common/meson.build ++++ b/src/common/meson.build +@@ -77,10 +77,6 @@ if get_option('with-ssl') + common_deps += libssl_dep + endif + +-if get_option('with-libproxy') +- common_deps += dependency('libproxy-1.0') +-endif +- + if get_option('with-libcanberra') + common_deps += dependency('libcanberra', version: '>= 0.22') + endif +diff --git a/src/common/server.c b/src/common/server.c +index 3db0a9635fc5..a96e81f43d15 100644 +--- a/src/common/server.c ++++ b/src/common/server.c +@@ -61,10 +61,6 @@ + #include "ssl.h" + #endif + +-#ifdef USE_LIBPROXY +-#include +-#endif +- + #ifdef USE_OPENSSL + /* local variables */ + static struct session *g_sess = NULL; +@@ -78,9 +74,15 @@ static void server_disconnect (session * sess, int sendquit, int err); + static int server_cleanup (server * serv); + static void server_connect (server *serv, char *hostname, int port, int no_login); + +-#ifdef USE_LIBPROXY +-extern pxProxyFactory *libproxy_factory; +-#endif ++static void ++write_error (char *message, GError **error) ++{ ++ if (error == NULL || *error == NULL) { ++ return; ++ } ++ g_printerr ("%s: %s\n", message, (*error)->message); ++ g_clear_error (error); ++} + + /* actually send to the socket. This might do a character translation or + send via SSL. server/dcc both use this function. */ +@@ -1365,14 +1367,16 @@ server_child (server * serv) + + if (!serv->dont_use_proxy) /* blocked in serverlist? */ + { +-#ifdef USE_LIBPROXY + if (prefs.hex_net_proxy_type == 5) + { + char **proxy_list; + char *url, *proxy; ++ GProxyResolver *resolver; ++ GError *error = NULL; + ++ resolver = g_proxy_resolver_get_default (); + url = g_strdup_printf ("irc://%s:%d", hostname, port); +- proxy_list = px_proxy_factory_get_proxies (libproxy_factory, url); ++ proxy_list = g_proxy_resolver_lookup (resolver, url, NULL, &error); + + if (proxy_list) { + /* can use only one */ +@@ -1385,6 +1389,8 @@ server_child (server * serv) + proxy_type = 3; + else if (!strncmp (proxy, "socks", 5)) + proxy_type = 2; ++ } else { ++ write_error ("Failed to lookup proxy", &error); + } + + if (proxy_type) { +@@ -1399,7 +1405,7 @@ server_child (server * serv) + g_strfreev (proxy_list); + g_free (url); + } +-#endif ++ + if (prefs.hex_net_proxy_host[0] && + prefs.hex_net_proxy_type > 0 && + prefs.hex_net_proxy_use != 2) /* proxy is NOT dcc-only */ +diff --git a/src/fe-gtk/setup.c b/src/fe-gtk/setup.c +index 3d003eefc776..a7e3a15cac52 100644 +--- a/src/fe-gtk/setup.c ++++ b/src/fe-gtk/setup.c +@@ -614,9 +614,7 @@ static const char *const proxytypes[] = + N_("SOCKS4"), + N_("SOCKS5"), + N_("HTTP"), +-#ifdef USE_LIBPROXY + N_("Auto"), +-#endif + NULL + }; + +-- +2.31.1 + diff --git a/SOURCES/python-plugin-from-master.patch b/SOURCES/python-plugin-from-master.patch new file mode 100644 index 0000000..80bc599 --- /dev/null +++ b/SOURCES/python-plugin-from-master.patch @@ -0,0 +1,4067 @@ +diff --color -Nur hexchat-2.14.3/plugins/python/generate_plugin.py hexchat/plugins/python/generate_plugin.py +--- hexchat-2.14.3/plugins/python/generate_plugin.py 1969-12-31 16:00:00.000000000 -0800 ++++ hexchat/plugins/python/generate_plugin.py 2020-11-06 15:03:00.401820501 -0800 +@@ -0,0 +1,89 @@ ++#!/usr/bin/env python3 ++ ++import sys ++import cffi ++ ++builder = cffi.FFI() ++ ++# hexchat-plugin.h ++with open(sys.argv[1]) as f: ++ output = [] ++ eat_until_endif = 0 ++ # This is very specific to hexchat-plugin.h, it is not a cpp ++ for line in f: ++ if line.startswith('#define'): ++ continue ++ elif line.endswith('HEXCHAT_PLUGIN_H\n'): ++ continue ++ elif 'time.h' in line: ++ output.append('typedef int... time_t;') ++ elif line.startswith('#if'): ++ eat_until_endif += 1 ++ elif line.startswith('#endif'): ++ eat_until_endif -= 1 ++ elif eat_until_endif and '_hexchat_context' not in line: ++ continue ++ else: ++ output.append(line) ++ builder.cdef(''.join(output)) ++ ++builder.embedding_api(''' ++extern "Python" int _on_py_command(char **, char **, void *); ++extern "Python" int _on_load_command(char **, char **, void *); ++extern "Python" int _on_unload_command(char **, char **, void *); ++extern "Python" int _on_reload_command(char **, char **, void *); ++extern "Python" int _on_say_command(char **, char **, void *); ++ ++extern "Python" int _on_command_hook(char **, char **, void *); ++extern "Python" int _on_print_hook(char **, void *); ++extern "Python" int _on_print_attrs_hook(char **, hexchat_event_attrs *, void *); ++extern "Python" int _on_server_hook(char **, char **, void *); ++extern "Python" int _on_server_attrs_hook(char **, char **, hexchat_event_attrs *, void *); ++extern "Python" int _on_timer_hook(void *); ++ ++extern "Python" int _on_plugin_init(char **, char **, char **, char *, char *); ++extern "Python" int _on_plugin_deinit(void); ++ ++static hexchat_plugin *ph; ++''') ++ ++builder.set_source('_hexchat_embedded', ''' ++/* Python's header defines these.. */ ++#undef HAVE_MEMRCHR ++#undef HAVE_STRINGS_H ++ ++#include "config.h" ++#include "hexchat-plugin.h" ++ ++static hexchat_plugin *ph; ++CFFI_DLLEXPORT int _on_plugin_init(char **, char **, char **, char *, char *); ++CFFI_DLLEXPORT int _on_plugin_deinit(void); ++ ++int hexchat_plugin_init(hexchat_plugin *plugin_handle, ++ char **name_out, char **description_out, ++ char **version_out, char *arg) ++{ ++ if (ph != NULL) ++ { ++ puts ("Python plugin already loaded\\n"); ++ return 0; /* Prevent loading twice */ ++ } ++ ++ ph = plugin_handle; ++ return _on_plugin_init(name_out, description_out, version_out, arg, HEXCHATLIBDIR); ++} ++ ++int hexchat_plugin_deinit(void) ++{ ++ int ret = _on_plugin_deinit(); ++ ph = NULL; ++ return ret; ++} ++''') ++ ++# python.py ++with open(sys.argv[2]) as f: ++ builder.embedding_init_code(f.read()) ++ ++# python.c ++builder.emit_c_code(sys.argv[3]) +diff --color -Nur hexchat-2.14.3/plugins/python/_hexchat.py hexchat/plugins/python/_hexchat.py +--- hexchat-2.14.3/plugins/python/_hexchat.py 1969-12-31 16:00:00.000000000 -0800 ++++ hexchat/plugins/python/_hexchat.py 2020-11-06 15:03:00.401820501 -0800 +@@ -0,0 +1,386 @@ ++import inspect ++import sys ++from contextlib import contextmanager ++ ++from _hexchat_embedded import ffi, lib ++ ++__all__ = [ ++ 'EAT_ALL', 'EAT_HEXCHAT', 'EAT_NONE', 'EAT_PLUGIN', 'EAT_XCHAT', ++ 'PRI_HIGH', 'PRI_HIGHEST', 'PRI_LOW', 'PRI_LOWEST', 'PRI_NORM', ++ '__doc__', '__version__', 'command', 'del_pluginpref', 'emit_print', ++ 'find_context', 'get_context', 'get_info', ++ 'get_list', 'get_lists', 'get_pluginpref', 'get_prefs', 'hook_command', ++ 'hook_print', 'hook_print_attrs', 'hook_server', 'hook_server_attrs', ++ 'hook_timer', 'hook_unload', 'list_pluginpref', 'nickcmp', 'prnt', ++ 'set_pluginpref', 'strip', 'unhook', ++] ++ ++__doc__ = 'HexChat Scripting Interface' ++__version__ = (2, 0) ++__license__ = 'GPL-2.0+' ++ ++EAT_NONE = 0 ++EAT_HEXCHAT = 1 ++EAT_XCHAT = EAT_HEXCHAT ++EAT_PLUGIN = 2 ++EAT_ALL = EAT_HEXCHAT | EAT_PLUGIN ++ ++PRI_LOWEST = -128 ++PRI_LOW = -64 ++PRI_NORM = 0 ++PRI_HIGH = 64 ++PRI_HIGHEST = 127 ++ ++ ++# We need each module to be able to reference their parent plugin ++# which is a bit tricky since they all share the exact same module. ++# Simply navigating up to what module called it seems to actually ++# be a fairly reliable and simple method of doing so if ugly. ++def __get_current_plugin(): ++ frame = inspect.stack()[1][0] ++ while '__plugin' not in frame.f_globals: ++ frame = frame.f_back ++ assert frame is not None ++ ++ return frame.f_globals['__plugin'] ++ ++ ++# Keeping API compat ++if sys.version_info[0] == 2: ++ def __decode(string): ++ return string ++ ++else: ++ def __decode(string): ++ return string.decode() ++ ++ ++# ------------ API ------------ ++def prnt(string): ++ lib.hexchat_print(lib.ph, string.encode()) ++ ++ ++def emit_print(event_name, *args, **kwargs): ++ time = kwargs.pop('time', 0) # For py2 compat ++ cargs = [] ++ for i in range(4): ++ arg = args[i].encode() if len(args) > i else b'' ++ cstring = ffi.new('char[]', arg) ++ cargs.append(cstring) ++ ++ if time == 0: ++ return lib.hexchat_emit_print(lib.ph, event_name.encode(), *cargs) ++ ++ attrs = lib.hexchat_event_attrs_create(lib.ph) ++ attrs.server_time_utc = time ++ ret = lib.hexchat_emit_print_attrs(lib.ph, attrs, event_name.encode(), *cargs) ++ lib.hexchat_event_attrs_free(lib.ph, attrs) ++ return ret ++ ++ ++# TODO: this shadows itself. command should be changed to cmd ++def command(command): ++ lib.hexchat_command(lib.ph, command.encode()) ++ ++ ++def nickcmp(string1, string2): ++ return lib.hexchat_nickcmp(lib.ph, string1.encode(), string2.encode()) ++ ++ ++def strip(text, length=-1, flags=3): ++ stripped = lib.hexchat_strip(lib.ph, text.encode(), length, flags) ++ ret = __decode(ffi.string(stripped)) ++ lib.hexchat_free(lib.ph, stripped) ++ return ret ++ ++ ++def get_info(name): ++ ret = lib.hexchat_get_info(lib.ph, name.encode()) ++ if ret == ffi.NULL: ++ return None ++ if name in ('gtkwin_ptr', 'win_ptr'): ++ # Surely there is a less dumb way? ++ ptr = repr(ret).rsplit(' ', 1)[1][:-1] ++ return ptr ++ ++ return __decode(ffi.string(ret)) ++ ++ ++def get_prefs(name): ++ string_out = ffi.new('char**') ++ int_out = ffi.new('int*') ++ _type = lib.hexchat_get_prefs(lib.ph, name.encode(), string_out, int_out) ++ if _type == 0: ++ return None ++ ++ if _type == 1: ++ return __decode(ffi.string(string_out[0])) ++ ++ if _type in (2, 3): # XXX: 3 should be a bool, but keeps API ++ return int_out[0] ++ ++ raise AssertionError('Out of bounds pref storage') ++ ++ ++def __cstrarray_to_list(arr): ++ i = 0 ++ ret = [] ++ while arr[i] != ffi.NULL: ++ ret.append(ffi.string(arr[i])) ++ i += 1 ++ ++ return ret ++ ++ ++__FIELD_CACHE = {} ++ ++ ++def __get_fields(name): ++ return __FIELD_CACHE.setdefault(name, __cstrarray_to_list(lib.hexchat_list_fields(lib.ph, name))) ++ ++ ++__FIELD_PROPERTY_CACHE = {} ++ ++ ++def __cached_decoded_str(string): ++ return __FIELD_PROPERTY_CACHE.setdefault(string, __decode(string)) ++ ++ ++def get_lists(): ++ return [__cached_decoded_str(field) for field in __get_fields(b'lists')] ++ ++ ++class ListItem: ++ def __init__(self, name): ++ self._listname = name ++ ++ def __repr__(self): ++ return '<{} list item at {}>'.format(self._listname, id(self)) ++ ++ ++# done this way for speed ++if sys.version_info[0] == 2: ++ def get_getter(name): ++ return ord(name[0]) ++ ++else: ++ def get_getter(name): ++ return name[0] ++ ++ ++def get_list(name): ++ # XXX: This function is extremely inefficient and could be interators and ++ # lazily loaded properties, but for API compat we stay slow ++ orig_name = name ++ name = name.encode() ++ ++ if name not in __get_fields(b'lists'): ++ raise KeyError('list not available') ++ ++ list_ = lib.hexchat_list_get(lib.ph, name) ++ if list_ == ffi.NULL: ++ return None ++ ++ ret = [] ++ fields = __get_fields(name) ++ ++ def string_getter(field): ++ string = lib.hexchat_list_str(lib.ph, list_, field) ++ if string != ffi.NULL: ++ return __decode(ffi.string(string)) ++ ++ return '' ++ ++ def ptr_getter(field): ++ if field == b'context': ++ ptr = lib.hexchat_list_str(lib.ph, list_, field) ++ ctx = ffi.cast('hexchat_context*', ptr) ++ return Context(ctx) ++ ++ return None ++ ++ getters = { ++ ord('s'): string_getter, ++ ord('i'): lambda field: lib.hexchat_list_int(lib.ph, list_, field), ++ ord('t'): lambda field: lib.hexchat_list_time(lib.ph, list_, field), ++ ord('p'): ptr_getter, ++ } ++ ++ while lib.hexchat_list_next(lib.ph, list_) == 1: ++ item = ListItem(orig_name) ++ for _field in fields: ++ getter = getters.get(get_getter(_field)) ++ if getter is not None: ++ field_name = _field[1:] ++ setattr(item, __cached_decoded_str(field_name), getter(field_name)) ++ ++ ret.append(item) ++ ++ lib.hexchat_list_free(lib.ph, list_) ++ return ret ++ ++ ++# TODO: 'command' here shadows command above, and should be renamed to cmd ++def hook_command(command, callback, userdata=None, priority=PRI_NORM, help=None): ++ plugin = __get_current_plugin() ++ hook = plugin.add_hook(callback, userdata) ++ handle = lib.hexchat_hook_command(lib.ph, command.encode(), priority, lib._on_command_hook, ++ help.encode() if help is not None else ffi.NULL, hook.handle) ++ ++ hook.hexchat_hook = handle ++ return id(hook) ++ ++ ++def hook_print(name, callback, userdata=None, priority=PRI_NORM): ++ plugin = __get_current_plugin() ++ hook = plugin.add_hook(callback, userdata) ++ handle = lib.hexchat_hook_print(lib.ph, name.encode(), priority, lib._on_print_hook, hook.handle) ++ hook.hexchat_hook = handle ++ return id(hook) ++ ++ ++def hook_print_attrs(name, callback, userdata=None, priority=PRI_NORM): ++ plugin = __get_current_plugin() ++ hook = plugin.add_hook(callback, userdata) ++ handle = lib.hexchat_hook_print_attrs(lib.ph, name.encode(), priority, lib._on_print_attrs_hook, hook.handle) ++ hook.hexchat_hook = handle ++ return id(hook) ++ ++ ++def hook_server(name, callback, userdata=None, priority=PRI_NORM): ++ plugin = __get_current_plugin() ++ hook = plugin.add_hook(callback, userdata) ++ handle = lib.hexchat_hook_server(lib.ph, name.encode(), priority, lib._on_server_hook, hook.handle) ++ hook.hexchat_hook = handle ++ return id(hook) ++ ++ ++def hook_server_attrs(name, callback, userdata=None, priority=PRI_NORM): ++ plugin = __get_current_plugin() ++ hook = plugin.add_hook(callback, userdata) ++ handle = lib.hexchat_hook_server_attrs(lib.ph, name.encode(), priority, lib._on_server_attrs_hook, hook.handle) ++ hook.hexchat_hook = handle ++ return id(hook) ++ ++ ++def hook_timer(timeout, callback, userdata=None): ++ plugin = __get_current_plugin() ++ hook = plugin.add_hook(callback, userdata) ++ handle = lib.hexchat_hook_timer(lib.ph, timeout, lib._on_timer_hook, hook.handle) ++ hook.hexchat_hook = handle ++ return id(hook) ++ ++ ++def hook_unload(callback, userdata=None): ++ plugin = __get_current_plugin() ++ hook = plugin.add_hook(callback, userdata, is_unload=True) ++ return id(hook) ++ ++ ++def unhook(handle): ++ plugin = __get_current_plugin() ++ return plugin.remove_hook(handle) ++ ++ ++def set_pluginpref(name, value): ++ if isinstance(value, str): ++ return bool(lib.hexchat_pluginpref_set_str(lib.ph, name.encode(), value.encode())) ++ ++ if isinstance(value, int): ++ return bool(lib.hexchat_pluginpref_set_int(lib.ph, name.encode(), value)) ++ ++ # XXX: This should probably raise but this keeps API ++ return False ++ ++ ++def get_pluginpref(name): ++ name = name.encode() ++ string_out = ffi.new('char[512]') ++ if lib.hexchat_pluginpref_get_str(lib.ph, name, string_out) != 1: ++ return None ++ ++ string = ffi.string(string_out) ++ # This API stores everything as a string so we have to figure out what ++ # its actual type was supposed to be. ++ if len(string) > 12: # Can't be a number ++ return __decode(string) ++ ++ number = lib.hexchat_pluginpref_get_int(lib.ph, name) ++ if number == -1 and string != b'-1': ++ return __decode(string) ++ ++ return number ++ ++ ++def del_pluginpref(name): ++ return bool(lib.hexchat_pluginpref_delete(lib.ph, name.encode())) ++ ++ ++def list_pluginpref(): ++ prefs_str = ffi.new('char[4096]') ++ if lib.hexchat_pluginpref_list(lib.ph, prefs_str) == 1: ++ return __decode(prefs_str).split(',') ++ ++ return [] ++ ++ ++class Context: ++ def __init__(self, ctx): ++ self._ctx = ctx ++ ++ def __eq__(self, value): ++ if not isinstance(value, Context): ++ return False ++ ++ return self._ctx == value._ctx ++ ++ @contextmanager ++ def __change_context(self): ++ old_ctx = lib.hexchat_get_context(lib.ph) ++ if not self.set(): ++ # XXX: Behavior change, previously used wrong context ++ lib.hexchat_print(lib.ph, b'Context object refers to closed context, ignoring call') ++ return ++ ++ yield ++ lib.hexchat_set_context(lib.ph, old_ctx) ++ ++ def set(self): ++ # XXX: API addition, C plugin silently ignored failure ++ return bool(lib.hexchat_set_context(lib.ph, self._ctx)) ++ ++ def prnt(self, string): ++ with self.__change_context(): ++ prnt(string) ++ ++ def emit_print(self, event_name, *args, **kwargs): ++ time = kwargs.pop('time', 0) # For py2 compat ++ with self.__change_context(): ++ return emit_print(event_name, *args, time=time) ++ ++ def command(self, string): ++ with self.__change_context(): ++ command(string) ++ ++ def get_info(self, name): ++ with self.__change_context(): ++ return get_info(name) ++ ++ def get_list(self, name): ++ with self.__change_context(): ++ return get_list(name) ++ ++ ++def get_context(): ++ ctx = lib.hexchat_get_context(lib.ph) ++ return Context(ctx) ++ ++ ++def find_context(server=None, channel=None): ++ server = server.encode() if server is not None else ffi.NULL ++ channel = channel.encode() if channel is not None else ffi.NULL ++ ctx = lib.hexchat_find_context(lib.ph, server, channel) ++ if ctx == ffi.NULL: ++ return None ++ ++ return Context(ctx) +diff --color -Nur hexchat-2.14.3/plugins/python/hexchat.py hexchat/plugins/python/hexchat.py +--- hexchat-2.14.3/plugins/python/hexchat.py 1969-12-31 16:00:00.000000000 -0800 ++++ hexchat/plugins/python/hexchat.py 2020-11-06 15:03:00.402820502 -0800 +@@ -0,0 +1 @@ ++from _hexchat import * +diff --color -Nur hexchat-2.14.3/plugins/python/meson.build hexchat/plugins/python/meson.build +--- hexchat-2.14.3/plugins/python/meson.build 2020-11-06 15:05:12.122924612 -0800 ++++ hexchat/plugins/python/meson.build 2020-11-06 15:03:00.402820502 -0800 +@@ -3,16 +3,28 @@ + # Python 3.8 introduced a new -embed variant + if not python_opt.endswith('-embed') + python_dep = dependency(python_opt + '-embed', version: '>= 3.3', required: false) +- endif +- if not python_dep.found() ++ if not python_dep.found() ++ python_dep = dependency(python_opt, version: '>= 3.3') ++ endif ++ else + python_dep = dependency(python_opt, version: '>= 3.3') + endif + else + python_dep = dependency(python_opt, version: '>= 2.7') + endif + +-shared_module('python', 'python.c', +- dependencies: [libgio_dep, hexchat_plugin_dep, python_dep], ++python3_source = custom_target('python-bindings', ++ input: ['../../src/common/hexchat-plugin.h', 'python.py'], ++ output: 'python.c', ++ command: [find_program('generate_plugin.py'), '@INPUT@', '@OUTPUT@'] ++) ++ ++install_data(['_hexchat.py', 'hexchat.py', 'xchat.py'], ++ install_dir: join_paths(get_option('libdir'), 'hexchat/python') ++) ++ ++shared_module('python', python3_source, ++ dependencies: [hexchat_plugin_dep, python_dep], + install: true, + install_dir: plugindir, + name_prefix: '', +diff --color -Nur hexchat-2.14.3/plugins/python/python2.vcxproj hexchat/plugins/python/python2.vcxproj +--- hexchat-2.14.3/plugins/python/python2.vcxproj 2019-12-20 22:43:47.652401700 -0800 ++++ hexchat/plugins/python/python2.vcxproj 2020-11-06 15:03:00.402820502 -0800 +@@ -37,6 +37,9 @@ + "$(Python2Lib).lib";$(DepLibs);%(AdditionalDependencies) + $(DepsRoot)\lib;$(Python2Path)\libs;%(AdditionalLibraryDirectories) + ++ ++ "$(Python3Path)\python.exe" generate_plugin.py ..\..\src\common\hexchat-plugin.h python.py "$(IntDir)python.c" ++ + + + +@@ -48,12 +51,15 @@ + "$(Python2Lib).lib";$(DepLibs);%(AdditionalDependencies) + $(DepsRoot)\lib;$(Python2Path)\libs;%(AdditionalLibraryDirectories) + ++ ++ "$(Python3Path)\python.exe" generate_plugin.py ..\..\src\common\hexchat-plugin.h python.py "$(IntDir)python.c" ++ + + + + + +- ++ + + + +diff --color -Nur hexchat-2.14.3/plugins/python/python3.vcxproj hexchat/plugins/python/python3.vcxproj +--- hexchat-2.14.3/plugins/python/python3.vcxproj 2019-12-20 22:43:47.652401700 -0800 ++++ hexchat/plugins/python/python3.vcxproj 2020-11-06 15:03:00.403820502 -0800 +@@ -37,6 +37,9 @@ + "$(Python3Lib).lib";$(DepLibs);%(AdditionalDependencies) + $(DepsRoot)\lib;$(Python3Path)\libs;%(AdditionalLibraryDirectories) + ++ ++ "$(Python3Path)\python.exe" generate_plugin.py ..\..\src\common\hexchat-plugin.h python.py "$(IntDir)python.c" ++ + + + +@@ -48,12 +51,20 @@ + "$(Python3Lib).lib";$(DepLibs);%(AdditionalDependencies) + $(DepsRoot)\lib;$(Python3Path)\libs;%(AdditionalLibraryDirectories) + ++ ++ "$(Python3Path)\python.exe" generate_plugin.py ..\..\src\common\hexchat-plugin.h python.py "$(IntDir)python.c" ++ + + ++ ++ + ++ ++ ++ + + +- ++ + + +- ++ +\ No newline at end of file +diff --color -Nur hexchat-2.14.3/plugins/python/python3.vcxproj.filters hexchat/plugins/python/python3.vcxproj.filters +--- hexchat-2.14.3/plugins/python/python3.vcxproj.filters 2019-12-20 22:43:47.652401700 -0800 ++++ hexchat/plugins/python/python3.vcxproj.filters 2020-11-06 15:03:00.403820502 -0800 +@@ -9,13 +9,26 @@ + + + +- +- Source Files +- ++ + + + + Resource Files + ++ ++ Source Files ++ ++ ++ Source Files ++ ++ ++ Source Files ++ ++ ++ Source Files ++ ++ ++ Source Files ++ + + +\ No newline at end of file +diff --color -Nur hexchat-2.14.3/plugins/python/python.c hexchat/plugins/python/python.c +--- hexchat-2.14.3/plugins/python/python.c 2019-12-20 22:43:47.652401700 -0800 ++++ hexchat/plugins/python/python.c 1969-12-31 16:00:00.000000000 -0800 +@@ -1,2837 +0,0 @@ +-/* +-* Copyright (c) 2002-2003 Gustavo Niemeyer +-* +-* XChat Python Plugin Interface +-* +-* Xchat Python Plugin Interface is free software; you can redistribute +-* it and/or modify it under the terms of the GNU General Public License +-* as published by the Free Software Foundation; either version 2 of the +-* License, or (at your option) any later version. +-* +-* pybot is distributed in the hope that it will be useful, +-* but WITHOUT ANY WARRANTY; without even the implied warranty of +-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-* GNU General Public License for more details. +-* +-* You should have received a copy of the GNU General Public License +-* along with this file; if not, write to the Free Software +-* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +-*/ +- +-/* Thread support +- * ============== +- * +- * The python interpreter has a global interpreter lock. Any thread +- * executing must acquire it before working with data accessible from +- * python code. Here we must also care about xchat not being +- * thread-safe. We do this by using an xchat lock, which protects +- * xchat instructions from being executed out of time (when this +- * plugin is not "active"). +- * +- * When xchat calls python code: +- * - Change the current_plugin for the executing plugin; +- * - Release xchat lock +- * - Acquire the global interpreter lock +- * - Make the python call +- * - Release the global interpreter lock +- * - Acquire xchat lock +- * +- * When python code calls xchat: +- * - Release the global interpreter lock +- * - Acquire xchat lock +- * - Restore context, if necessary +- * - Make the xchat call +- * - Release xchat lock +- * - Acquire the global interpreter lock +- * +- * Inside a timer, so that individual threads have a chance to run: +- * - Release xchat lock +- * - Go ahead threads. Have a nice time! +- * - Acquire xchat lock +- * +- */ +- +-#include "config.h" +- +-#include +-#include +-#include +-#include +-#include +- +-#ifdef WIN32 +-#include +-#else +-#include +-#include +-#endif +- +-#include "hexchat-plugin.h" +-#undef _POSIX_C_SOURCE /* Avoid warnings from /usr/include/features.h */ +-#undef _XOPEN_SOURCE +-#undef HAVE_MEMRCHR /* Avoid redefinition in Python.h */ +-#undef HAVE_STRINGS_H +-#include +-#include +-#include +- +-/* Macros to convert version macros into string literals. +- * The indirect macro is a well-known preprocessor trick to force X to be evaluated before the # operator acts to make it a string literal. +- * If STRINGIZE were to be directly defined as #X instead, VERSION would be "VERSION_MAJOR" instead of "1". +- */ +-#define STRINGIZE2(X) #X +-#define STRINGIZE(X) STRINGIZE2(X) +- +-/* Version number macros */ +-#define VERSION_MAJOR 1 +-#define VERSION_MINOR 0 +- +-/* Version string macro e.g 1.0/3.3 */ +-#define VERSION STRINGIZE(VERSION_MAJOR) "." STRINGIZE(VERSION_MINOR) "/" \ +- STRINGIZE(PY_MAJOR_VERSION) "." STRINGIZE (PY_MINOR_VERSION) +- +-/* #define's for Python 2 */ +-#if PY_MAJOR_VERSION == 2 +-#undef PyLong_Check +-#define PyLong_Check PyInt_Check +-#define PyLong_AsLong PyInt_AsLong +-#define PyLong_FromLong PyInt_FromLong +- +-#undef PyUnicode_Check +-#undef PyUnicode_FromString +-#undef PyUnicode_FromFormat +-#define PyUnicode_Check PyString_Check +-#define PyUnicode_AsFormat PyString_AsFormat +-#define PyUnicode_FromFormat PyString_FromFormat +-#define PyUnicode_FromString PyString_FromString +-#define PyUnicode_AsUTF8 PyString_AsString +- +-#ifdef WIN32 +-#undef WITH_THREAD +-#endif +-#endif +- +-/* #define for Python 3 */ +-#if PY_MAJOR_VERSION == 3 +-#define IS_PY3K +-#endif +- +-#define NONE 0 +-#define ALLOW_THREADS 1 +-#define RESTORE_CONTEXT 2 +- +-#ifdef WITH_THREAD +-#define ACQUIRE_XCHAT_LOCK() PyThread_acquire_lock(xchat_lock, 1) +-#define RELEASE_XCHAT_LOCK() PyThread_release_lock(xchat_lock) +-#define BEGIN_XCHAT_CALLS(x) \ +- do { \ +- PyObject *calls_plugin = NULL; \ +- PyThreadState *calls_thread; \ +- if ((x) & RESTORE_CONTEXT) \ +- calls_plugin = Plugin_GetCurrent(); \ +- calls_thread = PyEval_SaveThread(); \ +- ACQUIRE_XCHAT_LOCK(); \ +- if (!((x) & ALLOW_THREADS)) { \ +- PyEval_RestoreThread(calls_thread); \ +- calls_thread = NULL; \ +- } \ +- if (calls_plugin) \ +- hexchat_set_context(ph, \ +- Plugin_GetContext(calls_plugin)); \ +- while (0) +-#define END_XCHAT_CALLS() \ +- RELEASE_XCHAT_LOCK(); \ +- if (calls_thread) \ +- PyEval_RestoreThread(calls_thread); \ +- } while(0) +-#else +-#define ACQUIRE_XCHAT_LOCK() +-#define RELEASE_XCHAT_LOCK() +-#define BEGIN_XCHAT_CALLS(x) +-#define END_XCHAT_CALLS() +-#endif +- +-#ifdef WITH_THREAD +- +-#define BEGIN_PLUGIN(plg) \ +- do { \ +- hexchat_context *begin_plugin_ctx = hexchat_get_context(ph); \ +- RELEASE_XCHAT_LOCK(); \ +- Plugin_AcquireThread(plg); \ +- Plugin_SetContext(plg, begin_plugin_ctx); \ +- } while (0) +-#define END_PLUGIN(plg) \ +- do { \ +- Plugin_ReleaseThread(plg); \ +- ACQUIRE_XCHAT_LOCK(); \ +- } while (0) +- +-#else /* !WITH_THREAD (win32) */ +- +-static PyThreadState *pTempThread; +- +-#define BEGIN_PLUGIN(plg) \ +- do { \ +- hexchat_context *begin_plugin_ctx = hexchat_get_context(ph); \ +- RELEASE_XCHAT_LOCK(); \ +- PyEval_AcquireLock(); \ +- pTempThread = PyThreadState_Swap(((PluginObject *)(plg))->tstate); \ +- Plugin_SetContext(plg, begin_plugin_ctx); \ +- } while (0) +-#define END_PLUGIN(plg) \ +- do { \ +- ((PluginObject *)(plg))->tstate = PyThreadState_Swap(pTempThread); \ +- PyEval_ReleaseLock(); \ +- ACQUIRE_XCHAT_LOCK(); \ +- } while (0) +- +-#endif /* !WITH_THREAD */ +- +-#define Plugin_Swap(x) \ +- PyThreadState_Swap(((PluginObject *)(x))->tstate) +-#define Plugin_AcquireThread(x) \ +- PyEval_AcquireThread(((PluginObject *)(x))->tstate) +-#define Plugin_ReleaseThread(x) \ +- Util_ReleaseThread(((PluginObject *)(x))->tstate) +-#define Plugin_GetFilename(x) \ +- (((PluginObject *)(x))->filename) +-#define Plugin_GetName(x) \ +- (((PluginObject *)(x))->name) +-#define Plugin_GetVersion(x) \ +- (((PluginObject *)(x))->version) +-#define Plugin_GetDesc(x) \ +- (((PluginObject *)(x))->description) +-#define Plugin_GetHooks(x) \ +- (((PluginObject *)(x))->hooks) +-#define Plugin_GetContext(x) \ +- (((PluginObject *)(x))->context) +-#define Plugin_SetFilename(x, y) \ +- ((PluginObject *)(x))->filename = (y); +-#define Plugin_SetName(x, y) \ +- ((PluginObject *)(x))->name = (y); +-#define Plugin_SetVersion(x, y) \ +- ((PluginObject *)(x))->version = (y); +-#define Plugin_SetDescription(x, y) \ +- ((PluginObject *)(x))->description = (y); +-#define Plugin_SetHooks(x, y) \ +- ((PluginObject *)(x))->hooks = (y); +-#define Plugin_SetContext(x, y) \ +- ((PluginObject *)(x))->context = (y); +-#define Plugin_SetGui(x, y) \ +- ((PluginObject *)(x))->gui = (y); +- +-#define HOOK_XCHAT 1 +-#define HOOK_XCHAT_ATTR 2 +-#define HOOK_UNLOAD 3 +- +-/* ===================================================================== */ +-/* Object definitions */ +- +-typedef struct { +- PyObject_HEAD +- int softspace; /* We need it for print support. */ +-} XChatOutObject; +- +-typedef struct { +- PyObject_HEAD +- hexchat_context *context; +-} ContextObject; +- +-typedef struct { +- PyObject_HEAD +- PyObject *time; +-} AttributeObject; +- +-typedef struct { +- PyObject_HEAD +- const char *listname; +- PyObject *dict; +-} ListItemObject; +- +-typedef struct { +- PyObject_HEAD +- char *name; +- char *version; +- char *filename; +- char *description; +- GSList *hooks; +- PyThreadState *tstate; +- hexchat_context *context; +- void *gui; +-} PluginObject; +- +-typedef struct { +- int type; +- PyObject *plugin; +- PyObject *callback; +- PyObject *userdata; +- char *name; +- void *data; /* A handle, when type == HOOK_XCHAT */ +-} Hook; +- +- +-/* ===================================================================== */ +-/* Function declarations */ +- +-static PyObject *Util_BuildList(char *word[]); +-static PyObject *Util_BuildEOLList(char *word[]); +-static void Util_Autoload(void); +-static char *Util_Expand(char *filename); +- +-static int Callback_Server(char *word[], char *word_eol[], hexchat_event_attrs *attrs, void *userdata); +-static int Callback_Command(char *word[], char *word_eol[], void *userdata); +-static int Callback_Print_Attrs(char *word[], hexchat_event_attrs *attrs, void *userdata); +-static int Callback_Print(char *word[], void *userdata); +-static int Callback_Timer(void *userdata); +-static int Callback_ThreadTimer(void *userdata); +- +-static PyObject *XChatOut_New(void); +-static PyObject *XChatOut_write(PyObject *self, PyObject *args); +-static void XChatOut_dealloc(PyObject *self); +- +-static PyObject *Attribute_New(hexchat_event_attrs *attrs); +- +-static void Context_dealloc(PyObject *self); +-static PyObject *Context_set(ContextObject *self, PyObject *args); +-static PyObject *Context_command(ContextObject *self, PyObject *args); +-static PyObject *Context_prnt(ContextObject *self, PyObject *args); +-static PyObject *Context_get_info(ContextObject *self, PyObject *args); +-static PyObject *Context_get_list(ContextObject *self, PyObject *args); +-static PyObject *Context_compare(ContextObject *a, ContextObject *b, int op); +-static PyObject *Context_FromContext(hexchat_context *context); +-static PyObject *Context_FromServerAndChannel(char *server, char *channel); +- +-static PyObject *Plugin_New(char *filename, PyObject *xcoobj); +-static PyObject *Plugin_GetCurrent(void); +-static PluginObject *Plugin_ByString(char *str); +-static Hook *Plugin_AddHook(int type, PyObject *plugin, PyObject *callback, +- PyObject *userdata, char *name, void *data); +-static Hook *Plugin_FindHook(PyObject *plugin, char *name); +-static void Plugin_RemoveHook(PyObject *plugin, Hook *hook); +-static void Plugin_RemoveAllHooks(PyObject *plugin); +- +-static PyObject *Module_hexchat_command(PyObject *self, PyObject *args); +-static PyObject *Module_xchat_prnt(PyObject *self, PyObject *args); +-static PyObject *Module_hexchat_get_context(PyObject *self, PyObject *args); +-static PyObject *Module_hexchat_find_context(PyObject *self, PyObject *args, +- PyObject *kwargs); +-static PyObject *Module_hexchat_get_info(PyObject *self, PyObject *args); +-static PyObject *Module_hexchat_hook_command(PyObject *self, PyObject *args, +- PyObject *kwargs); +-static PyObject *Module_hexchat_hook_server(PyObject *self, PyObject *args, +- PyObject *kwargs); +-static PyObject *Module_hexchat_hook_print(PyObject *self, PyObject *args, +- PyObject *kwargs); +-static PyObject *Module_hexchat_hook_timer(PyObject *self, PyObject *args, +- PyObject *kwargs); +-static PyObject *Module_hexchat_unhook(PyObject *self, PyObject *args); +-static PyObject *Module_hexchat_get_info(PyObject *self, PyObject *args); +-static PyObject *Module_xchat_get_list(PyObject *self, PyObject *args); +-static PyObject *Module_xchat_get_lists(PyObject *self, PyObject *args); +-static PyObject *Module_hexchat_nickcmp(PyObject *self, PyObject *args); +-static PyObject *Module_hexchat_strip(PyObject *self, PyObject *args); +-static PyObject *Module_hexchat_pluginpref_set(PyObject *self, PyObject *args); +-static PyObject *Module_hexchat_pluginpref_get(PyObject *self, PyObject *args); +-static PyObject *Module_hexchat_pluginpref_delete(PyObject *self, PyObject *args); +-static PyObject *Module_hexchat_pluginpref_list(PyObject *self, PyObject *args); +- +-static void IInterp_Exec(char *command); +-static int IInterp_Cmd(char *word[], char *word_eol[], void *userdata); +- +-static void Command_PyList(void); +-static void Command_PyLoad(char *filename); +-static void Command_PyUnload(char *name); +-static void Command_PyReload(char *name); +-static void Command_PyAbout(void); +-static int Command_Py(char *word[], char *word_eol[], void *userdata); +- +-/* ===================================================================== */ +-/* Static declarations and definitions */ +- +-static PyTypeObject Plugin_Type; +-static PyTypeObject XChatOut_Type; +-static PyTypeObject Context_Type; +-static PyTypeObject ListItem_Type; +-static PyTypeObject Attribute_Type; +- +-static PyThreadState *main_tstate = NULL; +-static void *thread_timer = NULL; +- +-static hexchat_plugin *ph; +-static GSList *plugin_list = NULL; +- +-static PyObject *interp_plugin = NULL; +-static PyObject *xchatout = NULL; +- +-#ifdef WITH_THREAD +-static PyThread_type_lock xchat_lock = NULL; +-#endif +- +-static const char usage[] = "\ +-Usage: /PY LOAD \n\ +- UNLOAD \n\ +- RELOAD \n\ +- LIST\n\ +- EXEC \n\ +- CONSOLE\n\ +- ABOUT\n\ +-\n"; +- +-static const char about[] = "HexChat Python interface version " VERSION "\n"; +- +-/* ===================================================================== */ +-/* Utility functions */ +- +-static PyObject * +-Util_BuildList(char *word[]) +-{ +- PyObject *list; +- int listsize = 31; +- int i; +- /* Find the last valid array member; there may be intermediate NULLs that +- * would otherwise cause us to drop some members. */ +- while (listsize > 0 && +- (word[listsize] == NULL || word[listsize][0] == 0)) +- listsize--; +- list = PyList_New(listsize); +- if (list == NULL) { +- PyErr_Print(); +- return NULL; +- } +- for (i = 1; i <= listsize; i++) { +- PyObject *o; +- if (word[i] == NULL) { +- Py_INCREF(Py_None); +- o = Py_None; +- } else { +- /* This handles word[i][0] == 0 automatically. */ +- o = PyUnicode_FromString(word[i]); +- } +- PyList_SetItem(list, i - 1, o); +- } +- return list; +-} +- +-static PyObject * +-Util_BuildEOLList(char *word[]) +-{ +- PyObject *list; +- int listsize = 31; +- int i; +- char *accum = NULL; +- char *last = NULL; +- +- /* Find the last valid array member; there may be intermediate NULLs that +- * would otherwise cause us to drop some members. */ +- while (listsize > 0 && +- (word[listsize] == NULL || word[listsize][0] == 0)) +- listsize--; +- list = PyList_New(listsize); +- if (list == NULL) { +- PyErr_Print(); +- return NULL; +- } +- for (i = listsize; i > 0; i--) { +- char *part = word[i]; +- PyObject *uni_part; +- if (accum == NULL) { +- accum = g_strdup (part); +- } else if (part != NULL && part[0] != 0) { +- last = accum; +- accum = g_strjoin(" ", part, last, NULL); +- g_free (last); +- last = NULL; +- +- if (accum == NULL) { +- Py_DECREF(list); +- hexchat_print(ph, "Not enough memory to alloc accum" +- "for python plugin callback"); +- return NULL; +- } +- } +- uni_part = PyUnicode_FromString(accum); +- PyList_SetItem(list, i - 1, uni_part); +- } +- +- g_free (last); +- g_free (accum); +- +- return list; +-} +- +-static void +-Util_Autoload_from (const char *dir_name) +-{ +- gchar *oldcwd; +- const char *entry_name; +- GDir *dir; +- +- oldcwd = g_get_current_dir (); +- if (oldcwd == NULL) +- return; +- if (g_chdir(dir_name) != 0) +- { +- g_free (oldcwd); +- return; +- } +- dir = g_dir_open (".", 0, NULL); +- if (dir == NULL) +- { +- g_free (oldcwd); +- return; +- } +- while ((entry_name = g_dir_read_name (dir))) +- { +- if (g_str_has_suffix (entry_name, ".py")) +- Command_PyLoad((char*)entry_name); +- } +- g_dir_close (dir); +- g_chdir (oldcwd); +-} +- +-static void +-Util_Autoload() +-{ +- const char *xdir; +- char *sub_dir; +- /* we need local filesystem encoding for g_chdir, g_dir_open etc */ +- +- xdir = hexchat_get_info(ph, "configdir"); +- +- /* auto-load from subdirectory addons */ +- sub_dir = g_build_filename (xdir, "addons", NULL); +- Util_Autoload_from(sub_dir); +- g_free (sub_dir); +-} +- +-static char * +-Util_Expand(char *filename) +-{ +- char *expanded; +- +- /* Check if this is an absolute path. */ +- if (g_path_is_absolute(filename)) { +- if (g_file_test(filename, G_FILE_TEST_EXISTS)) +- return g_strdup(filename); +- else +- return NULL; +- } +- +- /* Check if it starts with ~/ and expand the home if positive. */ +- if (*filename == '~' && *(filename+1) == '/') { +- expanded = g_build_filename(g_get_home_dir(), +- filename+2, NULL); +- if (g_file_test(expanded, G_FILE_TEST_EXISTS)) +- return expanded; +- else { +- g_free(expanded); +- return NULL; +- } +- } +- +- /* Check if it's in the current directory. */ +- expanded = g_build_filename(g_get_current_dir(), +- filename, NULL); +- if (g_file_test(expanded, G_FILE_TEST_EXISTS)) +- return expanded; +- g_free(expanded); +- +- /* Check if ~/.config/hexchat/addons/ exists. */ +- expanded = g_build_filename(hexchat_get_info(ph, "configdir"), +- "addons", filename, NULL); +- if (g_file_test(expanded, G_FILE_TEST_EXISTS)) +- return expanded; +- g_free(expanded); +- +- return NULL; +-} +- +-/* Similar to PyEval_ReleaseThread, but accepts NULL thread states. */ +-static void +-Util_ReleaseThread(PyThreadState *tstate) +-{ +- PyThreadState *old_tstate; +- if (tstate == NULL) +- Py_FatalError("PyEval_ReleaseThread: NULL thread state"); +- old_tstate = PyThreadState_Swap(NULL); +- if (old_tstate != tstate && old_tstate != NULL) +- Py_FatalError("PyEval_ReleaseThread: wrong thread state"); +- PyEval_ReleaseLock(); +-} +- +-/* ===================================================================== */ +-/* Hookable functions. These are the entry points to python code, besides +- * the load function, and the hooks for interactive interpreter. */ +- +-static int +-Callback_Server(char *word[], char *word_eol[], hexchat_event_attrs *attrs, void *userdata) +-{ +- Hook *hook = (Hook *) userdata; +- PyObject *retobj; +- PyObject *word_list, *word_eol_list; +- PyObject *attributes; +- int ret = HEXCHAT_EAT_NONE; +- PyObject *plugin; +- +- plugin = hook->plugin; +- BEGIN_PLUGIN(plugin); +- +- word_list = Util_BuildList(word); +- if (word_list == NULL) { +- END_PLUGIN(plugin); +- return HEXCHAT_EAT_NONE; +- } +- word_eol_list = Util_BuildList(word_eol); +- if (word_eol_list == NULL) { +- Py_DECREF(word_list); +- END_PLUGIN(plugin); +- return HEXCHAT_EAT_NONE; +- } +- +- attributes = Attribute_New(attrs); +- +- if (hook->type == HOOK_XCHAT_ATTR) +- retobj = PyObject_CallFunction(hook->callback, "(OOOO)", word_list, +- word_eol_list, hook->userdata, attributes); +- else +- retobj = PyObject_CallFunction(hook->callback, "(OOO)", word_list, +- word_eol_list, hook->userdata); +- Py_DECREF(word_list); +- Py_DECREF(word_eol_list); +- Py_DECREF(attributes); +- +- if (retobj == Py_None) { +- ret = HEXCHAT_EAT_NONE; +- Py_DECREF(retobj); +- } else if (retobj) { +- ret = PyLong_AsLong(retobj); +- Py_DECREF(retobj); +- } else { +- PyErr_Print(); +- } +- +- END_PLUGIN(plugin); +- +- return ret; +-} +- +-static int +-Callback_Command(char *word[], char *word_eol[], void *userdata) +-{ +- Hook *hook = (Hook *) userdata; +- PyObject *retobj; +- PyObject *word_list, *word_eol_list; +- int ret = HEXCHAT_EAT_NONE; +- PyObject *plugin; +- +- plugin = hook->plugin; +- BEGIN_PLUGIN(plugin); +- +- word_list = Util_BuildList(word); +- if (word_list == NULL) { +- END_PLUGIN(plugin); +- return HEXCHAT_EAT_NONE; +- } +- word_eol_list = Util_BuildList(word_eol); +- if (word_eol_list == NULL) { +- Py_DECREF(word_list); +- END_PLUGIN(plugin); +- return HEXCHAT_EAT_NONE; +- } +- +- retobj = PyObject_CallFunction(hook->callback, "(OOO)", word_list, +- word_eol_list, hook->userdata); +- Py_DECREF(word_list); +- Py_DECREF(word_eol_list); +- +- if (retobj == Py_None) { +- ret = HEXCHAT_EAT_NONE; +- Py_DECREF(retobj); +- } else if (retobj) { +- ret = PyLong_AsLong(retobj); +- Py_DECREF(retobj); +- } else { +- PyErr_Print(); +- } +- +- END_PLUGIN(plugin); +- +- return ret; +-} +- +-static int +-Callback_Print_Attrs(char *word[], hexchat_event_attrs *attrs, void *userdata) +-{ +- Hook *hook = (Hook *) userdata; +- PyObject *retobj; +- PyObject *word_list; +- PyObject *word_eol_list; +- PyObject *attributes; +- int ret = HEXCHAT_EAT_NONE; +- PyObject *plugin; +- +- plugin = hook->plugin; +- BEGIN_PLUGIN(plugin); +- +- word_list = Util_BuildList(word); +- if (word_list == NULL) { +- END_PLUGIN(plugin); +- return HEXCHAT_EAT_NONE; +- } +- word_eol_list = Util_BuildEOLList(word); +- if (word_eol_list == NULL) { +- Py_DECREF(word_list); +- END_PLUGIN(plugin); +- return HEXCHAT_EAT_NONE; +- } +- +- attributes = Attribute_New(attrs); +- +- retobj = PyObject_CallFunction(hook->callback, "(OOOO)", word_list, +- word_eol_list, hook->userdata, attributes); +- +- Py_DECREF(word_list); +- Py_DECREF(word_eol_list); +- Py_DECREF(attributes); +- +- if (retobj == Py_None) { +- ret = HEXCHAT_EAT_NONE; +- Py_DECREF(retobj); +- } else if (retobj) { +- ret = PyLong_AsLong(retobj); +- Py_DECREF(retobj); +- } else { +- PyErr_Print(); +- } +- +- END_PLUGIN(plugin); +- +- return ret; +-} +- +-static int +-Callback_Print(char *word[], void *userdata) +-{ +- Hook *hook = (Hook *) userdata; +- PyObject *retobj; +- PyObject *word_list; +- PyObject *word_eol_list; +- int ret = HEXCHAT_EAT_NONE; +- PyObject *plugin; +- +- plugin = hook->plugin; +- BEGIN_PLUGIN(plugin); +- +- word_list = Util_BuildList(word); +- if (word_list == NULL) { +- END_PLUGIN(plugin); +- return HEXCHAT_EAT_NONE; +- } +- word_eol_list = Util_BuildEOLList(word); +- if (word_eol_list == NULL) { +- Py_DECREF(word_list); +- END_PLUGIN(plugin); +- return HEXCHAT_EAT_NONE; +- } +- +- retobj = PyObject_CallFunction(hook->callback, "(OOO)", word_list, +- word_eol_list, hook->userdata); +- +- Py_DECREF(word_list); +- Py_DECREF(word_eol_list); +- +- if (retobj == Py_None) { +- ret = HEXCHAT_EAT_NONE; +- Py_DECREF(retobj); +- } else if (retobj) { +- ret = PyLong_AsLong(retobj); +- Py_DECREF(retobj); +- } else { +- PyErr_Print(); +- } +- +- END_PLUGIN(plugin); +- +- return ret; +-} +- +-static int +-Callback_Timer(void *userdata) +-{ +- Hook *hook = (Hook *) userdata; +- PyObject *retobj; +- int ret = 0; +- PyObject *plugin; +- +- plugin = hook->plugin; +- +- BEGIN_PLUGIN(hook->plugin); +- +- retobj = PyObject_CallFunction(hook->callback, "(O)", hook->userdata); +- +- if (retobj) { +- ret = PyObject_IsTrue(retobj); +- Py_DECREF(retobj); +- } else { +- PyErr_Print(); +- } +- +- /* Returning 0 for this callback unhooks itself. */ +- if (ret == 0) +- Plugin_RemoveHook(plugin, hook); +- +- END_PLUGIN(plugin); +- +- return ret; +-} +- +-#ifdef WITH_THREAD +-static int +-Callback_ThreadTimer(void *userdata) +-{ +- RELEASE_XCHAT_LOCK(); +-#ifndef WIN32 +- usleep(1); +-#endif +- ACQUIRE_XCHAT_LOCK(); +- return 1; +-} +-#endif +- +-/* ===================================================================== */ +-/* XChatOut object */ +- +-/* We keep this information global, so we can reset it when the +- * deinit function is called. */ +-/* XXX This should be somehow bound to the printing context. */ +-static GString *xchatout_buffer = NULL; +- +-static PyObject * +-XChatOut_New() +-{ +- XChatOutObject *xcoobj; +- xcoobj = PyObject_New(XChatOutObject, &XChatOut_Type); +- if (xcoobj != NULL) +- xcoobj->softspace = 0; +- return (PyObject *) xcoobj; +-} +- +-static void +-XChatOut_dealloc(PyObject *self) +-{ +- Py_TYPE(self)->tp_free((PyObject *)self); +-} +- +-/* This is a little bit complex because we have to buffer data +- * until a \n is received, since xchat breaks the line automatically. +- * We also crop the last \n for this reason. */ +-static PyObject * +-XChatOut_write(PyObject *self, PyObject *args) +-{ +- gboolean add_space; +- char *data, *pos; +- +- if (!PyArg_ParseTuple(args, "s:write", &data)) +- return NULL; +- if (!data || !*data) { +- Py_RETURN_NONE; +- } +- BEGIN_XCHAT_CALLS(RESTORE_CONTEXT|ALLOW_THREADS); +- if (((XChatOutObject *)self)->softspace) { +- add_space = TRUE; +- ((XChatOutObject *)self)->softspace = 0; +- } else { +- add_space = FALSE; +- } +- +- g_string_append (xchatout_buffer, data); +- +- /* If not end of line add space to continue buffer later */ +- if (add_space && xchatout_buffer->str[xchatout_buffer->len - 1] != '\n') +- { +- g_string_append_c (xchatout_buffer, ' '); +- } +- +- /* If there is an end of line print up to that */ +- if ((pos = strrchr (xchatout_buffer->str, '\n'))) +- { +- *pos = '\0'; +- hexchat_print (ph, xchatout_buffer->str); +- +- /* Then remove it from buffer */ +- g_string_erase (xchatout_buffer, 0, pos - xchatout_buffer->str + 1); +- } +- +- END_XCHAT_CALLS(); +- Py_RETURN_NONE; +-} +- +-#define OFF(x) offsetof(XChatOutObject, x) +- +-static PyMemberDef XChatOut_members[] = { +- {"softspace", T_INT, OFF(softspace), 0}, +- {0} +-}; +- +-static PyMethodDef XChatOut_methods[] = { +- {"write", XChatOut_write, METH_VARARGS}, +- {NULL, NULL} +-}; +- +-static PyTypeObject XChatOut_Type = { +- PyVarObject_HEAD_INIT(NULL, 0) +- "hexchat.XChatOut", /*tp_name*/ +- sizeof(XChatOutObject), /*tp_basicsize*/ +- 0, /*tp_itemsize*/ +- XChatOut_dealloc, /*tp_dealloc*/ +- 0, /*tp_print*/ +- 0, /*tp_getattr*/ +- 0, /*tp_setattr*/ +- 0, /*tp_compare*/ +- 0, /*tp_repr*/ +- 0, /*tp_as_number*/ +- 0, /*tp_as_sequence*/ +- 0, /*tp_as_mapping*/ +- 0, /*tp_hash*/ +- 0, /*tp_call*/ +- 0, /*tp_str*/ +- PyObject_GenericGetAttr,/*tp_getattro*/ +- PyObject_GenericSetAttr,/*tp_setattro*/ +- 0, /*tp_as_buffer*/ +- Py_TPFLAGS_DEFAULT, /*tp_flags*/ +- 0, /*tp_doc*/ +- 0, /*tp_traverse*/ +- 0, /*tp_clear*/ +- 0, /*tp_richcompare*/ +- 0, /*tp_weaklistoffset*/ +- 0, /*tp_iter*/ +- 0, /*tp_iternext*/ +- XChatOut_methods, /*tp_methods*/ +- XChatOut_members, /*tp_members*/ +- 0, /*tp_getset*/ +- 0, /*tp_base*/ +- 0, /*tp_dict*/ +- 0, /*tp_descr_get*/ +- 0, /*tp_descr_set*/ +- 0, /*tp_dictoffset*/ +- 0, /*tp_init*/ +- PyType_GenericAlloc, /*tp_alloc*/ +- PyType_GenericNew, /*tp_new*/ +- PyObject_Del, /*tp_free*/ +- 0, /*tp_is_gc*/ +-}; +- +- +-/* ===================================================================== */ +-/* Attribute object */ +- +-#undef OFF +-#define OFF(x) offsetof(AttributeObject, x) +- +-static PyMemberDef Attribute_members[] = { +- {"time", T_OBJECT, OFF(time), 0}, +- {0} +-}; +- +-static void +-Attribute_dealloc(PyObject *self) +-{ +- Py_DECREF(((AttributeObject*)self)->time); +- Py_TYPE(self)->tp_free((PyObject *)self); +-} +- +-static PyObject * +-Attribute_repr(PyObject *self) +-{ +- return PyUnicode_FromFormat("", self); +-} +- +-static PyTypeObject Attribute_Type = { +- PyVarObject_HEAD_INIT(NULL, 0) +- "hexchat.Attribute", /*tp_name*/ +- sizeof(AttributeObject), /*tp_basicsize*/ +- 0, /*tp_itemsize*/ +- Attribute_dealloc, /*tp_dealloc*/ +- 0, /*tp_print*/ +- 0, /*tp_getattr*/ +- 0, /*tp_setattr*/ +- 0, /*tp_compare*/ +- Attribute_repr, /*tp_repr*/ +- 0, /*tp_as_number*/ +- 0, /*tp_as_sequence*/ +- 0, /*tp_as_mapping*/ +- 0, /*tp_hash*/ +- 0, /*tp_call*/ +- 0, /*tp_str*/ +- PyObject_GenericGetAttr,/*tp_getattro*/ +- PyObject_GenericSetAttr,/*tp_setattro*/ +- 0, /*tp_as_buffer*/ +- Py_TPFLAGS_DEFAULT, /*tp_flags*/ +- 0, /*tp_doc*/ +- 0, /*tp_traverse*/ +- 0, /*tp_clear*/ +- 0, /*tp_richcompare*/ +- 0, /*tp_weaklistoffset*/ +- 0, /*tp_iter*/ +- 0, /*tp_iternext*/ +- 0, /*tp_methods*/ +- Attribute_members, /*tp_members*/ +- 0, /*tp_getset*/ +- 0, /*tp_base*/ +- 0, /*tp_dict*/ +- 0, /*tp_descr_get*/ +- 0, /*tp_descr_set*/ +- 0, /*tp_dictoffset*/ +- 0, /*tp_init*/ +- PyType_GenericAlloc, /*tp_alloc*/ +- PyType_GenericNew, /*tp_new*/ +- PyObject_Del, /*tp_free*/ +- 0, /*tp_is_gc*/ +-}; +- +-static PyObject * +-Attribute_New(hexchat_event_attrs *attrs) +-{ +- AttributeObject *attr; +- attr = PyObject_New(AttributeObject, &Attribute_Type); +- if (attr != NULL) { +- attr->time = PyLong_FromLong((long)attrs->server_time_utc); +- } +- return (PyObject *) attr; +-} +- +- +-/* ===================================================================== */ +-/* Context object */ +- +-static void +-Context_dealloc(PyObject *self) +-{ +- Py_TYPE(self)->tp_free((PyObject *)self); +-} +- +-static PyObject * +-Context_set(ContextObject *self, PyObject *args) +-{ +- PyObject *plugin = Plugin_GetCurrent(); +- Plugin_SetContext(plugin, self->context); +- Py_RETURN_NONE; +-} +- +-static PyObject * +-Context_command(ContextObject *self, PyObject *args) +-{ +- char *text; +- if (!PyArg_ParseTuple(args, "s:command", &text)) +- return NULL; +- BEGIN_XCHAT_CALLS(ALLOW_THREADS); +- hexchat_set_context(ph, self->context); +- hexchat_command(ph, text); +- END_XCHAT_CALLS(); +- Py_RETURN_NONE; +-} +- +-static PyObject * +-Context_prnt(ContextObject *self, PyObject *args) +-{ +- char *text; +- if (!PyArg_ParseTuple(args, "s:prnt", &text)) +- return NULL; +- BEGIN_XCHAT_CALLS(ALLOW_THREADS); +- hexchat_set_context(ph, self->context); +- hexchat_print(ph, text); +- END_XCHAT_CALLS(); +- Py_RETURN_NONE; +-} +- +-static PyObject * +-Context_emit_print(ContextObject *self, PyObject *args, PyObject *kwargs) +-{ +- char *argv[6]; +- char *name; +- int res; +- long time = 0; +- hexchat_event_attrs *attrs; +- char *kwlist[] = {"name", "arg1", "arg2", "arg3", +- "arg4", "arg5", "arg6", +- "time", NULL}; +- memset(&argv, 0, sizeof(char*)*6); +- if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|ssssssl:print_event", kwlist, &name, +- &argv[0], &argv[1], &argv[2], +- &argv[3], &argv[4], &argv[5], +- &time)) +- return NULL; +- BEGIN_XCHAT_CALLS(ALLOW_THREADS); +- hexchat_set_context(ph, self->context); +- attrs = hexchat_event_attrs_create(ph); +- attrs->server_time_utc = (time_t)time; +- +- res = hexchat_emit_print_attrs(ph, attrs, name, argv[0], argv[1], argv[2], +- argv[3], argv[4], argv[5], NULL); +- +- hexchat_event_attrs_free(ph, attrs); +- END_XCHAT_CALLS(); +- return PyLong_FromLong(res); +-} +- +-static PyObject * +-Context_get_info(ContextObject *self, PyObject *args) +-{ +- const char *info; +- char *name; +- if (!PyArg_ParseTuple(args, "s:get_info", &name)) +- return NULL; +- BEGIN_XCHAT_CALLS(NONE); +- hexchat_set_context(ph, self->context); +- info = hexchat_get_info(ph, name); +- END_XCHAT_CALLS(); +- if (info == NULL) { +- Py_RETURN_NONE; +- } +- return PyUnicode_FromString(info); +-} +- +-static PyObject * +-Context_get_list(ContextObject *self, PyObject *args) +-{ +- PyObject *plugin = Plugin_GetCurrent(); +- hexchat_context *saved_context = Plugin_GetContext(plugin); +- PyObject *ret; +- Plugin_SetContext(plugin, self->context); +- ret = Module_xchat_get_list((PyObject*)self, args); +- Plugin_SetContext(plugin, saved_context); +- return ret; +-} +- +-/* needed to make context1 == context2 work */ +-static PyObject * +-Context_compare(ContextObject *a, ContextObject *b, int op) +-{ +- PyObject *ret; +- /* check for == */ +- if (op == Py_EQ) +- ret = (a->context == b->context ? Py_True : Py_False); +- /* check for != */ +- else if (op == Py_NE) +- ret = (a->context != b->context ? Py_True : Py_False); +- /* only makes sense as == and != */ +- else +- { +- PyErr_SetString(PyExc_TypeError, "contexts are either equal or not equal"); +- ret = Py_None; +- } +- +- Py_INCREF(ret); +- return ret; +-} +- +-static PyMethodDef Context_methods[] = { +- {"set", (PyCFunction) Context_set, METH_NOARGS}, +- {"command", (PyCFunction) Context_command, METH_VARARGS}, +- {"prnt", (PyCFunction) Context_prnt, METH_VARARGS}, +- {"emit_print", (PyCFunction) Context_emit_print, METH_VARARGS|METH_KEYWORDS}, +- {"get_info", (PyCFunction) Context_get_info, METH_VARARGS}, +- {"get_list", (PyCFunction) Context_get_list, METH_VARARGS}, +- {NULL, NULL} +-}; +- +-static PyTypeObject Context_Type = { +- PyVarObject_HEAD_INIT(NULL, 0) +- "hexchat.Context", /*tp_name*/ +- sizeof(ContextObject), /*tp_basicsize*/ +- 0, /*tp_itemsize*/ +- Context_dealloc, /*tp_dealloc*/ +- 0, /*tp_print*/ +- 0, /*tp_getattr*/ +- 0, /*tp_setattr*/ +- 0, /*tp_compare*/ +- 0, /*tp_repr*/ +- 0, /*tp_as_number*/ +- 0, /*tp_as_sequence*/ +- 0, /*tp_as_mapping*/ +- 0, /*tp_hash*/ +- 0, /*tp_call*/ +- 0, /*tp_str*/ +- PyObject_GenericGetAttr,/*tp_getattro*/ +- PyObject_GenericSetAttr,/*tp_setattro*/ +- 0, /*tp_as_buffer*/ +- Py_TPFLAGS_DEFAULT, /*tp_flags*/ +- 0, /*tp_doc*/ +- 0, /*tp_traverse*/ +- 0, /*tp_clear*/ +- (richcmpfunc)Context_compare, /*tp_richcompare*/ +- 0, /*tp_weaklistoffset*/ +- 0, /*tp_iter*/ +- 0, /*tp_iternext*/ +- Context_methods, /*tp_methods*/ +- 0, /*tp_members*/ +- 0, /*tp_getset*/ +- 0, /*tp_base*/ +- 0, /*tp_dict*/ +- 0, /*tp_descr_get*/ +- 0, /*tp_descr_set*/ +- 0, /*tp_dictoffset*/ +- 0, /*tp_init*/ +- PyType_GenericAlloc, /*tp_alloc*/ +- PyType_GenericNew, /*tp_new*/ +- PyObject_Del, /*tp_free*/ +- 0, /*tp_is_gc*/ +-}; +- +-static PyObject * +-Context_FromContext(hexchat_context *context) +-{ +- ContextObject *ctxobj = PyObject_New(ContextObject, &Context_Type); +- if (ctxobj != NULL) +- ctxobj->context = context; +- return (PyObject *) ctxobj; +-} +- +-static PyObject * +-Context_FromServerAndChannel(char *server, char *channel) +-{ +- ContextObject *ctxobj; +- hexchat_context *context; +- BEGIN_XCHAT_CALLS(NONE); +- context = hexchat_find_context(ph, server, channel); +- END_XCHAT_CALLS(); +- if (context == NULL) +- return NULL; +- ctxobj = PyObject_New(ContextObject, &Context_Type); +- if (ctxobj == NULL) +- return NULL; +- ctxobj->context = context; +- return (PyObject *) ctxobj; +-} +- +- +-/* ===================================================================== */ +-/* ListItem object */ +- +-#undef OFF +-#define OFF(x) offsetof(ListItemObject, x) +- +-static PyMemberDef ListItem_members[] = { +- {"__dict__", T_OBJECT, OFF(dict), 0}, +- {0} +-}; +- +-static void +-ListItem_dealloc(PyObject *self) +-{ +- Py_DECREF(((ListItemObject*)self)->dict); +- Py_TYPE(self)->tp_free((PyObject *)self); +-} +- +-static PyObject * +-ListItem_repr(PyObject *self) +-{ +- return PyUnicode_FromFormat("<%s list item at %p>", +- ((ListItemObject*)self)->listname, self); +-} +- +-static PyTypeObject ListItem_Type = { +- PyVarObject_HEAD_INIT(NULL, 0) +- "hexchat.ListItem", /*tp_name*/ +- sizeof(ListItemObject), /*tp_basicsize*/ +- 0, /*tp_itemsize*/ +- ListItem_dealloc, /*tp_dealloc*/ +- 0, /*tp_print*/ +- 0, /*tp_getattr*/ +- 0, /*tp_setattr*/ +- 0, /*tp_compare*/ +- ListItem_repr, /*tp_repr*/ +- 0, /*tp_as_number*/ +- 0, /*tp_as_sequence*/ +- 0, /*tp_as_mapping*/ +- 0, /*tp_hash*/ +- 0, /*tp_call*/ +- 0, /*tp_str*/ +- PyObject_GenericGetAttr,/*tp_getattro*/ +- PyObject_GenericSetAttr,/*tp_setattro*/ +- 0, /*tp_as_buffer*/ +- Py_TPFLAGS_DEFAULT, /*tp_flags*/ +- 0, /*tp_doc*/ +- 0, /*tp_traverse*/ +- 0, /*tp_clear*/ +- 0, /*tp_richcompare*/ +- 0, /*tp_weaklistoffset*/ +- 0, /*tp_iter*/ +- 0, /*tp_iternext*/ +- 0, /*tp_methods*/ +- ListItem_members, /*tp_members*/ +- 0, /*tp_getset*/ +- 0, /*tp_base*/ +- 0, /*tp_dict*/ +- 0, /*tp_descr_get*/ +- 0, /*tp_descr_set*/ +- OFF(dict), /*tp_dictoffset*/ +- 0, /*tp_init*/ +- PyType_GenericAlloc, /*tp_alloc*/ +- PyType_GenericNew, /*tp_new*/ +- PyObject_Del, /*tp_free*/ +- 0, /*tp_is_gc*/ +-}; +- +-static PyObject * +-ListItem_New(const char *listname) +-{ +- ListItemObject *item; +- item = PyObject_New(ListItemObject, &ListItem_Type); +- if (item != NULL) { +- /* listname parameter must be statically allocated. */ +- item->listname = listname; +- item->dict = PyDict_New(); +- if (item->dict == NULL) { +- Py_DECREF(item); +- item = NULL; +- } +- } +- return (PyObject *) item; +-} +- +- +-/* ===================================================================== */ +-/* Plugin object */ +- +-#define GET_MODULE_DATA(x, force) \ +- o = PyObject_GetAttrString(m, "__module_" #x "__"); \ +- if (o == NULL) { \ +- if (force) { \ +- hexchat_print(ph, "Module has no __module_" #x "__ " \ +- "defined"); \ +- goto error; \ +- } \ +- plugin->x = g_strdup(""); \ +- } else {\ +- if (!PyUnicode_Check(o)) { \ +- hexchat_print(ph, "Variable __module_" #x "__ " \ +- "must be a string"); \ +- goto error; \ +- } \ +- plugin->x = g_strdup(PyUnicode_AsUTF8(o)); \ +- if (plugin->x == NULL) { \ +- hexchat_print(ph, "Not enough memory to allocate " #x); \ +- goto error; \ +- } \ +- } +- +-static PyObject * +-Plugin_GetCurrent() +-{ +- PyObject *plugin; +- plugin = PySys_GetObject("__plugin__"); +- if (plugin == NULL) +- PyErr_SetString(PyExc_RuntimeError, "lost sys.__plugin__"); +- return plugin; +-} +- +-static hexchat_plugin * +-Plugin_GetHandle(PluginObject *plugin) +-{ +- /* This works but the issue is that the script must be ran to get +- * the name of it thus upon first use it will use the wrong handler +- * work around would be to run a fake script once to get name? */ +-#if 0 +- /* return fake handle for pluginpref */ +- if (plugin->gui != NULL) +- return plugin->gui; +- else +-#endif +- return ph; +-} +- +-static PluginObject * +-Plugin_ByString(char *str) +-{ +- GSList *list; +- PluginObject *plugin; +- char *basename; +- list = plugin_list; +- while (list != NULL) { +- plugin = (PluginObject *) list->data; +- basename = g_path_get_basename(plugin->filename); +- if (basename == NULL) +- break; +- if (strcasecmp(plugin->name, str) == 0 || +- strcasecmp(plugin->filename, str) == 0 || +- strcasecmp(basename, str) == 0) { +- g_free(basename); +- return plugin; +- } +- g_free(basename); +- list = list->next; +- } +- return NULL; +-} +- +-static Hook * +-Plugin_AddHook(int type, PyObject *plugin, PyObject *callback, +- PyObject *userdata, char *name, void *data) +-{ +- Hook *hook = g_new(Hook, 1); +- hook->type = type; +- hook->plugin = plugin; +- Py_INCREF(callback); +- hook->callback = callback; +- Py_INCREF(userdata); +- hook->userdata = userdata; +- hook->name = g_strdup (name); +- hook->data = NULL; +- Plugin_SetHooks(plugin, g_slist_append(Plugin_GetHooks(plugin), +- hook)); +- +- return hook; +-} +- +-static Hook * +-Plugin_FindHook(PyObject *plugin, char *name) +-{ +- Hook *hook = NULL; +- GSList *plugin_hooks = Plugin_GetHooks(plugin); +- +- while (plugin_hooks) +- { +- if (g_strcmp0 (((Hook *)plugin_hooks->data)->name, name) == 0) +- { +- hook = (Hook *)plugin_hooks->data; +- break; +- } +- +- plugin_hooks = g_slist_next(plugin_hooks); +- } +- +- return hook; +-} +- +-static void +-Plugin_RemoveHook(PyObject *plugin, Hook *hook) +-{ +- GSList *list; +- /* Is this really a hook of the running plugin? */ +- list = g_slist_find(Plugin_GetHooks(plugin), hook); +- if (list) { +- /* Ok, unhook it. */ +- if (hook->type != HOOK_UNLOAD) { +- /* This is an xchat hook. Unregister it. */ +- BEGIN_XCHAT_CALLS(NONE); +- hexchat_unhook(ph, (hexchat_hook*)hook->data); +- END_XCHAT_CALLS(); +- } +- Plugin_SetHooks(plugin, +- g_slist_remove(Plugin_GetHooks(plugin), +- hook)); +- Py_DECREF(hook->callback); +- Py_DECREF(hook->userdata); +- g_free(hook->name); +- g_free(hook); +- } +-} +- +-static void +-Plugin_RemoveAllHooks(PyObject *plugin) +-{ +- GSList *list = Plugin_GetHooks(plugin); +- while (list) { +- Hook *hook = (Hook *) list->data; +- if (hook->type != HOOK_UNLOAD) { +- /* This is an xchat hook. Unregister it. */ +- BEGIN_XCHAT_CALLS(NONE); +- hexchat_unhook(ph, (hexchat_hook*)hook->data); +- END_XCHAT_CALLS(); +- } +- Py_DECREF(hook->callback); +- Py_DECREF(hook->userdata); +- g_free(hook->name); +- g_free(hook); +- list = list->next; +- } +- Plugin_SetHooks(plugin, NULL); +-} +- +-static void +-Plugin_Delete(PyObject *plugin) +-{ +- PyThreadState *tstate = ((PluginObject*)plugin)->tstate; +- GSList *list = Plugin_GetHooks(plugin); +- while (list) { +- Hook *hook = (Hook *) list->data; +- if (hook->type == HOOK_UNLOAD) { +- PyObject *retobj; +- retobj = PyObject_CallFunction(hook->callback, "(O)", +- hook->userdata); +- if (retobj) { +- Py_DECREF(retobj); +- } else { +- PyErr_Print(); +- PyErr_Clear(); +- } +- } +- list = list->next; +- } +- Plugin_RemoveAllHooks(plugin); +- if (((PluginObject *)plugin)->gui != NULL) +- hexchat_plugingui_remove(ph, ((PluginObject *)plugin)->gui); +- Py_DECREF(plugin); +- /*PyThreadState_Swap(tstate); needed? */ +- Py_EndInterpreter(tstate); +-} +- +-static PyObject * +-Plugin_New(char *filename, PyObject *xcoobj) +-{ +- PluginObject *plugin = NULL; +- PyObject *m, *o; +-#ifdef IS_PY3K +- wchar_t *argv[] = { L"", 0 }; +-#else +- char *argv[] = { "", 0 }; +-#endif +- +- if (filename) { +- char *old_filename = filename; +- filename = Util_Expand(filename); +- if (filename == NULL) { +- hexchat_printf(ph, "File not found: %s", old_filename); +- return NULL; +- } +- } +- +- /* Allocate plugin structure. */ +- plugin = PyObject_New(PluginObject, &Plugin_Type); +- if (plugin == NULL) { +- hexchat_print(ph, "Can't create plugin object"); +- goto error; +- } +- +- Plugin_SetName(plugin, NULL); +- Plugin_SetVersion(plugin, NULL); +- Plugin_SetFilename(plugin, NULL); +- Plugin_SetDescription(plugin, NULL); +- Plugin_SetHooks(plugin, NULL); +- Plugin_SetContext(plugin, hexchat_get_context(ph)); +- Plugin_SetGui(plugin, NULL); +- +- /* Start a new interpreter environment for this plugin. */ +- PyEval_AcquireThread(main_tstate); +- plugin->tstate = Py_NewInterpreter(); +- if (plugin->tstate == NULL) { +- hexchat_print(ph, "Can't create interpreter state"); +- goto error; +- } +- +- PySys_SetArgv(1, argv); +- PySys_SetObject("__plugin__", (PyObject *) plugin); +- +- /* Set stdout and stderr to xchatout. */ +- Py_INCREF(xcoobj); +- PySys_SetObject("stdout", xcoobj); +- Py_INCREF(xcoobj); +- PySys_SetObject("stderr", xcoobj); +- +- if (filename) { +-#ifdef WIN32 +- char *file; +- if (!g_file_get_contents(filename, &file, NULL, NULL)) { +- hexchat_printf(ph, "Can't open file %s: %s\n", +- filename, strerror(errno)); +- goto error; +- } +- +- if (PyRun_SimpleString(file) != 0) { +- hexchat_printf(ph, "Error loading module %s\n", +- filename); +- g_free (file); +- goto error; +- } +- +- plugin->filename = filename; +- filename = NULL; +- g_free (file); +-#else +- FILE *fp; +- plugin->filename = filename; +- +- /* It's now owned by the plugin. */ +- filename = NULL; +- +- /* Open the plugin file. */ +- fp = fopen(plugin->filename, "r"); +- if (fp == NULL) { +- hexchat_printf(ph, "Can't open file %s: %s\n", +- plugin->filename, strerror(errno)); +- goto error; +- } +- +- /* Run the plugin. */ +- if (PyRun_SimpleFile(fp, plugin->filename) != 0) { +- hexchat_printf(ph, "Error loading module %s\n", +- plugin->filename); +- fclose(fp); +- goto error; +- } +- fclose(fp); +-#endif +- m = PyDict_GetItemString(PyImport_GetModuleDict(), +- "__main__"); +- if (m == NULL) { +- hexchat_print(ph, "Can't get __main__ module"); +- goto error; +- } +- GET_MODULE_DATA(name, 1); +- GET_MODULE_DATA(version, 0); +- GET_MODULE_DATA(description, 0); +- plugin->gui = hexchat_plugingui_add(ph, plugin->filename, +- plugin->name, +- plugin->description, +- plugin->version, NULL); +- } +- +- PyEval_ReleaseThread(plugin->tstate); +- +- return (PyObject *) plugin; +- +-error: +- g_free(filename); +- +- if (plugin) { +- if (plugin->tstate) +- Plugin_Delete((PyObject *)plugin); +- else +- Py_DECREF(plugin); +- } +- PyEval_ReleaseLock(); +- +- return NULL; +-} +- +-static void +-Plugin_dealloc(PluginObject *self) +-{ +- g_free(self->filename); +- g_free(self->name); +- g_free(self->version); +- g_free(self->description); +- Py_TYPE(self)->tp_free((PyObject *)self); +-} +- +-static PyTypeObject Plugin_Type = { +- PyVarObject_HEAD_INIT(NULL, 0) +- "hexchat.Plugin", /*tp_name*/ +- sizeof(PluginObject), /*tp_basicsize*/ +- 0, /*tp_itemsize*/ +- (destructor)Plugin_dealloc, /*tp_dealloc*/ +- 0, /*tp_print*/ +- 0, /*tp_getattr*/ +- 0, /*tp_setattr*/ +- 0, /*tp_compare*/ +- 0, /*tp_repr*/ +- 0, /*tp_as_number*/ +- 0, /*tp_as_sequence*/ +- 0, /*tp_as_mapping*/ +- 0, /*tp_hash*/ +- 0, /*tp_call*/ +- 0, /*tp_str*/ +- PyObject_GenericGetAttr,/*tp_getattro*/ +- PyObject_GenericSetAttr,/*tp_setattro*/ +- 0, /*tp_as_buffer*/ +- Py_TPFLAGS_DEFAULT, /*tp_flags*/ +- 0, /*tp_doc*/ +- 0, /*tp_traverse*/ +- 0, /*tp_clear*/ +- 0, /*tp_richcompare*/ +- 0, /*tp_weaklistoffset*/ +- 0, /*tp_iter*/ +- 0, /*tp_iternext*/ +- 0, /*tp_methods*/ +- 0, /*tp_members*/ +- 0, /*tp_getset*/ +- 0, /*tp_base*/ +- 0, /*tp_dict*/ +- 0, /*tp_descr_get*/ +- 0, /*tp_descr_set*/ +- 0, /*tp_dictoffset*/ +- 0, /*tp_init*/ +- PyType_GenericAlloc, /*tp_alloc*/ +- PyType_GenericNew, /*tp_new*/ +- PyObject_Del, /*tp_free*/ +- 0, /*tp_is_gc*/ +-}; +- +- +-/* ===================================================================== */ +-/* XChat module */ +- +-static PyObject * +-Module_hexchat_command(PyObject *self, PyObject *args) +-{ +- char *text; +- if (!PyArg_ParseTuple(args, "s:command", &text)) +- return NULL; +- BEGIN_XCHAT_CALLS(RESTORE_CONTEXT|ALLOW_THREADS); +- hexchat_command(ph, text); +- END_XCHAT_CALLS(); +- Py_RETURN_NONE; +-} +- +-static PyObject * +-Module_xchat_prnt(PyObject *self, PyObject *args) +-{ +- char *text; +- if (!PyArg_ParseTuple(args, "s:prnt", &text)) +- return NULL; +- BEGIN_XCHAT_CALLS(RESTORE_CONTEXT|ALLOW_THREADS); +- hexchat_print(ph, text); +- END_XCHAT_CALLS(); +- Py_RETURN_NONE; +-} +- +-static PyObject * +-Module_hexchat_emit_print(PyObject *self, PyObject *args, PyObject *kwargs) +-{ +- char *argv[6]; +- char *name; +- int res; +- long time = 0; +- hexchat_event_attrs *attrs; +- char *kwlist[] = {"name", "arg1", "arg2", "arg3", +- "arg4", "arg5", "arg6", +- "time", NULL}; +- memset(&argv, 0, sizeof(char*)*6); +- if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|ssssssl:print_event", kwlist, &name, +- &argv[0], &argv[1], &argv[2], +- &argv[3], &argv[4], &argv[5], +- &time)) +- return NULL; +- BEGIN_XCHAT_CALLS(RESTORE_CONTEXT|ALLOW_THREADS); +- attrs = hexchat_event_attrs_create(ph); +- attrs->server_time_utc = (time_t)time; +- +- res = hexchat_emit_print_attrs(ph, attrs, name, argv[0], argv[1], argv[2], +- argv[3], argv[4], argv[5], NULL); +- +- hexchat_event_attrs_free(ph, attrs); +- END_XCHAT_CALLS(); +- return PyLong_FromLong(res); +-} +- +-static PyObject * +-Module_hexchat_get_info(PyObject *self, PyObject *args) +-{ +- const char *info; +- char *name; +- if (!PyArg_ParseTuple(args, "s:get_info", &name)) +- return NULL; +- BEGIN_XCHAT_CALLS(RESTORE_CONTEXT); +- info = hexchat_get_info(ph, name); +- END_XCHAT_CALLS(); +- if (info == NULL) { +- Py_RETURN_NONE; +- } +- if (strcmp (name, "gtkwin_ptr") == 0 || strcmp (name, "win_ptr") == 0) +- return PyUnicode_FromFormat("%p", info); /* format as pointer */ +- else +- return PyUnicode_FromString(info); +-} +- +-static PyObject * +-Module_xchat_get_prefs(PyObject *self, PyObject *args) +-{ +- PyObject *res; +- const char *info; +- int integer; +- char *name; +- int type; +- if (!PyArg_ParseTuple(args, "s:get_prefs", &name)) +- return NULL; +- BEGIN_XCHAT_CALLS(NONE); +- type = hexchat_get_prefs(ph, name, &info, &integer); +- END_XCHAT_CALLS(); +- switch (type) { +- case 0: +- Py_INCREF(Py_None); +- res = Py_None; +- break; +- case 1: +- res = PyUnicode_FromString((char*)info); +- break; +- case 2: +- case 3: +- res = PyLong_FromLong(integer); +- break; +- default: +- PyErr_Format(PyExc_RuntimeError, +- "unknown get_prefs type (%d), " +- "please report", type); +- res = NULL; +- break; +- } +- return res; +-} +- +-static PyObject * +-Module_hexchat_get_context(PyObject *self, PyObject *args) +-{ +- PyObject *plugin; +- PyObject *ctxobj; +- plugin = Plugin_GetCurrent(); +- if (plugin == NULL) +- return NULL; +- ctxobj = Context_FromContext(Plugin_GetContext(plugin)); +- if (ctxobj == NULL) { +- Py_RETURN_NONE; +- } +- return ctxobj; +-} +- +-static PyObject * +-Module_hexchat_find_context(PyObject *self, PyObject *args, PyObject *kwargs) +-{ +- char *server = NULL; +- char *channel = NULL; +- PyObject *ctxobj; +- char *kwlist[] = {"server", "channel", 0}; +- if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|zz:find_context", +- kwlist, &server, &channel)) +- return NULL; +- ctxobj = Context_FromServerAndChannel(server, channel); +- if (ctxobj == NULL) { +- Py_RETURN_NONE; +- } +- return ctxobj; +-} +- +-static PyObject * +-Module_hexchat_pluginpref_set(PyObject *self, PyObject *args) +-{ +- PluginObject *plugin = (PluginObject*)Plugin_GetCurrent(); +- hexchat_plugin *prefph = Plugin_GetHandle(plugin); +- int result; +- char *var; +- PyObject *value; +- +- if (!PyArg_ParseTuple(args, "sO:set_pluginpref", &var, &value)) +- return NULL; +- if (PyLong_Check(value)) { +- int intvalue = PyLong_AsLong(value); +- BEGIN_XCHAT_CALLS(NONE); +- result = hexchat_pluginpref_set_int(prefph, var, intvalue); +- END_XCHAT_CALLS(); +- } +- else if (PyUnicode_Check(value)) { +- char *charvalue = PyUnicode_AsUTF8(value); +- BEGIN_XCHAT_CALLS(NONE); +- result = hexchat_pluginpref_set_str(prefph, var, charvalue); +- END_XCHAT_CALLS(); +- } +- else +- result = 0; +- return PyBool_FromLong(result); +-} +- +-static PyObject * +-Module_hexchat_pluginpref_get(PyObject *self, PyObject *args) +-{ +- PluginObject *plugin = (PluginObject*)Plugin_GetCurrent(); +- hexchat_plugin *prefph = Plugin_GetHandle(plugin); +- PyObject *ret; +- char *var; +- char retstr[512]; +- int retint; +- int result; +- if (!PyArg_ParseTuple(args, "s:get_pluginpref", &var)) +- return NULL; +- +- /* This will always return numbers as integers. */ +- BEGIN_XCHAT_CALLS(NONE); +- result = hexchat_pluginpref_get_str(prefph, var, retstr); +- END_XCHAT_CALLS(); +- if (result) { +- if (strlen (retstr) <= 12) { +- BEGIN_XCHAT_CALLS(NONE); +- retint = hexchat_pluginpref_get_int(prefph, var); +- END_XCHAT_CALLS(); +- if ((retint == -1) && (strcmp(retstr, "-1") != 0)) +- ret = PyUnicode_FromString(retstr); +- else +- ret = PyLong_FromLong(retint); +- } else +- ret = PyUnicode_FromString(retstr); +- } +- else +- { +- Py_INCREF(Py_None); +- ret = Py_None; +- } +- return ret; +-} +- +-static PyObject * +-Module_hexchat_pluginpref_delete(PyObject *self, PyObject *args) +-{ +- PluginObject *plugin = (PluginObject*)Plugin_GetCurrent(); +- hexchat_plugin *prefph = Plugin_GetHandle(plugin); +- char *var; +- int result; +- if (!PyArg_ParseTuple(args, "s:del_pluginpref", &var)) +- return NULL; +- BEGIN_XCHAT_CALLS(NONE); +- result = hexchat_pluginpref_delete(prefph, var); +- END_XCHAT_CALLS(); +- return PyBool_FromLong(result); +-} +- +-static PyObject * +-Module_hexchat_pluginpref_list(PyObject *self, PyObject *args) +-{ +- PluginObject *plugin = (PluginObject*)Plugin_GetCurrent(); +- hexchat_plugin *prefph = Plugin_GetHandle(plugin); +- char list[4096]; +- char* token; +- int result; +- PyObject *pylist; +- pylist = PyList_New(0); +- BEGIN_XCHAT_CALLS(NONE); +- result = hexchat_pluginpref_list(prefph, list); +- END_XCHAT_CALLS(); +- if (result) { +- token = strtok(list, ","); +- while (token != NULL) { +- PyList_Append(pylist, PyUnicode_FromString(token)); +- token = strtok (NULL, ","); +- } +- } +- return pylist; +-} +- +-static PyObject * +-Module_hexchat_hook_command(PyObject *self, PyObject *args, PyObject *kwargs) +-{ +- char *name; +- PyObject *callback; +- PyObject *userdata = Py_None; +- int priority = HEXCHAT_PRI_NORM; +- char *help = NULL; +- PyObject *plugin; +- Hook *hook; +- char *kwlist[] = {"name", "callback", "userdata", +- "priority", "help", 0}; +- +- if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|Oiz:hook_command", +- kwlist, &name, &callback, &userdata, +- &priority, &help)) +- return NULL; +- +- plugin = Plugin_GetCurrent(); +- if (plugin == NULL) +- return NULL; +- if (!PyCallable_Check(callback)) { +- PyErr_SetString(PyExc_TypeError, "callback is not callable"); +- return NULL; +- } +- +- hook = Plugin_AddHook(HOOK_XCHAT, plugin, callback, userdata, name, NULL); +- if (hook == NULL) +- return NULL; +- +- BEGIN_XCHAT_CALLS(NONE); +- hook->data = (void*)hexchat_hook_command(ph, name, priority, +- Callback_Command, help, hook); +- END_XCHAT_CALLS(); +- +- return PyLong_FromVoidPtr(hook); +-} +- +-static PyObject * +-Module_hexchat_hook_server(PyObject *self, PyObject *args, PyObject *kwargs) +-{ +- char *name; +- PyObject *callback; +- PyObject *userdata = Py_None; +- int priority = HEXCHAT_PRI_NORM; +- PyObject *plugin; +- Hook *hook; +- char *kwlist[] = {"name", "callback", "userdata", "priority", 0}; +- +- if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|Oi:hook_server", +- kwlist, &name, &callback, &userdata, +- &priority)) +- return NULL; +- +- plugin = Plugin_GetCurrent(); +- if (plugin == NULL) +- return NULL; +- if (!PyCallable_Check(callback)) { +- PyErr_SetString(PyExc_TypeError, "callback is not callable"); +- return NULL; +- } +- +- hook = Plugin_AddHook(HOOK_XCHAT, plugin, callback, userdata, NULL, NULL); +- if (hook == NULL) +- return NULL; +- +- BEGIN_XCHAT_CALLS(NONE); +- hook->data = (void*)hexchat_hook_server_attrs(ph, name, priority, +- Callback_Server, hook); +- END_XCHAT_CALLS(); +- +- return PyLong_FromVoidPtr(hook); +-} +- +-static PyObject * +-Module_hexchat_hook_server_attrs(PyObject *self, PyObject *args, PyObject *kwargs) +-{ +- char *name; +- PyObject *callback; +- PyObject *userdata = Py_None; +- int priority = HEXCHAT_PRI_NORM; +- PyObject *plugin; +- Hook *hook; +- char *kwlist[] = {"name", "callback", "userdata", "priority", 0}; +- +- if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|Oi:hook_server", +- kwlist, &name, &callback, &userdata, +- &priority)) +- return NULL; +- +- plugin = Plugin_GetCurrent(); +- if (plugin == NULL) +- return NULL; +- if (!PyCallable_Check(callback)) { +- PyErr_SetString(PyExc_TypeError, "callback is not callable"); +- return NULL; +- } +- +- hook = Plugin_AddHook(HOOK_XCHAT_ATTR, plugin, callback, userdata, NULL, NULL); +- if (hook == NULL) +- return NULL; +- +- BEGIN_XCHAT_CALLS(NONE); +- hook->data = (void*)hexchat_hook_server_attrs(ph, name, priority, +- Callback_Server, hook); +- END_XCHAT_CALLS(); +- +- return PyLong_FromVoidPtr(hook); +-} +- +-static PyObject * +-Module_hexchat_hook_print(PyObject *self, PyObject *args, PyObject *kwargs) +-{ +- char *name; +- PyObject *callback; +- PyObject *userdata = Py_None; +- int priority = HEXCHAT_PRI_NORM; +- PyObject *plugin; +- Hook *hook; +- char *kwlist[] = {"name", "callback", "userdata", "priority", 0}; +- +- if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|Oi:hook_print", +- kwlist, &name, &callback, &userdata, +- &priority)) +- return NULL; +- +- plugin = Plugin_GetCurrent(); +- if (plugin == NULL) +- return NULL; +- if (!PyCallable_Check(callback)) { +- PyErr_SetString(PyExc_TypeError, "callback is not callable"); +- return NULL; +- } +- +- hook = Plugin_AddHook(HOOK_XCHAT, plugin, callback, userdata, name, NULL); +- if (hook == NULL) +- return NULL; +- +- BEGIN_XCHAT_CALLS(NONE); +- hook->data = (void*)hexchat_hook_print(ph, name, priority, +- Callback_Print, hook); +- END_XCHAT_CALLS(); +- +- return PyLong_FromVoidPtr(hook); +-} +- +-static PyObject * +-Module_hexchat_hook_print_attrs(PyObject *self, PyObject *args, PyObject *kwargs) +-{ +- char *name; +- PyObject *callback; +- PyObject *userdata = Py_None; +- int priority = HEXCHAT_PRI_NORM; +- PyObject *plugin; +- Hook *hook; +- char *kwlist[] = {"name", "callback", "userdata", "priority", 0}; +- +- if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|Oi:hook_print_attrs", +- kwlist, &name, &callback, &userdata, +- &priority)) +- return NULL; +- +- plugin = Plugin_GetCurrent(); +- if (plugin == NULL) +- return NULL; +- if (!PyCallable_Check(callback)) { +- PyErr_SetString(PyExc_TypeError, "callback is not callable"); +- return NULL; +- } +- +- hook = Plugin_AddHook(HOOK_XCHAT_ATTR, plugin, callback, userdata, name, NULL); +- if (hook == NULL) +- return NULL; +- +- BEGIN_XCHAT_CALLS(NONE); +- hook->data = (void*)hexchat_hook_print_attrs(ph, name, priority, +- Callback_Print_Attrs, hook); +- END_XCHAT_CALLS(); +- +- return PyLong_FromVoidPtr(hook); +-} +- +-static PyObject * +-Module_hexchat_hook_timer(PyObject *self, PyObject *args, PyObject *kwargs) +-{ +- int timeout; +- PyObject *callback; +- PyObject *userdata = Py_None; +- PyObject *plugin; +- Hook *hook; +- char *kwlist[] = {"timeout", "callback", "userdata", 0}; +- +- if (!PyArg_ParseTupleAndKeywords(args, kwargs, "iO|O:hook_timer", +- kwlist, &timeout, &callback, +- &userdata)) +- return NULL; +- +- plugin = Plugin_GetCurrent(); +- if (plugin == NULL) +- return NULL; +- if (!PyCallable_Check(callback)) { +- PyErr_SetString(PyExc_TypeError, "callback is not callable"); +- return NULL; +- } +- +- hook = Plugin_AddHook(HOOK_XCHAT, plugin, callback, userdata, NULL, NULL); +- if (hook == NULL) +- return NULL; +- +- BEGIN_XCHAT_CALLS(NONE); +- hook->data = (void*)hexchat_hook_timer(ph, timeout, +- Callback_Timer, hook); +- END_XCHAT_CALLS(); +- +- return PyLong_FromVoidPtr(hook); +-} +- +-static PyObject * +-Module_hexchat_hook_unload(PyObject *self, PyObject *args, PyObject *kwargs) +-{ +- PyObject *callback; +- PyObject *userdata = Py_None; +- PyObject *plugin; +- Hook *hook; +- char *kwlist[] = {"callback", "userdata", 0}; +- +- if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|O:hook_unload", +- kwlist, &callback, &userdata)) +- return NULL; +- +- plugin = Plugin_GetCurrent(); +- if (plugin == NULL) +- return NULL; +- if (!PyCallable_Check(callback)) { +- PyErr_SetString(PyExc_TypeError, "callback is not callable"); +- return NULL; +- } +- +- hook = Plugin_AddHook(HOOK_UNLOAD, plugin, callback, userdata, NULL, NULL); +- if (hook == NULL) +- return NULL; +- +- return PyLong_FromVoidPtr(hook); +-} +- +-static PyObject * +-Module_hexchat_unhook(PyObject *self, PyObject *args) +-{ +- PyObject *plugin; +- PyObject *obj; +- Hook *hook; +- if (!PyArg_ParseTuple(args, "O:unhook", &obj)) +- return NULL; +- plugin = Plugin_GetCurrent(); +- if (plugin == NULL) +- return NULL; +- +- if (PyUnicode_Check (obj)) +- { +- hook = Plugin_FindHook(plugin, PyUnicode_AsUTF8 (obj)); +- while (hook) +- { +- Plugin_RemoveHook(plugin, hook); +- hook = Plugin_FindHook(plugin, PyUnicode_AsUTF8 (obj)); +- } +- } +- else +- { +- hook = (Hook *)PyLong_AsVoidPtr(obj); +- Plugin_RemoveHook(plugin, hook); +- } +- +- Py_RETURN_NONE; +-} +- +-static PyObject * +-Module_xchat_get_list(PyObject *self, PyObject *args) +-{ +- hexchat_list *list; +- PyObject *l; +- const char *name; +- const char *const *fields; +- int i; +- +- if (!PyArg_ParseTuple(args, "s:get_list", &name)) +- return NULL; +- /* This function is thread safe, and returns statically +- * allocated data. */ +- fields = hexchat_list_fields(ph, "lists"); +- for (i = 0; fields[i]; i++) { +- if (strcmp(fields[i], name) == 0) { +- /* Use the static allocated one. */ +- name = fields[i]; +- break; +- } +- } +- if (fields[i] == NULL) { +- PyErr_SetString(PyExc_KeyError, "list not available"); +- return NULL; +- } +- l = PyList_New(0); +- if (l == NULL) +- return NULL; +- BEGIN_XCHAT_CALLS(RESTORE_CONTEXT); +- list = hexchat_list_get(ph, (char*)name); +- if (list == NULL) +- goto error; +- fields = hexchat_list_fields(ph, (char*)name); +- while (hexchat_list_next(ph, list)) { +- PyObject *o = ListItem_New(name); +- if (o == NULL || PyList_Append(l, o) == -1) { +- Py_XDECREF(o); +- goto error; +- } +- Py_DECREF(o); /* l is holding a reference */ +- for (i = 0; fields[i]; i++) { +- const char *fld = fields[i]+1; +- PyObject *attr = NULL; +- const char *sattr; +- int iattr; +- time_t tattr; +- switch(fields[i][0]) { +- case 's': +- sattr = hexchat_list_str(ph, list, (char*)fld); +- attr = PyUnicode_FromString(sattr?sattr:""); +- break; +- case 'i': +- iattr = hexchat_list_int(ph, list, (char*)fld); +- attr = PyLong_FromLong((long)iattr); +- break; +- case 't': +- tattr = hexchat_list_time(ph, list, (char*)fld); +- attr = PyLong_FromLong((long)tattr); +- break; +- case 'p': +- sattr = hexchat_list_str(ph, list, (char*)fld); +- if (strcmp(fld, "context") == 0) { +- attr = Context_FromContext( +- (hexchat_context*)sattr); +- break; +- } +- default: /* ignore unknown (newly added?) types */ +- continue; +- } +- if (attr == NULL) +- goto error; +- PyObject_SetAttrString(o, (char*)fld, attr); /* add reference on attr in o */ +- Py_DECREF(attr); /* make o own attr */ +- } +- } +- hexchat_list_free(ph, list); +- goto exit; +-error: +- if (list) +- hexchat_list_free(ph, list); +- Py_DECREF(l); +- l = NULL; +- +-exit: +- END_XCHAT_CALLS(); +- return l; +-} +- +-static PyObject * +-Module_xchat_get_lists(PyObject *self, PyObject *args) +-{ +- PyObject *l, *o; +- const char *const *fields; +- int i; +- /* This function is thread safe, and returns statically +- * allocated data. */ +- fields = hexchat_list_fields(ph, "lists"); +- l = PyList_New(0); +- if (l == NULL) +- return NULL; +- for (i = 0; fields[i]; i++) { +- o = PyUnicode_FromString(fields[i]); +- if (o == NULL || PyList_Append(l, o) == -1) { +- Py_DECREF(l); +- Py_XDECREF(o); +- return NULL; +- } +- Py_DECREF(o); /* l is holding a reference */ +- } +- return l; +-} +- +-static PyObject * +-Module_hexchat_nickcmp(PyObject *self, PyObject *args) +-{ +- char *s1, *s2; +- if (!PyArg_ParseTuple(args, "ss:nickcmp", &s1, &s2)) +- return NULL; +- return PyLong_FromLong((long) hexchat_nickcmp(ph, s1, s2)); +-} +- +-static PyObject * +-Module_hexchat_strip(PyObject *self, PyObject *args) +-{ +- PyObject *result; +- char *str, *str2; +- int len = -1, flags = 1 | 2; +- if (!PyArg_ParseTuple(args, "s|ii:strip", &str, &len, &flags)) +- return NULL; +- str2 = hexchat_strip(ph, str, len, flags); +- result = PyUnicode_FromString(str2); +- hexchat_free(ph, str2); +- return result; +-} +- +-static PyMethodDef Module_xchat_methods[] = { +- {"command", Module_hexchat_command, +- METH_VARARGS}, +- {"prnt", Module_xchat_prnt, +- METH_VARARGS}, +- {"emit_print", (PyCFunction)Module_hexchat_emit_print, +- METH_VARARGS|METH_KEYWORDS}, +- {"get_info", Module_hexchat_get_info, +- METH_VARARGS}, +- {"get_prefs", Module_xchat_get_prefs, +- METH_VARARGS}, +- {"get_context", Module_hexchat_get_context, +- METH_NOARGS}, +- {"find_context", (PyCFunction)Module_hexchat_find_context, +- METH_VARARGS|METH_KEYWORDS}, +- {"set_pluginpref", Module_hexchat_pluginpref_set, +- METH_VARARGS}, +- {"get_pluginpref", Module_hexchat_pluginpref_get, +- METH_VARARGS}, +- {"del_pluginpref", Module_hexchat_pluginpref_delete, +- METH_VARARGS}, +- {"list_pluginpref", Module_hexchat_pluginpref_list, +- METH_VARARGS}, +- {"hook_command", (PyCFunction)Module_hexchat_hook_command, +- METH_VARARGS|METH_KEYWORDS}, +- {"hook_server", (PyCFunction)Module_hexchat_hook_server, +- METH_VARARGS|METH_KEYWORDS}, +- {"hook_server_attrs", (PyCFunction)Module_hexchat_hook_server_attrs, +- METH_VARARGS|METH_KEYWORDS}, +- {"hook_print", (PyCFunction)Module_hexchat_hook_print, +- METH_VARARGS|METH_KEYWORDS}, +- {"hook_print_attrs", (PyCFunction)Module_hexchat_hook_print_attrs, +- METH_VARARGS|METH_KEYWORDS}, +- {"hook_timer", (PyCFunction)Module_hexchat_hook_timer, +- METH_VARARGS|METH_KEYWORDS}, +- {"hook_unload", (PyCFunction)Module_hexchat_hook_unload, +- METH_VARARGS|METH_KEYWORDS}, +- {"unhook", Module_hexchat_unhook, +- METH_VARARGS}, +- {"get_list", Module_xchat_get_list, +- METH_VARARGS}, +- {"get_lists", Module_xchat_get_lists, +- METH_NOARGS}, +- {"nickcmp", Module_hexchat_nickcmp, +- METH_VARARGS}, +- {"strip", Module_hexchat_strip, +- METH_VARARGS}, +- {NULL, NULL} +-}; +- +-#ifdef IS_PY3K +-static struct PyModuleDef moduledef = { +- PyModuleDef_HEAD_INIT, +- "hexchat", /* m_name */ +- "HexChat Scripting Interface", /* m_doc */ +- -1, /* m_size */ +- Module_xchat_methods, /* m_methods */ +- NULL, /* m_reload */ +- NULL, /* m_traverse */ +- NULL, /* m_clear */ +- NULL, /* m_free */ +-}; +- +-static struct PyModuleDef xchat_moduledef = { +- PyModuleDef_HEAD_INIT, +- "xchat", /* m_name */ +- "HexChat Scripting Interface", /* m_doc */ +- -1, /* m_size */ +- Module_xchat_methods, /* m_methods */ +- NULL, /* m_reload */ +- NULL, /* m_traverse */ +- NULL, /* m_clear */ +- NULL, /* m_free */ +-}; +-#endif +- +-static PyObject * +-moduleinit_hexchat(void) +-{ +- PyObject *hm; +-#ifdef IS_PY3K +- hm = PyModule_Create(&moduledef); +-#else +- hm = Py_InitModule3("hexchat", Module_xchat_methods, "HexChat Scripting Interface"); +-#endif +- +- PyModule_AddIntConstant(hm, "EAT_NONE", HEXCHAT_EAT_NONE); +- PyModule_AddIntConstant(hm, "EAT_HEXCHAT", HEXCHAT_EAT_HEXCHAT); +- PyModule_AddIntConstant(hm, "EAT_XCHAT", HEXCHAT_EAT_HEXCHAT); /* for compat */ +- PyModule_AddIntConstant(hm, "EAT_PLUGIN", HEXCHAT_EAT_PLUGIN); +- PyModule_AddIntConstant(hm, "EAT_ALL", HEXCHAT_EAT_ALL); +- PyModule_AddIntConstant(hm, "PRI_HIGHEST", HEXCHAT_PRI_HIGHEST); +- PyModule_AddIntConstant(hm, "PRI_HIGH", HEXCHAT_PRI_HIGH); +- PyModule_AddIntConstant(hm, "PRI_NORM", HEXCHAT_PRI_NORM); +- PyModule_AddIntConstant(hm, "PRI_LOW", HEXCHAT_PRI_LOW); +- PyModule_AddIntConstant(hm, "PRI_LOWEST", HEXCHAT_PRI_LOWEST); +- +- PyObject_SetAttrString(hm, "__version__", Py_BuildValue("(ii)", VERSION_MAJOR, VERSION_MINOR)); +- +- return hm; +-} +- +-static PyObject * +-moduleinit_xchat(void) +-{ +- PyObject *xm; +-#ifdef IS_PY3K +- xm = PyModule_Create(&xchat_moduledef); +-#else +- xm = Py_InitModule3("xchat", Module_xchat_methods, "HexChat Scripting Interface"); +-#endif +- +- PyModule_AddIntConstant(xm, "EAT_NONE", HEXCHAT_EAT_NONE); +- PyModule_AddIntConstant(xm, "EAT_XCHAT", HEXCHAT_EAT_HEXCHAT); +- PyModule_AddIntConstant(xm, "EAT_PLUGIN", HEXCHAT_EAT_PLUGIN); +- PyModule_AddIntConstant(xm, "EAT_ALL", HEXCHAT_EAT_ALL); +- PyModule_AddIntConstant(xm, "PRI_HIGHEST", HEXCHAT_PRI_HIGHEST); +- PyModule_AddIntConstant(xm, "PRI_HIGH", HEXCHAT_PRI_HIGH); +- PyModule_AddIntConstant(xm, "PRI_NORM", HEXCHAT_PRI_NORM); +- PyModule_AddIntConstant(xm, "PRI_LOW", HEXCHAT_PRI_LOW); +- PyModule_AddIntConstant(xm, "PRI_LOWEST", HEXCHAT_PRI_LOWEST); +- +- PyObject_SetAttrString(xm, "__version__", Py_BuildValue("(ii)", VERSION_MAJOR, VERSION_MINOR)); +- +- return xm; +-} +- +-#ifdef IS_PY3K +-PyMODINIT_FUNC +-PyInit_hexchat(void) +-{ +- return moduleinit_hexchat(); +-} +-PyMODINIT_FUNC +-PyInit_xchat(void) +-{ +- return moduleinit_xchat(); +-} +-#else +-PyMODINIT_FUNC +-inithexchat(void) +-{ +- moduleinit_hexchat(); +-} +-PyMODINIT_FUNC +-initxchat(void) +-{ +- moduleinit_xchat(); +-} +-#endif +- +-/* ===================================================================== */ +-/* Python interactive interpreter functions */ +- +-static void +-IInterp_Exec(char *command) +-{ +- PyObject *m, *d, *o; +- char *buffer; +- int len; +- +- BEGIN_PLUGIN(interp_plugin); +- +- m = PyImport_AddModule("__main__"); +- if (m == NULL) { +- hexchat_print(ph, "Can't get __main__ module"); +- goto fail; +- } +- d = PyModule_GetDict(m); +- len = strlen(command); +- +- buffer = g_malloc(len + 2); +- memcpy(buffer, command, len); +- buffer[len] = '\n'; +- buffer[len+1] = 0; +- PyRun_SimpleString("import hexchat"); +- o = PyRun_StringFlags(buffer, Py_single_input, d, d, NULL); +- g_free(buffer); +- if (o == NULL) { +- PyErr_Print(); +- goto fail; +- } +- Py_DECREF(o); +- +-fail: +- END_PLUGIN(interp_plugin); +- return; +-} +- +-static int +-IInterp_Cmd(char *word[], char *word_eol[], void *userdata) +-{ +- char *channel = (char *) hexchat_get_info(ph, "channel"); +- if (channel && channel[0] == '>' && strcmp(channel, ">>python<<") == 0) { +- hexchat_printf(ph, ">>> %s\n", word_eol[1]); +- IInterp_Exec(word_eol[1]); +- return HEXCHAT_EAT_HEXCHAT; +- } +- return HEXCHAT_EAT_NONE; +-} +- +- +-/* ===================================================================== */ +-/* Python command handling */ +- +-static void +-Command_PyList(void) +-{ +- GSList *list; +- list = plugin_list; +- if (list == NULL) { +- hexchat_print(ph, "No python modules loaded"); +- } else { +- hexchat_print(ph, +- "Name Version Filename Description\n" +- "---- ------- -------- -----------\n"); +- while (list != NULL) { +- PluginObject *plg = (PluginObject *) list->data; +- char *basename = g_path_get_basename(plg->filename); +- hexchat_printf(ph, "%-12s %-8s %-20s %-10s\n", +- plg->name, +- *plg->version ? plg->version +- : "", +- basename, +- *plg->description ? plg->description +- : ""); +- g_free(basename); +- list = list->next; +- } +- hexchat_print(ph, "\n"); +- } +-} +- +-static void +-Command_PyLoad(char *filename) +-{ +- PyObject *plugin; +- RELEASE_XCHAT_LOCK(); +- plugin = Plugin_New(filename, xchatout); +- ACQUIRE_XCHAT_LOCK(); +- if (plugin) +- plugin_list = g_slist_append(plugin_list, plugin); +-} +- +-static void +-Command_PyUnload(char *name) +-{ +- PluginObject *plugin = Plugin_ByString(name); +- if (!plugin) { +- hexchat_print(ph, "Can't find a python plugin with that name"); +- } else { +- BEGIN_PLUGIN(plugin); +- Plugin_Delete((PyObject*)plugin); +- END_PLUGIN(plugin); +- plugin_list = g_slist_remove(plugin_list, plugin); +- } +-} +- +-static void +-Command_PyReload(char *name) +-{ +- PluginObject *plugin = Plugin_ByString(name); +- if (!plugin) { +- hexchat_print(ph, "Can't find a python plugin with that name"); +- } else { +- char *filename = g_strdup(plugin->filename); +- Command_PyUnload(filename); +- Command_PyLoad(filename); +- g_free(filename); +- } +-} +- +-static void +-Command_PyAbout(void) +-{ +- hexchat_print(ph, about); +-} +- +-static int +-Command_Py(char *word[], char *word_eol[], void *userdata) +-{ +- char *cmd = word[2]; +- int ok = 0; +- if (strcasecmp(cmd, "LIST") == 0) { +- ok = 1; +- Command_PyList(); +- } else if (strcasecmp(cmd, "EXEC") == 0) { +- if (word[3][0]) { +- ok = 1; +- IInterp_Exec(word_eol[3]); +- } +- } else if (strcasecmp(cmd, "LOAD") == 0) { +- if (word[3][0]) { +- ok = 1; +- Command_PyLoad(word[3]); +- } +- } else if (strcasecmp(cmd, "UNLOAD") == 0) { +- if (word[3][0]) { +- ok = 1; +- Command_PyUnload(word[3]); +- } +- } else if (strcasecmp(cmd, "RELOAD") == 0) { +- if (word[3][0]) { +- ok = 1; +- Command_PyReload(word[3]); +- } +- } else if (strcasecmp(cmd, "CONSOLE") == 0) { +- ok = 1; +- hexchat_command(ph, "QUERY >>python<<"); +- } else if (strcasecmp(cmd, "ABOUT") == 0) { +- ok = 1; +- Command_PyAbout(); +- } +- if (!ok) +- hexchat_print(ph, usage); +- return HEXCHAT_EAT_ALL; +-} +- +-static int +-Command_Load(char *word[], char *word_eol[], void *userdata) +-{ +- int len = strlen(word[2]); +- if (len > 3 && strcasecmp(".py", word[2]+len-3) == 0) { +- Command_PyLoad(word[2]); +- return HEXCHAT_EAT_HEXCHAT; +- } +- return HEXCHAT_EAT_NONE; +-} +- +-static int +-Command_Reload(char *word[], char *word_eol[], void *userdata) +-{ +- int len = strlen(word[2]); +- if (len > 3 && strcasecmp(".py", word[2]+len-3) == 0) { +- Command_PyReload(word[2]); +- return HEXCHAT_EAT_HEXCHAT; +- } +- return HEXCHAT_EAT_NONE; +-} +- +-static int +-Command_Unload(char *word[], char *word_eol[], void *userdata) +-{ +- int len = strlen(word[2]); +- if (len > 3 && strcasecmp(".py", word[2]+len-3) == 0) { +- Command_PyUnload(word[2]); +- return HEXCHAT_EAT_HEXCHAT; +- } +- return HEXCHAT_EAT_NONE; +-} +- +-/* ===================================================================== */ +-/* Autoload function */ +- +-/* ===================================================================== */ +-/* (De)initialization functions */ +- +-static int initialized = 0; +-static int reinit_tried = 0; +- +-void +-hexchat_plugin_get_info(char **name, char **desc, char **version, void **reserved) +-{ +- *name = "Python"; +- *version = VERSION; +- *desc = "Python scripting interface"; +- if (reserved) +- *reserved = NULL; +-} +- +-int +-hexchat_plugin_init(hexchat_plugin *plugin_handle, +- char **plugin_name, +- char **plugin_desc, +- char **plugin_version, +- char *arg) +-{ +-#ifdef IS_PY3K +- wchar_t *argv[] = { L"", 0 }; +-#else +- char *argv[] = { "", 0 }; +-#endif +- +- ph = plugin_handle; +- +- /* Block double initalization. */ +- if (initialized != 0) { +- hexchat_print(ph, "Python interface already loaded"); +- /* deinit is called even when init fails, so keep track +- * of a reinit failure. */ +- reinit_tried++; +- return 0; +- } +- initialized = 1; +- +- *plugin_name = "Python"; +- *plugin_version = VERSION; +- +- /* FIXME You can't free this since it's used as long as the plugin's +- * loaded, but if you unload it, everything belonging to the plugin is +- * supposed to be freed anyway. +- */ +- *plugin_desc = g_strdup_printf ("Python %d scripting interface", PY_MAJOR_VERSION); +- +- /* Initialize python. */ +-#ifdef IS_PY3K +- Py_SetProgramName(L"hexchat"); +- PyImport_AppendInittab("hexchat", PyInit_hexchat); +- PyImport_AppendInittab("xchat", PyInit_xchat); +-#else +- Py_SetProgramName("hexchat"); +- PyImport_AppendInittab("hexchat", inithexchat); +- PyImport_AppendInittab("xchat", initxchat); +-#endif +- Py_Initialize(); +- PySys_SetArgv(1, argv); +- +- xchatout_buffer = g_string_new (NULL); +- xchatout = XChatOut_New(); +- if (xchatout == NULL) { +- hexchat_print(ph, "Can't allocate xchatout object"); +- return 0; +- } +- +-#ifdef WITH_THREAD +- PyEval_InitThreads(); +- xchat_lock = PyThread_allocate_lock(); +- if (xchat_lock == NULL) { +- hexchat_print(ph, "Can't allocate hexchat lock"); +- Py_DECREF(xchatout); +- xchatout = NULL; +- return 0; +- } +-#endif +- +- main_tstate = PyEval_SaveThread(); +- +- interp_plugin = Plugin_New(NULL, xchatout); +- if (interp_plugin == NULL) { +- hexchat_print(ph, "Plugin_New() failed.\n"); +-#ifdef WITH_THREAD +- PyThread_free_lock(xchat_lock); +-#endif +- Py_DECREF(xchatout); +- xchatout = NULL; +- return 0; +- } +- +- +- hexchat_hook_command(ph, "", HEXCHAT_PRI_NORM, IInterp_Cmd, 0, 0); +- hexchat_hook_command(ph, "PY", HEXCHAT_PRI_NORM, Command_Py, usage, 0); +- hexchat_hook_command(ph, "LOAD", HEXCHAT_PRI_NORM, Command_Load, 0, 0); +- hexchat_hook_command(ph, "UNLOAD", HEXCHAT_PRI_NORM, Command_Unload, 0, 0); +- hexchat_hook_command(ph, "RELOAD", HEXCHAT_PRI_NORM, Command_Reload, 0, 0); +-#ifdef WITH_THREAD +- thread_timer = hexchat_hook_timer(ph, 300, Callback_ThreadTimer, NULL); +-#endif +- +- hexchat_print(ph, "Python interface loaded\n"); +- +- Util_Autoload(); +- return 1; +-} +- +-int +-hexchat_plugin_deinit(void) +-{ +- GSList *list; +- +- /* A reinitialization was tried. Just give up and live the +- * environment as is. We are still alive. */ +- if (reinit_tried) { +- reinit_tried--; +- return 1; +- } +- +- list = plugin_list; +- while (list != NULL) { +- PyObject *plugin = (PyObject *) list->data; +- BEGIN_PLUGIN(plugin); +- Plugin_Delete(plugin); +- END_PLUGIN(plugin); +- list = list->next; +- } +- g_slist_free(plugin_list); +- plugin_list = NULL; +- +- /* Reset xchatout buffer. */ +- g_string_free (xchatout_buffer, TRUE); +- xchatout_buffer = NULL; +- +- if (interp_plugin) { +- PyThreadState *tstate = ((PluginObject*)interp_plugin)->tstate; +- PyThreadState_Swap(tstate); +- Py_EndInterpreter(tstate); +- Py_DECREF(interp_plugin); +- interp_plugin = NULL; +- } +- +- /* Switch back to the main thread state. */ +- if (main_tstate) { +- PyEval_RestoreThread(main_tstate); +- PyThreadState_Swap(main_tstate); +- main_tstate = NULL; +- } +- Py_Finalize(); +- +-#ifdef WITH_THREAD +- if (thread_timer != NULL) { +- hexchat_unhook(ph, thread_timer); +- thread_timer = NULL; +- } +- PyThread_free_lock(xchat_lock); +-#endif +- +- hexchat_print(ph, "Python interface unloaded\n"); +- initialized = 0; +- +- return 1; +-} +- +diff --color -Nur hexchat-2.14.3/plugins/python/python.def hexchat/plugins/python/python.def +--- hexchat-2.14.3/plugins/python/python.def 2019-12-20 22:43:47.652401700 -0800 ++++ hexchat/plugins/python/python.def 2020-11-06 15:03:00.402820502 -0800 +@@ -1,4 +1,3 @@ + EXPORTS + hexchat_plugin_init + hexchat_plugin_deinit +-hexchat_plugin_get_info +diff --color -Nur hexchat-2.14.3/plugins/python/python.py hexchat/plugins/python/python.py +--- hexchat-2.14.3/plugins/python/python.py 1969-12-31 16:00:00.000000000 -0800 ++++ hexchat/plugins/python/python.py 2020-11-06 15:03:00.402820502 -0800 +@@ -0,0 +1,554 @@ ++from __future__ import print_function ++ ++import importlib ++import os ++import pydoc ++import signal ++import sys ++import traceback ++import weakref ++from contextlib import contextmanager ++ ++from _hexchat_embedded import ffi, lib ++ ++if sys.version_info < (3, 0): ++ from io import BytesIO as HelpEater ++else: ++ from io import StringIO as HelpEater ++ ++if not hasattr(sys, 'argv'): ++ sys.argv = [''] ++ ++VERSION = b'2.0' # Sync with hexchat.__version__ ++PLUGIN_NAME = ffi.new('char[]', b'Python') ++PLUGIN_DESC = ffi.new('char[]', b'Python %d.%d scripting interface' % (sys.version_info[0], sys.version_info[1])) ++PLUGIN_VERSION = ffi.new('char[]', VERSION) ++ ++# TODO: Constants should be screaming snake case ++hexchat = None ++local_interp = None ++hexchat_stdout = None ++plugins = set() ++ ++ ++@contextmanager ++def redirected_stdout(): ++ sys.stdout = sys.__stdout__ ++ sys.stderr = sys.__stderr__ ++ yield ++ sys.stdout = hexchat_stdout ++ sys.stderr = hexchat_stdout ++ ++ ++if os.getenv('HEXCHAT_LOG_PYTHON'): ++ def log(*args): ++ with redirected_stdout(): ++ print(*args) ++ ++else: ++ def log(*args): ++ pass ++ ++ ++class Stdout: ++ def __init__(self): ++ self.buffer = bytearray() ++ ++ def write(self, string): ++ string = string.encode() ++ idx = string.rfind(b'\n') ++ if idx != -1: ++ self.buffer += string[:idx] ++ lib.hexchat_print(lib.ph, bytes(self.buffer)) ++ self.buffer = bytearray(string[idx + 1:]) ++ else: ++ self.buffer += string ++ ++ def isatty(self): ++ return False ++ ++ ++class Attribute: ++ def __init__(self): ++ self.time = 0 ++ ++ def __repr__(self): ++ return ''.format(id(self)) ++ ++ ++class Hook: ++ def __init__(self, plugin, callback, userdata, is_unload): ++ self.is_unload = is_unload ++ self.plugin = weakref.proxy(plugin) ++ self.callback = callback ++ self.userdata = userdata ++ self.hexchat_hook = None ++ self.handle = ffi.new_handle(weakref.proxy(self)) ++ ++ def __del__(self): ++ log('Removing hook', id(self)) ++ if self.is_unload is False: ++ assert self.hexchat_hook is not None ++ lib.hexchat_unhook(lib.ph, self.hexchat_hook) ++ ++ ++if sys.version_info[0] == 2: ++ def compile_file(data, filename): ++ return compile(data, filename, 'exec', dont_inherit=True) ++ ++ ++ def compile_line(string): ++ try: ++ return compile(string, '', 'eval', dont_inherit=True) ++ ++ except SyntaxError: ++ # For some reason `print` is invalid for eval ++ # This will hide any return value though ++ return compile(string, '', 'exec', dont_inherit=True) ++else: ++ def compile_file(data, filename): ++ return compile(data, filename, 'exec', optimize=2, dont_inherit=True) ++ ++ ++ def compile_line(string): ++ # newline appended to solve unexpected EOF issues ++ return compile(string + '\n', '', 'single', optimize=2, dont_inherit=True) ++ ++ ++class Plugin: ++ def __init__(self): ++ self.ph = None ++ self.name = '' ++ self.filename = '' ++ self.version = '' ++ self.description = '' ++ self.hooks = set() ++ self.globals = { ++ '__plugin': weakref.proxy(self), ++ '__name__': '__main__', ++ } ++ ++ def add_hook(self, callback, userdata, is_unload=False): ++ hook = Hook(self, callback, userdata, is_unload=is_unload) ++ self.hooks.add(hook) ++ return hook ++ ++ def remove_hook(self, hook): ++ for h in self.hooks: ++ if id(h) == hook: ++ ud = h.userdata ++ self.hooks.remove(h) ++ return ud ++ ++ log('Hook not found') ++ return None ++ ++ def loadfile(self, filename): ++ try: ++ self.filename = filename ++ with open(filename) as f: ++ data = f.read() ++ compiled = compile_file(data, filename) ++ exec(compiled, self.globals) ++ ++ try: ++ self.name = self.globals['__module_name__'] ++ ++ except KeyError: ++ lib.hexchat_print(lib.ph, b'Failed to load module: __module_name__ must be set') ++ ++ return False ++ ++ self.version = self.globals.get('__module_version__', '') ++ self.description = self.globals.get('__module_description__', '') ++ self.ph = lib.hexchat_plugingui_add(lib.ph, filename.encode(), self.name.encode(), ++ self.description.encode(), self.version.encode(), ffi.NULL) ++ ++ except Exception as e: ++ lib.hexchat_print(lib.ph, 'Failed to load module: {}'.format(e).encode()) ++ traceback.print_exc() ++ return False ++ ++ return True ++ ++ def __del__(self): ++ log('unloading', self.filename) ++ for hook in self.hooks: ++ if hook.is_unload is True: ++ try: ++ hook.callback(hook.userdata) ++ ++ except Exception as e: ++ log('Failed to run hook:', e) ++ traceback.print_exc() ++ ++ del self.hooks ++ if self.ph is not None: ++ lib.hexchat_plugingui_remove(lib.ph, self.ph) ++ ++ ++if sys.version_info[0] == 2: ++ def __decode(string): ++ return string ++ ++else: ++ def __decode(string): ++ return string.decode() ++ ++ ++# There can be empty entries between non-empty ones so find the actual last value ++def wordlist_len(words): ++ for i in range(31, 1, -1): ++ if ffi.string(words[i]): ++ return i ++ ++ return 0 ++ ++ ++def create_wordlist(words): ++ size = wordlist_len(words) ++ return [__decode(ffi.string(words[i])) for i in range(1, size + 1)] ++ ++ ++# This function only exists for compat reasons with the C plugin ++# It turns the word list from print hooks into a word_eol list ++# This makes no sense to do... ++def create_wordeollist(words): ++ words = reversed(words) ++ accum = None ++ ret = [] ++ for word in words: ++ if accum is None: ++ accum = word ++ ++ elif word: ++ last = accum ++ accum = ' '.join((word, last)) ++ ++ ret.insert(0, accum) ++ ++ return ret ++ ++ ++def to_cb_ret(value): ++ if value is None: ++ return 0 ++ ++ return int(value) ++ ++ ++@ffi.def_extern() ++def _on_command_hook(word, word_eol, userdata): ++ hook = ffi.from_handle(userdata) ++ word = create_wordlist(word) ++ word_eol = create_wordlist(word_eol) ++ return to_cb_ret(hook.callback(word, word_eol, hook.userdata)) ++ ++ ++@ffi.def_extern() ++def _on_print_hook(word, userdata): ++ hook = ffi.from_handle(userdata) ++ word = create_wordlist(word) ++ word_eol = create_wordeollist(word) ++ return to_cb_ret(hook.callback(word, word_eol, hook.userdata)) ++ ++ ++@ffi.def_extern() ++def _on_print_attrs_hook(word, attrs, userdata): ++ hook = ffi.from_handle(userdata) ++ word = create_wordlist(word) ++ word_eol = create_wordeollist(word) ++ attr = Attribute() ++ attr.time = attrs.server_time_utc ++ return to_cb_ret(hook.callback(word, word_eol, hook.userdata, attr)) ++ ++ ++@ffi.def_extern() ++def _on_server_hook(word, word_eol, userdata): ++ hook = ffi.from_handle(userdata) ++ word = create_wordlist(word) ++ word_eol = create_wordlist(word_eol) ++ return to_cb_ret(hook.callback(word, word_eol, hook.userdata)) ++ ++ ++@ffi.def_extern() ++def _on_server_attrs_hook(word, word_eol, attrs, userdata): ++ hook = ffi.from_handle(userdata) ++ word = create_wordlist(word) ++ word_eol = create_wordlist(word_eol) ++ attr = Attribute() ++ attr.time = attrs.server_time_utc ++ return to_cb_ret(hook.callback(word, word_eol, hook.userdata, attr)) ++ ++ ++@ffi.def_extern() ++def _on_timer_hook(userdata): ++ hook = ffi.from_handle(userdata) ++ if hook.callback(hook.userdata) is True: ++ return 1 ++ ++ hook.is_unload = True # Don't unhook ++ for h in hook.plugin.hooks: ++ if h == hook: ++ hook.plugin.hooks.remove(h) ++ break ++ ++ return 0 ++ ++ ++@ffi.def_extern(error=3) ++def _on_say_command(word, word_eol, userdata): ++ channel = ffi.string(lib.hexchat_get_info(lib.ph, b'channel')) ++ if channel == b'>>python<<': ++ python = ffi.string(word_eol[1]) ++ lib.hexchat_print(lib.ph, b'>>> ' + python) ++ exec_in_interp(__decode(python)) ++ return 1 ++ ++ return 0 ++ ++ ++def load_filename(filename): ++ filename = os.path.expanduser(filename) ++ if not os.path.isabs(filename): ++ configdir = __decode(ffi.string(lib.hexchat_get_info(lib.ph, b'configdir'))) ++ ++ filename = os.path.join(configdir, 'addons', filename) ++ ++ if filename and not any(plugin.filename == filename for plugin in plugins): ++ plugin = Plugin() ++ if plugin.loadfile(filename): ++ plugins.add(plugin) ++ return True ++ ++ return False ++ ++ ++def unload_name(name): ++ if name: ++ for plugin in plugins: ++ if name in (plugin.name, plugin.filename, os.path.basename(plugin.filename)): ++ plugins.remove(plugin) ++ return True ++ ++ return False ++ ++ ++def reload_name(name): ++ if name: ++ for plugin in plugins: ++ if name in (plugin.name, plugin.filename, os.path.basename(plugin.filename)): ++ filename = plugin.filename ++ plugins.remove(plugin) ++ return load_filename(filename) ++ ++ return False ++ ++ ++@contextmanager ++def change_cwd(path): ++ old_cwd = os.getcwd() ++ os.chdir(path) ++ yield ++ os.chdir(old_cwd) ++ ++ ++def autoload(): ++ configdir = __decode(ffi.string(lib.hexchat_get_info(lib.ph, b'configdir'))) ++ addondir = os.path.join(configdir, 'addons') ++ try: ++ with change_cwd(addondir): # Maintaining old behavior ++ for f in os.listdir(addondir): ++ if f.endswith('.py'): ++ log('Autoloading', f) ++ # TODO: Set cwd ++ load_filename(os.path.join(addondir, f)) ++ ++ except FileNotFoundError as e: ++ log('Autoload failed', e) ++ ++ ++def list_plugins(): ++ if not plugins: ++ lib.hexchat_print(lib.ph, b'No python modules loaded') ++ return ++ ++ tbl_headers = [b'Name', b'Version', b'Filename', b'Description'] ++ tbl = [ ++ tbl_headers, ++ [(b'-' * len(s)) for s in tbl_headers] ++ ] ++ ++ for plugin in plugins: ++ basename = os.path.basename(plugin.filename).encode() ++ name = plugin.name.encode() ++ version = plugin.version.encode() if plugin.version else b'' ++ description = plugin.description.encode() if plugin.description else b'' ++ tbl.append((name, version, basename, description)) ++ ++ column_sizes = [ ++ max(len(item) for item in column) ++ for column in zip(*tbl) ++ ] ++ ++ for row in tbl: ++ lib.hexchat_print(lib.ph, b' '.join(item.ljust(column_sizes[i]) ++ for i, item in enumerate(row))) ++ lib.hexchat_print(lib.ph, b'') ++ ++ ++def exec_in_interp(python): ++ global local_interp ++ ++ if not python: ++ return ++ ++ if local_interp is None: ++ local_interp = Plugin() ++ local_interp.locals = {} ++ local_interp.globals['hexchat'] = hexchat ++ ++ code = compile_line(python) ++ try: ++ ret = eval(code, local_interp.globals, local_interp.locals) ++ if ret is not None: ++ lib.hexchat_print(lib.ph, '{}'.format(ret).encode()) ++ ++ except Exception as e: ++ traceback.print_exc(file=hexchat_stdout) ++ ++ ++@ffi.def_extern() ++def _on_load_command(word, word_eol, userdata): ++ filename = ffi.string(word[2]) ++ if filename.endswith(b'.py'): ++ load_filename(__decode(filename)) ++ return 3 ++ ++ return 0 ++ ++ ++@ffi.def_extern() ++def _on_unload_command(word, word_eol, userdata): ++ filename = ffi.string(word[2]) ++ if filename.endswith(b'.py'): ++ unload_name(__decode(filename)) ++ return 3 ++ ++ return 0 ++ ++ ++@ffi.def_extern() ++def _on_reload_command(word, word_eol, userdata): ++ filename = ffi.string(word[2]) ++ if filename.endswith(b'.py'): ++ reload_name(__decode(filename)) ++ return 3 ++ ++ return 0 ++ ++ ++@ffi.def_extern(error=3) ++def _on_py_command(word, word_eol, userdata): ++ subcmd = __decode(ffi.string(word[2])).lower() ++ ++ if subcmd == 'exec': ++ python = __decode(ffi.string(word_eol[3])) ++ exec_in_interp(python) ++ ++ elif subcmd == 'load': ++ filename = __decode(ffi.string(word[3])) ++ load_filename(filename) ++ ++ elif subcmd == 'unload': ++ name = __decode(ffi.string(word[3])) ++ if not unload_name(name): ++ lib.hexchat_print(lib.ph, b'Can\'t find a python plugin with that name') ++ ++ elif subcmd == 'reload': ++ name = __decode(ffi.string(word[3])) ++ if not reload_name(name): ++ lib.hexchat_print(lib.ph, b'Can\'t find a python plugin with that name') ++ ++ elif subcmd == 'console': ++ lib.hexchat_command(lib.ph, b'QUERY >>python<<') ++ ++ elif subcmd == 'list': ++ list_plugins() ++ ++ elif subcmd == 'about': ++ lib.hexchat_print(lib.ph, b'HexChat Python interface version ' + VERSION) ++ ++ else: ++ lib.hexchat_command(lib.ph, b'HELP PY') ++ ++ return 3 ++ ++ ++@ffi.def_extern() ++def _on_plugin_init(plugin_name, plugin_desc, plugin_version, arg, libdir): ++ global hexchat ++ global hexchat_stdout ++ ++ signal.signal(signal.SIGINT, signal.SIG_DFL) ++ ++ plugin_name[0] = PLUGIN_NAME ++ plugin_desc[0] = PLUGIN_DESC ++ plugin_version[0] = PLUGIN_VERSION ++ ++ try: ++ libdir = __decode(ffi.string(libdir)) ++ modpath = os.path.join(libdir, '..', 'python') ++ sys.path.append(os.path.abspath(modpath)) ++ hexchat = importlib.import_module('hexchat') ++ ++ except (UnicodeDecodeError, ImportError) as e: ++ lib.hexchat_print(lib.ph, b'Failed to import module: ' + repr(e).encode()) ++ ++ return 0 ++ ++ hexchat_stdout = Stdout() ++ sys.stdout = hexchat_stdout ++ sys.stderr = hexchat_stdout ++ pydoc.help = pydoc.Helper(HelpEater(), HelpEater()) ++ ++ lib.hexchat_hook_command(lib.ph, b'', 0, lib._on_say_command, ffi.NULL, ffi.NULL) ++ lib.hexchat_hook_command(lib.ph, b'LOAD', 0, lib._on_load_command, ffi.NULL, ffi.NULL) ++ lib.hexchat_hook_command(lib.ph, b'UNLOAD', 0, lib._on_unload_command, ffi.NULL, ffi.NULL) ++ lib.hexchat_hook_command(lib.ph, b'RELOAD', 0, lib._on_reload_command, ffi.NULL, ffi.NULL) ++ lib.hexchat_hook_command(lib.ph, b'PY', 0, lib._on_py_command, b'''Usage: /PY LOAD ++ UNLOAD ++ RELOAD ++ LIST ++ EXEC ++ CONSOLE ++ ABOUT''', ffi.NULL) ++ ++ lib.hexchat_print(lib.ph, b'Python interface loaded') ++ autoload() ++ return 1 ++ ++ ++@ffi.def_extern() ++def _on_plugin_deinit(): ++ global local_interp ++ global hexchat ++ global hexchat_stdout ++ global plugins ++ ++ plugins = set() ++ local_interp = None ++ hexchat = None ++ hexchat_stdout = None ++ sys.stdout = sys.__stdout__ ++ sys.stderr = sys.__stderr__ ++ pydoc.help = pydoc.Helper() ++ ++ for mod in ('_hexchat', 'hexchat', 'xchat', '_hexchat_embedded'): ++ try: ++ del sys.modules[mod] ++ ++ except KeyError: ++ pass ++ ++ return 1 +diff --color -Nur hexchat-2.14.3/plugins/python/python_style_guide.md hexchat/plugins/python/python_style_guide.md +--- hexchat-2.14.3/plugins/python/python_style_guide.md 1969-12-31 16:00:00.000000000 -0800 ++++ hexchat/plugins/python/python_style_guide.md 2020-11-06 15:03:00.403820502 -0800 +@@ -0,0 +1,26 @@ ++# HexChat Python Module Style Guide ++ ++(This is a work in progress). ++ ++## General rules ++ ++- PEP8 as general fallback recommendations ++- Max line length: 120 ++- Avoid overcomplex compound statements. i.e. dont do this: `somevar = x if x == y else z if a == b and c == b else x` ++ ++## Indentation style ++ ++### Multi-line functions ++ ++```python ++foo(really_long_arg_1, ++ really_long_arg_2) ++``` ++ ++### Mutli-line lists/dicts ++ ++```python ++foo = { ++ 'bar': 'baz', ++} ++``` +diff --color -Nur hexchat-2.14.3/plugins/python/xchat.py hexchat/plugins/python/xchat.py +--- hexchat-2.14.3/plugins/python/xchat.py 1969-12-31 16:00:00.000000000 -0800 ++++ hexchat/plugins/python/xchat.py 2020-11-06 15:03:00.403820502 -0800 +@@ -0,0 +1 @@ ++from _hexchat import * diff --git a/SPECS/hexchat.spec b/SPECS/hexchat.spec new file mode 100644 index 0000000..b991b2b --- /dev/null +++ b/SPECS/hexchat.spec @@ -0,0 +1,307 @@ +%global app_id io.github.Hexchat + +Summary: A popular and easy to use graphical IRC (chat) client +Name: hexchat +Version: 2.14.3 +Release: 17%{?dist} +License: GPLv2+ +URL: https://hexchat.github.io +Source: https://dl.hexchat.net/hexchat/%{name}-%{version}.tar.xz + +# Patch to link better to python 3.8 +Patch1: https://github.com/hexchat/hexchat/commit/5deb69591992d4fede9090b60d3dc847612a4d60.patch +Patch2: python-plugin-from-master.patch +Patch3: https://github.com/hexchat/hexchat/commit/163608d7fd861c2c4911a38f45be484c88626bdc.patch +Patch4: https://github.com/hexchat/hexchat/commit/090fd29acf4af0d8e13fcf2861b14a356db72641.patch + +# https://github.com/hexchat/hexchat/pull/2609 +# https://bugzilla.redhat.com/show_bug.cgi?id=1981509 +Patch5: hexchat-Avoid-direct-use-of-libproxy.patch + +BuildRequires: gcc +BuildRequires: meson +BuildRequires: hicolor-icon-theme +BuildRequires: pkgconfig(glib-2.0) +BuildRequires: pkgconfig(gtk+-2.0) +BuildRequires: pkgconfig(dbus-glib-1) +BuildRequires: pkgconfig(libcanberra) +BuildRequires: pkgconfig(libnotify) +BuildRequires: pkgconfig(iso-codes) +BuildRequires: pkgconfig(openssl) +BuildRequires: pkgconfig(python3) +BuildRequires: pkgconfig(libpci) +BuildRequires: pkgconfig(lua) +BuildRequires: perl-devel, perl-ExtUtils-Embed +BuildRequires: python3-cffi +Requires: python3-cffi +Requires: (enchant or enchant2) +Recommends: sound-theme-freedesktop + +%description +HexChat is an easy to use graphical IRC chat client for the X Window System. +It allows you to join multiple IRC channels (chat rooms) at the same time, +talk publicly, private one-on-one conversations etc. Even file transfers +are possible. + +%package devel +Summary: Development files for %{name} +Requires: %{name}%{?_isa} = %{version}-%{release} +%description devel +This package contains the development files for %{name}. + +%prep +%autosetup -p1 + +%build +%meson -Dwith-lua=lua +%meson_build + +%install +%meson_install +%find_lang %{name} + +%files -f %{name}.lang +%{_bindir}/hexchat +%license COPYING +%doc readme.md +%dir %{_libdir}/hexchat +%dir %{_libdir}/hexchat/plugins +%{_libdir}/hexchat/plugins/checksum.so +%{_libdir}/hexchat/plugins/fishlim.so +%{_libdir}/hexchat/plugins/lua.so +%{_libdir}/hexchat/plugins/sysinfo.so +%{_libdir}/hexchat/plugins/perl.so +%{_libdir}/hexchat/plugins/python.so +%{_libdir}/hexchat/python +%{_datadir}/applications/%{app_id}.desktop +%{_datadir}/icons/hicolor/*/apps/%{name}.* +%{_datadir}/metainfo/%{app_id}.appdata.xml +%{_datadir}/dbus-1/services/org.hexchat.service.service +%{_mandir}/man1/*.gz + +%files devel +%{_includedir}/hexchat-plugin.h +%{_libdir}/pkgconfig/hexchat-plugin.pc + +%changelog +* Mon Aug 09 2021 Mohan Boddu - 2.14.3-17 +- Rebuilt for IMA sigs, glibc 2.34, aarch64 flags + Related: rhbz#1991688 + +* Wed Jul 28 2021 Florian Weimer - 2.14.3-16 +- Rebuild to pick up OpenSSL 3.0 Beta ABI (#1984097) + +* Mon Jul 12 2021 Debarshi Ray - 2.14.3-15 +- Avoid direct use of libproxy +Resolves: #1981509 + +* Wed Jun 16 2021 Mohan Boddu - 2.14.3-14 +- Rebuilt for RHEL 9 BETA for openssl 3.0 + Related: rhbz#1971065 + +* Fri Apr 16 2021 Mohan Boddu - 2.14.3-13 +- Rebuilt for RHEL 9 BETA on Apr 15th 2021. Related: rhbz#1947937 + +* Fri Mar 12 2021 Kevin Fenzi - 2.14.3-12 +- Add patch to fix exception in python3 plugin. + +* Sat Feb 06 2021 Mikel Olasagasti Uranga - 2.14.3-11 +- Include patch to fix underscore not being printed correctly + +* Tue Jan 26 2021 Fedora Release Engineering - 2.14.3-10 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass_Rebuild + +* Fri Jan 15 2021 Kevin Fenzi - 2.14.3-9 +- Fix typo in last commit + +* Fri Jan 15 2021 Kevin Fenzi - 2.14.3-8 +- Add missing dependency on cffi. Fixes rhbz#1914874 + +* Wed Dec 30 2020 Kevin Fenzi - 2.14.3-7 +- Backport new python plugin from devel upstream. RHBZ#1883982 + +* Tue Jul 28 2020 Fedora Release Engineering - 2.14.3-6 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild + +* Mon Jun 22 2020 Jitka Plesnikova - 2.14.3-5 +- Perl 5.32 rebuild + +* Tue May 26 2020 Miro Hrončok - 2.14.3-4 +- Rebuilt for Python 3.9 + +* Fri Mar 27 2020 Kevin Fenzi - 2.14.3-3 +- Add patch to link better against python3.8. Fixes #1817862 + +* Wed Jan 29 2020 Fedora Release Engineering - 2.14.3-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild + +* Tue Jan 07 2020 Kevin Fenzi - 2.14.3-1 +- Update to 2.14.3. + +* Mon Aug 19 2019 Miro Hrončok - 2.14.2-6 +- Rebuilt for Python 3.8 + +* Thu Jul 25 2019 Fedora Release Engineering - 2.14.2-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild + +* Thu May 30 2019 Jitka Plesnikova - 2.14.2-4 +- Perl 5.30 rebuild + +* Tue Apr 30 2019 Adam Williamson - 2.14.2-3 +- Apply a fix suggested by ncoghlan for the crash-on-exit bug (#1632039) + +* Fri Feb 01 2019 Fedora Release Engineering - 2.14.2-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild + +* Thu Sep 06 2018 Gwyn Ciesla - 2.14.2-1 +- 2.14.2 + +* Fri Jul 13 2018 Fedora Release Engineering - 2.14.1-6 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild + +* Wed Jul 04 2018 Petr Pisar - 2.14.1-5 +- Perl 5.28 rebuild + +* Mon Jul 02 2018 Kevin Fenzi - 2.14.1-4 +- Rebuild for python 3.7 + +* Wed Jun 27 2018 Jitka Plesnikova - 2.14.1-3 +- Perl 5.28 rebuild + +* Wed Mar 21 2018 Patrick Griffis 2.14.1-2 +- Always use lua over luajit, just more common +- Remove no longer needed snippets +- Add enchant/enchant2 to requires + +* Wed Mar 14 2018 Patrick Griffis 2.14.1-1 +- Version bump to 2.14.1 + +* Sat Mar 10 2018 Patrick Griffis 2.14.0-1 +- Version bump to 2.14.0 + +* Thu Feb 08 2018 Patrick Griffis 2.12.4-11 +- Add patch fixing rendering issue: https://bugzilla.redhat.com/show_bug.cgi?id=1536298 + +* Tue Feb 6 2018 Peter Robinson 2.12.4-10 +- Disable luajit on aarch64 due to crashes + +* Sat Jan 20 2018 Björn Esser - 2.12.4-9 +- Rebuilt for switch to libxcrypt + +* Wed Aug 02 2017 Fedora Release Engineering - 2.12.4-8 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Binutils_Mass_Rebuild + +* Wed Jul 26 2017 Fedora Release Engineering - 2.12.4-7 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild + +* Sun Jun 11 2017 Kevin Fenzi - 2.12.4-6 +- Add patch for python LC_ALL / LANGUAGES. +- https://github.com/hexchat/hexchat/issues/2013 + +* Sun Jun 04 2017 Jitka Plesnikova - 2.12.4-5 +- Perl 5.26 rebuild + +* Sun Mar 12 2017 Peter Robinson 2.12.4-4 +- Explicitly require openssl-devel so we build against 1.1 + +* Fri Feb 10 2017 Fedora Release Engineering - 2.12.4-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild + +* Mon Dec 19 2016 Miro Hrončok - 2.12.4-2 +- Rebuild for Python 3.6 + +* Sat Dec 10 2016 Patrick Griffis - 2.12.4-1 +- Version bump to 2.12.4 + +* Sat Oct 22 2016 Patrick Griffis - 2.12.3-1 +- Version bump to 2.12.3 +- Fix building against OpenSSL 1.1.0 + +* Sun Oct 9 2016 Dan Horák - 2.12.2-2 +- Not all arches have luajit + +* Sat Oct 8 2016 Patrick Griffis - 2.12.2-1 +- Version bump to 2.12.2 + +* Sat Oct 8 2016 Patrick Griffis - 2.12.1-7 +- Modernize spec file + +* Mon Sep 19 2016 Peter Robinson 2.12.1-6 +- aarch64 now has LuaJIT + +* Mon Aug 29 2016 Igor Gnatenko - 2.12.1-5 +- Rebuild for LuaJIT 2.1.0 + +* Tue Jul 5 2016 TingPing - 2.12.1-4 +- Fix input style theming with Adwaita + +* Tue May 17 2016 Jitka Plesnikova - 2.12.1-3 +- Perl 5.24 rebuild + +* Tue May 3 2016 Peter Robinson 2.12.1-2 +- Not all arches have luajit + +* Sun May 1 2016 TingPing - 2.12.1-1 +- Version bump to 2.12.1 + +* Sat Mar 12 2016 TingPing - 2.12.0-1 +- Version bump to 2.12.0 + +* Wed Feb 03 2016 Fedora Release Engineering - 2.10.2-7 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_24_Mass_Rebuild + +* Thu Nov 12 2015 Kevin Fenzi - 2.10.2-6 +- Build against python 3.5 specifically. + +* Tue Nov 10 2015 Fedora Release Engineering - 2.10.2-5 +- Rebuilt for https://fedoraproject.org/wiki/Changes/python3.5 + +* Wed Jun 17 2015 Fedora Release Engineering - 2.10.2-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_23_Mass_Rebuild + +* Sat Jun 06 2015 Jitka Plesnikova - 2.10.2-3 +- Perl 5.22 rebuild + +* Fri Jan 30 2015 TingPing - 2.10.2-2 +- Do not own icon directories owned by hicolor-icon-theme (#1171904) +- Build against python3 +- Make use of license macro + +* Tue Nov 25 2014 TingPing - 2.10.2-1 +- Version bump to 2.10.2 + +* Thu Aug 28 2014 Jitka Plesnikova - 2.10.1-3 +- Perl 5.20 rebuild + +* Sat Aug 16 2014 Fedora Release Engineering - 2.10.1-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_22_Mass_Rebuild + +* Mon Jul 28 2014 TingPing - 2.10.1-1 +- Version bump to 2.10.1 + +* Sat Jun 07 2014 Fedora Release Engineering - 2.10.0-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_Mass_Rebuild + +* Mon Jun 2 2014 TingPing - 2.10.0-1 +- Version bump to 2.10.0 + +* Mon Sep 16 2013 TingPing - 2.9.6.1-1 +- Version bump to 2.9.6.1 + +* Wed Sep 11 2013 TingPing - 2.9.6-1 +- Version bump to 2.9.6 + +* Sat Aug 03 2013 Fedora Release Engineering - 2.9.5-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_20_Mass_Rebuild + +* Sat Aug 03 2013 Kevin Fenzi 2.9.5-2 +- Rebuild for new perl + +* Mon Apr 1 2013 TingPing - 2.9.5-1 +- Version bump to 2.9.5 + +* Thu Feb 14 2013 Fedora Release Engineering - 2.9.4-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild + +* Thu Dec 27 2012 TingPing - 2.9.4-1 +- Initial HexChat package