hexchat/python-plugin-from-master.patch
DistroBaker ba271c6e75 Merged update from upstream sources
This is an automated DistroBaker update from upstream sources.
If you do not know what this is about or would like to opt out,
contact the OSCI team.

Source: https://src.fedoraproject.org/rpms/hexchat.git#0803858a956c090d40f98998e9d1057d7fd040f5
2020-12-31 21:53:44 +00:00

4068 lines
112 KiB
Diff

diff --color -Nur hexchat-2.14.3/plugins/python/generate_plugin.py hexchat/plugins/python/generate_plugin.py
--- hexchat-2.14.3/plugins/python/generate_plugin.py 1969-12-31 16:00:00.000000000 -0800
+++ hexchat/plugins/python/generate_plugin.py 2020-11-06 15:03:00.401820501 -0800
@@ -0,0 +1,89 @@
+#!/usr/bin/env python3
+
+import sys
+import cffi
+
+builder = cffi.FFI()
+
+# hexchat-plugin.h
+with open(sys.argv[1]) as f:
+ output = []
+ eat_until_endif = 0
+ # This is very specific to hexchat-plugin.h, it is not a cpp
+ for line in f:
+ if line.startswith('#define'):
+ continue
+ elif line.endswith('HEXCHAT_PLUGIN_H\n'):
+ continue
+ elif 'time.h' in line:
+ output.append('typedef int... time_t;')
+ elif line.startswith('#if'):
+ eat_until_endif += 1
+ elif line.startswith('#endif'):
+ eat_until_endif -= 1
+ elif eat_until_endif and '_hexchat_context' not in line:
+ continue
+ else:
+ output.append(line)
+ builder.cdef(''.join(output))
+
+builder.embedding_api('''
+extern "Python" int _on_py_command(char **, char **, void *);
+extern "Python" int _on_load_command(char **, char **, void *);
+extern "Python" int _on_unload_command(char **, char **, void *);
+extern "Python" int _on_reload_command(char **, char **, void *);
+extern "Python" int _on_say_command(char **, char **, void *);
+
+extern "Python" int _on_command_hook(char **, char **, void *);
+extern "Python" int _on_print_hook(char **, void *);
+extern "Python" int _on_print_attrs_hook(char **, hexchat_event_attrs *, void *);
+extern "Python" int _on_server_hook(char **, char **, void *);
+extern "Python" int _on_server_attrs_hook(char **, char **, hexchat_event_attrs *, void *);
+extern "Python" int _on_timer_hook(void *);
+
+extern "Python" int _on_plugin_init(char **, char **, char **, char *, char *);
+extern "Python" int _on_plugin_deinit(void);
+
+static hexchat_plugin *ph;
+''')
+
+builder.set_source('_hexchat_embedded', '''
+/* Python's header defines these.. */
+#undef HAVE_MEMRCHR
+#undef HAVE_STRINGS_H
+
+#include "config.h"
+#include "hexchat-plugin.h"
+
+static hexchat_plugin *ph;
+CFFI_DLLEXPORT int _on_plugin_init(char **, char **, char **, char *, char *);
+CFFI_DLLEXPORT int _on_plugin_deinit(void);
+
+int hexchat_plugin_init(hexchat_plugin *plugin_handle,
+ char **name_out, char **description_out,
+ char **version_out, char *arg)
+{
+ if (ph != NULL)
+ {
+ puts ("Python plugin already loaded\\n");
+ return 0; /* Prevent loading twice */
+ }
+
+ ph = plugin_handle;
+ return _on_plugin_init(name_out, description_out, version_out, arg, HEXCHATLIBDIR);
+}
+
+int hexchat_plugin_deinit(void)
+{
+ int ret = _on_plugin_deinit();
+ ph = NULL;
+ return ret;
+}
+''')
+
+# python.py
+with open(sys.argv[2]) as f:
+ builder.embedding_init_code(f.read())
+
+# python.c
+builder.emit_c_code(sys.argv[3])
diff --color -Nur hexchat-2.14.3/plugins/python/_hexchat.py hexchat/plugins/python/_hexchat.py
--- hexchat-2.14.3/plugins/python/_hexchat.py 1969-12-31 16:00:00.000000000 -0800
+++ hexchat/plugins/python/_hexchat.py 2020-11-06 15:03:00.401820501 -0800
@@ -0,0 +1,386 @@
+import inspect
+import sys
+from contextlib import contextmanager
+
+from _hexchat_embedded import ffi, lib
+
+__all__ = [
+ 'EAT_ALL', 'EAT_HEXCHAT', 'EAT_NONE', 'EAT_PLUGIN', 'EAT_XCHAT',
+ 'PRI_HIGH', 'PRI_HIGHEST', 'PRI_LOW', 'PRI_LOWEST', 'PRI_NORM',
+ '__doc__', '__version__', 'command', 'del_pluginpref', 'emit_print',
+ 'find_context', 'get_context', 'get_info',
+ 'get_list', 'get_lists', 'get_pluginpref', 'get_prefs', 'hook_command',
+ 'hook_print', 'hook_print_attrs', 'hook_server', 'hook_server_attrs',
+ 'hook_timer', 'hook_unload', 'list_pluginpref', 'nickcmp', 'prnt',
+ 'set_pluginpref', 'strip', 'unhook',
+]
+
+__doc__ = 'HexChat Scripting Interface'
+__version__ = (2, 0)
+__license__ = 'GPL-2.0+'
+
+EAT_NONE = 0
+EAT_HEXCHAT = 1
+EAT_XCHAT = EAT_HEXCHAT
+EAT_PLUGIN = 2
+EAT_ALL = EAT_HEXCHAT | EAT_PLUGIN
+
+PRI_LOWEST = -128
+PRI_LOW = -64
+PRI_NORM = 0
+PRI_HIGH = 64
+PRI_HIGHEST = 127
+
+
+# We need each module to be able to reference their parent plugin
+# which is a bit tricky since they all share the exact same module.
+# Simply navigating up to what module called it seems to actually
+# be a fairly reliable and simple method of doing so if ugly.
+def __get_current_plugin():
+ frame = inspect.stack()[1][0]
+ while '__plugin' not in frame.f_globals:
+ frame = frame.f_back
+ assert frame is not None
+
+ return frame.f_globals['__plugin']
+
+
+# Keeping API compat
+if sys.version_info[0] == 2:
+ def __decode(string):
+ return string
+
+else:
+ def __decode(string):
+ return string.decode()
+
+
+# ------------ API ------------
+def prnt(string):
+ lib.hexchat_print(lib.ph, string.encode())
+
+
+def emit_print(event_name, *args, **kwargs):
+ time = kwargs.pop('time', 0) # For py2 compat
+ cargs = []
+ for i in range(4):
+ arg = args[i].encode() if len(args) > i else b''
+ cstring = ffi.new('char[]', arg)
+ cargs.append(cstring)
+
+ if time == 0:
+ return lib.hexchat_emit_print(lib.ph, event_name.encode(), *cargs)
+
+ attrs = lib.hexchat_event_attrs_create(lib.ph)
+ attrs.server_time_utc = time
+ ret = lib.hexchat_emit_print_attrs(lib.ph, attrs, event_name.encode(), *cargs)
+ lib.hexchat_event_attrs_free(lib.ph, attrs)
+ return ret
+
+
+# TODO: this shadows itself. command should be changed to cmd
+def command(command):
+ lib.hexchat_command(lib.ph, command.encode())
+
+
+def nickcmp(string1, string2):
+ return lib.hexchat_nickcmp(lib.ph, string1.encode(), string2.encode())
+
+
+def strip(text, length=-1, flags=3):
+ stripped = lib.hexchat_strip(lib.ph, text.encode(), length, flags)
+ ret = __decode(ffi.string(stripped))
+ lib.hexchat_free(lib.ph, stripped)
+ return ret
+
+
+def get_info(name):
+ ret = lib.hexchat_get_info(lib.ph, name.encode())
+ if ret == ffi.NULL:
+ return None
+ if name in ('gtkwin_ptr', 'win_ptr'):
+ # Surely there is a less dumb way?
+ ptr = repr(ret).rsplit(' ', 1)[1][:-1]
+ return ptr
+
+ return __decode(ffi.string(ret))
+
+
+def get_prefs(name):
+ string_out = ffi.new('char**')
+ int_out = ffi.new('int*')
+ _type = lib.hexchat_get_prefs(lib.ph, name.encode(), string_out, int_out)
+ if _type == 0:
+ return None
+
+ if _type == 1:
+ return __decode(ffi.string(string_out[0]))
+
+ if _type in (2, 3): # XXX: 3 should be a bool, but keeps API
+ return int_out[0]
+
+ raise AssertionError('Out of bounds pref storage')
+
+
+def __cstrarray_to_list(arr):
+ i = 0
+ ret = []
+ while arr[i] != ffi.NULL:
+ ret.append(ffi.string(arr[i]))
+ i += 1
+
+ return ret
+
+
+__FIELD_CACHE = {}
+
+
+def __get_fields(name):
+ return __FIELD_CACHE.setdefault(name, __cstrarray_to_list(lib.hexchat_list_fields(lib.ph, name)))
+
+
+__FIELD_PROPERTY_CACHE = {}
+
+
+def __cached_decoded_str(string):
+ return __FIELD_PROPERTY_CACHE.setdefault(string, __decode(string))
+
+
+def get_lists():
+ return [__cached_decoded_str(field) for field in __get_fields(b'lists')]
+
+
+class ListItem:
+ def __init__(self, name):
+ self._listname = name
+
+ def __repr__(self):
+ return '<{} list item at {}>'.format(self._listname, id(self))
+
+
+# done this way for speed
+if sys.version_info[0] == 2:
+ def get_getter(name):
+ return ord(name[0])
+
+else:
+ def get_getter(name):
+ return name[0]
+
+
+def get_list(name):
+ # XXX: This function is extremely inefficient and could be interators and
+ # lazily loaded properties, but for API compat we stay slow
+ orig_name = name
+ name = name.encode()
+
+ if name not in __get_fields(b'lists'):
+ raise KeyError('list not available')
+
+ list_ = lib.hexchat_list_get(lib.ph, name)
+ if list_ == ffi.NULL:
+ return None
+
+ ret = []
+ fields = __get_fields(name)
+
+ def string_getter(field):
+ string = lib.hexchat_list_str(lib.ph, list_, field)
+ if string != ffi.NULL:
+ return __decode(ffi.string(string))
+
+ return ''
+
+ def ptr_getter(field):
+ if field == b'context':
+ ptr = lib.hexchat_list_str(lib.ph, list_, field)
+ ctx = ffi.cast('hexchat_context*', ptr)
+ return Context(ctx)
+
+ return None
+
+ getters = {
+ ord('s'): string_getter,
+ ord('i'): lambda field: lib.hexchat_list_int(lib.ph, list_, field),
+ ord('t'): lambda field: lib.hexchat_list_time(lib.ph, list_, field),
+ ord('p'): ptr_getter,
+ }
+
+ while lib.hexchat_list_next(lib.ph, list_) == 1:
+ item = ListItem(orig_name)
+ for _field in fields:
+ getter = getters.get(get_getter(_field))
+ if getter is not None:
+ field_name = _field[1:]
+ setattr(item, __cached_decoded_str(field_name), getter(field_name))
+
+ ret.append(item)
+
+ lib.hexchat_list_free(lib.ph, list_)
+ return ret
+
+
+# TODO: 'command' here shadows command above, and should be renamed to cmd
+def hook_command(command, callback, userdata=None, priority=PRI_NORM, help=None):
+ plugin = __get_current_plugin()
+ hook = plugin.add_hook(callback, userdata)
+ handle = lib.hexchat_hook_command(lib.ph, command.encode(), priority, lib._on_command_hook,
+ help.encode() if help is not None else ffi.NULL, hook.handle)
+
+ hook.hexchat_hook = handle
+ return id(hook)
+
+
+def hook_print(name, callback, userdata=None, priority=PRI_NORM):
+ plugin = __get_current_plugin()
+ hook = plugin.add_hook(callback, userdata)
+ handle = lib.hexchat_hook_print(lib.ph, name.encode(), priority, lib._on_print_hook, hook.handle)
+ hook.hexchat_hook = handle
+ return id(hook)
+
+
+def hook_print_attrs(name, callback, userdata=None, priority=PRI_NORM):
+ plugin = __get_current_plugin()
+ hook = plugin.add_hook(callback, userdata)
+ handle = lib.hexchat_hook_print_attrs(lib.ph, name.encode(), priority, lib._on_print_attrs_hook, hook.handle)
+ hook.hexchat_hook = handle
+ return id(hook)
+
+
+def hook_server(name, callback, userdata=None, priority=PRI_NORM):
+ plugin = __get_current_plugin()
+ hook = plugin.add_hook(callback, userdata)
+ handle = lib.hexchat_hook_server(lib.ph, name.encode(), priority, lib._on_server_hook, hook.handle)
+ hook.hexchat_hook = handle
+ return id(hook)
+
+
+def hook_server_attrs(name, callback, userdata=None, priority=PRI_NORM):
+ plugin = __get_current_plugin()
+ hook = plugin.add_hook(callback, userdata)
+ handle = lib.hexchat_hook_server_attrs(lib.ph, name.encode(), priority, lib._on_server_attrs_hook, hook.handle)
+ hook.hexchat_hook = handle
+ return id(hook)
+
+
+def hook_timer(timeout, callback, userdata=None):
+ plugin = __get_current_plugin()
+ hook = plugin.add_hook(callback, userdata)
+ handle = lib.hexchat_hook_timer(lib.ph, timeout, lib._on_timer_hook, hook.handle)
+ hook.hexchat_hook = handle
+ return id(hook)
+
+
+def hook_unload(callback, userdata=None):
+ plugin = __get_current_plugin()
+ hook = plugin.add_hook(callback, userdata, is_unload=True)
+ return id(hook)
+
+
+def unhook(handle):
+ plugin = __get_current_plugin()
+ return plugin.remove_hook(handle)
+
+
+def set_pluginpref(name, value):
+ if isinstance(value, str):
+ return bool(lib.hexchat_pluginpref_set_str(lib.ph, name.encode(), value.encode()))
+
+ if isinstance(value, int):
+ return bool(lib.hexchat_pluginpref_set_int(lib.ph, name.encode(), value))
+
+ # XXX: This should probably raise but this keeps API
+ return False
+
+
+def get_pluginpref(name):
+ name = name.encode()
+ string_out = ffi.new('char[512]')
+ if lib.hexchat_pluginpref_get_str(lib.ph, name, string_out) != 1:
+ return None
+
+ string = ffi.string(string_out)
+ # This API stores everything as a string so we have to figure out what
+ # its actual type was supposed to be.
+ if len(string) > 12: # Can't be a number
+ return __decode(string)
+
+ number = lib.hexchat_pluginpref_get_int(lib.ph, name)
+ if number == -1 and string != b'-1':
+ return __decode(string)
+
+ return number
+
+
+def del_pluginpref(name):
+ return bool(lib.hexchat_pluginpref_delete(lib.ph, name.encode()))
+
+
+def list_pluginpref():
+ prefs_str = ffi.new('char[4096]')
+ if lib.hexchat_pluginpref_list(lib.ph, prefs_str) == 1:
+ return __decode(prefs_str).split(',')
+
+ return []
+
+
+class Context:
+ def __init__(self, ctx):
+ self._ctx = ctx
+
+ def __eq__(self, value):
+ if not isinstance(value, Context):
+ return False
+
+ return self._ctx == value._ctx
+
+ @contextmanager
+ def __change_context(self):
+ old_ctx = lib.hexchat_get_context(lib.ph)
+ if not self.set():
+ # XXX: Behavior change, previously used wrong context
+ lib.hexchat_print(lib.ph, b'Context object refers to closed context, ignoring call')
+ return
+
+ yield
+ lib.hexchat_set_context(lib.ph, old_ctx)
+
+ def set(self):
+ # XXX: API addition, C plugin silently ignored failure
+ return bool(lib.hexchat_set_context(lib.ph, self._ctx))
+
+ def prnt(self, string):
+ with self.__change_context():
+ prnt(string)
+
+ def emit_print(self, event_name, *args, **kwargs):
+ time = kwargs.pop('time', 0) # For py2 compat
+ with self.__change_context():
+ return emit_print(event_name, *args, time=time)
+
+ def command(self, string):
+ with self.__change_context():
+ command(string)
+
+ def get_info(self, name):
+ with self.__change_context():
+ return get_info(name)
+
+ def get_list(self, name):
+ with self.__change_context():
+ return get_list(name)
+
+
+def get_context():
+ ctx = lib.hexchat_get_context(lib.ph)
+ return Context(ctx)
+
+
+def find_context(server=None, channel=None):
+ server = server.encode() if server is not None else ffi.NULL
+ channel = channel.encode() if channel is not None else ffi.NULL
+ ctx = lib.hexchat_find_context(lib.ph, server, channel)
+ if ctx == ffi.NULL:
+ return None
+
+ return Context(ctx)
diff --color -Nur hexchat-2.14.3/plugins/python/hexchat.py hexchat/plugins/python/hexchat.py
--- hexchat-2.14.3/plugins/python/hexchat.py 1969-12-31 16:00:00.000000000 -0800
+++ hexchat/plugins/python/hexchat.py 2020-11-06 15:03:00.402820502 -0800
@@ -0,0 +1 @@
+from _hexchat import *
diff --color -Nur hexchat-2.14.3/plugins/python/meson.build hexchat/plugins/python/meson.build
--- hexchat-2.14.3/plugins/python/meson.build 2020-11-06 15:05:12.122924612 -0800
+++ hexchat/plugins/python/meson.build 2020-11-06 15:03:00.402820502 -0800
@@ -3,16 +3,28 @@
# Python 3.8 introduced a new -embed variant
if not python_opt.endswith('-embed')
python_dep = dependency(python_opt + '-embed', version: '>= 3.3', required: false)
- endif
- if not python_dep.found()
+ if not python_dep.found()
+ python_dep = dependency(python_opt, version: '>= 3.3')
+ endif
+ else
python_dep = dependency(python_opt, version: '>= 3.3')
endif
else
python_dep = dependency(python_opt, version: '>= 2.7')
endif
-shared_module('python', 'python.c',
- dependencies: [libgio_dep, hexchat_plugin_dep, python_dep],
+python3_source = custom_target('python-bindings',
+ input: ['../../src/common/hexchat-plugin.h', 'python.py'],
+ output: 'python.c',
+ command: [find_program('generate_plugin.py'), '@INPUT@', '@OUTPUT@']
+)
+
+install_data(['_hexchat.py', 'hexchat.py', 'xchat.py'],
+ install_dir: join_paths(get_option('libdir'), 'hexchat/python')
+)
+
+shared_module('python', python3_source,
+ dependencies: [hexchat_plugin_dep, python_dep],
install: true,
install_dir: plugindir,
name_prefix: '',
diff --color -Nur hexchat-2.14.3/plugins/python/python2.vcxproj hexchat/plugins/python/python2.vcxproj
--- hexchat-2.14.3/plugins/python/python2.vcxproj 2019-12-20 22:43:47.652401700 -0800
+++ hexchat/plugins/python/python2.vcxproj 2020-11-06 15:03:00.402820502 -0800
@@ -37,6 +37,9 @@
<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 --color -Nur hexchat-2.14.3/plugins/python/python3.vcxproj hexchat/plugins/python/python3.vcxproj
--- hexchat-2.14.3/plugins/python/python3.vcxproj 2019-12-20 22:43:47.652401700 -0800
+++ hexchat/plugins/python/python3.vcxproj 2020-11-06 15:03:00.403820502 -0800
@@ -37,6 +37,9 @@
<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 --color -Nur hexchat-2.14.3/plugins/python/python3.vcxproj.filters hexchat/plugins/python/python3.vcxproj.filters
--- hexchat-2.14.3/plugins/python/python3.vcxproj.filters 2019-12-20 22:43:47.652401700 -0800
+++ hexchat/plugins/python/python3.vcxproj.filters 2020-11-06 15:03:00.403820502 -0800
@@ -9,13 +9,26 @@
</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 --color -Nur hexchat-2.14.3/plugins/python/python.c hexchat/plugins/python/python.c
--- hexchat-2.14.3/plugins/python/python.c 2019-12-20 22:43:47.652401700 -0800
+++ hexchat/plugins/python/python.c 1969-12-31 16:00:00.000000000 -0800
@@ -1,2837 +0,0 @@
-/*
-* Copyright (c) 2002-2003 Gustavo Niemeyer <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 --color -Nur hexchat-2.14.3/plugins/python/python.def hexchat/plugins/python/python.def
--- hexchat-2.14.3/plugins/python/python.def 2019-12-20 22:43:47.652401700 -0800
+++ hexchat/plugins/python/python.def 2020-11-06 15:03:00.402820502 -0800
@@ -1,4 +1,3 @@
EXPORTS
hexchat_plugin_init
hexchat_plugin_deinit
-hexchat_plugin_get_info
diff --color -Nur hexchat-2.14.3/plugins/python/python.py hexchat/plugins/python/python.py
--- hexchat-2.14.3/plugins/python/python.py 1969-12-31 16:00:00.000000000 -0800
+++ hexchat/plugins/python/python.py 2020-11-06 15:03:00.402820502 -0800
@@ -0,0 +1,554 @@
+from __future__ import print_function
+
+import importlib
+import os
+import pydoc
+import signal
+import sys
+import traceback
+import weakref
+from contextlib import contextmanager
+
+from _hexchat_embedded import ffi, lib
+
+if sys.version_info < (3, 0):
+ from io import BytesIO as HelpEater
+else:
+ from io import StringIO as HelpEater
+
+if not hasattr(sys, 'argv'):
+ sys.argv = ['<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 --color -Nur hexchat-2.14.3/plugins/python/python_style_guide.md hexchat/plugins/python/python_style_guide.md
--- hexchat-2.14.3/plugins/python/python_style_guide.md 1969-12-31 16:00:00.000000000 -0800
+++ hexchat/plugins/python/python_style_guide.md 2020-11-06 15:03:00.403820502 -0800
@@ -0,0 +1,26 @@
+# HexChat Python Module Style Guide
+
+(This is a work in progress).
+
+## General rules
+
+- PEP8 as general fallback recommendations
+- Max line length: 120
+- Avoid overcomplex compound statements. i.e. dont do this: `somevar = x if x == y else z if a == b and c == b else x`
+
+## Indentation style
+
+### Multi-line functions
+
+```python
+foo(really_long_arg_1,
+ really_long_arg_2)
+```
+
+### Mutli-line lists/dicts
+
+```python
+foo = {
+ 'bar': 'baz',
+}
+```
diff --color -Nur hexchat-2.14.3/plugins/python/xchat.py hexchat/plugins/python/xchat.py
--- hexchat-2.14.3/plugins/python/xchat.py 1969-12-31 16:00:00.000000000 -0800
+++ hexchat/plugins/python/xchat.py 2020-11-06 15:03:00.403820502 -0800
@@ -0,0 +1 @@
+from _hexchat import *