summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDanny Rawlins <contact@romster.me>2021-04-11 01:07:35 +1000
committerDanny Rawlins <contact@romster.me>2021-04-11 01:16:57 +1000
commitba35b07af199eb3001e8a60c3e7a3357311c4cb4 (patch)
treea4d95944cb4f27417869e2afdf2b8725566eccb2
parent8d423c41e3808ddc65c3ead694ff643dd92b1f2a (diff)
downloadopt-ba35b07af199eb3001e8a60c3e7a3357311c4cb4.tar.gz
opt-ba35b07af199eb3001e8a60c3e7a3357311c4cb4.tar.xz
hexchat: lua fix compile and fix font height
-rw-r--r--hexchat/.signature8
-rw-r--r--hexchat/0001-python-cffi.patch4183
-rw-r--r--hexchat/0002-python-3-8.patch13
-rw-r--r--hexchat/0003-Use-pango_font_metrics_get_height-to-calculate-font-height.patch44
-rw-r--r--hexchat/0004-fix_segfault_on_lua_pop_with_Lua_5.4.3.patch30
-rw-r--r--hexchat/Pkgfile13
6 files changed, 4287 insertions, 4 deletions
diff --git a/hexchat/.signature b/hexchat/.signature
index a56334e02..8f341362e 100644
--- a/hexchat/.signature
+++ b/hexchat/.signature
@@ -1,5 +1,9 @@
untrusted comment: verify with /etc/ports/opt.pub
-RWSE3ohX2g5d/SSEViDGzwTcNKrlgAUbcat9KErKyd2EuLXR31nXVwmEsusIwWhFH9Q+q4lfk6xkKnW0qCQZofBFFGyaEWGhLgA=
-SHA256 (Pkgfile) = 9b4340d4a94abbb873d92edd20711fe5b4bc75ad7062d4f0f19a4c08a75502ce
+RWSE3ohX2g5d/Wm4zjW1uczdblLrQpa6pzE6d7ciX+ACK1ku+iPepb0PdgmLT0iKwdGf2WSrJ5ROIeKmJKwBkmKQtDNiAHvHIwA=
+SHA256 (Pkgfile) = dd4468441be2ac002c08c1f45eccb1da13e98b26b7435eb67f1dd65931b9b2a8
SHA256 (.footprint) = 890fbae8258fa479df98a709305dec29ce2e972446df597faf3fc2013476308b
SHA256 (hexchat-2.14.3.tar.xz) = 901a9d13db5a4da69b827f6093306bbd16863dc49016f7668bd3e4506512e882
+SHA256 (0001-python-cffi.patch) = 92af0d106627c9b9716036ce81f697de35f37b4ba2e7bd34244824520e485bba
+SHA256 (0002-python-3-8.patch) = 8d17ce657e744272815b5fb33d8ad959f79ece3294349637eaadcf86d90496fa
+SHA256 (0003-Use-pango_font_metrics_get_height-to-calculate-font-height.patch) = 691f344479a8b1186008516f0ebe7e3d482059cd297f58573634fa5a58f3d0ca
+SHA256 (0004-fix_segfault_on_lua_pop_with_Lua_5.4.3.patch) = a8d6917deec758b3a822471316f0bd7819d3661ef6f657c52953711227a2cbe6
diff --git a/hexchat/0001-python-cffi.patch b/hexchat/0001-python-cffi.patch
new file mode 100644
index 000000000..a4f461617
--- /dev/null
+++ b/hexchat/0001-python-cffi.patch
@@ -0,0 +1,4183 @@
+diff --git a/.travis.yml b/.travis.yml
+index df0c7e1f..9e226f0c 100644
+--- a/.travis.yml
++++ b/.travis.yml
+@@ -2,7 +2,7 @@ sudo: required
+ services: docker
+ before_install:
+ - docker pull ubuntu:16.04
+- - docker run --privileged --cidfile=/tmp/cid ubuntu:16.04 /bin/sh -c 'apt-get update && apt-get install -y meson/xenial-backports libcanberra-dev libdbus-glib-1-dev libglib2.0-dev libgtk2.0-dev libluajit-5.1-dev libnotify-dev libpci-dev libperl-dev libproxy-dev libssl-dev python3-dev mono-devel desktop-file-utils'
++ - docker run --privileged --cidfile=/tmp/cid ubuntu:16.04 /bin/sh -c 'apt-get update && apt-get install -y meson/xenial-backports libcanberra-dev libdbus-glib-1-dev libglib2.0-dev libgtk2.0-dev libluajit-5.1-dev libnotify-dev libpci-dev libperl-dev libproxy-dev libssl-dev python3-dev python3-cffi mono-devel desktop-file-utils'
+ - docker commit `cat /tmp/cid` hexchat/ubuntu-ci
+ - rm -f /tmp/cid
+ install:
+diff --git a/meson.build b/meson.build
+index 18baf26e..645e685e 100644
+--- a/meson.build
++++ b/meson.build
+@@ -49,6 +49,10 @@ config_h.set('GLIB_VERSION_MIN_REQUIRED', 'GLIB_VERSION_2_34')
+ config_h.set('HAVE_MEMRCHR', cc.has_function('memrchr'))
+ config_h.set('HAVE_STRINGS_H', cc.has_header('strings.h'))
+
++config_h.set_quoted('HEXCHATLIBDIR',
++ join_paths(get_option('prefix'), get_option('libdir'), 'hexchat/plugins')
++)
++
+ if libssl_dep.found()
+ config_h.set('HAVE_X509_GET_SIGNATURE_NID',
+ cc.has_function('X509_get_signature_nid', dependencies: libssl_dep)
+diff --git a/plugins/python/_hexchat.py b/plugins/python/_hexchat.py
+new file mode 100644
+index 00000000..567b3493
+--- /dev/null
++++ b/plugins/python/_hexchat.py
+@@ -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 --git a/plugins/python/generate_plugin.py b/plugins/python/generate_plugin.py
+new file mode 100755
+index 00000000..5c52b37b
+--- /dev/null
++++ b/plugins/python/generate_plugin.py
+@@ -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 --git a/plugins/python/hexchat.py b/plugins/python/hexchat.py
+new file mode 100644
+index 00000000..6922490b
+--- /dev/null
++++ b/plugins/python/hexchat.py
+@@ -0,0 +1 @@
++from _hexchat import *
+diff --git a/plugins/python/meson.build b/plugins/python/meson.build
+index e24f0c6f..5fd7ec2f 100644
+--- a/plugins/python/meson.build
++++ b/plugins/python/meson.build
+@@ -1,12 +1,30 @@
+ 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)
++ 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 --git a/plugins/python/python.c b/plugins/python/python.c
+deleted file mode 100644
+index 475756ba..00000000
+--- a/plugins/python/python.c
++++ /dev/null
+@@ -1,2837 +0,0 @@
+-/*
+-* Copyright (c) 2002-2003 Gustavo Niemeyer <niemeyer@conectiva.com>
+-*
+-* 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 <glib.h>
+-#include <glib/gstdio.h>
+-#include <string.h>
+-#include <stdlib.h>
+-#include <sys/types.h>
+-
+-#ifdef WIN32
+-#include <direct.h>
+-#else
+-#include <unistd.h>
+-#include <dirent.h>
+-#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 <Python.h>
+-#include <structmember.h>
+-#include <pythread.h>
+-
+-/* 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 <filename>\n\
+- UNLOAD <filename|name>\n\
+- RELOAD <filename|name>\n\
+- LIST\n\
+- EXEC <command>\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/<filename> 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("<Attribute object at %p>", 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"<hexchat>", 0 };
+-#else
+- char *argv[] = { "<hexchat>", 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
+- : "<none>",
+- basename,
+- *plg->description ? plg->description
+- : "<none>");
+- 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"<hexchat>", 0 };
+-#else
+- char *argv[] = { "<hexchat>", 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 --git a/plugins/python/python.def b/plugins/python/python.def
+index 6ce04e98..e560f50f 100644
+--- a/plugins/python/python.def
++++ b/plugins/python/python.def
+@@ -1,4 +1,3 @@
+ EXPORTS
+ hexchat_plugin_init
+ hexchat_plugin_deinit
+-hexchat_plugin_get_info
+diff --git a/plugins/python/python.py b/plugins/python/python.py
+new file mode 100644
+index 00000000..30694802
+--- /dev/null
++++ b/plugins/python/python.py
+@@ -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 = ['<hexchat>']
++
++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 '<Attribute object at {}>'.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, '<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, '<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', '<string>', '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'<none>'
++ description = plugin.description.encode() if plugin.description else b'<none>'
++ 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 <filename>
++ UNLOAD <filename|name>
++ RELOAD <filename|name>
++ LIST
++ EXEC <command>
++ 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 --git a/plugins/python/python2.vcxproj b/plugins/python/python2.vcxproj
+index f914a865..0b098112 100644
+--- a/plugins/python/python2.vcxproj
++++ b/plugins/python/python2.vcxproj
+@@ -37,6 +37,9 @@
+ <AdditionalDependencies>"$(Python2Lib).lib";$(DepLibs);%(AdditionalDependencies)</AdditionalDependencies>
+ <AdditionalLibraryDirectories>$(DepsRoot)\lib;$(Python2Path)\libs;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
++ <PreBuildEvent>
++ <Command>"$(Python3Path)\python.exe" generate_plugin.py ..\..\src\common\hexchat-plugin.h python.py "$(IntDir)python.c"</Command>
++ </PreBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+@@ -48,12 +51,15 @@
+ <AdditionalDependencies>"$(Python2Lib).lib";$(DepLibs);%(AdditionalDependencies)</AdditionalDependencies>
+ <AdditionalLibraryDirectories>$(DepsRoot)\lib;$(Python2Path)\libs;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
++ <PreBuildEvent>
++ <Command>"$(Python3Path)\python.exe" generate_plugin.py ..\..\src\common\hexchat-plugin.h python.py "$(IntDir)python.c"</Command>
++ </PreBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <None Include="python.def" />
+ </ItemGroup>
+ <ItemGroup>
+- <ClCompile Include="python.c" />
++ <ClCompile Include="$(IntDir)python.c" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ </Project>
+diff --git a/plugins/python/python3.vcxproj b/plugins/python/python3.vcxproj
+index 815dc8b1..5868d3b0 100644
+--- a/plugins/python/python3.vcxproj
++++ b/plugins/python/python3.vcxproj
+@@ -37,6 +37,9 @@
+ <AdditionalDependencies>"$(Python3Lib).lib";$(DepLibs);%(AdditionalDependencies)</AdditionalDependencies>
+ <AdditionalLibraryDirectories>$(DepsRoot)\lib;$(Python3Path)\libs;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
++ <PreBuildEvent>
++ <Command>"$(Python3Path)\python.exe" generate_plugin.py ..\..\src\common\hexchat-plugin.h python.py "$(IntDir)python.c"</Command>
++ </PreBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+@@ -48,12 +51,20 @@
+ <AdditionalDependencies>"$(Python3Lib).lib";$(DepLibs);%(AdditionalDependencies)</AdditionalDependencies>
+ <AdditionalLibraryDirectories>$(DepsRoot)\lib;$(Python3Path)\libs;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
++ <PreBuildEvent>
++ <Command>"$(Python3Path)\python.exe" generate_plugin.py ..\..\src\common\hexchat-plugin.h python.py "$(IntDir)python.c"</Command>
++ </PreBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemGroup>
++ <None Include="generate_plugin.py" />
++ <None Include="hexchat.py" />
+ <None Include="python.def" />
++ <None Include="python.py" />
++ <None Include="xchat.py" />
++ <None Include="_hexchat.py" />
+ </ItemGroup>
+ <ItemGroup>
+- <ClCompile Include="python.c" />
++ <ClCompile Include="$(IntDir)python.c" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+-</Project>
++</Project>
+\ No newline at end of file
+diff --git a/plugins/python/python3.vcxproj.filters b/plugins/python/python3.vcxproj.filters
+index 9165e798..5c5834bb 100644
+--- a/plugins/python/python3.vcxproj.filters
++++ b/plugins/python/python3.vcxproj.filters
+@@ -9,13 +9,26 @@
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+- <ClCompile Include="python.c">
+- <Filter>Source Files</Filter>
+- </ClCompile>
++ <ClCompile Include="$(IntDir)python.c" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="python.def">
+ <Filter>Resource Files</Filter>
+ </None>
++ <None Include="_hexchat.py">
++ <Filter>Source Files</Filter>
++ </None>
++ <None Include="generate_plugin.py">
++ <Filter>Source Files</Filter>
++ </None>
++ <None Include="hexchat.py">
++ <Filter>Source Files</Filter>
++ </None>
++ <None Include="python.py">
++ <Filter>Source Files</Filter>
++ </None>
++ <None Include="xchat.py">
++ <Filter>Source Files</Filter>
++ </None>
+ </ItemGroup>
+ </Project>
+\ No newline at end of file
+diff --git a/plugins/python/python_style_guide.md b/plugins/python/python_style_guide.md
+new file mode 100644
+index 00000000..41db2474
+--- /dev/null
++++ b/plugins/python/python_style_guide.md
+@@ -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 --git a/plugins/python/xchat.py b/plugins/python/xchat.py
+new file mode 100644
+index 00000000..6922490b
+--- /dev/null
++++ b/plugins/python/xchat.py
+@@ -0,0 +1 @@
++from _hexchat import *
+diff --git a/src/common/meson.build b/src/common/meson.build
+index bbb64645..492227b2 100644
+--- a/src/common/meson.build
++++ b/src/common/meson.build
+@@ -93,10 +93,6 @@ endif
+
+ if get_option('with-plugin')
+ common_deps += libgmodule_dep
+- common_cflags += '-DHEXCHATLIBDIR="@0@"'.format(join_paths(get_option('prefix'),
+- get_option('libdir'),
+- 'hexchat/plugins'))
+-
+ install_headers('hexchat-plugin.h')
+ endif
+
+diff --git a/win32/copy/copy.vcxproj b/win32/copy/copy.vcxproj
+index c508a7f3..72f2c032 100644
+--- a/win32/copy/copy.vcxproj
++++ b/win32/copy/copy.vcxproj
+@@ -64,6 +64,8 @@
+ <LuaShare Include="$(DepsRoot)\share\lua\**\*.lua" />
+ <LuaShare Include="$(DepsRoot)\share\lua\**\**\*.lua" />
+ <Typelib Include="$(DepsRoot)\lib\girepository-1.0\*.typelib" />
++ <None Include="$(Python3Path)\Lib\site-packages\_cffi_backend.*.pyd" />
++ <None Include="$(Python2Path)\Lib\site-packages\_cffi_backend.pyd" />
+
+ <Engines Include="$(DepsRoot)\lib\gtk-2.0\i686-pc-vs14\engines\**\*" />
+
+@@ -91,6 +93,9 @@
+ <Copy SourceFiles="@(LuaShare)" DestinationFiles="@(LuaShare->'$(HexChatRel)\share\lua\%(RecursiveDir)%(Filename)%(Extension)')" />
+ <Copy SourceFiles="@(LuaLib)" DestinationFiles="@(LuaLib->'$(HexChatRel)\lib\lua\%(RecursiveDir)%(Filename)%(Extension)')" />
+ <Copy SourceFiles="@(Typelib)" DestinationFiles="@(Typelib->'$(HexChatRel)\lib\girepository-1.0\%(Filename)%(Extension)')" />
++ <Copy SourceFiles="..\..\plugins\python\xchat.py" DestinationFolder="$(HexChatRel)\python" />
++ <Copy SourceFiles="..\..\plugins\python\hexchat.py" DestinationFolder="$(HexChatRel)\python" />
++ <Copy SourceFiles="..\..\plugins\python\_hexchat.py" DestinationFolder="$(HexChatRel)\python" />
+
+ <WriteLinesToFile File="$(HexChatRel)portable-mode" Lines="2" Overwrite="true" />
+
+diff --git a/win32/installer/hexchat.iss.tt b/win32/installer/hexchat.iss.tt
+index e242ee96..3ac5ec41 100644
+--- a/win32/installer/hexchat.iss.tt
++++ b/win32/installer/hexchat.iss.tt
+@@ -164,6 +164,7 @@ Source: "lib\girepository-1.0\*.typelib"; DestDir: "{app}\lib\girepository-1.0";
+ Source: "share\lua\*.lua"; DestDir: "{app}\share\lua"; Flags: ignoreversion; Components: langs\lua
+ Source: "share\lua\lgi\*.lua"; DestDir: "{app}\share\lua\lgi"; Flags: ignoreversion; Components: langs\lua
+ Source: "share\lua\lgi\override\*.lua"; DestDir: "{app}\share\lua\lgi\override"; Flags: ignoreversion; Components: langs\lua
++Source: "plugins\hclua.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Components: langs\lua
+
+ Source: "plugins\hcchecksum.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Components: plugins\checksum
+ Source: "plugins\hcexec.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Components: plugins\exec
+@@ -175,11 +176,15 @@ Source: "WinSparkle.dll"; DestDir: "{app}"; Flags: ignoreversion; Components: pl
+ Source: "plugins\hcwinamp.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Components: plugins\winamp
+ Source: "share\system.png"; DestDir: "{app}\share"; Flags: ignoreversion; Components: plugins\sysinfo
+ Source: "plugins\hcsysinfo.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Components: plugins\sysinfo
++Source: "plugins\hcperl.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Components: langs\perl
++
++Source: "python\*.py"; DestDir: "{app}\python"; Flags: ignoreversion; Components: langs\python
+
+ Source: "plugins\hcpython2.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Components: langs\python\python2
++Source: "_cffi_backend.pyd"; DestDir: "{app}"; Flags: ignoreversion; Components: langs\python\python2
++
+ Source: "plugins\hcpython3.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Components: langs\python\python3
+-Source: "plugins\hcperl.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Components: langs\perl
+-Source: "plugins\hclua.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Components: langs\lua
++Source: "_cffi_backend.cp3*.pyd"; DestDir: "{app}"; Flags: ignoreversion; Components: langs\python\python3
+
+ Source: "hexchat.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: libs
+ Source: "hexchat-text.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: xctext
diff --git a/hexchat/0002-python-3-8.patch b/hexchat/0002-python-3-8.patch
new file mode 100644
index 000000000..dbfd7f179
--- /dev/null
+++ b/hexchat/0002-python-3-8.patch
@@ -0,0 +1,13 @@
+diff --git a/meson_options.txt b/meson_options.txt
+index 100a5ee7..c2a2f53c 100644
+--- a/meson_options.txt
++++ b/meson_options.txt
+@@ -48,7 +48,7 @@ option('with-lua', type: 'string', value: 'luajit',
+ option('with-perl', type: 'string', value: 'perl',
+ description: 'Perl scripting plugin, value is path to perl executable or "false"'
+ )
+-option('with-python', type: 'string', value: 'python3',
++option('with-python', type: 'string', value: 'python3-embed',
+ description: 'Python scripting plugin. value is pkg-config name to use or "false"'
+ )
+ option('with-sysinfo', type: 'boolean',
diff --git a/hexchat/0003-Use-pango_font_metrics_get_height-to-calculate-font-height.patch b/hexchat/0003-Use-pango_font_metrics_get_height-to-calculate-font-height.patch
new file mode 100644
index 000000000..35434eef5
--- /dev/null
+++ b/hexchat/0003-Use-pango_font_metrics_get_height-to-calculate-font-height.patch
@@ -0,0 +1,44 @@
+commit 163608d7fd861c2c4911a38f45be484c88626bdc
+Author: John Levon <levon@movementarian.org>
+Date: Mon Sep 7 17:53:31 2020 +0100
+
+ Use pango_font_metrics_get_height() to calculate font height (#2500)
+
+diff --git a/src/fe-gtk/xtext.c b/src/fe-gtk/xtext.c
+index fac0c4e6..418bb4da 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/hexchat/0004-fix_segfault_on_lua_pop_with_Lua_5.4.3.patch b/hexchat/0004-fix_segfault_on_lua_pop_with_Lua_5.4.3.patch
new file mode 100644
index 000000000..67c2e1e7f
--- /dev/null
+++ b/hexchat/0004-fix_segfault_on_lua_pop_with_Lua_5.4.3.patch
@@ -0,0 +1,30 @@
+From d6319bd8f21042db62f6c7094066052d894416ef Mon Sep 17 00:00:00 2001
+From: Mateusz Gozdek <mgozdekof@gmail.com>
+Date: Sun, 4 Apr 2021 21:07:30 +0200
+Subject: [PATCH] plugins/lua/lua.c: fix segfault on lua_pop with Lua 5.4.3
+
+Closes #2558
+
+Co-authored-by: "Jan Alexander Steffens (heftig)" <jan.steffens@gmail.com>
+Signed-off-by: Mateusz Gozdek <mgozdekof@gmail.com>
+---
+ plugins/lua/lua.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/plugins/lua/lua.c b/plugins/lua/lua.c
+index d73fbb230..d1370eafb 100644
+--- a/plugins/lua/lua.c
++++ b/plugins/lua/lua.c
+@@ -1189,11 +1189,11 @@ static void patch_clibs(lua_State *L)
+ if(lua_type(L, -2) == LUA_TLIGHTUSERDATA && lua_type(L, -1) == LUA_TTABLE)
+ {
+ lua_setfield(L, LUA_REGISTRYINDEX, "_CLIBS");
++ lua_pop(L, 1);
+ break;
+ }
+ lua_pop(L, 1);
+ }
+- lua_pop(L, 1);
+ }
+
+ static GPtrArray *scripts;
diff --git a/hexchat/Pkgfile b/hexchat/Pkgfile
index 7c05748c5..8088820a1 100644
--- a/hexchat/Pkgfile
+++ b/hexchat/Pkgfile
@@ -6,12 +6,21 @@
name=hexchat
version=2.14.3
-release=1
-source=(https://dl.hexchat.net/hexchat/$name-$version.tar.xz)
+release=2
+source=(https://dl.hexchat.net/hexchat/$name-$version.tar.xz
+ 0001-python-cffi.patch
+ 0002-python-3-8.patch
+ 0003-Use-pango_font_metrics_get_height-to-calculate-font-height.patch
+ 0004-fix_segfault_on_lua_pop_with_Lua_5.4.3.patch)
build() {
cd $name-$version
+ patch -p1 -i $SRC/0001-python-cffi.patch
+ patch -p1 -i $SRC/0002-python-3-8.patch
+ patch -p1 -i $SRC/0003-Use-pango_font_metrics_get_height-to-calculate-font-height.patch
+ patch -p1 -i $SRC/0004-fix_segfault_on_lua_pop_with_Lua_5.4.3.patch
+
# Lua scripting plugin, value is pkg-config name
prt-get isinst lua && PKGMK_HEXCHAT+=' -Dwith-lua=lua' || PKGMK_HEXCHAT+=' -Dwith-lua=false'

Generated by cgit