1 diff --git a/.travis.yml b/.travis.yml
2 index df0c7e1f..9e226f0c 100644
3 --- a/.travis.yml
4 +++ b/.travis.yml
5 @@ -2,7 +2,7 @@ sudo: required
6 services: docker
7 before_install:
8 - docker pull ubuntu:16.04
9 - - docker run --privileged --cidfile=/tmp/cid ubuntu:16.04 /bin/sh -c 'apt-get update && apt-get install -y meson/xenial-backports libcanberra-dev libdbus-glib-1-dev libglib2.0-dev libgtk2.0-dev libluajit-5.1-dev libnotify-dev libpci-dev libperl-dev libproxy-dev libssl-dev python3-dev mono-devel desktop-file-utils'
10 + - docker run --privileged --cidfile=/tmp/cid ubuntu:16.04 /bin/sh -c 'apt-get update && apt-get install -y meson/xenial-backports libcanberra-dev libdbus-glib-1-dev libglib2.0-dev libgtk2.0-dev libluajit-5.1-dev libnotify-dev libpci-dev libperl-dev libproxy-dev libssl-dev python3-dev python3-cffi mono-devel desktop-file-utils'
11 - docker commit `cat /tmp/cid` hexchat/ubuntu-ci
12 - rm -f /tmp/cid
13 install:
14 diff --git a/meson.build b/meson.build
15 index 18baf26e..645e685e 100644
16 --- a/meson.build
17 +++ b/meson.build
18 @@ -49,6 +49,10 @@ config_h.set('GLIB_VERSION_MIN_REQUIRED', 'GLIB_VERSION_2_34')
19 config_h.set('HAVE_MEMRCHR', cc.has_function('memrchr'))
20 config_h.set('HAVE_STRINGS_H', cc.has_header('strings.h'))
21
22 +config_h.set_quoted('HEXCHATLIBDIR',
23 + join_paths(get_option('prefix'), get_option('libdir'), 'hexchat/plugins')
24 +)
25 +
26 if libssl_dep.found()
27 config_h.set('HAVE_X509_GET_SIGNATURE_NID',
28 cc.has_function('X509_get_signature_nid', dependencies: libssl_dep)
29 diff --git a/plugins/python/_hexchat.py b/plugins/python/_hexchat.py
30 new file mode 100644
31 index 00000000..567b3493
32 --- /dev/null
33 +++ b/plugins/python/_hexchat.py
34 @@ -0,0 +1,386 @@
35 +import inspect
36 +import sys
37 +from contextlib import contextmanager
38 +
39 +from _hexchat_embedded import ffi, lib
40 +
41 +__all__ = [
42 + 'EAT_ALL', 'EAT_HEXCHAT', 'EAT_NONE', 'EAT_PLUGIN', 'EAT_XCHAT',
43 + 'PRI_HIGH', 'PRI_HIGHEST', 'PRI_LOW', 'PRI_LOWEST', 'PRI_NORM',
44 + '__doc__', '__version__', 'command', 'del_pluginpref', 'emit_print',
45 + 'find_context', 'get_context', 'get_info',
46 + 'get_list', 'get_lists', 'get_pluginpref', 'get_prefs', 'hook_command',
47 + 'hook_print', 'hook_print_attrs', 'hook_server', 'hook_server_attrs',
48 + 'hook_timer', 'hook_unload', 'list_pluginpref', 'nickcmp', 'prnt',
49 + 'set_pluginpref', 'strip', 'unhook',
50 +]
51 +
52 +__doc__ = 'HexChat Scripting Interface'
53 +__version__ = (2, 0)
54 +__license__ = 'GPL-2.0+'
55 +
56 +EAT_NONE = 0
57 +EAT_HEXCHAT = 1
58 +EAT_XCHAT = EAT_HEXCHAT
59 +EAT_PLUGIN = 2
60 +EAT_ALL = EAT_HEXCHAT | EAT_PLUGIN
61 +
62 +PRI_LOWEST = -128
63 +PRI_LOW = -64
64 +PRI_NORM = 0
65 +PRI_HIGH = 64
66 +PRI_HIGHEST = 127
67 +
68 +
69 +# We need each module to be able to reference their parent plugin
70 +# which is a bit tricky since they all share the exact same module.
71 +# Simply navigating up to what module called it seems to actually
72 +# be a fairly reliable and simple method of doing so if ugly.
73 +def __get_current_plugin():
74 + frame = inspect.stack()[1][0]
75 + while '__plugin' not in frame.f_globals:
76 + frame = frame.f_back
77 + assert frame is not None
78 +
79 + return frame.f_globals['__plugin']
80 +
81 +
82 +# Keeping API compat
83 +if sys.version_info[0] == 2:
84 + def __decode(string):
85 + return string
86 +
87 +else:
88 + def __decode(string):
89 + return string.decode()
90 +
91 +
92 +# ------------ API ------------
93 +def prnt(string):
94 + lib.hexchat_print(lib.ph, string.encode())
95 +
96 +
97 +def emit_print(event_name, *args, **kwargs):
98 + time = kwargs.pop('time', 0) # For py2 compat
99 + cargs = []
100 + for i in range(4):
101 + arg = args[i].encode() if len(args) > i else b''
102 + cstring = ffi.new('char[]', arg)
103 + cargs.append(cstring)
104 +
105 + if time == 0:
106 + return lib.hexchat_emit_print(lib.ph, event_name.encode(), *cargs)
107 +
108 + attrs = lib.hexchat_event_attrs_create(lib.ph)
109 + attrs.server_time_utc = time
110 + ret = lib.hexchat_emit_print_attrs(lib.ph, attrs, event_name.encode(), *cargs)
111 + lib.hexchat_event_attrs_free(lib.ph, attrs)
112 + return ret
113 +
114 +
115 +# TODO: this shadows itself. command should be changed to cmd
116 +def command(command):
117 + lib.hexchat_command(lib.ph, command.encode())
118 +
119 +
120 +def nickcmp(string1, string2):
121 + return lib.hexchat_nickcmp(lib.ph, string1.encode(), string2.encode())
122 +
123 +
124 +def strip(text, length=-1, flags=3):
125 + stripped = lib.hexchat_strip(lib.ph, text.encode(), length, flags)
126 + ret = __decode(ffi.string(stripped))
127 + lib.hexchat_free(lib.ph, stripped)
128 + return ret
129 +
130 +
131 +def get_info(name):
132 + ret = lib.hexchat_get_info(lib.ph, name.encode())
133 + if ret == ffi.NULL:
134 + return None
135 + if name in ('gtkwin_ptr', 'win_ptr'):
136 + # Surely there is a less dumb way?
137 + ptr = repr(ret).rsplit(' ', 1)[1][:-1]
138 + return ptr
139 +
140 + return __decode(ffi.string(ret))
141 +
142 +
143 +def get_prefs(name):
144 + string_out = ffi.new('char**')
145 + int_out = ffi.new('int*')
146 + _type = lib.hexchat_get_prefs(lib.ph, name.encode(), string_out, int_out)
147 + if _type == 0:
148 + return None
149 +
150 + if _type == 1:
151 + return __decode(ffi.string(string_out[0]))
152 +
153 + if _type in (2, 3): # XXX: 3 should be a bool, but keeps API
154 + return int_out[0]
155 +
156 + raise AssertionError('Out of bounds pref storage')
157 +
158 +
159 +def __cstrarray_to_list(arr):
160 + i = 0
161 + ret = []
162 + while arr[i] != ffi.NULL:
163 + ret.append(ffi.string(arr[i]))
164 + i += 1
165 +
166 + return ret
167 +
168 +
169 +__FIELD_CACHE = {}
170 +
171 +
172 +def __get_fields(name):
173 + return __FIELD_CACHE.setdefault(name, __cstrarray_to_list(lib.hexchat_list_fields(lib.ph, name)))
174 +
175 +
176 +__FIELD_PROPERTY_CACHE = {}
177 +
178 +
179 +def __cached_decoded_str(string):
180 + return __FIELD_PROPERTY_CACHE.setdefault(string, __decode(string))
181 +
182 +
183 +def get_lists():
184 + return [__cached_decoded_str(field) for field in __get_fields(b'lists')]
185 +
186 +
187 +class ListItem:
188 + def __init__(self, name):
189 + self._listname = name
190 +
191 + def __repr__(self):
192 + return '<{} list item at {}>'.format(self._listname, id(self))
193 +
194 +
195 +# done this way for speed
196 +if sys.version_info[0] == 2:
197 + def get_getter(name):
198 + return ord(name[0])
199 +
200 +else:
201 + def get_getter(name):
202 + return name[0]
203 +
204 +
205 +def get_list(name):
206 + # XXX: This function is extremely inefficient and could be interators and
207 + # lazily loaded properties, but for API compat we stay slow
208 + orig_name = name
209 + name = name.encode()
210 +
211 + if name not in __get_fields(b'lists'):
212 + raise KeyError('list not available')
213 +
214 + list_ = lib.hexchat_list_get(lib.ph, name)
215 + if list_ == ffi.NULL:
216 + return None
217 +
218 + ret = []
219 + fields = __get_fields(name)
220 +
221 + def string_getter(field):
222 + string = lib.hexchat_list_str(lib.ph, list_, field)
223 + if string != ffi.NULL:
224 + return __decode(ffi.string(string))
225 +
226 + return ''
227 +
228 + def ptr_getter(field):
229 + if field == b'context':
230 + ptr = lib.hexchat_list_str(lib.ph, list_, field)
231 + ctx = ffi.cast('hexchat_context*', ptr)
232 + return Context(ctx)
233 +
234 + return None
235 +
236 + getters = {
237 + ord('s'): string_getter,
238 + ord('i'): lambda field: lib.hexchat_list_int(lib.ph, list_, field),
239 + ord('t'): lambda field: lib.hexchat_list_time(lib.ph, list_, field),
240 + ord('p'): ptr_getter,
241 + }
242 +
243 + while lib.hexchat_list_next(lib.ph, list_) == 1:
244 + item = ListItem(orig_name)
245 + for _field in fields:
246 + getter = getters.get(get_getter(_field))
247 + if getter is not None:
248 + field_name = _field[1:]
249 + setattr(item, __cached_decoded_str(field_name), getter(field_name))
250 +
251 + ret.append(item)
252 +
253 + lib.hexchat_list_free(lib.ph, list_)
254 + return ret
255 +
256 +
257 +# TODO: 'command' here shadows command above, and should be renamed to cmd
258 +def hook_command(command, callback, userdata=None, priority=PRI_NORM, help=None):
259 + plugin = __get_current_plugin()
260 + hook = plugin.add_hook(callback, userdata)
261 + handle = lib.hexchat_hook_command(lib.ph, command.encode(), priority, lib._on_command_hook,
262 + help.encode() if help is not None else ffi.NULL, hook.handle)
263 +
264 + hook.hexchat_hook = handle
265 + return id(hook)
266 +
267 +
268 +def hook_print(name, callback, userdata=None, priority=PRI_NORM):
269 + plugin = __get_current_plugin()
270 + hook = plugin.add_hook(callback, userdata)
271 + handle = lib.hexchat_hook_print(lib.ph, name.encode(), priority, lib._on_print_hook, hook.handle)
272 + hook.hexchat_hook = handle
273 + return id(hook)
274 +
275 +
276 +def hook_print_attrs(name, callback, userdata=None, priority=PRI_NORM):
277 + plugin = __get_current_plugin()
278 + hook = plugin.add_hook(callback, userdata)
279 + handle = lib.hexchat_hook_print_attrs(lib.ph, name.encode(), priority, lib._on_print_attrs_hook, hook.handle)
280 + hook.hexchat_hook = handle
281 + return id(hook)
282 +
283 +
284 +def hook_server(name, callback, userdata=None, priority=PRI_NORM):
285 + plugin = __get_current_plugin()
286 + hook = plugin.add_hook(callback, userdata)
287 + handle = lib.hexchat_hook_server(lib.ph, name.encode(), priority, lib._on_server_hook, hook.handle)
288 + hook.hexchat_hook = handle
289 + return id(hook)
290 +
291 +
292 +def hook_server_attrs(name, callback, userdata=None, priority=PRI_NORM):
293 + plugin = __get_current_plugin()
294 + hook = plugin.add_hook(callback, userdata)
295 + handle = lib.hexchat_hook_server_attrs(lib.ph, name.encode(), priority, lib._on_server_attrs_hook, hook.handle)
296 + hook.hexchat_hook = handle
297 + return id(hook)
298 +
299 +
300 +def hook_timer(timeout, callback, userdata=None):
301 + plugin = __get_current_plugin()
302 + hook = plugin.add_hook(callback, userdata)
303 + handle = lib.hexchat_hook_timer(lib.ph, timeout, lib._on_timer_hook, hook.handle)
304 + hook.hexchat_hook = handle
305 + return id(hook)
306 +
307 +
308 +def hook_unload(callback, userdata=None):
309 + plugin = __get_current_plugin()
310 + hook = plugin.add_hook(callback, userdata, is_unload=True)
311 + return id(hook)
312 +
313 +
314 +def unhook(handle):
315 + plugin = __get_current_plugin()
316 + return plugin.remove_hook(handle)
317 +
318 +
319 +def set_pluginpref(name, value):
320 + if isinstance(value, str):
321 + return bool(lib.hexchat_pluginpref_set_str(lib.ph, name.encode(), value.encode()))
322 +
323 + if isinstance(value, int):
324 + return bool(lib.hexchat_pluginpref_set_int(lib.ph, name.encode(), value))
325 +
326 + # XXX: This should probably raise but this keeps API
327 + return False
328 +
329 +
330 +def get_pluginpref(name):
331 + name = name.encode()
332 + string_out = ffi.new('char[512]')
333 + if lib.hexchat_pluginpref_get_str(lib.ph, name, string_out) != 1:
334 + return None
335 +
336 + string = ffi.string(string_out)
337 + # This API stores everything as a string so we have to figure out what
338 + # its actual type was supposed to be.
339 + if len(string) > 12: # Can't be a number
340 + return __decode(string)
341 +
342 + number = lib.hexchat_pluginpref_get_int(lib.ph, name)
343 + if number == -1 and string != b'-1':
344 + return __decode(string)
345 +
346 + return number
347 +
348 +
349 +def del_pluginpref(name):
350 + return bool(lib.hexchat_pluginpref_delete(lib.ph, name.encode()))
351 +
352 +
353 +def list_pluginpref():
354 + prefs_str = ffi.new('char[4096]')
355 + if lib.hexchat_pluginpref_list(lib.ph, prefs_str) == 1:
356 + return __decode(prefs_str).split(',')
357 +
358 + return []
359 +
360 +
361 +class Context:
362 + def __init__(self, ctx):
363 + self._ctx = ctx
364 +
365 + def __eq__(self, value):
366 + if not isinstance(value, Context):
367 + return False
368 +
369 + return self._ctx == value._ctx
370 +
371 + @contextmanager
372 + def __change_context(self):
373 + old_ctx = lib.hexchat_get_context(lib.ph)
374 + if not self.set():
375 + # XXX: Behavior change, previously used wrong context
376 + lib.hexchat_print(lib.ph, b'Context object refers to closed context, ignoring call')
377 + return
378 +
379 + yield
380 + lib.hexchat_set_context(lib.ph, old_ctx)
381 +
382 + def set(self):
383 + # XXX: API addition, C plugin silently ignored failure
384 + return bool(lib.hexchat_set_context(lib.ph, self._ctx))
385 +
386 + def prnt(self, string):
387 + with self.__change_context():
388 + prnt(string)
389 +
390 + def emit_print(self, event_name, *args, **kwargs):
391 + time = kwargs.pop('time', 0) # For py2 compat
392 + with self.__change_context():
393 + return emit_print(event_name, *args, time=time)
394 +
395 + def command(self, string):
396 + with self.__change_context():
397 + command(string)
398 +
399 + def get_info(self, name):
400 + with self.__change_context():
401 + return get_info(name)
402 +
403 + def get_list(self, name):
404 + with self.__change_context():
405 + return get_list(name)
406 +
407 +
408 +def get_context():
409 + ctx = lib.hexchat_get_context(lib.ph)
410 + return Context(ctx)
411 +
412 +
413 +def find_context(server=None, channel=None):
414 + server = server.encode() if server is not None else ffi.NULL
415 + channel = channel.encode() if channel is not None else ffi.NULL
416 + ctx = lib.hexchat_find_context(lib.ph, server, channel)
417 + if ctx == ffi.NULL:
418 + return None
419 +
420 + return Context(ctx)
421 diff --git a/plugins/python/generate_plugin.py b/plugins/python/generate_plugin.py
422 new file mode 100755
423 index 00000000..5c52b37b
424 --- /dev/null
425 +++ b/plugins/python/generate_plugin.py
426 @@ -0,0 +1,89 @@
427 +#!/usr/bin/env python3
428 +
429 +import sys
430 +import cffi
431 +
432 +builder = cffi.FFI()
433 +
434 +# hexchat-plugin.h
435 +with open(sys.argv[1]) as f:
436 + output = []
437 + eat_until_endif = 0
438 + # This is very specific to hexchat-plugin.h, it is not a cpp
439 + for line in f:
440 + if line.startswith('#define'):
441 + continue
442 + elif line.endswith('HEXCHAT_PLUGIN_H\n'):
443 + continue
444 + elif 'time.h' in line:
445 + output.append('typedef int... time_t;')
446 + elif line.startswith('#if'):
447 + eat_until_endif += 1
448 + elif line.startswith('#endif'):
449 + eat_until_endif -= 1
450 + elif eat_until_endif and '_hexchat_context' not in line:
451 + continue
452 + else:
453 + output.append(line)
454 + builder.cdef(''.join(output))
455 +
456 +builder.embedding_api('''
457 +extern "Python" int _on_py_command(char **, char **, void *);
458 +extern "Python" int _on_load_command(char **, char **, void *);
459 +extern "Python" int _on_unload_command(char **, char **, void *);
460 +extern "Python" int _on_reload_command(char **, char **, void *);
461 +extern "Python" int _on_say_command(char **, char **, void *);
462 +
463 +extern "Python" int _on_command_hook(char **, char **, void *);
464 +extern "Python" int _on_print_hook(char **, void *);
465 +extern "Python" int _on_print_attrs_hook(char **, hexchat_event_attrs *, void *);
466 +extern "Python" int _on_server_hook(char **, char **, void *);
467 +extern "Python" int _on_server_attrs_hook(char **, char **, hexchat_event_attrs *, void *);
468 +extern "Python" int _on_timer_hook(void *);
469 +
470 +extern "Python" int _on_plugin_init(char **, char **, char **, char *, char *);
471 +extern "Python" int _on_plugin_deinit(void);
472 +
473 +static hexchat_plugin *ph;
474 +''')
475 +
476 +builder.set_source('_hexchat_embedded', '''
477 +/* Python's header defines these.. */
478 +#undef HAVE_MEMRCHR
479 +#undef HAVE_STRINGS_H
480 +
481 +#include "config.h"
482 +#include "hexchat-plugin.h"
483 +
484 +static hexchat_plugin *ph;
485 +CFFI_DLLEXPORT int _on_plugin_init(char **, char **, char **, char *, char *);
486 +CFFI_DLLEXPORT int _on_plugin_deinit(void);
487 +
488 +int hexchat_plugin_init(hexchat_plugin *plugin_handle,
489 + char **name_out, char **description_out,
490 + char **version_out, char *arg)
491 +{
492 + if (ph != NULL)
493 + {
494 + puts ("Python plugin already loaded\\n");
495 + return 0; /* Prevent loading twice */
496 + }
497 +
498 + ph = plugin_handle;
499 + return _on_plugin_init(name_out, description_out, version_out, arg, HEXCHATLIBDIR);
500 +}
501 +
502 +int hexchat_plugin_deinit(void)
503 +{
504 + int ret = _on_plugin_deinit();
505 + ph = NULL;
506 + return ret;
507 +}
508 +''')
509 +
510 +# python.py
511 +with open(sys.argv[2]) as f:
512 + builder.embedding_init_code(f.read())
513 +
514 +# python.c
515 +builder.emit_c_code(sys.argv[3])
516 diff --git a/plugins/python/hexchat.py b/plugins/python/hexchat.py
517 new file mode 100644
518 index 00000000..6922490b
519 --- /dev/null
520 +++ b/plugins/python/hexchat.py
521 @@ -0,0 +1 @@
522 +from _hexchat import *
523 diff --git a/plugins/python/meson.build b/plugins/python/meson.build
524 index e24f0c6f..5fd7ec2f 100644
525 --- a/plugins/python/meson.build
526 +++ b/plugins/python/meson.build
527 @@ -1,12 +1,30 @@
528 python_opt = get_option('with-python')
529 if python_opt.startswith('python3')
530 - python_dep = dependency(python_opt, version: '>= 3.3')
531 + # Python 3.8 introduced a new -embed variant
532 + if not python_opt.endswith('-embed')
533 + python_dep = dependency(python_opt + '-embed', version: '>= 3.3', required: false)
534 + if not python_dep.found()
535 + python_dep = dependency(python_opt, version: '>= 3.3')
536 + endif
537 + else
538 + python_dep = dependency(python_opt, version: '>= 3.3')
539 + endif
540 else
541 python_dep = dependency(python_opt, version: '>= 2.7')
542 endif
543
544 -shared_module('python', 'python.c',
545 - dependencies: [libgio_dep, hexchat_plugin_dep, python_dep],
546 +python3_source = custom_target('python-bindings',
547 + input: ['../../src/common/hexchat-plugin.h', 'python.py'],
548 + output: 'python.c',
549 + command: [find_program('generate_plugin.py'), '@INPUT@', '@OUTPUT@']
550 +)
551 +
552 +install_data(['_hexchat.py', 'hexchat.py', 'xchat.py'],
553 + install_dir: join_paths(get_option('libdir'), 'hexchat/python')
554 +)
555 +
556 +shared_module('python', python3_source,
557 + dependencies: [hexchat_plugin_dep, python_dep],
558 install: true,
559 install_dir: plugindir,
560 name_prefix: '',
561 diff --git a/plugins/python/python.c b/plugins/python/python.c
562 deleted file mode 100644
563 index 475756ba..00000000
564 --- a/plugins/python/python.c
565 +++ /dev/null
566 @@ -1,2837 +0,0 @@
567 -/*
568 -* Copyright (c) 2002-2003 Gustavo Niemeyer <niemeyer@conectiva.com>
569 -*
570 -* XChat Python Plugin Interface
571 -*
572 -* Xchat Python Plugin Interface is free software; you can redistribute
573 -* it and/or modify it under the terms of the GNU General Public License
574 -* as published by the Free Software Foundation; either version 2 of the
575 -* License, or (at your option) any later version.
576 -*
577 -* pybot is distributed in the hope that it will be useful,
578 -* but WITHOUT ANY WARRANTY; without even the implied warranty of
579 -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
580 -* GNU General Public License for more details.
581 -*
582 -* You should have received a copy of the GNU General Public License
583 -* along with this file; if not, write to the Free Software
584 -* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
585 -*/
586 -
587 -/* Thread support
588 - * ==============
589 - *
590 - * The python interpreter has a global interpreter lock. Any thread
591 - * executing must acquire it before working with data accessible from
592 - * python code. Here we must also care about xchat not being
593 - * thread-safe. We do this by using an xchat lock, which protects
594 - * xchat instructions from being executed out of time (when this
595 - * plugin is not "active").
596 - *
597 - * When xchat calls python code:
598 - * - Change the current_plugin for the executing plugin;
599 - * - Release xchat lock
600 - * - Acquire the global interpreter lock
601 - * - Make the python call
602 - * - Release the global interpreter lock
603 - * - Acquire xchat lock
604 - *
605 - * When python code calls xchat:
606 - * - Release the global interpreter lock
607 - * - Acquire xchat lock
608 - * - Restore context, if necessary
609 - * - Make the xchat call
610 - * - Release xchat lock
611 - * - Acquire the global interpreter lock
612 - *
613 - * Inside a timer, so that individual threads have a chance to run:
614 - * - Release xchat lock
615 - * - Go ahead threads. Have a nice time!
616 - * - Acquire xchat lock
617 - *
618 - */
619 -
620 -#include "config.h"
621 -
622 -#include <glib.h>
623 -#include <glib/gstdio.h>
624 -#include <string.h>
625 -#include <stdlib.h>
626 -#include <sys/types.h>
627 -
628 -#ifdef WIN32
629 -#include <direct.h>
630 -#else
631 -#include <unistd.h>
632 -#include <dirent.h>
633 -#endif
634 -
635 -#include "hexchat-plugin.h"
636 -#undef _POSIX_C_SOURCE /* Avoid warnings from /usr/include/features.h */
637 -#undef _XOPEN_SOURCE
638 -#undef HAVE_MEMRCHR /* Avoid redefinition in Python.h */
639 -#undef HAVE_STRINGS_H
640 -#include <Python.h>
641 -#include <structmember.h>
642 -#include <pythread.h>
643 -
644 -/* Macros to convert version macros into string literals.
645 - * 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.
646 - * If STRINGIZE were to be directly defined as #X instead, VERSION would be "VERSION_MAJOR" instead of "1".
647 - */
648 -#define STRINGIZE2(X) #X
649 -#define STRINGIZE(X) STRINGIZE2(X)
650 -
651 -/* Version number macros */
652 -#define VERSION_MAJOR 1
653 -#define VERSION_MINOR 0
654 -
655 -/* Version string macro e.g 1.0/3.3 */
656 -#define VERSION STRINGIZE(VERSION_MAJOR) "." STRINGIZE(VERSION_MINOR) "/" \
657 - STRINGIZE(PY_MAJOR_VERSION) "." STRINGIZE (PY_MINOR_VERSION)
658 -
659 -/* #define's for Python 2 */
660 -#if PY_MAJOR_VERSION == 2
661 -#undef PyLong_Check
662 -#define PyLong_Check PyInt_Check
663 -#define PyLong_AsLong PyInt_AsLong
664 -#define PyLong_FromLong PyInt_FromLong
665 -
666 -#undef PyUnicode_Check
667 -#undef PyUnicode_FromString
668 -#undef PyUnicode_FromFormat
669 -#define PyUnicode_Check PyString_Check
670 -#define PyUnicode_AsFormat PyString_AsFormat
671 -#define PyUnicode_FromFormat PyString_FromFormat
672 -#define PyUnicode_FromString PyString_FromString
673 -#define PyUnicode_AsUTF8 PyString_AsString
674 -
675 -#ifdef WIN32
676 -#undef WITH_THREAD
677 -#endif
678 -#endif
679 -
680 -/* #define for Python 3 */
681 -#if PY_MAJOR_VERSION == 3
682 -#define IS_PY3K
683 -#endif
684 -
685 -#define NONE 0
686 -#define ALLOW_THREADS 1
687 -#define RESTORE_CONTEXT 2
688 -
689 -#ifdef WITH_THREAD
690 -#define ACQUIRE_XCHAT_LOCK() PyThread_acquire_lock(xchat_lock, 1)
691 -#define RELEASE_XCHAT_LOCK() PyThread_release_lock(xchat_lock)
692 -#define BEGIN_XCHAT_CALLS(x) \
693 - do { \
694 - PyObject *calls_plugin = NULL; \
695 - PyThreadState *calls_thread; \
696 - if ((x) & RESTORE_CONTEXT) \
697 - calls_plugin = Plugin_GetCurrent(); \
698 - calls_thread = PyEval_SaveThread(); \
699 - ACQUIRE_XCHAT_LOCK(); \
700 - if (!((x) & ALLOW_THREADS)) { \
701 - PyEval_RestoreThread(calls_thread); \
702 - calls_thread = NULL; \
703 - } \
704 - if (calls_plugin) \
705 - hexchat_set_context(ph, \
706 - Plugin_GetContext(calls_plugin)); \
707 - while (0)
708 -#define END_XCHAT_CALLS() \
709 - RELEASE_XCHAT_LOCK(); \
710 - if (calls_thread) \
711 - PyEval_RestoreThread(calls_thread); \
712 - } while(0)
713 -#else
714 -#define ACQUIRE_XCHAT_LOCK()
715 -#define RELEASE_XCHAT_LOCK()
716 -#define BEGIN_XCHAT_CALLS(x)
717 -#define END_XCHAT_CALLS()
718 -#endif
719 -
720 -#ifdef WITH_THREAD
721 -
722 -#define BEGIN_PLUGIN(plg) \
723 - do { \
724 - hexchat_context *begin_plugin_ctx = hexchat_get_context(ph); \
725 - RELEASE_XCHAT_LOCK(); \
726 - Plugin_AcquireThread(plg); \
727 - Plugin_SetContext(plg, begin_plugin_ctx); \
728 - } while (0)
729 -#define END_PLUGIN(plg) \
730 - do { \
731 - Plugin_ReleaseThread(plg); \
732 - ACQUIRE_XCHAT_LOCK(); \
733 - } while (0)
734 -
735 -#else /* !WITH_THREAD (win32) */
736 -
737 -static PyThreadState *pTempThread;
738 -
739 -#define BEGIN_PLUGIN(plg) \
740 - do { \
741 - hexchat_context *begin_plugin_ctx = hexchat_get_context(ph); \
742 - RELEASE_XCHAT_LOCK(); \
743 - PyEval_AcquireLock(); \
744 - pTempThread = PyThreadState_Swap(((PluginObject *)(plg))->tstate); \
745 - Plugin_SetContext(plg, begin_plugin_ctx); \
746 - } while (0)
747 -#define END_PLUGIN(plg) \
748 - do { \
749 - ((PluginObject *)(plg))->tstate = PyThreadState_Swap(pTempThread); \
750 - PyEval_ReleaseLock(); \
751 - ACQUIRE_XCHAT_LOCK(); \
752 - } while (0)
753 -
754 -#endif /* !WITH_THREAD */
755 -
756 -#define Plugin_Swap(x) \
757 - PyThreadState_Swap(((PluginObject *)(x))->tstate)
758 -#define Plugin_AcquireThread(x) \
759 - PyEval_AcquireThread(((PluginObject *)(x))->tstate)
760 -#define Plugin_ReleaseThread(x) \
761 - Util_ReleaseThread(((PluginObject *)(x))->tstate)
762 -#define Plugin_GetFilename(x) \
763 - (((PluginObject *)(x))->filename)
764 -#define Plugin_GetName(x) \
765 - (((PluginObject *)(x))->name)
766 -#define Plugin_GetVersion(x) \
767 - (((PluginObject *)(x))->version)
768 -#define Plugin_GetDesc(x) \
769 - (((PluginObject *)(x))->description)
770 -#define Plugin_GetHooks(x) \
771 - (((PluginObject *)(x))->hooks)
772 -#define Plugin_GetContext(x) \
773 - (((PluginObject *)(x))->context)
774 -#define Plugin_SetFilename(x, y) \
775 - ((PluginObject *)(x))->filename = (y);
776 -#define Plugin_SetName(x, y) \
777 - ((PluginObject *)(x))->name = (y);
778 -#define Plugin_SetVersion(x, y) \
779 - ((PluginObject *)(x))->version = (y);
780 -#define Plugin_SetDescription(x, y) \
781 - ((PluginObject *)(x))->description = (y);
782 -#define Plugin_SetHooks(x, y) \
783 - ((PluginObject *)(x))->hooks = (y);
784 -#define Plugin_SetContext(x, y) \
785 - ((PluginObject *)(x))->context = (y);
786 -#define Plugin_SetGui(x, y) \
787 - ((PluginObject *)(x))->gui = (y);
788 -
789 -#define HOOK_XCHAT 1
790 -#define HOOK_XCHAT_ATTR 2
791 -#define HOOK_UNLOAD 3
792 -
793 -/* ===================================================================== */
794 -/* Object definitions */
795 -
796 -typedef struct {
797 - PyObject_HEAD
798 - int softspace; /* We need it for print support. */
799 -} XChatOutObject;
800 -
801 -typedef struct {
802 - PyObject_HEAD
803 - hexchat_context *context;
804 -} ContextObject;
805 -
806 -typedef struct {
807 - PyObject_HEAD
808 - PyObject *time;
809 -} AttributeObject;
810 -
811 -typedef struct {
812 - PyObject_HEAD
813 - const char *listname;
814 - PyObject *dict;
815 -} ListItemObject;
816 -
817 -typedef struct {
818 - PyObject_HEAD
819 - char *name;
820 - char *version;
821 - char *filename;
822 - char *description;
823 - GSList *hooks;
824 - PyThreadState *tstate;
825 - hexchat_context *context;
826 - void *gui;
827 -} PluginObject;
828 -
829 -typedef struct {
830 - int type;
831 - PyObject *plugin;
832 - PyObject *callback;
833 - PyObject *userdata;
834 - char *name;
835 - void *data; /* A handle, when type == HOOK_XCHAT */
836 -} Hook;
837 -
838 -
839 -/* ===================================================================== */
840 -/* Function declarations */
841 -
842 -static PyObject *Util_BuildList(char *word[]);
843 -static PyObject *Util_BuildEOLList(char *word[]);
844 -static void Util_Autoload(void);
845 -static char *Util_Expand(char *filename);
846 -
847 -static int Callback_Server(char *word[], char *word_eol[], hexchat_event_attrs *attrs, void *userdata);
848 -static int Callback_Command(char *word[], char *word_eol[], void *userdata);
849 -static int Callback_Print_Attrs(char *word[], hexchat_event_attrs *attrs, void *userdata);
850 -static int Callback_Print(char *word[], void *userdata);
851 -static int Callback_Timer(void *userdata);
852 -static int Callback_ThreadTimer(void *userdata);
853 -
854 -static PyObject *XChatOut_New(void);
855 -static PyObject *XChatOut_write(PyObject *self, PyObject *args);
856 -static void XChatOut_dealloc(PyObject *self);
857 -
858 -static PyObject *Attribute_New(hexchat_event_attrs *attrs);
859 -
860 -static void Context_dealloc(PyObject *self);
861 -static PyObject *Context_set(ContextObject *self, PyObject *args);
862 -static PyObject *Context_command(ContextObject *self, PyObject *args);
863 -static PyObject *Context_prnt(ContextObject *self, PyObject *args);
864 -static PyObject *Context_get_info(ContextObject *self, PyObject *args);
865 -static PyObject *Context_get_list(ContextObject *self, PyObject *args);
866 -static PyObject *Context_compare(ContextObject *a, ContextObject *b, int op);
867 -static PyObject *Context_FromContext(hexchat_context *context);
868 -static PyObject *Context_FromServerAndChannel(char *server, char *channel);
869 -
870 -static PyObject *Plugin_New(char *filename, PyObject *xcoobj);
871 -static PyObject *Plugin_GetCurrent(void);
872 -static PluginObject *Plugin_ByString(char *str);
873 -static Hook *Plugin_AddHook(int type, PyObject *plugin, PyObject *callback,
874 - PyObject *userdata, char *name, void *data);
875 -static Hook *Plugin_FindHook(PyObject *plugin, char *name);
876 -static void Plugin_RemoveHook(PyObject *plugin, Hook *hook);
877 -static void Plugin_RemoveAllHooks(PyObject *plugin);
878 -
879 -static PyObject *Module_hexchat_command(PyObject *self, PyObject *args);
880 -static PyObject *Module_xchat_prnt(PyObject *self, PyObject *args);
881 -static PyObject *Module_hexchat_get_context(PyObject *self, PyObject *args);
882 -static PyObject *Module_hexchat_find_context(PyObject *self, PyObject *args,
883 - PyObject *kwargs);
884 -static PyObject *Module_hexchat_get_info(PyObject *self, PyObject *args);
885 -static PyObject *Module_hexchat_hook_command(PyObject *self, PyObject *args,
886 - PyObject *kwargs);
887 -static PyObject *Module_hexchat_hook_server(PyObject *self, PyObject *args,
888 - PyObject *kwargs);
889 -static PyObject *Module_hexchat_hook_print(PyObject *self, PyObject *args,
890 - PyObject *kwargs);
891 -static PyObject *Module_hexchat_hook_timer(PyObject *self, PyObject *args,
892 - PyObject *kwargs);
893 -static PyObject *Module_hexchat_unhook(PyObject *self, PyObject *args);
894 -static PyObject *Module_hexchat_get_info(PyObject *self, PyObject *args);
895 -static PyObject *Module_xchat_get_list(PyObject *self, PyObject *args);
896 -static PyObject *Module_xchat_get_lists(PyObject *self, PyObject *args);
897 -static PyObject *Module_hexchat_nickcmp(PyObject *self, PyObject *args);
898 -static PyObject *Module_hexchat_strip(PyObject *self, PyObject *args);
899 -static PyObject *Module_hexchat_pluginpref_set(PyObject *self, PyObject *args);
900 -static PyObject *Module_hexchat_pluginpref_get(PyObject *self, PyObject *args);
901 -static PyObject *Module_hexchat_pluginpref_delete(PyObject *self, PyObject *args);
902 -static PyObject *Module_hexchat_pluginpref_list(PyObject *self, PyObject *args);
903 -
904 -static void IInterp_Exec(char *command);
905 -static int IInterp_Cmd(char *word[], char *word_eol[], void *userdata);
906 -
907 -static void Command_PyList(void);
908 -static void Command_PyLoad(char *filename);
909 -static void Command_PyUnload(char *name);
910 -static void Command_PyReload(char *name);
911 -static void Command_PyAbout(void);
912 -static int Command_Py(char *word[], char *word_eol[], void *userdata);
913 -
914 -/* ===================================================================== */
915 -/* Static declarations and definitions */
916 -
917 -static PyTypeObject Plugin_Type;
918 -static PyTypeObject XChatOut_Type;
919 -static PyTypeObject Context_Type;
920 -static PyTypeObject ListItem_Type;
921 -static PyTypeObject Attribute_Type;
922 -
923 -static PyThreadState *main_tstate = NULL;
924 -static void *thread_timer = NULL;
925 -
926 -static hexchat_plugin *ph;
927 -static GSList *plugin_list = NULL;
928 -
929 -static PyObject *interp_plugin = NULL;
930 -static PyObject *xchatout = NULL;
931 -
932 -#ifdef WITH_THREAD
933 -static PyThread_type_lock xchat_lock = NULL;
934 -#endif
935 -
936 -static const char usage[] = "\
937 -Usage: /PY LOAD <filename>\n\
938 - UNLOAD <filename|name>\n\
939 - RELOAD <filename|name>\n\
940 - LIST\n\
941 - EXEC <command>\n\
942 - CONSOLE\n\
943 - ABOUT\n\
944 -\n";
945 -
946 -static const char about[] = "HexChat Python interface version " VERSION "\n";
947 -
948 -/* ===================================================================== */
949 -/* Utility functions */
950 -
951 -static PyObject *
952 -Util_BuildList(char *word[])
953 -{
954 - PyObject *list;
955 - int listsize = 31;
956 - int i;
957 - /* Find the last valid array member; there may be intermediate NULLs that
958 - * would otherwise cause us to drop some members. */
959 - while (listsize > 0 &&
960 - (word[listsize] == NULL || word[listsize][0] == 0))
961 - listsize--;
962 - list = PyList_New(listsize);
963 - if (list == NULL) {
964 - PyErr_Print();
965 - return NULL;
966 - }
967 - for (i = 1; i <= listsize; i++) {
968 - PyObject *o;
969 - if (word[i] == NULL) {
970 - Py_INCREF(Py_None);
971 - o = Py_None;
972 - } else {
973 - /* This handles word[i][0] == 0 automatically. */
974 - o = PyUnicode_FromString(word[i]);
975 - }
976 - PyList_SetItem(list, i - 1, o);
977 - }
978 - return list;
979 -}
980 -
981 -static PyObject *
982 -Util_BuildEOLList(char *word[])
983 -{
984 - PyObject *list;
985 - int listsize = 31;
986 - int i;
987 - char *accum = NULL;
988 - char *last = NULL;
989 -
990 - /* Find the last valid array member; there may be intermediate NULLs that
991 - * would otherwise cause us to drop some members. */
992 - while (listsize > 0 &&
993 - (word[listsize] == NULL || word[listsize][0] == 0))
994 - listsize--;
995 - list = PyList_New(listsize);
996 - if (list == NULL) {
997 - PyErr_Print();
998 - return NULL;
999 - }
1000 - for (i = listsize; i > 0; i--) {
1001 - char *part = word[i];
1002 - PyObject *uni_part;
1003 - if (accum == NULL) {
1004 - accum = g_strdup (part);
1005 - } else if (part != NULL && part[0] != 0) {
1006 - last = accum;
1007 - accum = g_strjoin(" ", part, last, NULL);
1008 - g_free (last);
1009 - last = NULL;
1010 -
1011 - if (accum == NULL) {
1012 - Py_DECREF(list);
1013 - hexchat_print(ph, "Not enough memory to alloc accum"
1014 - "for python plugin callback");
1015 - return NULL;
1016 - }
1017 - }
1018 - uni_part = PyUnicode_FromString(accum);
1019 - PyList_SetItem(list, i - 1, uni_part);
1020 - }
1021 -
1022 - g_free (last);
1023 - g_free (accum);
1024 -
1025 - return list;
1026 -}
1027 -
1028 -static void
1029 -Util_Autoload_from (const char *dir_name)
1030 -{
1031 - gchar *oldcwd;
1032 - const char *entry_name;
1033 - GDir *dir;
1034 -
1035 - oldcwd = g_get_current_dir ();
1036 - if (oldcwd == NULL)
1037 - return;
1038 - if (g_chdir(dir_name) != 0)
1039 - {
1040 - g_free (oldcwd);
1041 - return;
1042 - }
1043 - dir = g_dir_open (".", 0, NULL);
1044 - if (dir == NULL)
1045 - {
1046 - g_free (oldcwd);
1047 - return;
1048 - }
1049 - while ((entry_name = g_dir_read_name (dir)))
1050 - {
1051 - if (g_str_has_suffix (entry_name, ".py"))
1052 - Command_PyLoad((char*)entry_name);
1053 - }
1054 - g_dir_close (dir);
1055 - g_chdir (oldcwd);
1056 -}
1057 -
1058 -static void
1059 -Util_Autoload()
1060 -{
1061 - const char *xdir;
1062 - char *sub_dir;
1063 - /* we need local filesystem encoding for g_chdir, g_dir_open etc */
1064 -
1065 - xdir = hexchat_get_info(ph, "configdir");
1066 -
1067 - /* auto-load from subdirectory addons */
1068 - sub_dir = g_build_filename (xdir, "addons", NULL);
1069 - Util_Autoload_from(sub_dir);
1070 - g_free (sub_dir);
1071 -}
1072 -
1073 -static char *
1074 -Util_Expand(char *filename)
1075 -{
1076 - char *expanded;
1077 -
1078 - /* Check if this is an absolute path. */
1079 - if (g_path_is_absolute(filename)) {
1080 - if (g_file_test(filename, G_FILE_TEST_EXISTS))
1081 - return g_strdup(filename);
1082 - else
1083 - return NULL;
1084 - }
1085 -
1086 - /* Check if it starts with ~/ and expand the home if positive. */
1087 - if (*filename == '~' && *(filename+1) == '/') {
1088 - expanded = g_build_filename(g_get_home_dir(),
1089 - filename+2, NULL);
1090 - if (g_file_test(expanded, G_FILE_TEST_EXISTS))
1091 - return expanded;
1092 - else {
1093 - g_free(expanded);
1094 - return NULL;
1095 - }
1096 - }
1097 -
1098 - /* Check if it's in the current directory. */
1099 - expanded = g_build_filename(g_get_current_dir(),
1100 - filename, NULL);
1101 - if (g_file_test(expanded, G_FILE_TEST_EXISTS))
1102 - return expanded;
1103 - g_free(expanded);
1104 -
1105 - /* Check if ~/.config/hexchat/addons/<filename> exists. */
1106 - expanded = g_build_filename(hexchat_get_info(ph, "configdir"),
1107 - "addons", filename, NULL);
1108 - if (g_file_test(expanded, G_FILE_TEST_EXISTS))
1109 - return expanded;
1110 - g_free(expanded);
1111 -
1112 - return NULL;
1113 -}
1114 -
1115 -/* Similar to PyEval_ReleaseThread, but accepts NULL thread states. */
1116 -static void
1117 -Util_ReleaseThread(PyThreadState *tstate)
1118 -{
1119 - PyThreadState *old_tstate;
1120 - if (tstate == NULL)
1121 - Py_FatalError("PyEval_ReleaseThread: NULL thread state");
1122 - old_tstate = PyThreadState_Swap(NULL);
1123 - if (old_tstate != tstate && old_tstate != NULL)
1124 - Py_FatalError("PyEval_ReleaseThread: wrong thread state");
1125 - PyEval_ReleaseLock();
1126 -}
1127 -
1128 -/* ===================================================================== */
1129 -/* Hookable functions. These are the entry points to python code, besides
1130 - * the load function, and the hooks for interactive interpreter. */
1131 -
1132 -static int
1133 -Callback_Server(char *word[], char *word_eol[], hexchat_event_attrs *attrs, void *userdata)
1134 -{
1135 - Hook *hook = (Hook *) userdata;
1136 - PyObject *retobj;
1137 - PyObject *word_list, *word_eol_list;
1138 - PyObject *attributes;
1139 - int ret = HEXCHAT_EAT_NONE;
1140 - PyObject *plugin;
1141 -
1142 - plugin = hook->plugin;
1143 - BEGIN_PLUGIN(plugin);
1144 -
1145 - word_list = Util_BuildList(word);
1146 - if (word_list == NULL) {
1147 - END_PLUGIN(plugin);
1148 - return HEXCHAT_EAT_NONE;
1149 - }
1150 - word_eol_list = Util_BuildList(word_eol);
1151 - if (word_eol_list == NULL) {
1152 - Py_DECREF(word_list);
1153 - END_PLUGIN(plugin);
1154 - return HEXCHAT_EAT_NONE;
1155 - }
1156 -
1157 - attributes = Attribute_New(attrs);
1158 -
1159 - if (hook->type == HOOK_XCHAT_ATTR)
1160 - retobj = PyObject_CallFunction(hook->callback, "(OOOO)", word_list,
1161 - word_eol_list, hook->userdata, attributes);
1162 - else
1163 - retobj = PyObject_CallFunction(hook->callback, "(OOO)", word_list,
1164 - word_eol_list, hook->userdata);
1165 - Py_DECREF(word_list);
1166 - Py_DECREF(word_eol_list);
1167 - Py_DECREF(attributes);
1168 -
1169 - if (retobj == Py_None) {
1170 - ret = HEXCHAT_EAT_NONE;
1171 - Py_DECREF(retobj);
1172 - } else if (retobj) {
1173 - ret = PyLong_AsLong(retobj);
1174 - Py_DECREF(retobj);
1175 - } else {
1176 - PyErr_Print();
1177 - }
1178 -
1179 - END_PLUGIN(plugin);
1180 -
1181 - return ret;
1182 -}
1183 -
1184 -static int
1185 -Callback_Command(char *word[], char *word_eol[], void *userdata)
1186 -{
1187 - Hook *hook = (Hook *) userdata;
1188 - PyObject *retobj;
1189 - PyObject *word_list, *word_eol_list;
1190 - int ret = HEXCHAT_EAT_NONE;
1191 - PyObject *plugin;
1192 -
1193 - plugin = hook->plugin;
1194 - BEGIN_PLUGIN(plugin);
1195 -
1196 - word_list = Util_BuildList(word);
1197 - if (word_list == NULL) {
1198 - END_PLUGIN(plugin);
1199 - return HEXCHAT_EAT_NONE;
1200 - }
1201 - word_eol_list = Util_BuildList(word_eol);
1202 - if (word_eol_list == NULL) {
1203 - Py_DECREF(word_list);
1204 - END_PLUGIN(plugin);
1205 - return HEXCHAT_EAT_NONE;
1206 - }
1207 -
1208 - retobj = PyObject_CallFunction(hook->callback, "(OOO)", word_list,
1209 - word_eol_list, hook->userdata);
1210 - Py_DECREF(word_list);
1211 - Py_DECREF(word_eol_list);
1212 -
1213 - if (retobj == Py_None) {
1214 - ret = HEXCHAT_EAT_NONE;
1215 - Py_DECREF(retobj);
1216 - } else if (retobj) {
1217 - ret = PyLong_AsLong(retobj);
1218 - Py_DECREF(retobj);
1219 - } else {
1220 - PyErr_Print();
1221 - }
1222 -
1223 - END_PLUGIN(plugin);
1224 -
1225 - return ret;
1226 -}
1227 -
1228 -static int
1229 -Callback_Print_Attrs(char *word[], hexchat_event_attrs *attrs, void *userdata)
1230 -{
1231 - Hook *hook = (Hook *) userdata;
1232 - PyObject *retobj;
1233 - PyObject *word_list;
1234 - PyObject *word_eol_list;
1235 - PyObject *attributes;
1236 - int ret = HEXCHAT_EAT_NONE;
1237 - PyObject *plugin;
1238 -
1239 - plugin = hook->plugin;
1240 - BEGIN_PLUGIN(plugin);
1241 -
1242 - word_list = Util_BuildList(word);
1243 - if (word_list == NULL) {
1244 - END_PLUGIN(plugin);
1245 - return HEXCHAT_EAT_NONE;
1246 - }
1247 - word_eol_list = Util_BuildEOLList(word);
1248 - if (word_eol_list == NULL) {
1249 - Py_DECREF(word_list);
1250 - END_PLUGIN(plugin);
1251 - return HEXCHAT_EAT_NONE;
1252 - }
1253 -
1254 - attributes = Attribute_New(attrs);
1255 -
1256 - retobj = PyObject_CallFunction(hook->callback, "(OOOO)", word_list,
1257 - word_eol_list, hook->userdata, attributes);
1258 -
1259 - Py_DECREF(word_list);
1260 - Py_DECREF(word_eol_list);
1261 - Py_DECREF(attributes);
1262 -
1263 - if (retobj == Py_None) {
1264 - ret = HEXCHAT_EAT_NONE;
1265 - Py_DECREF(retobj);
1266 - } else if (retobj) {
1267 - ret = PyLong_AsLong(retobj);
1268 - Py_DECREF(retobj);
1269 - } else {
1270 - PyErr_Print();
1271 - }
1272 -
1273 - END_PLUGIN(plugin);
1274 -
1275 - return ret;
1276 -}
1277 -
1278 -static int
1279 -Callback_Print(char *word[], void *userdata)
1280 -{
1281 - Hook *hook = (Hook *) userdata;
1282 - PyObject *retobj;
1283 - PyObject *word_list;
1284 - PyObject *word_eol_list;
1285 - int ret = HEXCHAT_EAT_NONE;
1286 - PyObject *plugin;
1287 -
1288 - plugin = hook->plugin;
1289 - BEGIN_PLUGIN(plugin);
1290 -
1291 - word_list = Util_BuildList(word);
1292 - if (word_list == NULL) {
1293 - END_PLUGIN(plugin);
1294 - return HEXCHAT_EAT_NONE;
1295 - }
1296 - word_eol_list = Util_BuildEOLList(word);
1297 - if (word_eol_list == NULL) {
1298 - Py_DECREF(word_list);
1299 - END_PLUGIN(plugin);
1300 - return HEXCHAT_EAT_NONE;
1301 - }
1302 -
1303 - retobj = PyObject_CallFunction(hook->callback, "(OOO)", word_list,
1304 - word_eol_list, hook->userdata);
1305 -
1306 - Py_DECREF(word_list);
1307 - Py_DECREF(word_eol_list);
1308 -
1309 - if (retobj == Py_None) {
1310 - ret = HEXCHAT_EAT_NONE;
1311 - Py_DECREF(retobj);
1312 - } else if (retobj) {
1313 - ret = PyLong_AsLong(retobj);
1314 - Py_DECREF(retobj);
1315 - } else {
1316 - PyErr_Print();
1317 - }
1318 -
1319 - END_PLUGIN(plugin);
1320 -
1321 - return ret;
1322 -}
1323 -
1324 -static int
1325 -Callback_Timer(void *userdata)
1326 -{
1327 - Hook *hook = (Hook *) userdata;
1328 - PyObject *retobj;
1329 - int ret = 0;
1330 - PyObject *plugin;
1331 -
1332 - plugin = hook->plugin;
1333 -
1334 - BEGIN_PLUGIN(hook->plugin);
1335 -
1336 - retobj = PyObject_CallFunction(hook->callback, "(O)", hook->userdata);
1337 -
1338 - if (retobj) {
1339 - ret = PyObject_IsTrue(retobj);
1340 - Py_DECREF(retobj);
1341 - } else {
1342 - PyErr_Print();
1343 - }
1344 -
1345 - /* Returning 0 for this callback unhooks itself. */
1346 - if (ret == 0)
1347 - Plugin_RemoveHook(plugin, hook);
1348 -
1349 - END_PLUGIN(plugin);
1350 -
1351 - return ret;
1352 -}
1353 -
1354 -#ifdef WITH_THREAD
1355 -static int
1356 -Callback_ThreadTimer(void *userdata)
1357 -{
1358 - RELEASE_XCHAT_LOCK();
1359 -#ifndef WIN32
1360 - usleep(1);
1361 -#endif
1362 - ACQUIRE_XCHAT_LOCK();
1363 - return 1;
1364 -}
1365 -#endif
1366 -
1367 -/* ===================================================================== */
1368 -/* XChatOut object */
1369 -
1370 -/* We keep this information global, so we can reset it when the
1371 - * deinit function is called. */
1372 -/* XXX This should be somehow bound to the printing context. */
1373 -static GString *xchatout_buffer = NULL;
1374 -
1375 -static PyObject *
1376 -XChatOut_New()
1377 -{
1378 - XChatOutObject *xcoobj;
1379 - xcoobj = PyObject_New(XChatOutObject, &XChatOut_Type);
1380 - if (xcoobj != NULL)
1381 - xcoobj->softspace = 0;
1382 - return (PyObject *) xcoobj;
1383 -}
1384 -
1385 -static void
1386 -XChatOut_dealloc(PyObject *self)
1387 -{
1388 - Py_TYPE(self)->tp_free((PyObject *)self);
1389 -}
1390 -
1391 -/* This is a little bit complex because we have to buffer data
1392 - * until a \n is received, since xchat breaks the line automatically.
1393 - * We also crop the last \n for this reason. */
1394 -static PyObject *
1395 -XChatOut_write(PyObject *self, PyObject *args)
1396 -{
1397 - gboolean add_space;
1398 - char *data, *pos;
1399 -
1400 - if (!PyArg_ParseTuple(args, "s:write", &data))
1401 - return NULL;
1402 - if (!data || !*data) {
1403 - Py_RETURN_NONE;
1404 - }
1405 - BEGIN_XCHAT_CALLS(RESTORE_CONTEXT|ALLOW_THREADS);
1406 - if (((XChatOutObject *)self)->softspace) {
1407 - add_space = TRUE;
1408 - ((XChatOutObject *)self)->softspace = 0;
1409 - } else {
1410 - add_space = FALSE;
1411 - }
1412 -
1413 - g_string_append (xchatout_buffer, data);
1414 -
1415 - /* If not end of line add space to continue buffer later */
1416 - if (add_space && xchatout_buffer->str[xchatout_buffer->len - 1] != '\n')
1417 - {
1418 - g_string_append_c (xchatout_buffer, ' ');
1419 - }
1420 -
1421 - /* If there is an end of line print up to that */
1422 - if ((pos = strrchr (xchatout_buffer->str, '\n')))
1423 - {
1424 - *pos = '\0';
1425 - hexchat_print (ph, xchatout_buffer->str);
1426 -
1427 - /* Then remove it from buffer */
1428 - g_string_erase (xchatout_buffer, 0, pos - xchatout_buffer->str + 1);
1429 - }
1430 -
1431 - END_XCHAT_CALLS();
1432 - Py_RETURN_NONE;
1433 -}
1434 -
1435 -#define OFF(x) offsetof(XChatOutObject, x)
1436 -
1437 -static PyMemberDef XChatOut_members[] = {
1438 - {"softspace", T_INT, OFF(softspace), 0},
1439 - {0}
1440 -};
1441 -
1442 -static PyMethodDef XChatOut_methods[] = {
1443 - {"write", XChatOut_write, METH_VARARGS},
1444 - {NULL, NULL}
1445 -};
1446 -
1447 -static PyTypeObject XChatOut_Type = {
1448 - PyVarObject_HEAD_INIT(NULL, 0)
1449 - "hexchat.XChatOut", /*tp_name*/
1450 - sizeof(XChatOutObject), /*tp_basicsize*/
1451 - 0, /*tp_itemsize*/
1452 - XChatOut_dealloc, /*tp_dealloc*/
1453 - 0, /*tp_print*/
1454 - 0, /*tp_getattr*/
1455 - 0, /*tp_setattr*/
1456 - 0, /*tp_compare*/
1457 - 0, /*tp_repr*/
1458 - 0, /*tp_as_number*/
1459 - 0, /*tp_as_sequence*/
1460 - 0, /*tp_as_mapping*/
1461 - 0, /*tp_hash*/
1462 - 0, /*tp_call*/
1463 - 0, /*tp_str*/
1464 - PyObject_GenericGetAttr,/*tp_getattro*/
1465 - PyObject_GenericSetAttr,/*tp_setattro*/
1466 - 0, /*tp_as_buffer*/
1467 - Py_TPFLAGS_DEFAULT, /*tp_flags*/
1468 - 0, /*tp_doc*/
1469 - 0, /*tp_traverse*/
1470 - 0, /*tp_clear*/
1471 - 0, /*tp_richcompare*/
1472 - 0, /*tp_weaklistoffset*/
1473 - 0, /*tp_iter*/
1474 - 0, /*tp_iternext*/
1475 - XChatOut_methods, /*tp_methods*/
1476 - XChatOut_members, /*tp_members*/
1477 - 0, /*tp_getset*/
1478 - 0, /*tp_base*/
1479 - 0, /*tp_dict*/
1480 - 0, /*tp_descr_get*/
1481 - 0, /*tp_descr_set*/
1482 - 0, /*tp_dictoffset*/
1483 - 0, /*tp_init*/
1484 - PyType_GenericAlloc, /*tp_alloc*/
1485 - PyType_GenericNew, /*tp_new*/
1486 - PyObject_Del, /*tp_free*/
1487 - 0, /*tp_is_gc*/
1488 -};
1489 -
1490 -
1491 -/* ===================================================================== */
1492 -/* Attribute object */
1493 -
1494 -#undef OFF
1495 -#define OFF(x) offsetof(AttributeObject, x)
1496 -
1497 -static PyMemberDef Attribute_members[] = {
1498 - {"time", T_OBJECT, OFF(time), 0},
1499 - {0}
1500 -};
1501 -
1502 -static void
1503 -Attribute_dealloc(PyObject *self)
1504 -{
1505 - Py_DECREF(((AttributeObject*)self)->time);
1506 - Py_TYPE(self)->tp_free((PyObject *)self);
1507 -}
1508 -
1509 -static PyObject *
1510 -Attribute_repr(PyObject *self)
1511 -{
1512 - return PyUnicode_FromFormat("<Attribute object at %p>", self);
1513 -}
1514 -
1515 -static PyTypeObject Attribute_Type = {
1516 - PyVarObject_HEAD_INIT(NULL, 0)
1517 - "hexchat.Attribute", /*tp_name*/
1518 - sizeof(AttributeObject), /*tp_basicsize*/
1519 - 0, /*tp_itemsize*/
1520 - Attribute_dealloc, /*tp_dealloc*/
1521 - 0, /*tp_print*/
1522 - 0, /*tp_getattr*/
1523 - 0, /*tp_setattr*/
1524 - 0, /*tp_compare*/
1525 - Attribute_repr, /*tp_repr*/
1526 - 0, /*tp_as_number*/
1527 - 0, /*tp_as_sequence*/
1528 - 0, /*tp_as_mapping*/
1529 - 0, /*tp_hash*/
1530 - 0, /*tp_call*/
1531 - 0, /*tp_str*/
1532 - PyObject_GenericGetAttr,/*tp_getattro*/
1533 - PyObject_GenericSetAttr,/*tp_setattro*/
1534 - 0, /*tp_as_buffer*/
1535 - Py_TPFLAGS_DEFAULT, /*tp_flags*/
1536 - 0, /*tp_doc*/
1537 - 0, /*tp_traverse*/
1538 - 0, /*tp_clear*/
1539 - 0, /*tp_richcompare*/
1540 - 0, /*tp_weaklistoffset*/
1541 - 0, /*tp_iter*/
1542 - 0, /*tp_iternext*/
1543 - 0, /*tp_methods*/
1544 - Attribute_members, /*tp_members*/
1545 - 0, /*tp_getset*/
1546 - 0, /*tp_base*/
1547 - 0, /*tp_dict*/
1548 - 0, /*tp_descr_get*/
1549 - 0, /*tp_descr_set*/
1550 - 0, /*tp_dictoffset*/
1551 - 0, /*tp_init*/
1552 - PyType_GenericAlloc, /*tp_alloc*/
1553 - PyType_GenericNew, /*tp_new*/
1554 - PyObject_Del, /*tp_free*/
1555 - 0, /*tp_is_gc*/
1556 -};
1557 -
1558 -static PyObject *
1559 -Attribute_New(hexchat_event_attrs *attrs)
1560 -{
1561 - AttributeObject *attr;
1562 - attr = PyObject_New(AttributeObject, &Attribute_Type);
1563 - if (attr != NULL) {
1564 - attr->time = PyLong_FromLong((long)attrs->server_time_utc);
1565 - }
1566 - return (PyObject *) attr;
1567 -}
1568 -
1569 -
1570 -/* ===================================================================== */
1571 -/* Context object */
1572 -
1573 -static void
1574 -Context_dealloc(PyObject *self)
1575 -{
1576 - Py_TYPE(self)->tp_free((PyObject *)self);
1577 -}
1578 -
1579 -static PyObject *
1580 -Context_set(ContextObject *self, PyObject *args)
1581 -{
1582 - PyObject *plugin = Plugin_GetCurrent();
1583 - Plugin_SetContext(plugin, self->context);
1584 - Py_RETURN_NONE;
1585 -}
1586 -
1587 -static PyObject *
1588 -Context_command(ContextObject *self, PyObject *args)
1589 -{
1590 - char *text;
1591 - if (!PyArg_ParseTuple(args, "s:command", &text))
1592 - return NULL;
1593 - BEGIN_XCHAT_CALLS(ALLOW_THREADS);
1594 - hexchat_set_context(ph, self->context);
1595 - hexchat_command(ph, text);
1596 - END_XCHAT_CALLS();
1597 - Py_RETURN_NONE;
1598 -}
1599 -
1600 -static PyObject *
1601 -Context_prnt(ContextObject *self, PyObject *args)
1602 -{
1603 - char *text;
1604 - if (!PyArg_ParseTuple(args, "s:prnt", &text))
1605 - return NULL;
1606 - BEGIN_XCHAT_CALLS(ALLOW_THREADS);
1607 - hexchat_set_context(ph, self->context);
1608 - hexchat_print(ph, text);
1609 - END_XCHAT_CALLS();
1610 - Py_RETURN_NONE;
1611 -}
1612 -
1613 -static PyObject *
1614 -Context_emit_print(ContextObject *self, PyObject *args, PyObject *kwargs)
1615 -{
1616 - char *argv[6];
1617 - char *name;
1618 - int res;
1619 - long time = 0;
1620 - hexchat_event_attrs *attrs;
1621 - char *kwlist[] = {"name", "arg1", "arg2", "arg3",
1622 - "arg4", "arg5", "arg6",
1623 - "time", NULL};
1624 - memset(&argv, 0, sizeof(char*)*6);
1625 - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|ssssssl:print_event", kwlist, &name,
1626 - &argv[0], &argv[1], &argv[2],
1627 - &argv[3], &argv[4], &argv[5],
1628 - &time))
1629 - return NULL;
1630 - BEGIN_XCHAT_CALLS(ALLOW_THREADS);
1631 - hexchat_set_context(ph, self->context);
1632 - attrs = hexchat_event_attrs_create(ph);
1633 - attrs->server_time_utc = (time_t)time;
1634 -
1635 - res = hexchat_emit_print_attrs(ph, attrs, name, argv[0], argv[1], argv[2],
1636 - argv[3], argv[4], argv[5], NULL);
1637 -
1638 - hexchat_event_attrs_free(ph, attrs);
1639 - END_XCHAT_CALLS();
1640 - return PyLong_FromLong(res);
1641 -}
1642 -
1643 -static PyObject *
1644 -Context_get_info(ContextObject *self, PyObject *args)
1645 -{
1646 - const char *info;
1647 - char *name;
1648 - if (!PyArg_ParseTuple(args, "s:get_info", &name))
1649 - return NULL;
1650 - BEGIN_XCHAT_CALLS(NONE);
1651 - hexchat_set_context(ph, self->context);
1652 - info = hexchat_get_info(ph, name);
1653 - END_XCHAT_CALLS();
1654 - if (info == NULL) {
1655 - Py_RETURN_NONE;
1656 - }
1657 - return PyUnicode_FromString(info);
1658 -}
1659 -
1660 -static PyObject *
1661 -Context_get_list(ContextObject *self, PyObject *args)
1662 -{
1663 - PyObject *plugin = Plugin_GetCurrent();
1664 - hexchat_context *saved_context = Plugin_GetContext(plugin);
1665 - PyObject *ret;
1666 - Plugin_SetContext(plugin, self->context);
1667 - ret = Module_xchat_get_list((PyObject*)self, args);
1668 - Plugin_SetContext(plugin, saved_context);
1669 - return ret;
1670 -}
1671 -
1672 -/* needed to make context1 == context2 work */
1673 -static PyObject *
1674 -Context_compare(ContextObject *a, ContextObject *b, int op)
1675 -{
1676 - PyObject *ret;
1677 - /* check for == */
1678 - if (op == Py_EQ)
1679 - ret = (a->context == b->context ? Py_True : Py_False);
1680 - /* check for != */
1681 - else if (op == Py_NE)
1682 - ret = (a->context != b->context ? Py_True : Py_False);
1683 - /* only makes sense as == and != */
1684 - else
1685 - {
1686 - PyErr_SetString(PyExc_TypeError, "contexts are either equal or not equal");
1687 - ret = Py_None;
1688 - }
1689 -
1690 - Py_INCREF(ret);
1691 - return ret;
1692 -}
1693 -
1694 -static PyMethodDef Context_methods[] = {
1695 - {"set", (PyCFunction) Context_set, METH_NOARGS},
1696 - {"command", (PyCFunction) Context_command, METH_VARARGS},
1697 - {"prnt", (PyCFunction) Context_prnt, METH_VARARGS},
1698 - {"emit_print", (PyCFunction) Context_emit_print, METH_VARARGS|METH_KEYWORDS},
1699 - {"get_info", (PyCFunction) Context_get_info, METH_VARARGS},
1700 - {"get_list", (PyCFunction) Context_get_list, METH_VARARGS},
1701 - {NULL, NULL}
1702 -};
1703 -
1704 -static PyTypeObject Context_Type = {
1705 - PyVarObject_HEAD_INIT(NULL, 0)
1706 - "hexchat.Context", /*tp_name*/
1707 - sizeof(ContextObject), /*tp_basicsize*/
1708 - 0, /*tp_itemsize*/
1709 - Context_dealloc, /*tp_dealloc*/
1710 - 0, /*tp_print*/
1711 - 0, /*tp_getattr*/
1712 - 0, /*tp_setattr*/
1713 - 0, /*tp_compare*/
1714 - 0, /*tp_repr*/
1715 - 0, /*tp_as_number*/
1716 - 0, /*tp_as_sequence*/
1717 - 0, /*tp_as_mapping*/
1718 - 0, /*tp_hash*/
1719 - 0, /*tp_call*/
1720 - 0, /*tp_str*/
1721 - PyObject_GenericGetAttr,/*tp_getattro*/
1722 - PyObject_GenericSetAttr,/*tp_setattro*/
1723 - 0, /*tp_as_buffer*/
1724 - Py_TPFLAGS_DEFAULT, /*tp_flags*/
1725 - 0, /*tp_doc*/
1726 - 0, /*tp_traverse*/
1727 - 0, /*tp_clear*/
1728 - (richcmpfunc)Context_compare, /*tp_richcompare*/
1729 - 0, /*tp_weaklistoffset*/
1730 - 0, /*tp_iter*/
1731 - 0, /*tp_iternext*/
1732 - Context_methods, /*tp_methods*/
1733 - 0, /*tp_members*/
1734 - 0, /*tp_getset*/
1735 - 0, /*tp_base*/
1736 - 0, /*tp_dict*/
1737 - 0, /*tp_descr_get*/
1738 - 0, /*tp_descr_set*/
1739 - 0, /*tp_dictoffset*/
1740 - 0, /*tp_init*/
1741 - PyType_GenericAlloc, /*tp_alloc*/
1742 - PyType_GenericNew, /*tp_new*/
1743 - PyObject_Del, /*tp_free*/
1744 - 0, /*tp_is_gc*/
1745 -};
1746 -
1747 -static PyObject *
1748 -Context_FromContext(hexchat_context *context)
1749 -{
1750 - ContextObject *ctxobj = PyObject_New(ContextObject, &Context_Type);
1751 - if (ctxobj != NULL)
1752 - ctxobj->context = context;
1753 - return (PyObject *) ctxobj;
1754 -}
1755 -
1756 -static PyObject *
1757 -Context_FromServerAndChannel(char *server, char *channel)
1758 -{
1759 - ContextObject *ctxobj;
1760 - hexchat_context *context;
1761 - BEGIN_XCHAT_CALLS(NONE);
1762 - context = hexchat_find_context(ph, server, channel);
1763 - END_XCHAT_CALLS();
1764 - if (context == NULL)
1765 - return NULL;
1766 - ctxobj = PyObject_New(ContextObject, &Context_Type);
1767 - if (ctxobj == NULL)
1768 - return NULL;
1769 - ctxobj->context = context;
1770 - return (PyObject *) ctxobj;
1771 -}
1772 -
1773 -
1774 -/* ===================================================================== */
1775 -/* ListItem object */
1776 -
1777 -#undef OFF
1778 -#define OFF(x) offsetof(ListItemObject, x)
1779 -
1780 -static PyMemberDef ListItem_members[] = {
1781 - {"__dict__", T_OBJECT, OFF(dict), 0},
1782 - {0}
1783 -};
1784 -
1785 -static void
1786 -ListItem_dealloc(PyObject *self)
1787 -{
1788 - Py_DECREF(((ListItemObject*)self)->dict);
1789 - Py_TYPE(self)->tp_free((PyObject *)self);
1790 -}
1791 -
1792 -static PyObject *
1793 -ListItem_repr(PyObject *self)
1794 -{
1795 - return PyUnicode_FromFormat("<%s list item at %p>",
1796 - ((ListItemObject*)self)->listname, self);
1797 -}
1798 -
1799 -static PyTypeObject ListItem_Type = {
1800 - PyVarObject_HEAD_INIT(NULL, 0)
1801 - "hexchat.ListItem", /*tp_name*/
1802 - sizeof(ListItemObject), /*tp_basicsize*/
1803 - 0, /*tp_itemsize*/
1804 - ListItem_dealloc, /*tp_dealloc*/
1805 - 0, /*tp_print*/
1806 - 0, /*tp_getattr*/
1807 - 0, /*tp_setattr*/
1808 - 0, /*tp_compare*/
1809 - ListItem_repr, /*tp_repr*/
1810 - 0, /*tp_as_number*/
1811 - 0, /*tp_as_sequence*/
1812 - 0, /*tp_as_mapping*/
1813 - 0, /*tp_hash*/
1814 - 0, /*tp_call*/
1815 - 0, /*tp_str*/
1816 - PyObject_GenericGetAttr,/*tp_getattro*/
1817 - PyObject_GenericSetAttr,/*tp_setattro*/
1818 - 0, /*tp_as_buffer*/
1819 - Py_TPFLAGS_DEFAULT, /*tp_flags*/
1820 - 0, /*tp_doc*/
1821 - 0, /*tp_traverse*/
1822 - 0, /*tp_clear*/
1823 - 0, /*tp_richcompare*/
1824 - 0, /*tp_weaklistoffset*/
1825 - 0, /*tp_iter*/
1826 - 0, /*tp_iternext*/
1827 - 0, /*tp_methods*/
1828 - ListItem_members, /*tp_members*/
1829 - 0, /*tp_getset*/
1830 - 0, /*tp_base*/
1831 - 0, /*tp_dict*/
1832 - 0, /*tp_descr_get*/
1833 - 0, /*tp_descr_set*/
1834 - OFF(dict), /*tp_dictoffset*/
1835 - 0, /*tp_init*/
1836 - PyType_GenericAlloc, /*tp_alloc*/
1837 - PyType_GenericNew, /*tp_new*/
1838 - PyObject_Del, /*tp_free*/
1839 - 0, /*tp_is_gc*/
1840 -};
1841 -
1842 -static PyObject *
1843 -ListItem_New(const char *listname)
1844 -{
1845 - ListItemObject *item;
1846 - item = PyObject_New(ListItemObject, &ListItem_Type);
1847 - if (item != NULL) {
1848 - /* listname parameter must be statically allocated. */
1849 - item->listname = listname;
1850 - item->dict = PyDict_New();
1851 - if (item->dict == NULL) {
1852 - Py_DECREF(item);
1853 - item = NULL;
1854 - }
1855 - }
1856 - return (PyObject *) item;
1857 -}
1858 -
1859 -
1860 -/* ===================================================================== */
1861 -/* Plugin object */
1862 -
1863 -#define GET_MODULE_DATA(x, force) \
1864 - o = PyObject_GetAttrString(m, "__module_" #x "__"); \
1865 - if (o == NULL) { \
1866 - if (force) { \
1867 - hexchat_print(ph, "Module has no __module_" #x "__ " \
1868 - "defined"); \
1869 - goto error; \
1870 - } \
1871 - plugin->x = g_strdup(""); \
1872 - } else {\
1873 - if (!PyUnicode_Check(o)) { \
1874 - hexchat_print(ph, "Variable __module_" #x "__ " \
1875 - "must be a string"); \
1876 - goto error; \
1877 - } \
1878 - plugin->x = g_strdup(PyUnicode_AsUTF8(o)); \
1879 - if (plugin->x == NULL) { \
1880 - hexchat_print(ph, "Not enough memory to allocate " #x); \
1881 - goto error; \
1882 - } \
1883 - }
1884 -
1885 -static PyObject *
1886 -Plugin_GetCurrent()
1887 -{
1888 - PyObject *plugin;
1889 - plugin = PySys_GetObject("__plugin__");
1890 - if (plugin == NULL)
1891 - PyErr_SetString(PyExc_RuntimeError, "lost sys.__plugin__");
1892 - return plugin;
1893 -}
1894 -
1895 -static hexchat_plugin *
1896 -Plugin_GetHandle(PluginObject *plugin)
1897 -{
1898 - /* This works but the issue is that the script must be ran to get
1899 - * the name of it thus upon first use it will use the wrong handler
1900 - * work around would be to run a fake script once to get name? */
1901 -#if 0
1902 - /* return fake handle for pluginpref */
1903 - if (plugin->gui != NULL)
1904 - return plugin->gui;
1905 - else
1906 -#endif
1907 - return ph;
1908 -}
1909 -
1910 -static PluginObject *
1911 -Plugin_ByString(char *str)
1912 -{
1913 - GSList *list;
1914 - PluginObject *plugin;
1915 - char *basename;
1916 - list = plugin_list;
1917 - while (list != NULL) {
1918 - plugin = (PluginObject *) list->data;
1919 - basename = g_path_get_basename(plugin->filename);
1920 - if (basename == NULL)
1921 - break;
1922 - if (strcasecmp(plugin->name, str) == 0 ||
1923 - strcasecmp(plugin->filename, str) == 0 ||
1924 - strcasecmp(basename, str) == 0) {
1925 - g_free(basename);
1926 - return plugin;
1927 - }
1928 - g_free(basename);
1929 - list = list->next;
1930 - }
1931 - return NULL;
1932 -}
1933 -
1934 -static Hook *
1935 -Plugin_AddHook(int type, PyObject *plugin, PyObject *callback,
1936 - PyObject *userdata, char *name, void *data)
1937 -{
1938 - Hook *hook = g_new(Hook, 1);
1939 - hook->type = type;
1940 - hook->plugin = plugin;
1941 - Py_INCREF(callback);
1942 - hook->callback = callback;
1943 - Py_INCREF(userdata);
1944 - hook->userdata = userdata;
1945 - hook->name = g_strdup (name);
1946 - hook->data = NULL;
1947 - Plugin_SetHooks(plugin, g_slist_append(Plugin_GetHooks(plugin),
1948 - hook));
1949 -
1950 - return hook;
1951 -}
1952 -
1953 -static Hook *
1954 -Plugin_FindHook(PyObject *plugin, char *name)
1955 -{
1956 - Hook *hook = NULL;
1957 - GSList *plugin_hooks = Plugin_GetHooks(plugin);
1958 -
1959 - while (plugin_hooks)
1960 - {
1961 - if (g_strcmp0 (((Hook *)plugin_hooks->data)->name, name) == 0)
1962 - {
1963 - hook = (Hook *)plugin_hooks->data;
1964 - break;
1965 - }
1966 -
1967 - plugin_hooks = g_slist_next(plugin_hooks);
1968 - }
1969 -
1970 - return hook;
1971 -}
1972 -
1973 -static void
1974 -Plugin_RemoveHook(PyObject *plugin, Hook *hook)
1975 -{
1976 - GSList *list;
1977 - /* Is this really a hook of the running plugin? */
1978 - list = g_slist_find(Plugin_GetHooks(plugin), hook);
1979 - if (list) {
1980 - /* Ok, unhook it. */
1981 - if (hook->type != HOOK_UNLOAD) {
1982 - /* This is an xchat hook. Unregister it. */
1983 - BEGIN_XCHAT_CALLS(NONE);
1984 - hexchat_unhook(ph, (hexchat_hook*)hook->data);
1985 - END_XCHAT_CALLS();
1986 - }
1987 - Plugin_SetHooks(plugin,
1988 - g_slist_remove(Plugin_GetHooks(plugin),
1989 - hook));
1990 - Py_DECREF(hook->callback);
1991 - Py_DECREF(hook->userdata);
1992 - g_free(hook->name);
1993 - g_free(hook);
1994 - }
1995 -}
1996 -
1997 -static void
1998 -Plugin_RemoveAllHooks(PyObject *plugin)
1999 -{
2000 - GSList *list = Plugin_GetHooks(plugin);
2001 - while (list) {
2002 - Hook *hook = (Hook *) list->data;
2003 - if (hook->type != HOOK_UNLOAD) {
2004 - /* This is an xchat hook. Unregister it. */
2005 - BEGIN_XCHAT_CALLS(NONE);
2006 - hexchat_unhook(ph, (hexchat_hook*)hook->data);
2007 - END_XCHAT_CALLS();
2008 - }
2009 - Py_DECREF(hook->callback);
2010 - Py_DECREF(hook->userdata);
2011 - g_free(hook->name);
2012 - g_free(hook);
2013 - list = list->next;
2014 - }
2015 - Plugin_SetHooks(plugin, NULL);
2016 -}
2017 -
2018 -static void
2019 -Plugin_Delete(PyObject *plugin)
2020 -{
2021 - PyThreadState *tstate = ((PluginObject*)plugin)->tstate;
2022 - GSList *list = Plugin_GetHooks(plugin);
2023 - while (list) {
2024 - Hook *hook = (Hook *) list->data;
2025 - if (hook->type == HOOK_UNLOAD) {
2026 - PyObject *retobj;
2027 - retobj = PyObject_CallFunction(hook->callback, "(O)",
2028 - hook->userdata);
2029 - if (retobj) {
2030 - Py_DECREF(retobj);
2031 - } else {
2032 - PyErr_Print();
2033 - PyErr_Clear();
2034 - }
2035 - }
2036 - list = list->next;
2037 - }
2038 - Plugin_RemoveAllHooks(plugin);
2039 - if (((PluginObject *)plugin)->gui != NULL)
2040 - hexchat_plugingui_remove(ph, ((PluginObject *)plugin)->gui);
2041 - Py_DECREF(plugin);
2042 - /*PyThreadState_Swap(tstate); needed? */
2043 - Py_EndInterpreter(tstate);
2044 -}
2045 -
2046 -static PyObject *
2047 -Plugin_New(char *filename, PyObject *xcoobj)
2048 -{
2049 - PluginObject *plugin = NULL;
2050 - PyObject *m, *o;
2051 -#ifdef IS_PY3K
2052 - wchar_t *argv[] = { L"<hexchat>", 0 };
2053 -#else
2054 - char *argv[] = { "<hexchat>", 0 };
2055 -#endif
2056 -
2057 - if (filename) {
2058 - char *old_filename = filename;
2059 - filename = Util_Expand(filename);
2060 - if (filename == NULL) {
2061 - hexchat_printf(ph, "File not found: %s", old_filename);
2062 - return NULL;
2063 - }
2064 - }
2065 -
2066 - /* Allocate plugin structure. */
2067 - plugin = PyObject_New(PluginObject, &Plugin_Type);
2068 - if (plugin == NULL) {
2069 - hexchat_print(ph, "Can't create plugin object");
2070 - goto error;
2071 - }
2072 -
2073 - Plugin_SetName(plugin, NULL);
2074 - Plugin_SetVersion(plugin, NULL);
2075 - Plugin_SetFilename(plugin, NULL);
2076 - Plugin_SetDescription(plugin, NULL);
2077 - Plugin_SetHooks(plugin, NULL);
2078 - Plugin_SetContext(plugin, hexchat_get_context(ph));
2079 - Plugin_SetGui(plugin, NULL);
2080 -
2081 - /* Start a new interpreter environment for this plugin. */
2082 - PyEval_AcquireThread(main_tstate);
2083 - plugin->tstate = Py_NewInterpreter();
2084 - if (plugin->tstate == NULL) {
2085 - hexchat_print(ph, "Can't create interpreter state");
2086 - goto error;
2087 - }
2088 -
2089 - PySys_SetArgv(1, argv);
2090 - PySys_SetObject("__plugin__", (PyObject *) plugin);
2091 -
2092 - /* Set stdout and stderr to xchatout. */
2093 - Py_INCREF(xcoobj);
2094 - PySys_SetObject("stdout", xcoobj);
2095 - Py_INCREF(xcoobj);
2096 - PySys_SetObject("stderr", xcoobj);
2097 -
2098 - if (filename) {
2099 -#ifdef WIN32
2100 - char *file;
2101 - if (!g_file_get_contents(filename, &file, NULL, NULL)) {
2102 - hexchat_printf(ph, "Can't open file %s: %s\n",
2103 - filename, strerror(errno));
2104 - goto error;
2105 - }
2106 -
2107 - if (PyRun_SimpleString(file) != 0) {
2108 - hexchat_printf(ph, "Error loading module %s\n",
2109 - filename);
2110 - g_free (file);
2111 - goto error;
2112 - }
2113 -
2114 - plugin->filename = filename;
2115 - filename = NULL;
2116 - g_free (file);
2117 -#else
2118 - FILE *fp;
2119 - plugin->filename = filename;
2120 -
2121 - /* It's now owned by the plugin. */
2122 - filename = NULL;
2123 -
2124 - /* Open the plugin file. */
2125 - fp = fopen(plugin->filename, "r");
2126 - if (fp == NULL) {
2127 - hexchat_printf(ph, "Can't open file %s: %s\n",
2128 - plugin->filename, strerror(errno));
2129 - goto error;
2130 - }
2131 -
2132 - /* Run the plugin. */
2133 - if (PyRun_SimpleFile(fp, plugin->filename) != 0) {
2134 - hexchat_printf(ph, "Error loading module %s\n",
2135 - plugin->filename);
2136 - fclose(fp);
2137 - goto error;
2138 - }
2139 - fclose(fp);
2140 -#endif
2141 - m = PyDict_GetItemString(PyImport_GetModuleDict(),
2142 - "__main__");
2143 - if (m == NULL) {
2144 - hexchat_print(ph, "Can't get __main__ module");
2145 - goto error;
2146 - }
2147 - GET_MODULE_DATA(name, 1);
2148 - GET_MODULE_DATA(version, 0);
2149 - GET_MODULE_DATA(description, 0);
2150 - plugin->gui = hexchat_plugingui_add(ph, plugin->filename,
2151 - plugin->name,
2152 - plugin->description,
2153 - plugin->version, NULL);
2154 - }
2155 -
2156 - PyEval_ReleaseThread(plugin->tstate);
2157 -
2158 - return (PyObject *) plugin;
2159 -
2160 -error:
2161 - g_free(filename);
2162 -
2163 - if (plugin) {
2164 - if (plugin->tstate)
2165 - Plugin_Delete((PyObject *)plugin);
2166 - else
2167 - Py_DECREF(plugin);
2168 - }
2169 - PyEval_ReleaseLock();
2170 -
2171 - return NULL;
2172 -}
2173 -
2174 -static void
2175 -Plugin_dealloc(PluginObject *self)
2176 -{
2177 - g_free(self->filename);
2178 - g_free(self->name);
2179 - g_free(self->version);
2180 - g_free(self->description);
2181 - Py_TYPE(self)->tp_free((PyObject *)self);
2182 -}
2183 -
2184 -static PyTypeObject Plugin_Type = {
2185 - PyVarObject_HEAD_INIT(NULL, 0)
2186 - "hexchat.Plugin", /*tp_name*/
2187 - sizeof(PluginObject), /*tp_basicsize*/
2188 - 0, /*tp_itemsize*/
2189 - (destructor)Plugin_dealloc, /*tp_dealloc*/
2190 - 0, /*tp_print*/
2191 - 0, /*tp_getattr*/
2192 - 0, /*tp_setattr*/
2193 - 0, /*tp_compare*/
2194 - 0, /*tp_repr*/
2195 - 0, /*tp_as_number*/
2196 - 0, /*tp_as_sequence*/
2197 - 0, /*tp_as_mapping*/
2198 - 0, /*tp_hash*/
2199 - 0, /*tp_call*/
2200 - 0, /*tp_str*/
2201 - PyObject_GenericGetAttr,/*tp_getattro*/
2202 - PyObject_GenericSetAttr,/*tp_setattro*/
2203 - 0, /*tp_as_buffer*/
2204 - Py_TPFLAGS_DEFAULT, /*tp_flags*/
2205 - 0, /*tp_doc*/
2206 - 0, /*tp_traverse*/
2207 - 0, /*tp_clear*/
2208 - 0, /*tp_richcompare*/
2209 - 0, /*tp_weaklistoffset*/
2210 - 0, /*tp_iter*/
2211 - 0, /*tp_iternext*/
2212 - 0, /*tp_methods*/
2213 - 0, /*tp_members*/
2214 - 0, /*tp_getset*/
2215 - 0, /*tp_base*/
2216 - 0, /*tp_dict*/
2217 - 0, /*tp_descr_get*/
2218 - 0, /*tp_descr_set*/
2219 - 0, /*tp_dictoffset*/
2220 - 0, /*tp_init*/
2221 - PyType_GenericAlloc, /*tp_alloc*/
2222 - PyType_GenericNew, /*tp_new*/
2223 - PyObject_Del, /*tp_free*/
2224 - 0, /*tp_is_gc*/
2225 -};
2226 -
2227 -
2228 -/* ===================================================================== */
2229 -/* XChat module */
2230 -
2231 -static PyObject *
2232 -Module_hexchat_command(PyObject *self, PyObject *args)
2233 -{
2234 - char *text;
2235 - if (!PyArg_ParseTuple(args, "s:command", &text))
2236 - return NULL;
2237 - BEGIN_XCHAT_CALLS(RESTORE_CONTEXT|ALLOW_THREADS);
2238 - hexchat_command(ph, text);
2239 - END_XCHAT_CALLS();
2240 - Py_RETURN_NONE;
2241 -}
2242 -
2243 -static PyObject *
2244 -Module_xchat_prnt(PyObject *self, PyObject *args)
2245 -{
2246 - char *text;
2247 - if (!PyArg_ParseTuple(args, "s:prnt", &text))
2248 - return NULL;
2249 - BEGIN_XCHAT_CALLS(RESTORE_CONTEXT|ALLOW_THREADS);
2250 - hexchat_print(ph, text);
2251 - END_XCHAT_CALLS();
2252 - Py_RETURN_NONE;
2253 -}
2254 -
2255 -static PyObject *
2256 -Module_hexchat_emit_print(PyObject *self, PyObject *args, PyObject *kwargs)
2257 -{
2258 - char *argv[6];
2259 - char *name;
2260 - int res;
2261 - long time = 0;
2262 - hexchat_event_attrs *attrs;
2263 - char *kwlist[] = {"name", "arg1", "arg2", "arg3",
2264 - "arg4", "arg5", "arg6",
2265 - "time", NULL};
2266 - memset(&argv, 0, sizeof(char*)*6);
2267 - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|ssssssl:print_event", kwlist, &name,
2268 - &argv[0], &argv[1], &argv[2],
2269 - &argv[3], &argv[4], &argv[5],
2270 - &time))
2271 - return NULL;
2272 - BEGIN_XCHAT_CALLS(RESTORE_CONTEXT|ALLOW_THREADS);
2273 - attrs = hexchat_event_attrs_create(ph);
2274 - attrs->server_time_utc = (time_t)time;
2275 -
2276 - res = hexchat_emit_print_attrs(ph, attrs, name, argv[0], argv[1], argv[2],
2277 - argv[3], argv[4], argv[5], NULL);
2278 -
2279 - hexchat_event_attrs_free(ph, attrs);
2280 - END_XCHAT_CALLS();
2281 - return PyLong_FromLong(res);
2282 -}
2283 -
2284 -static PyObject *
2285 -Module_hexchat_get_info(PyObject *self, PyObject *args)
2286 -{
2287 - const char *info;
2288 - char *name;
2289 - if (!PyArg_ParseTuple(args, "s:get_info", &name))
2290 - return NULL;
2291 - BEGIN_XCHAT_CALLS(RESTORE_CONTEXT);
2292 - info = hexchat_get_info(ph, name);
2293 - END_XCHAT_CALLS();
2294 - if (info == NULL) {
2295 - Py_RETURN_NONE;
2296 - }
2297 - if (strcmp (name, "gtkwin_ptr") == 0 || strcmp (name, "win_ptr") == 0)
2298 - return PyUnicode_FromFormat("%p", info); /* format as pointer */
2299 - else
2300 - return PyUnicode_FromString(info);
2301 -}
2302 -
2303 -static PyObject *
2304 -Module_xchat_get_prefs(PyObject *self, PyObject *args)
2305 -{
2306 - PyObject *res;
2307 - const char *info;
2308 - int integer;
2309 - char *name;
2310 - int type;
2311 - if (!PyArg_ParseTuple(args, "s:get_prefs", &name))
2312 - return NULL;
2313 - BEGIN_XCHAT_CALLS(NONE);
2314 - type = hexchat_get_prefs(ph, name, &info, &integer);
2315 - END_XCHAT_CALLS();
2316 - switch (type) {
2317 - case 0:
2318 - Py_INCREF(Py_None);
2319 - res = Py_None;
2320 - break;
2321 - case 1:
2322 - res = PyUnicode_FromString((char*)info);
2323 - break;
2324 - case 2:
2325 - case 3:
2326 - res = PyLong_FromLong(integer);
2327 - break;
2328 - default:
2329 - PyErr_Format(PyExc_RuntimeError,
2330 - "unknown get_prefs type (%d), "
2331 - "please report", type);
2332 - res = NULL;
2333 - break;
2334 - }
2335 - return res;
2336 -}
2337 -
2338 -static PyObject *
2339 -Module_hexchat_get_context(PyObject *self, PyObject *args)
2340 -{
2341 - PyObject *plugin;
2342 - PyObject *ctxobj;
2343 - plugin = Plugin_GetCurrent();
2344 - if (plugin == NULL)
2345 - return NULL;
2346 - ctxobj = Context_FromContext(Plugin_GetContext(plugin));
2347 - if (ctxobj == NULL) {
2348 - Py_RETURN_NONE;
2349 - }
2350 - return ctxobj;
2351 -}
2352 -
2353 -static PyObject *
2354 -Module_hexchat_find_context(PyObject *self, PyObject *args, PyObject *kwargs)
2355 -{
2356 - char *server = NULL;
2357 - char *channel = NULL;
2358 - PyObject *ctxobj;
2359 - char *kwlist[] = {"server", "channel", 0};
2360 - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|zz:find_context",
2361 - kwlist, &server, &channel))
2362 - return NULL;
2363 - ctxobj = Context_FromServerAndChannel(server, channel);
2364 - if (ctxobj == NULL) {
2365 - Py_RETURN_NONE;
2366 - }
2367 - return ctxobj;
2368 -}
2369 -
2370 -static PyObject *
2371 -Module_hexchat_pluginpref_set(PyObject *self, PyObject *args)
2372 -{
2373 - PluginObject *plugin = (PluginObject*)Plugin_GetCurrent();
2374 - hexchat_plugin *prefph = Plugin_GetHandle(plugin);
2375 - int result;
2376 - char *var;
2377 - PyObject *value;
2378 -
2379 - if (!PyArg_ParseTuple(args, "sO:set_pluginpref", &var, &value))
2380 - return NULL;
2381 - if (PyLong_Check(value)) {
2382 - int intvalue = PyLong_AsLong(value);
2383 - BEGIN_XCHAT_CALLS(NONE);
2384 - result = hexchat_pluginpref_set_int(prefph, var, intvalue);
2385 - END_XCHAT_CALLS();
2386 - }
2387 - else if (PyUnicode_Check(value)) {
2388 - char *charvalue = PyUnicode_AsUTF8(value);
2389 - BEGIN_XCHAT_CALLS(NONE);
2390 - result = hexchat_pluginpref_set_str(prefph, var, charvalue);
2391 - END_XCHAT_CALLS();
2392 - }
2393 - else
2394 - result = 0;
2395 - return PyBool_FromLong(result);
2396 -}
2397 -
2398 -static PyObject *
2399 -Module_hexchat_pluginpref_get(PyObject *self, PyObject *args)
2400 -{
2401 - PluginObject *plugin = (PluginObject*)Plugin_GetCurrent();
2402 - hexchat_plugin *prefph = Plugin_GetHandle(plugin);
2403 - PyObject *ret;
2404 - char *var;
2405 - char retstr[512];
2406 - int retint;
2407 - int result;
2408 - if (!PyArg_ParseTuple(args, "s:get_pluginpref", &var))
2409 - return NULL;
2410 -
2411 - /* This will always return numbers as integers. */
2412 - BEGIN_XCHAT_CALLS(NONE);
2413 - result = hexchat_pluginpref_get_str(prefph, var, retstr);
2414 - END_XCHAT_CALLS();
2415 - if (result) {
2416 - if (strlen (retstr) <= 12) {
2417 - BEGIN_XCHAT_CALLS(NONE);
2418 - retint = hexchat_pluginpref_get_int(prefph, var);
2419 - END_XCHAT_CALLS();
2420 - if ((retint == -1) && (strcmp(retstr, "-1") != 0))
2421 - ret = PyUnicode_FromString(retstr);
2422 - else
2423 - ret = PyLong_FromLong(retint);
2424 - } else
2425 - ret = PyUnicode_FromString(retstr);
2426 - }
2427 - else
2428 - {
2429 - Py_INCREF(Py_None);
2430 - ret = Py_None;
2431 - }
2432 - return ret;
2433 -}
2434 -
2435 -static PyObject *
2436 -Module_hexchat_pluginpref_delete(PyObject *self, PyObject *args)
2437 -{
2438 - PluginObject *plugin = (PluginObject*)Plugin_GetCurrent();
2439 - hexchat_plugin *prefph = Plugin_GetHandle(plugin);
2440 - char *var;
2441 - int result;
2442 - if (!PyArg_ParseTuple(args, "s:del_pluginpref", &var))
2443 - return NULL;
2444 - BEGIN_XCHAT_CALLS(NONE);
2445 - result = hexchat_pluginpref_delete(prefph, var);
2446 - END_XCHAT_CALLS();
2447 - return PyBool_FromLong(result);
2448 -}
2449 -
2450 -static PyObject *
2451 -Module_hexchat_pluginpref_list(PyObject *self, PyObject *args)
2452 -{
2453 - PluginObject *plugin = (PluginObject*)Plugin_GetCurrent();
2454 - hexchat_plugin *prefph = Plugin_GetHandle(plugin);
2455 - char list[4096];
2456 - char* token;
2457 - int result;
2458 - PyObject *pylist;
2459 - pylist = PyList_New(0);
2460 - BEGIN_XCHAT_CALLS(NONE);
2461 - result = hexchat_pluginpref_list(prefph, list);
2462 - END_XCHAT_CALLS();
2463 - if (result) {
2464 - token = strtok(list, ",");
2465 - while (token != NULL) {
2466 - PyList_Append(pylist, PyUnicode_FromString(token));
2467 - token = strtok (NULL, ",");
2468 - }
2469 - }
2470 - return pylist;
2471 -}
2472 -
2473 -static PyObject *
2474 -Module_hexchat_hook_command(PyObject *self, PyObject *args, PyObject *kwargs)
2475 -{
2476 - char *name;
2477 - PyObject *callback;
2478 - PyObject *userdata = Py_None;
2479 - int priority = HEXCHAT_PRI_NORM;
2480 - char *help = NULL;
2481 - PyObject *plugin;
2482 - Hook *hook;
2483 - char *kwlist[] = {"name", "callback", "userdata",
2484 - "priority", "help", 0};
2485 -
2486 - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|Oiz:hook_command",
2487 - kwlist, &name, &callback, &userdata,
2488 - &priority, &help))
2489 - return NULL;
2490 -
2491 - plugin = Plugin_GetCurrent();
2492 - if (plugin == NULL)
2493 - return NULL;
2494 - if (!PyCallable_Check(callback)) {
2495 - PyErr_SetString(PyExc_TypeError, "callback is not callable");
2496 - return NULL;
2497 - }
2498 -
2499 - hook = Plugin_AddHook(HOOK_XCHAT, plugin, callback, userdata, name, NULL);
2500 - if (hook == NULL)
2501 - return NULL;
2502 -
2503 - BEGIN_XCHAT_CALLS(NONE);
2504 - hook->data = (void*)hexchat_hook_command(ph, name, priority,
2505 - Callback_Command, help, hook);
2506 - END_XCHAT_CALLS();
2507 -
2508 - return PyLong_FromVoidPtr(hook);
2509 -}
2510 -
2511 -static PyObject *
2512 -Module_hexchat_hook_server(PyObject *self, PyObject *args, PyObject *kwargs)
2513 -{
2514 - char *name;
2515 - PyObject *callback;
2516 - PyObject *userdata = Py_None;
2517 - int priority = HEXCHAT_PRI_NORM;
2518 - PyObject *plugin;
2519 - Hook *hook;
2520 - char *kwlist[] = {"name", "callback", "userdata", "priority", 0};
2521 -
2522 - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|Oi:hook_server",
2523 - kwlist, &name, &callback, &userdata,
2524 - &priority))
2525 - return NULL;
2526 -
2527 - plugin = Plugin_GetCurrent();
2528 - if (plugin == NULL)
2529 - return NULL;
2530 - if (!PyCallable_Check(callback)) {
2531 - PyErr_SetString(PyExc_TypeError, "callback is not callable");
2532 - return NULL;
2533 - }
2534 -
2535 - hook = Plugin_AddHook(HOOK_XCHAT, plugin, callback, userdata, NULL, NULL);
2536 - if (hook == NULL)
2537 - return NULL;
2538 -
2539 - BEGIN_XCHAT_CALLS(NONE);
2540 - hook->data = (void*)hexchat_hook_server_attrs(ph, name, priority,
2541 - Callback_Server, hook);
2542 - END_XCHAT_CALLS();
2543 -
2544 - return PyLong_FromVoidPtr(hook);
2545 -}
2546 -
2547 -static PyObject *
2548 -Module_hexchat_hook_server_attrs(PyObject *self, PyObject *args, PyObject *kwargs)
2549 -{
2550 - char *name;
2551 - PyObject *callback;
2552 - PyObject *userdata = Py_None;
2553 - int priority = HEXCHAT_PRI_NORM;
2554 - PyObject *plugin;
2555 - Hook *hook;
2556 - char *kwlist[] = {"name", "callback", "userdata", "priority", 0};
2557 -
2558 - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|Oi:hook_server",
2559 - kwlist, &name, &callback, &userdata,
2560 - &priority))
2561 - return NULL;
2562 -
2563 - plugin = Plugin_GetCurrent();
2564 - if (plugin == NULL)
2565 - return NULL;
2566 - if (!PyCallable_Check(callback)) {
2567 - PyErr_SetString(PyExc_TypeError, "callback is not callable");
2568 - return NULL;
2569 - }
2570 -
2571 - hook = Plugin_AddHook(HOOK_XCHAT_ATTR, plugin, callback, userdata, NULL, NULL);
2572 - if (hook == NULL)
2573 - return NULL;
2574 -
2575 - BEGIN_XCHAT_CALLS(NONE);
2576 - hook->data = (void*)hexchat_hook_server_attrs(ph, name, priority,
2577 - Callback_Server, hook);
2578 - END_XCHAT_CALLS();
2579 -
2580 - return PyLong_FromVoidPtr(hook);
2581 -}
2582 -
2583 -static PyObject *
2584 -Module_hexchat_hook_print(PyObject *self, PyObject *args, PyObject *kwargs)
2585 -{
2586 - char *name;
2587 - PyObject *callback;
2588 - PyObject *userdata = Py_None;
2589 - int priority = HEXCHAT_PRI_NORM;
2590 - PyObject *plugin;
2591 - Hook *hook;
2592 - char *kwlist[] = {"name", "callback", "userdata", "priority", 0};
2593 -
2594 - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|Oi:hook_print",
2595 - kwlist, &name, &callback, &userdata,
2596 - &priority))
2597 - return NULL;
2598 -
2599 - plugin = Plugin_GetCurrent();
2600 - if (plugin == NULL)
2601 - return NULL;
2602 - if (!PyCallable_Check(callback)) {
2603 - PyErr_SetString(PyExc_TypeError, "callback is not callable");
2604 - return NULL;
2605 - }
2606 -
2607 - hook = Plugin_AddHook(HOOK_XCHAT, plugin, callback, userdata, name, NULL);
2608 - if (hook == NULL)
2609 - return NULL;
2610 -
2611 - BEGIN_XCHAT_CALLS(NONE);
2612 - hook->data = (void*)hexchat_hook_print(ph, name, priority,
2613 - Callback_Print, hook);
2614 - END_XCHAT_CALLS();
2615 -
2616 - return PyLong_FromVoidPtr(hook);
2617 -}
2618 -
2619 -static PyObject *
2620 -Module_hexchat_hook_print_attrs(PyObject *self, PyObject *args, PyObject *kwargs)
2621 -{
2622 - char *name;
2623 - PyObject *callback;
2624 - PyObject *userdata = Py_None;
2625 - int priority = HEXCHAT_PRI_NORM;
2626 - PyObject *plugin;
2627 - Hook *hook;
2628 - char *kwlist[] = {"name", "callback", "userdata", "priority", 0};
2629 -
2630 - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|Oi:hook_print_attrs",
2631 - kwlist, &name, &callback, &userdata,
2632 - &priority))
2633 - return NULL;
2634 -
2635 - plugin = Plugin_GetCurrent();
2636 - if (plugin == NULL)
2637 - return NULL;
2638 - if (!PyCallable_Check(callback)) {
2639 - PyErr_SetString(PyExc_TypeError, "callback is not callable");
2640 - return NULL;
2641 - }
2642 -
2643 - hook = Plugin_AddHook(HOOK_XCHAT_ATTR, plugin, callback, userdata, name, NULL);
2644 - if (hook == NULL)
2645 - return NULL;
2646 -
2647 - BEGIN_XCHAT_CALLS(NONE);
2648 - hook->data = (void*)hexchat_hook_print_attrs(ph, name, priority,
2649 - Callback_Print_Attrs, hook);
2650 - END_XCHAT_CALLS();
2651 -
2652 - return PyLong_FromVoidPtr(hook);
2653 -}
2654 -
2655 -static PyObject *
2656 -Module_hexchat_hook_timer(PyObject *self, PyObject *args, PyObject *kwargs)
2657 -{
2658 - int timeout;
2659 - PyObject *callback;
2660 - PyObject *userdata = Py_None;
2661 - PyObject *plugin;
2662 - Hook *hook;
2663 - char *kwlist[] = {"timeout", "callback", "userdata", 0};
2664 -
2665 - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "iO|O:hook_timer",
2666 - kwlist, &timeout, &callback,
2667 - &userdata))
2668 - return NULL;
2669 -
2670 - plugin = Plugin_GetCurrent();
2671 - if (plugin == NULL)
2672 - return NULL;
2673 - if (!PyCallable_Check(callback)) {
2674 - PyErr_SetString(PyExc_TypeError, "callback is not callable");
2675 - return NULL;
2676 - }
2677 -
2678 - hook = Plugin_AddHook(HOOK_XCHAT, plugin, callback, userdata, NULL, NULL);
2679 - if (hook == NULL)
2680 - return NULL;
2681 -
2682 - BEGIN_XCHAT_CALLS(NONE);
2683 - hook->data = (void*)hexchat_hook_timer(ph, timeout,
2684 - Callback_Timer, hook);
2685 - END_XCHAT_CALLS();
2686 -
2687 - return PyLong_FromVoidPtr(hook);
2688 -}
2689 -
2690 -static PyObject *
2691 -Module_hexchat_hook_unload(PyObject *self, PyObject *args, PyObject *kwargs)
2692 -{
2693 - PyObject *callback;
2694 - PyObject *userdata = Py_None;
2695 - PyObject *plugin;
2696 - Hook *hook;
2697 - char *kwlist[] = {"callback", "userdata", 0};
2698 -
2699 - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|O:hook_unload",
2700 - kwlist, &callback, &userdata))
2701 - return NULL;
2702 -
2703 - plugin = Plugin_GetCurrent();
2704 - if (plugin == NULL)
2705 - return NULL;
2706 - if (!PyCallable_Check(callback)) {
2707 - PyErr_SetString(PyExc_TypeError, "callback is not callable");
2708 - return NULL;
2709 - }
2710 -
2711 - hook = Plugin_AddHook(HOOK_UNLOAD, plugin, callback, userdata, NULL, NULL);
2712 - if (hook == NULL)
2713 - return NULL;
2714 -
2715 - return PyLong_FromVoidPtr(hook);
2716 -}
2717 -
2718 -static PyObject *
2719 -Module_hexchat_unhook(PyObject *self, PyObject *args)
2720 -{
2721 - PyObject *plugin;
2722 - PyObject *obj;
2723 - Hook *hook;
2724 - if (!PyArg_ParseTuple(args, "O:unhook", &obj))
2725 - return NULL;
2726 - plugin = Plugin_GetCurrent();
2727 - if (plugin == NULL)
2728 - return NULL;
2729 -
2730 - if (PyUnicode_Check (obj))
2731 - {
2732 - hook = Plugin_FindHook(plugin, PyUnicode_AsUTF8 (obj));
2733 - while (hook)
2734 - {
2735 - Plugin_RemoveHook(plugin, hook);
2736 - hook = Plugin_FindHook(plugin, PyUnicode_AsUTF8 (obj));
2737 - }
2738 - }
2739 - else
2740 - {
2741 - hook = (Hook *)PyLong_AsVoidPtr(obj);
2742 - Plugin_RemoveHook(plugin, hook);
2743 - }
2744 -
2745 - Py_RETURN_NONE;
2746 -}
2747 -
2748 -static PyObject *
2749 -Module_xchat_get_list(PyObject *self, PyObject *args)
2750 -{
2751 - hexchat_list *list;
2752 - PyObject *l;
2753 - const char *name;
2754 - const char *const *fields;
2755 - int i;
2756 -
2757 - if (!PyArg_ParseTuple(args, "s:get_list", &name))
2758 - return NULL;
2759 - /* This function is thread safe, and returns statically
2760 - * allocated data. */
2761 - fields = hexchat_list_fields(ph, "lists");
2762 - for (i = 0; fields[i]; i++) {
2763 - if (strcmp(fields[i], name) == 0) {
2764 - /* Use the static allocated one. */
2765 - name = fields[i];
2766 - break;
2767 - }
2768 - }
2769 - if (fields[i] == NULL) {
2770 - PyErr_SetString(PyExc_KeyError, "list not available");
2771 - return NULL;
2772 - }
2773 - l = PyList_New(0);
2774 - if (l == NULL)
2775 - return NULL;
2776 - BEGIN_XCHAT_CALLS(RESTORE_CONTEXT);
2777 - list = hexchat_list_get(ph, (char*)name);
2778 - if (list == NULL)
2779 - goto error;
2780 - fields = hexchat_list_fields(ph, (char*)name);
2781 - while (hexchat_list_next(ph, list)) {
2782 - PyObject *o = ListItem_New(name);
2783 - if (o == NULL || PyList_Append(l, o) == -1) {
2784 - Py_XDECREF(o);
2785 - goto error;
2786 - }
2787 - Py_DECREF(o); /* l is holding a reference */
2788 - for (i = 0; fields[i]; i++) {
2789 - const char *fld = fields[i]+1;
2790 - PyObject *attr = NULL;
2791 - const char *sattr;
2792 - int iattr;
2793 - time_t tattr;
2794 - switch(fields[i][0]) {
2795 - case 's':
2796 - sattr = hexchat_list_str(ph, list, (char*)fld);
2797 - attr = PyUnicode_FromString(sattr?sattr:"");
2798 - break;
2799 - case 'i':
2800 - iattr = hexchat_list_int(ph, list, (char*)fld);
2801 - attr = PyLong_FromLong((long)iattr);
2802 - break;
2803 - case 't':
2804 - tattr = hexchat_list_time(ph, list, (char*)fld);
2805 - attr = PyLong_FromLong((long)tattr);
2806 - break;
2807 - case 'p':
2808 - sattr = hexchat_list_str(ph, list, (char*)fld);
2809 - if (strcmp(fld, "context") == 0) {
2810 - attr = Context_FromContext(
2811 - (hexchat_context*)sattr);
2812 - break;
2813 - }
2814 - default: /* ignore unknown (newly added?) types */
2815 - continue;
2816 - }
2817 - if (attr == NULL)
2818 - goto error;
2819 - PyObject_SetAttrString(o, (char*)fld, attr); /* add reference on attr in o */
2820 - Py_DECREF(attr); /* make o own attr */
2821 - }
2822 - }
2823 - hexchat_list_free(ph, list);
2824 - goto exit;
2825 -error:
2826 - if (list)
2827 - hexchat_list_free(ph, list);
2828 - Py_DECREF(l);
2829 - l = NULL;
2830 -
2831 -exit:
2832 - END_XCHAT_CALLS();
2833 - return l;
2834 -}
2835 -
2836 -static PyObject *
2837 -Module_xchat_get_lists(PyObject *self, PyObject *args)
2838 -{
2839 - PyObject *l, *o;
2840 - const char *const *fields;
2841 - int i;
2842 - /* This function is thread safe, and returns statically
2843 - * allocated data. */
2844 - fields = hexchat_list_fields(ph, "lists");
2845 - l = PyList_New(0);
2846 - if (l == NULL)
2847 - return NULL;
2848 - for (i = 0; fields[i]; i++) {
2849 - o = PyUnicode_FromString(fields[i]);
2850 - if (o == NULL || PyList_Append(l, o) == -1) {
2851 - Py_DECREF(l);
2852 - Py_XDECREF(o);
2853 - return NULL;
2854 - }
2855 - Py_DECREF(o); /* l is holding a reference */
2856 - }
2857 - return l;
2858 -}
2859 -
2860 -static PyObject *
2861 -Module_hexchat_nickcmp(PyObject *self, PyObject *args)
2862 -{
2863 - char *s1, *s2;
2864 - if (!PyArg_ParseTuple(args, "ss:nickcmp", &s1, &s2))
2865 - return NULL;
2866 - return PyLong_FromLong((long) hexchat_nickcmp(ph, s1, s2));
2867 -}
2868 -
2869 -static PyObject *
2870 -Module_hexchat_strip(PyObject *self, PyObject *args)
2871 -{
2872 - PyObject *result;
2873 - char *str, *str2;
2874 - int len = -1, flags = 1 | 2;
2875 - if (!PyArg_ParseTuple(args, "s|ii:strip", &str, &len, &flags))
2876 - return NULL;
2877 - str2 = hexchat_strip(ph, str, len, flags);
2878 - result = PyUnicode_FromString(str2);
2879 - hexchat_free(ph, str2);
2880 - return result;
2881 -}
2882 -
2883 -static PyMethodDef Module_xchat_methods[] = {
2884 - {"command", Module_hexchat_command,
2885 - METH_VARARGS},
2886 - {"prnt", Module_xchat_prnt,
2887 - METH_VARARGS},
2888 - {"emit_print", (PyCFunction)Module_hexchat_emit_print,
2889 - METH_VARARGS|METH_KEYWORDS},
2890 - {"get_info", Module_hexchat_get_info,
2891 - METH_VARARGS},
2892 - {"get_prefs", Module_xchat_get_prefs,
2893 - METH_VARARGS},
2894 - {"get_context", Module_hexchat_get_context,
2895 - METH_NOARGS},
2896 - {"find_context", (PyCFunction)Module_hexchat_find_context,
2897 - METH_VARARGS|METH_KEYWORDS},
2898 - {"set_pluginpref", Module_hexchat_pluginpref_set,
2899 - METH_VARARGS},
2900 - {"get_pluginpref", Module_hexchat_pluginpref_get,
2901 - METH_VARARGS},
2902 - {"del_pluginpref", Module_hexchat_pluginpref_delete,
2903 - METH_VARARGS},
2904 - {"list_pluginpref", Module_hexchat_pluginpref_list,
2905 - METH_VARARGS},
2906 - {"hook_command", (PyCFunction)Module_hexchat_hook_command,
2907 - METH_VARARGS|METH_KEYWORDS},
2908 - {"hook_server", (PyCFunction)Module_hexchat_hook_server,
2909 - METH_VARARGS|METH_KEYWORDS},
2910 - {"hook_server_attrs", (PyCFunction)Module_hexchat_hook_server_attrs,
2911 - METH_VARARGS|METH_KEYWORDS},
2912 - {"hook_print", (PyCFunction)Module_hexchat_hook_print,
2913 - METH_VARARGS|METH_KEYWORDS},
2914 - {"hook_print_attrs", (PyCFunction)Module_hexchat_hook_print_attrs,
2915 - METH_VARARGS|METH_KEYWORDS},
2916 - {"hook_timer", (PyCFunction)Module_hexchat_hook_timer,
2917 - METH_VARARGS|METH_KEYWORDS},
2918 - {"hook_unload", (PyCFunction)Module_hexchat_hook_unload,
2919 - METH_VARARGS|METH_KEYWORDS},
2920 - {"unhook", Module_hexchat_unhook,
2921 - METH_VARARGS},
2922 - {"get_list", Module_xchat_get_list,
2923 - METH_VARARGS},
2924 - {"get_lists", Module_xchat_get_lists,
2925 - METH_NOARGS},
2926 - {"nickcmp", Module_hexchat_nickcmp,
2927 - METH_VARARGS},
2928 - {"strip", Module_hexchat_strip,
2929 - METH_VARARGS},
2930 - {NULL, NULL}
2931 -};
2932 -
2933 -#ifdef IS_PY3K
2934 -static struct PyModuleDef moduledef = {
2935 - PyModuleDef_HEAD_INIT,
2936 - "hexchat", /* m_name */
2937 - "HexChat Scripting Interface", /* m_doc */
2938 - -1, /* m_size */
2939 - Module_xchat_methods, /* m_methods */
2940 - NULL, /* m_reload */
2941 - NULL, /* m_traverse */
2942 - NULL, /* m_clear */
2943 - NULL, /* m_free */
2944 -};
2945 -
2946 -static struct PyModuleDef xchat_moduledef = {
2947 - PyModuleDef_HEAD_INIT,
2948 - "xchat", /* m_name */
2949 - "HexChat Scripting Interface", /* m_doc */
2950 - -1, /* m_size */
2951 - Module_xchat_methods, /* m_methods */
2952 - NULL, /* m_reload */
2953 - NULL, /* m_traverse */
2954 - NULL, /* m_clear */
2955 - NULL, /* m_free */
2956 -};
2957 -#endif
2958 -
2959 -static PyObject *
2960 -moduleinit_hexchat(void)
2961 -{
2962 - PyObject *hm;
2963 -#ifdef IS_PY3K
2964 - hm = PyModule_Create(&moduledef);
2965 -#else
2966 - hm = Py_InitModule3("hexchat", Module_xchat_methods, "HexChat Scripting Interface");
2967 -#endif
2968 -
2969 - PyModule_AddIntConstant(hm, "EAT_NONE", HEXCHAT_EAT_NONE);
2970 - PyModule_AddIntConstant(hm, "EAT_HEXCHAT", HEXCHAT_EAT_HEXCHAT);
2971 - PyModule_AddIntConstant(hm, "EAT_XCHAT", HEXCHAT_EAT_HEXCHAT); /* for compat */
2972 - PyModule_AddIntConstant(hm, "EAT_PLUGIN", HEXCHAT_EAT_PLUGIN);
2973 - PyModule_AddIntConstant(hm, "EAT_ALL", HEXCHAT_EAT_ALL);
2974 - PyModule_AddIntConstant(hm, "PRI_HIGHEST", HEXCHAT_PRI_HIGHEST);
2975 - PyModule_AddIntConstant(hm, "PRI_HIGH", HEXCHAT_PRI_HIGH);
2976 - PyModule_AddIntConstant(hm, "PRI_NORM", HEXCHAT_PRI_NORM);
2977 - PyModule_AddIntConstant(hm, "PRI_LOW", HEXCHAT_PRI_LOW);
2978 - PyModule_AddIntConstant(hm, "PRI_LOWEST", HEXCHAT_PRI_LOWEST);
2979 -
2980 - PyObject_SetAttrString(hm, "__version__", Py_BuildValue("(ii)", VERSION_MAJOR, VERSION_MINOR));
2981 -
2982 - return hm;
2983 -}
2984 -
2985 -static PyObject *
2986 -moduleinit_xchat(void)
2987 -{
2988 - PyObject *xm;
2989 -#ifdef IS_PY3K
2990 - xm = PyModule_Create(&xchat_moduledef);
2991 -#else
2992 - xm = Py_InitModule3("xchat", Module_xchat_methods, "HexChat Scripting Interface");
2993 -#endif
2994 -
2995 - PyModule_AddIntConstant(xm, "EAT_NONE", HEXCHAT_EAT_NONE);
2996 - PyModule_AddIntConstant(xm, "EAT_XCHAT", HEXCHAT_EAT_HEXCHAT);
2997 - PyModule_AddIntConstant(xm, "EAT_PLUGIN", HEXCHAT_EAT_PLUGIN);
2998 - PyModule_AddIntConstant(xm, "EAT_ALL", HEXCHAT_EAT_ALL);
2999 - PyModule_AddIntConstant(xm, "PRI_HIGHEST", HEXCHAT_PRI_HIGHEST);
3000 - PyModule_AddIntConstant(xm, "PRI_HIGH", HEXCHAT_PRI_HIGH);
3001 - PyModule_AddIntConstant(xm, "PRI_NORM", HEXCHAT_PRI_NORM);
3002 - PyModule_AddIntConstant(xm, "PRI_LOW", HEXCHAT_PRI_LOW);
3003 - PyModule_AddIntConstant(xm, "PRI_LOWEST", HEXCHAT_PRI_LOWEST);
3004 -
3005 - PyObject_SetAttrString(xm, "__version__", Py_BuildValue("(ii)", VERSION_MAJOR, VERSION_MINOR));
3006 -
3007 - return xm;
3008 -}
3009 -
3010 -#ifdef IS_PY3K
3011 -PyMODINIT_FUNC
3012 -PyInit_hexchat(void)
3013 -{
3014 - return moduleinit_hexchat();
3015 -}
3016 -PyMODINIT_FUNC
3017 -PyInit_xchat(void)
3018 -{
3019 - return moduleinit_xchat();
3020 -}
3021 -#else
3022 -PyMODINIT_FUNC
3023 -inithexchat(void)
3024 -{
3025 - moduleinit_hexchat();
3026 -}
3027 -PyMODINIT_FUNC
3028 -initxchat(void)
3029 -{
3030 - moduleinit_xchat();
3031 -}
3032 -#endif
3033 -
3034 -/* ===================================================================== */
3035 -/* Python interactive interpreter functions */
3036 -
3037 -static void
3038 -IInterp_Exec(char *command)
3039 -{
3040 - PyObject *m, *d, *o;
3041 - char *buffer;
3042 - int len;
3043 -
3044 - BEGIN_PLUGIN(interp_plugin);
3045 -
3046 - m = PyImport_AddModule("__main__");
3047 - if (m == NULL) {
3048 - hexchat_print(ph, "Can't get __main__ module");
3049 - goto fail;
3050 - }
3051 - d = PyModule_GetDict(m);
3052 - len = strlen(command);
3053 -
3054 - buffer = g_malloc(len + 2);
3055 - memcpy(buffer, command, len);
3056 - buffer[len] = '\n';
3057 - buffer[len+1] = 0;
3058 - PyRun_SimpleString("import hexchat");
3059 - o = PyRun_StringFlags(buffer, Py_single_input, d, d, NULL);
3060 - g_free(buffer);
3061 - if (o == NULL) {
3062 - PyErr_Print();
3063 - goto fail;
3064 - }
3065 - Py_DECREF(o);
3066 -
3067 -fail:
3068 - END_PLUGIN(interp_plugin);
3069 - return;
3070 -}
3071 -
3072 -static int
3073 -IInterp_Cmd(char *word[], char *word_eol[], void *userdata)
3074 -{
3075 - char *channel = (char *) hexchat_get_info(ph, "channel");
3076 - if (channel && channel[0] == '>' && strcmp(channel, ">>python<<") == 0) {
3077 - hexchat_printf(ph, ">>> %s\n", word_eol[1]);
3078 - IInterp_Exec(word_eol[1]);
3079 - return HEXCHAT_EAT_HEXCHAT;
3080 - }
3081 - return HEXCHAT_EAT_NONE;
3082 -}
3083 -
3084 -
3085 -/* ===================================================================== */
3086 -/* Python command handling */
3087 -
3088 -static void
3089 -Command_PyList(void)
3090 -{
3091 - GSList *list;
3092 - list = plugin_list;
3093 - if (list == NULL) {
3094 - hexchat_print(ph, "No python modules loaded");
3095 - } else {
3096 - hexchat_print(ph,
3097 - "Name Version Filename Description\n"
3098 - "---- ------- -------- -----------\n");
3099 - while (list != NULL) {
3100 - PluginObject *plg = (PluginObject *) list->data;
3101 - char *basename = g_path_get_basename(plg->filename);
3102 - hexchat_printf(ph, "%-12s %-8s %-20s %-10s\n",
3103 - plg->name,
3104 - *plg->version ? plg->version
3105 - : "<none>",
3106 - basename,
3107 - *plg->description ? plg->description
3108 - : "<none>");
3109 - g_free(basename);
3110 - list = list->next;
3111 - }
3112 - hexchat_print(ph, "\n");
3113 - }
3114 -}
3115 -
3116 -static void
3117 -Command_PyLoad(char *filename)
3118 -{
3119 - PyObject *plugin;
3120 - RELEASE_XCHAT_LOCK();
3121 - plugin = Plugin_New(filename, xchatout);
3122 - ACQUIRE_XCHAT_LOCK();
3123 - if (plugin)
3124 - plugin_list = g_slist_append(plugin_list, plugin);
3125 -}
3126 -
3127 -static void
3128 -Command_PyUnload(char *name)
3129 -{
3130 - PluginObject *plugin = Plugin_ByString(name);
3131 - if (!plugin) {
3132 - hexchat_print(ph, "Can't find a python plugin with that name");
3133 - } else {
3134 - BEGIN_PLUGIN(plugin);
3135 - Plugin_Delete((PyObject*)plugin);
3136 - END_PLUGIN(plugin);
3137 - plugin_list = g_slist_remove(plugin_list, plugin);
3138 - }
3139 -}
3140 -
3141 -static void
3142 -Command_PyReload(char *name)
3143 -{
3144 - PluginObject *plugin = Plugin_ByString(name);
3145 - if (!plugin) {
3146 - hexchat_print(ph, "Can't find a python plugin with that name");
3147 - } else {
3148 - char *filename = g_strdup(plugin->filename);
3149 - Command_PyUnload(filename);
3150 - Command_PyLoad(filename);
3151 - g_free(filename);
3152 - }
3153 -}
3154 -
3155 -static void
3156 -Command_PyAbout(void)
3157 -{
3158 - hexchat_print(ph, about);
3159 -}
3160 -
3161 -static int
3162 -Command_Py(char *word[], char *word_eol[], void *userdata)
3163 -{
3164 - char *cmd = word[2];
3165 - int ok = 0;
3166 - if (strcasecmp(cmd, "LIST") == 0) {
3167 - ok = 1;
3168 - Command_PyList();
3169 - } else if (strcasecmp(cmd, "EXEC") == 0) {
3170 - if (word[3][0]) {
3171 - ok = 1;
3172 - IInterp_Exec(word_eol[3]);
3173 - }
3174 - } else if (strcasecmp(cmd, "LOAD") == 0) {
3175 - if (word[3][0]) {
3176 - ok = 1;
3177 - Command_PyLoad(word[3]);
3178 - }
3179 - } else if (strcasecmp(cmd, "UNLOAD") == 0) {
3180 - if (word[3][0]) {
3181 - ok = 1;
3182 - Command_PyUnload(word[3]);
3183 - }
3184 - } else if (strcasecmp(cmd, "RELOAD") == 0) {
3185 - if (word[3][0]) {
3186 - ok = 1;
3187 - Command_PyReload(word[3]);
3188 - }
3189 - } else if (strcasecmp(cmd, "CONSOLE") == 0) {
3190 - ok = 1;
3191 - hexchat_command(ph, "QUERY >>python<<");
3192 - } else if (strcasecmp(cmd, "ABOUT") == 0) {
3193 - ok = 1;
3194 - Command_PyAbout();
3195 - }
3196 - if (!ok)
3197 - hexchat_print(ph, usage);
3198 - return HEXCHAT_EAT_ALL;
3199 -}
3200 -
3201 -static int
3202 -Command_Load(char *word[], char *word_eol[], void *userdata)
3203 -{
3204 - int len = strlen(word[2]);
3205 - if (len > 3 && strcasecmp(".py", word[2]+len-3) == 0) {
3206 - Command_PyLoad(word[2]);
3207 - return HEXCHAT_EAT_HEXCHAT;
3208 - }
3209 - return HEXCHAT_EAT_NONE;
3210 -}
3211 -
3212 -static int
3213 -Command_Reload(char *word[], char *word_eol[], void *userdata)
3214 -{
3215 - int len = strlen(word[2]);
3216 - if (len > 3 && strcasecmp(".py", word[2]+len-3) == 0) {
3217 - Command_PyReload(word[2]);
3218 - return HEXCHAT_EAT_HEXCHAT;
3219 - }
3220 - return HEXCHAT_EAT_NONE;
3221 -}
3222 -
3223 -static int
3224 -Command_Unload(char *word[], char *word_eol[], void *userdata)
3225 -{
3226 - int len = strlen(word[2]);
3227 - if (len > 3 && strcasecmp(".py", word[2]+len-3) == 0) {
3228 - Command_PyUnload(word[2]);
3229 - return HEXCHAT_EAT_HEXCHAT;
3230 - }
3231 - return HEXCHAT_EAT_NONE;
3232 -}
3233 -
3234 -/* ===================================================================== */
3235 -/* Autoload function */
3236 -
3237 -/* ===================================================================== */
3238 -/* (De)initialization functions */
3239 -
3240 -static int initialized = 0;
3241 -static int reinit_tried = 0;
3242 -
3243 -void
3244 -hexchat_plugin_get_info(char **name, char **desc, char **version, void **reserved)
3245 -{
3246 - *name = "Python";
3247 - *version = VERSION;
3248 - *desc = "Python scripting interface";
3249 - if (reserved)
3250 - *reserved = NULL;
3251 -}
3252 -
3253 -int
3254 -hexchat_plugin_init(hexchat_plugin *plugin_handle,
3255 - char **plugin_name,
3256 - char **plugin_desc,
3257 - char **plugin_version,
3258 - char *arg)
3259 -{
3260 -#ifdef IS_PY3K
3261 - wchar_t *argv[] = { L"<hexchat>", 0 };
3262 -#else
3263 - char *argv[] = { "<hexchat>", 0 };
3264 -#endif
3265 -
3266 - ph = plugin_handle;
3267 -
3268 - /* Block double initalization. */
3269 - if (initialized != 0) {
3270 - hexchat_print(ph, "Python interface already loaded");
3271 - /* deinit is called even when init fails, so keep track
3272 - * of a reinit failure. */
3273 - reinit_tried++;
3274 - return 0;
3275 - }
3276 - initialized = 1;
3277 -
3278 - *plugin_name = "Python";
3279 - *plugin_version = VERSION;
3280 -
3281 - /* FIXME You can't free this since it's used as long as the plugin's
3282 - * loaded, but if you unload it, everything belonging to the plugin is
3283 - * supposed to be freed anyway.
3284 - */
3285 - *plugin_desc = g_strdup_printf ("Python %d scripting interface", PY_MAJOR_VERSION);
3286 -
3287 - /* Initialize python. */
3288 -#ifdef IS_PY3K
3289 - Py_SetProgramName(L"hexchat");
3290 - PyImport_AppendInittab("hexchat", PyInit_hexchat);
3291 - PyImport_AppendInittab("xchat", PyInit_xchat);
3292 -#else
3293 - Py_SetProgramName("hexchat");
3294 - PyImport_AppendInittab("hexchat", inithexchat);
3295 - PyImport_AppendInittab("xchat", initxchat);
3296 -#endif
3297 - Py_Initialize();
3298 - PySys_SetArgv(1, argv);
3299 -
3300 - xchatout_buffer = g_string_new (NULL);
3301 - xchatout = XChatOut_New();
3302 - if (xchatout == NULL) {
3303 - hexchat_print(ph, "Can't allocate xchatout object");
3304 - return 0;
3305 - }
3306 -
3307 -#ifdef WITH_THREAD
3308 - PyEval_InitThreads();
3309 - xchat_lock = PyThread_allocate_lock();
3310 - if (xchat_lock == NULL) {
3311 - hexchat_print(ph, "Can't allocate hexchat lock");
3312 - Py_DECREF(xchatout);
3313 - xchatout = NULL;
3314 - return 0;
3315 - }
3316 -#endif
3317 -
3318 - main_tstate = PyEval_SaveThread();
3319 -
3320 - interp_plugin = Plugin_New(NULL, xchatout);
3321 - if (interp_plugin == NULL) {
3322 - hexchat_print(ph, "Plugin_New() failed.\n");
3323 -#ifdef WITH_THREAD
3324 - PyThread_free_lock(xchat_lock);
3325 -#endif
3326 - Py_DECREF(xchatout);
3327 - xchatout = NULL;
3328 - return 0;
3329 - }
3330 -
3331 -
3332 - hexchat_hook_command(ph, "", HEXCHAT_PRI_NORM, IInterp_Cmd, 0, 0);
3333 - hexchat_hook_command(ph, "PY", HEXCHAT_PRI_NORM, Command_Py, usage, 0);
3334 - hexchat_hook_command(ph, "LOAD", HEXCHAT_PRI_NORM, Command_Load, 0, 0);
3335 - hexchat_hook_command(ph, "UNLOAD", HEXCHAT_PRI_NORM, Command_Unload, 0, 0);
3336 - hexchat_hook_command(ph, "RELOAD", HEXCHAT_PRI_NORM, Command_Reload, 0, 0);
3337 -#ifdef WITH_THREAD
3338 - thread_timer = hexchat_hook_timer(ph, 300, Callback_ThreadTimer, NULL);
3339 -#endif
3340 -
3341 - hexchat_print(ph, "Python interface loaded\n");
3342 -
3343 - Util_Autoload();
3344 - return 1;
3345 -}
3346 -
3347 -int
3348 -hexchat_plugin_deinit(void)
3349 -{
3350 - GSList *list;
3351 -
3352 - /* A reinitialization was tried. Just give up and live the
3353 - * environment as is. We are still alive. */
3354 - if (reinit_tried) {
3355 - reinit_tried--;
3356 - return 1;
3357 - }
3358 -
3359 - list = plugin_list;
3360 - while (list != NULL) {
3361 - PyObject *plugin = (PyObject *) list->data;
3362 - BEGIN_PLUGIN(plugin);
3363 - Plugin_Delete(plugin);
3364 - END_PLUGIN(plugin);
3365 - list = list->next;
3366 - }
3367 - g_slist_free(plugin_list);
3368 - plugin_list = NULL;
3369 -
3370 - /* Reset xchatout buffer. */
3371 - g_string_free (xchatout_buffer, TRUE);
3372 - xchatout_buffer = NULL;
3373 -
3374 - if (interp_plugin) {
3375 - PyThreadState *tstate = ((PluginObject*)interp_plugin)->tstate;
3376 - PyThreadState_Swap(tstate);
3377 - Py_EndInterpreter(tstate);
3378 - Py_DECREF(interp_plugin);
3379 - interp_plugin = NULL;
3380 - }
3381 -
3382 - /* Switch back to the main thread state. */
3383 - if (main_tstate) {
3384 - PyEval_RestoreThread(main_tstate);
3385 - PyThreadState_Swap(main_tstate);
3386 - main_tstate = NULL;
3387 - }
3388 - Py_Finalize();
3389 -
3390 -#ifdef WITH_THREAD
3391 - if (thread_timer != NULL) {
3392 - hexchat_unhook(ph, thread_timer);
3393 - thread_timer = NULL;
3394 - }
3395 - PyThread_free_lock(xchat_lock);
3396 -#endif
3397 -
3398 - hexchat_print(ph, "Python interface unloaded\n");
3399 - initialized = 0;
3400 -
3401 - return 1;
3402 -}
3403 -
3404 diff --git a/plugins/python/python.def b/plugins/python/python.def
3405 index 6ce04e98..e560f50f 100644
3406 --- a/plugins/python/python.def
3407 +++ b/plugins/python/python.def
3408 @@ -1,4 +1,3 @@
3409 EXPORTS
3410 hexchat_plugin_init
3411 hexchat_plugin_deinit
3412 -hexchat_plugin_get_info
3413 diff --git a/plugins/python/python.py b/plugins/python/python.py
3414 new file mode 100644
3415 index 00000000..30694802
3416 --- /dev/null
3417 +++ b/plugins/python/python.py
3418 @@ -0,0 +1,554 @@
3419 +from __future__ import print_function
3420 +
3421 +import importlib
3422 +import os
3423 +import pydoc
3424 +import signal
3425 +import sys
3426 +import traceback
3427 +import weakref
3428 +from contextlib import contextmanager
3429 +
3430 +from _hexchat_embedded import ffi, lib
3431 +
3432 +if sys.version_info < (3, 0):
3433 + from io import BytesIO as HelpEater
3434 +else:
3435 + from io import StringIO as HelpEater
3436 +
3437 +if not hasattr(sys, 'argv'):
3438 + sys.argv = ['<hexchat>']
3439 +
3440 +VERSION = b'2.0' # Sync with hexchat.__version__
3441 +PLUGIN_NAME = ffi.new('char[]', b'Python')
3442 +PLUGIN_DESC = ffi.new('char[]', b'Python %d.%d scripting interface' % (sys.version_info[0], sys.version_info[1]))
3443 +PLUGIN_VERSION = ffi.new('char[]', VERSION)
3444 +
3445 +# TODO: Constants should be screaming snake case
3446 +hexchat = None
3447 +local_interp = None
3448 +hexchat_stdout = None
3449 +plugins = set()
3450 +
3451 +
3452 +@contextmanager
3453 +def redirected_stdout():
3454 + sys.stdout = sys.__stdout__
3455 + sys.stderr = sys.__stderr__
3456 + yield
3457 + sys.stdout = hexchat_stdout
3458 + sys.stderr = hexchat_stdout
3459 +
3460 +
3461 +if os.getenv('HEXCHAT_LOG_PYTHON'):
3462 + def log(*args):
3463 + with redirected_stdout():
3464 + print(*args)
3465 +
3466 +else:
3467 + def log(*args):
3468 + pass
3469 +
3470 +
3471 +class Stdout:
3472 + def __init__(self):
3473 + self.buffer = bytearray()
3474 +
3475 + def write(self, string):
3476 + string = string.encode()
3477 + idx = string.rfind(b'\n')
3478 + if idx != -1:
3479 + self.buffer += string[:idx]
3480 + lib.hexchat_print(lib.ph, bytes(self.buffer))
3481 + self.buffer = bytearray(string[idx + 1:])
3482 + else:
3483 + self.buffer += string
3484 +
3485 + def isatty(self):
3486 + return False
3487 +
3488 +
3489 +class Attribute:
3490 + def __init__(self):
3491 + self.time = 0
3492 +
3493 + def __repr__(self):
3494 + return '<Attribute object at {}>'.format(id(self))
3495 +
3496 +
3497 +class Hook:
3498 + def __init__(self, plugin, callback, userdata, is_unload):
3499 + self.is_unload = is_unload
3500 + self.plugin = weakref.proxy(plugin)
3501 + self.callback = callback
3502 + self.userdata = userdata
3503 + self.hexchat_hook = None
3504 + self.handle = ffi.new_handle(weakref.proxy(self))
3505 +
3506 + def __del__(self):
3507 + log('Removing hook', id(self))
3508 + if self.is_unload is False:
3509 + assert self.hexchat_hook is not None
3510 + lib.hexchat_unhook(lib.ph, self.hexchat_hook)
3511 +
3512 +
3513 +if sys.version_info[0] == 2:
3514 + def compile_file(data, filename):
3515 + return compile(data, filename, 'exec', dont_inherit=True)
3516 +
3517 +
3518 + def compile_line(string):
3519 + try:
3520 + return compile(string, '<string>', 'eval', dont_inherit=True)
3521 +
3522 + except SyntaxError:
3523 + # For some reason `print` is invalid for eval
3524 + # This will hide any return value though
3525 + return compile(string, '<string>', 'exec', dont_inherit=True)
3526 +else:
3527 + def compile_file(data, filename):
3528 + return compile(data, filename, 'exec', optimize=2, dont_inherit=True)
3529 +
3530 +
3531 + def compile_line(string):
3532 + # newline appended to solve unexpected EOF issues
3533 + return compile(string + '\n', '<string>', 'single', optimize=2, dont_inherit=True)
3534 +
3535 +
3536 +class Plugin:
3537 + def __init__(self):
3538 + self.ph = None
3539 + self.name = ''
3540 + self.filename = ''
3541 + self.version = ''
3542 + self.description = ''
3543 + self.hooks = set()
3544 + self.globals = {
3545 + '__plugin': weakref.proxy(self),
3546 + '__name__': '__main__',
3547 + }
3548 +
3549 + def add_hook(self, callback, userdata, is_unload=False):
3550 + hook = Hook(self, callback, userdata, is_unload=is_unload)
3551 + self.hooks.add(hook)
3552 + return hook
3553 +
3554 + def remove_hook(self, hook):
3555 + for h in self.hooks:
3556 + if id(h) == hook:
3557 + ud = h.userdata
3558 + self.hooks.remove(h)
3559 + return ud
3560 +
3561 + log('Hook not found')
3562 + return None
3563 +
3564 + def loadfile(self, filename):
3565 + try:
3566 + self.filename = filename
3567 + with open(filename) as f:
3568 + data = f.read()
3569 + compiled = compile_file(data, filename)
3570 + exec(compiled, self.globals)
3571 +
3572 + try:
3573 + self.name = self.globals['__module_name__']
3574 +
3575 + except KeyError:
3576 + lib.hexchat_print(lib.ph, b'Failed to load module: __module_name__ must be set')
3577 +
3578 + return False
3579 +
3580 + self.version = self.globals.get('__module_version__', '')
3581 + self.description = self.globals.get('__module_description__', '')
3582 + self.ph = lib.hexchat_plugingui_add(lib.ph, filename.encode(), self.name.encode(),
3583 + self.description.encode(), self.version.encode(), ffi.NULL)
3584 +
3585 + except Exception as e:
3586 + lib.hexchat_print(lib.ph, 'Failed to load module: {}'.format(e).encode())
3587 + traceback.print_exc()
3588 + return False
3589 +
3590 + return True
3591 +
3592 + def __del__(self):
3593 + log('unloading', self.filename)
3594 + for hook in self.hooks:
3595 + if hook.is_unload is True:
3596 + try:
3597 + hook.callback(hook.userdata)
3598 +
3599 + except Exception as e:
3600 + log('Failed to run hook:', e)
3601 + traceback.print_exc()
3602 +
3603 + del self.hooks
3604 + if self.ph is not None:
3605 + lib.hexchat_plugingui_remove(lib.ph, self.ph)
3606 +
3607 +
3608 +if sys.version_info[0] == 2:
3609 + def __decode(string):
3610 + return string
3611 +
3612 +else:
3613 + def __decode(string):
3614 + return string.decode()
3615 +
3616 +
3617 +# There can be empty entries between non-empty ones so find the actual last value
3618 +def wordlist_len(words):
3619 + for i in range(31, 1, -1):
3620 + if ffi.string(words[i]):
3621 + return i
3622 +
3623 + return 0
3624 +
3625 +
3626 +def create_wordlist(words):
3627 + size = wordlist_len(words)
3628 + return [__decode(ffi.string(words[i])) for i in range(1, size + 1)]
3629 +
3630 +
3631 +# This function only exists for compat reasons with the C plugin
3632 +# It turns the word list from print hooks into a word_eol list
3633 +# This makes no sense to do...
3634 +def create_wordeollist(words):
3635 + words = reversed(words)
3636 + accum = None
3637 + ret = []
3638 + for word in words:
3639 + if accum is None:
3640 + accum = word
3641 +
3642 + elif word:
3643 + last = accum
3644 + accum = ' '.join((word, last))
3645 +
3646 + ret.insert(0, accum)
3647 +
3648 + return ret
3649 +
3650 +
3651 +def to_cb_ret(value):
3652 + if value is None:
3653 + return 0
3654 +
3655 + return int(value)
3656 +
3657 +
3658 +@ffi.def_extern()
3659 +def _on_command_hook(word, word_eol, userdata):
3660 + hook = ffi.from_handle(userdata)
3661 + word = create_wordlist(word)
3662 + word_eol = create_wordlist(word_eol)
3663 + return to_cb_ret(hook.callback(word, word_eol, hook.userdata))
3664 +
3665 +
3666 +@ffi.def_extern()
3667 +def _on_print_hook(word, userdata):
3668 + hook = ffi.from_handle(userdata)
3669 + word = create_wordlist(word)
3670 + word_eol = create_wordeollist(word)
3671 + return to_cb_ret(hook.callback(word, word_eol, hook.userdata))
3672 +
3673 +
3674 +@ffi.def_extern()
3675 +def _on_print_attrs_hook(word, attrs, userdata):
3676 + hook = ffi.from_handle(userdata)
3677 + word = create_wordlist(word)
3678 + word_eol = create_wordeollist(word)
3679 + attr = Attribute()
3680 + attr.time = attrs.server_time_utc
3681 + return to_cb_ret(hook.callback(word, word_eol, hook.userdata, attr))
3682 +
3683 +
3684 +@ffi.def_extern()
3685 +def _on_server_hook(word, word_eol, userdata):
3686 + hook = ffi.from_handle(userdata)
3687 + word = create_wordlist(word)
3688 + word_eol = create_wordlist(word_eol)
3689 + return to_cb_ret(hook.callback(word, word_eol, hook.userdata))
3690 +
3691 +
3692 +@ffi.def_extern()
3693 +def _on_server_attrs_hook(word, word_eol, attrs, userdata):
3694 + hook = ffi.from_handle(userdata)
3695 + word = create_wordlist(word)
3696 + word_eol = create_wordlist(word_eol)
3697 + attr = Attribute()
3698 + attr.time = attrs.server_time_utc
3699 + return to_cb_ret(hook.callback(word, word_eol, hook.userdata, attr))
3700 +
3701 +
3702 +@ffi.def_extern()
3703 +def _on_timer_hook(userdata):
3704 + hook = ffi.from_handle(userdata)
3705 + if hook.callback(hook.userdata) is True:
3706 + return 1
3707 +
3708 + hook.is_unload = True # Don't unhook
3709 + for h in hook.plugin.hooks:
3710 + if h == hook:
3711 + hook.plugin.hooks.remove(h)
3712 + break
3713 +
3714 + return 0
3715 +
3716 +
3717 +@ffi.def_extern(error=3)
3718 +def _on_say_command(word, word_eol, userdata):
3719 + channel = ffi.string(lib.hexchat_get_info(lib.ph, b'channel'))
3720 + if channel == b'>>python<<':
3721 + python = ffi.string(word_eol[1])
3722 + lib.hexchat_print(lib.ph, b'>>> ' + python)
3723 + exec_in_interp(__decode(python))
3724 + return 1
3725 +
3726 + return 0
3727 +
3728 +
3729 +def load_filename(filename):
3730 + filename = os.path.expanduser(filename)
3731 + if not os.path.isabs(filename):
3732 + configdir = __decode(ffi.string(lib.hexchat_get_info(lib.ph, b'configdir')))
3733 +
3734 + filename = os.path.join(configdir, 'addons', filename)
3735 +
3736 + if filename and not any(plugin.filename == filename for plugin in plugins):
3737 + plugin = Plugin()
3738 + if plugin.loadfile(filename):
3739 + plugins.add(plugin)
3740 + return True
3741 +
3742 + return False
3743 +
3744 +
3745 +def unload_name(name):
3746 + if name:
3747 + for plugin in plugins:
3748 + if name in (plugin.name, plugin.filename, os.path.basename(plugin.filename)):
3749 + plugins.remove(plugin)
3750 + return True
3751 +
3752 + return False
3753 +
3754 +
3755 +def reload_name(name):
3756 + if name:
3757 + for plugin in plugins:
3758 + if name in (plugin.name, plugin.filename, os.path.basename(plugin.filename)):
3759 + filename = plugin.filename
3760 + plugins.remove(plugin)
3761 + return load_filename(filename)
3762 +
3763 + return False
3764 +
3765 +
3766 +@contextmanager
3767 +def change_cwd(path):
3768 + old_cwd = os.getcwd()
3769 + os.chdir(path)
3770 + yield
3771 + os.chdir(old_cwd)
3772 +
3773 +
3774 +def autoload():
3775 + configdir = __decode(ffi.string(lib.hexchat_get_info(lib.ph, b'configdir')))
3776 + addondir = os.path.join(configdir, 'addons')
3777 + try:
3778 + with change_cwd(addondir): # Maintaining old behavior
3779 + for f in os.listdir(addondir):
3780 + if f.endswith('.py'):
3781 + log('Autoloading', f)
3782 + # TODO: Set cwd
3783 + load_filename(os.path.join(addondir, f))
3784 +
3785 + except FileNotFoundError as e:
3786 + log('Autoload failed', e)
3787 +
3788 +
3789 +def list_plugins():
3790 + if not plugins:
3791 + lib.hexchat_print(lib.ph, b'No python modules loaded')
3792 + return
3793 +
3794 + tbl_headers = [b'Name', b'Version', b'Filename', b'Description']
3795 + tbl = [
3796 + tbl_headers,
3797 + [(b'-' * len(s)) for s in tbl_headers]
3798 + ]
3799 +
3800 + for plugin in plugins:
3801 + basename = os.path.basename(plugin.filename).encode()
3802 + name = plugin.name.encode()
3803 + version = plugin.version.encode() if plugin.version else b'<none>'
3804 + description = plugin.description.encode() if plugin.description else b'<none>'
3805 + tbl.append((name, version, basename, description))
3806 +
3807 + column_sizes = [
3808 + max(len(item) for item in column)
3809 + for column in zip(*tbl)
3810 + ]
3811 +
3812 + for row in tbl:
3813 + lib.hexchat_print(lib.ph, b' '.join(item.ljust(column_sizes[i])
3814 + for i, item in enumerate(row)))
3815 + lib.hexchat_print(lib.ph, b'')
3816 +
3817 +
3818 +def exec_in_interp(python):
3819 + global local_interp
3820 +
3821 + if not python:
3822 + return
3823 +
3824 + if local_interp is None:
3825 + local_interp = Plugin()
3826 + local_interp.locals = {}
3827 + local_interp.globals['hexchat'] = hexchat
3828 +
3829 + code = compile_line(python)
3830 + try:
3831 + ret = eval(code, local_interp.globals, local_interp.locals)
3832 + if ret is not None:
3833 + lib.hexchat_print(lib.ph, '{}'.format(ret).encode())
3834 +
3835 + except Exception as e:
3836 + traceback.print_exc(file=hexchat_stdout)
3837 +
3838 +
3839 +@ffi.def_extern()
3840 +def _on_load_command(word, word_eol, userdata):
3841 + filename = ffi.string(word[2])
3842 + if filename.endswith(b'.py'):
3843 + load_filename(__decode(filename))
3844 + return 3
3845 +
3846 + return 0
3847 +
3848 +
3849 +@ffi.def_extern()
3850 +def _on_unload_command(word, word_eol, userdata):
3851 + filename = ffi.string(word[2])
3852 + if filename.endswith(b'.py'):
3853 + unload_name(__decode(filename))
3854 + return 3
3855 +
3856 + return 0
3857 +
3858 +
3859 +@ffi.def_extern()
3860 +def _on_reload_command(word, word_eol, userdata):
3861 + filename = ffi.string(word[2])
3862 + if filename.endswith(b'.py'):
3863 + reload_name(__decode(filename))
3864 + return 3
3865 +
3866 + return 0
3867 +
3868 +
3869 +@ffi.def_extern(error=3)
3870 +def _on_py_command(word, word_eol, userdata):
3871 + subcmd = __decode(ffi.string(word[2])).lower()
3872 +
3873 + if subcmd == 'exec':
3874 + python = __decode(ffi.string(word_eol[3]))
3875 + exec_in_interp(python)
3876 +
3877 + elif subcmd == 'load':
3878 + filename = __decode(ffi.string(word[3]))
3879 + load_filename(filename)
3880 +
3881 + elif subcmd == 'unload':
3882 + name = __decode(ffi.string(word[3]))
3883 + if not unload_name(name):
3884 + lib.hexchat_print(lib.ph, b'Can\'t find a python plugin with that name')
3885 +
3886 + elif subcmd == 'reload':
3887 + name = __decode(ffi.string(word[3]))
3888 + if not reload_name(name):
3889 + lib.hexchat_print(lib.ph, b'Can\'t find a python plugin with that name')
3890 +
3891 + elif subcmd == 'console':
3892 + lib.hexchat_command(lib.ph, b'QUERY >>python<<')
3893 +
3894 + elif subcmd == 'list':
3895 + list_plugins()
3896 +
3897 + elif subcmd == 'about':
3898 + lib.hexchat_print(lib.ph, b'HexChat Python interface version ' + VERSION)
3899 +
3900 + else:
3901 + lib.hexchat_command(lib.ph, b'HELP PY')
3902 +
3903 + return 3
3904 +
3905 +
3906 +@ffi.def_extern()
3907 +def _on_plugin_init(plugin_name, plugin_desc, plugin_version, arg, libdir):
3908 + global hexchat
3909 + global hexchat_stdout
3910 +
3911 + signal.signal(signal.SIGINT, signal.SIG_DFL)
3912 +
3913 + plugin_name[0] = PLUGIN_NAME
3914 + plugin_desc[0] = PLUGIN_DESC
3915 + plugin_version[0] = PLUGIN_VERSION
3916 +
3917 + try:
3918 + libdir = __decode(ffi.string(libdir))
3919 + modpath = os.path.join(libdir, '..', 'python')
3920 + sys.path.append(os.path.abspath(modpath))
3921 + hexchat = importlib.import_module('hexchat')
3922 +
3923 + except (UnicodeDecodeError, ImportError) as e:
3924 + lib.hexchat_print(lib.ph, b'Failed to import module: ' + repr(e).encode())
3925 +
3926 + return 0
3927 +
3928 + hexchat_stdout = Stdout()
3929 + sys.stdout = hexchat_stdout
3930 + sys.stderr = hexchat_stdout
3931 + pydoc.help = pydoc.Helper(HelpEater(), HelpEater())
3932 +
3933 + lib.hexchat_hook_command(lib.ph, b'', 0, lib._on_say_command, ffi.NULL, ffi.NULL)
3934 + lib.hexchat_hook_command(lib.ph, b'LOAD', 0, lib._on_load_command, ffi.NULL, ffi.NULL)
3935 + lib.hexchat_hook_command(lib.ph, b'UNLOAD', 0, lib._on_unload_command, ffi.NULL, ffi.NULL)
3936 + lib.hexchat_hook_command(lib.ph, b'RELOAD', 0, lib._on_reload_command, ffi.NULL, ffi.NULL)
3937 + lib.hexchat_hook_command(lib.ph, b'PY', 0, lib._on_py_command, b'''Usage: /PY LOAD <filename>
3938 + UNLOAD <filename|name>
3939 + RELOAD <filename|name>
3940 + LIST
3941 + EXEC <command>
3942 + CONSOLE
3943 + ABOUT''', ffi.NULL)
3944 +
3945 + lib.hexchat_print(lib.ph, b'Python interface loaded')
3946 + autoload()
3947 + return 1
3948 +
3949 +
3950 +@ffi.def_extern()
3951 +def _on_plugin_deinit():
3952 + global local_interp
3953 + global hexchat
3954 + global hexchat_stdout
3955 + global plugins
3956 +
3957 + plugins = set()
3958 + local_interp = None
3959 + hexchat = None
3960 + hexchat_stdout = None
3961 + sys.stdout = sys.__stdout__
3962 + sys.stderr = sys.__stderr__
3963 + pydoc.help = pydoc.Helper()
3964 +
3965 + for mod in ('_hexchat', 'hexchat', 'xchat', '_hexchat_embedded'):
3966 + try:
3967 + del sys.modules[mod]
3968 +
3969 + except KeyError:
3970 + pass
3971 +
3972 + return 1
3973 diff --git a/plugins/python/python2.vcxproj b/plugins/python/python2.vcxproj
3974 index f914a865..0b098112 100644
3975 --- a/plugins/python/python2.vcxproj
3976 +++ b/plugins/python/python2.vcxproj
3977 @@ -37,6 +37,9 @@
3978 <AdditionalDependencies>"$(Python2Lib).lib";$(DepLibs);%(AdditionalDependencies)</AdditionalDependencies>
3979 <AdditionalLibraryDirectories>$(DepsRoot)\lib;$(Python2Path)\libs;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
3980 </Link>
3981 + <PreBuildEvent>
3982 + <Command>"$(Python3Path)\python.exe" generate_plugin.py ..\..\src\common\hexchat-plugin.h python.py "$(IntDir)python.c"</Command>
3983 + </PreBuildEvent>
3984 </ItemDefinitionGroup>
3985 <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
3986 <ClCompile>
3987 @@ -48,12 +51,15 @@
3988 <AdditionalDependencies>"$(Python2Lib).lib";$(DepLibs);%(AdditionalDependencies)</AdditionalDependencies>
3989 <AdditionalLibraryDirectories>$(DepsRoot)\lib;$(Python2Path)\libs;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
3990 </Link>
3991 + <PreBuildEvent>
3992 + <Command>"$(Python3Path)\python.exe" generate_plugin.py ..\..\src\common\hexchat-plugin.h python.py "$(IntDir)python.c"</Command>
3993 + </PreBuildEvent>
3994 </ItemDefinitionGroup>
3995 <ItemGroup>
3996 <None Include="python.def" />
3997 </ItemGroup>
3998 <ItemGroup>
3999 - <ClCompile Include="python.c" />
4000 + <ClCompile Include="$(IntDir)python.c" />
4001 </ItemGroup>
4002 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
4003 </Project>
4004 diff --git a/plugins/python/python3.vcxproj b/plugins/python/python3.vcxproj
4005 index 815dc8b1..5868d3b0 100644
4006 --- a/plugins/python/python3.vcxproj
4007 +++ b/plugins/python/python3.vcxproj
4008 @@ -37,6 +37,9 @@
4009 <AdditionalDependencies>"$(Python3Lib).lib";$(DepLibs);%(AdditionalDependencies)</AdditionalDependencies>
4010 <AdditionalLibraryDirectories>$(DepsRoot)\lib;$(Python3Path)\libs;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
4011 </Link>
4012 + <PreBuildEvent>
4013 + <Command>"$(Python3Path)\python.exe" generate_plugin.py ..\..\src\common\hexchat-plugin.h python.py "$(IntDir)python.c"</Command>
4014 + </PreBuildEvent>
4015 </ItemDefinitionGroup>
4016 <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
4017 <ClCompile>
4018 @@ -48,12 +51,20 @@
4019 <AdditionalDependencies>"$(Python3Lib).lib";$(DepLibs);%(AdditionalDependencies)</AdditionalDependencies>
4020 <AdditionalLibraryDirectories>$(DepsRoot)\lib;$(Python3Path)\libs;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
4021 </Link>
4022 + <PreBuildEvent>
4023 + <Command>"$(Python3Path)\python.exe" generate_plugin.py ..\..\src\common\hexchat-plugin.h python.py "$(IntDir)python.c"</Command>
4024 + </PreBuildEvent>
4025 </ItemDefinitionGroup>
4026 <ItemGroup>
4027 + <None Include="generate_plugin.py" />
4028 + <None Include="hexchat.py" />
4029 <None Include="python.def" />
4030 + <None Include="python.py" />
4031 + <None Include="xchat.py" />
4032 + <None Include="_hexchat.py" />
4033 </ItemGroup>
4034 <ItemGroup>
4035 - <ClCompile Include="python.c" />
4036 + <ClCompile Include="$(IntDir)python.c" />
4037 </ItemGroup>
4038 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
4039 -</Project>
4040 +</Project>
4041 \ No newline at end of file
4042 diff --git a/plugins/python/python3.vcxproj.filters b/plugins/python/python3.vcxproj.filters
4043 index 9165e798..5c5834bb 100644
4044 --- a/plugins/python/python3.vcxproj.filters
4045 +++ b/plugins/python/python3.vcxproj.filters
4046 @@ -9,13 +9,26 @@
4047 </Filter>
4048 </ItemGroup>
4049 <ItemGroup>
4050 - <ClCompile Include="python.c">
4051 - <Filter>Source Files</Filter>
4052 - </ClCompile>
4053 + <ClCompile Include="$(IntDir)python.c" />
4054 </ItemGroup>
4055 <ItemGroup>
4056 <None Include="python.def">
4057 <Filter>Resource Files</Filter>
4058 </None>
4059 + <None Include="_hexchat.py">
4060 + <Filter>Source Files</Filter>
4061 + </None>
4062 + <None Include="generate_plugin.py">
4063 + <Filter>Source Files</Filter>
4064 + </None>
4065 + <None Include="hexchat.py">
4066 + <Filter>Source Files</Filter>
4067 + </None>
4068 + <None Include="python.py">
4069 + <Filter>Source Files</Filter>
4070 + </None>
4071 + <None Include="xchat.py">
4072 + <Filter>Source Files</Filter>
4073 + </None>
4074 </ItemGroup>
4075 </Project>
4076 \ No newline at end of file
4077 diff --git a/plugins/python/python_style_guide.md b/plugins/python/python_style_guide.md
4078 new file mode 100644
4079 index 00000000..41db2474
4080 --- /dev/null
4081 +++ b/plugins/python/python_style_guide.md
4082 @@ -0,0 +1,26 @@
4083 +# HexChat Python Module Style Guide
4084 +
4085 +(This is a work in progress).
4086 +
4087 +## General rules
4088 +
4089 +- PEP8 as general fallback recommendations
4090 +- Max line length: 120
4091 +- Avoid overcomplex compound statements. i.e. dont do this: `somevar = x if x == y else z if a == b and c == b else x`
4092 +
4093 +## Indentation style
4094 +
4095 +### Multi-line functions
4096 +
4097 +```python
4098 +foo(really_long_arg_1,
4099 + really_long_arg_2)
4100 +```
4101 +
4102 +### Mutli-line lists/dicts
4103 +
4104 +```python
4105 +foo = {
4106 + 'bar': 'baz',
4107 +}
4108 +```
4109 diff --git a/plugins/python/xchat.py b/plugins/python/xchat.py
4110 new file mode 100644
4111 index 00000000..6922490b
4112 --- /dev/null
4113 +++ b/plugins/python/xchat.py
4114 @@ -0,0 +1 @@
4115 +from _hexchat import *
4116 diff --git a/src/common/meson.build b/src/common/meson.build
4117 index bbb64645..492227b2 100644
4118 --- a/src/common/meson.build
4119 +++ b/src/common/meson.build
4120 @@ -93,10 +93,6 @@ endif
4121
4122 if get_option('with-plugin')
4123 common_deps += libgmodule_dep
4124 - common_cflags += '-DHEXCHATLIBDIR="@0@"'.format(join_paths(get_option('prefix'),
4125 - get_option('libdir'),
4126 - 'hexchat/plugins'))
4127 -
4128 install_headers('hexchat-plugin.h')
4129 endif
4130
4131 diff --git a/win32/copy/copy.vcxproj b/win32/copy/copy.vcxproj
4132 index c508a7f3..72f2c032 100644
4133 --- a/win32/copy/copy.vcxproj
4134 +++ b/win32/copy/copy.vcxproj
4135 @@ -64,6 +64,8 @@
4136 <LuaShare Include="$(DepsRoot)\share\lua\**\*.lua" />
4137 <LuaShare Include="$(DepsRoot)\share\lua\**\**\*.lua" />
4138 <Typelib Include="$(DepsRoot)\lib\girepository-1.0\*.typelib" />
4139 + <None Include="$(Python3Path)\Lib\site-packages\_cffi_backend.*.pyd" />
4140 + <None Include="$(Python2Path)\Lib\site-packages\_cffi_backend.pyd" />
4141
4142 <Engines Include="$(DepsRoot)\lib\gtk-2.0\i686-pc-vs14\engines\**\*" />
4143
4144 @@ -91,6 +93,9 @@
4145 <Copy SourceFiles="@(LuaShare)" DestinationFiles="@(LuaShare->'$(HexChatRel)\share\lua\%(RecursiveDir)%(Filename)%(Extension)')" />
4146 <Copy SourceFiles="@(LuaLib)" DestinationFiles="@(LuaLib->'$(HexChatRel)\lib\lua\%(RecursiveDir)%(Filename)%(Extension)')" />
4147 <Copy SourceFiles="@(Typelib)" DestinationFiles="@(Typelib->'$(HexChatRel)\lib\girepository-1.0\%(Filename)%(Extension)')" />
4148 + <Copy SourceFiles="..\..\plugins\python\xchat.py" DestinationFolder="$(HexChatRel)\python" />
4149 + <Copy SourceFiles="..\..\plugins\python\hexchat.py" DestinationFolder="$(HexChatRel)\python" />
4150 + <Copy SourceFiles="..\..\plugins\python\_hexchat.py" DestinationFolder="$(HexChatRel)\python" />
4151
4152 <WriteLinesToFile File="$(HexChatRel)portable-mode" Lines="2" Overwrite="true" />
4153
4154 diff --git a/win32/installer/hexchat.iss.tt b/win32/installer/hexchat.iss.tt
4155 index e242ee96..3ac5ec41 100644
4156 --- a/win32/installer/hexchat.iss.tt
4157 +++ b/win32/installer/hexchat.iss.tt
4158 @@ -164,6 +164,7 @@ Source: "lib\girepository-1.0\*.typelib"; DestDir: "{app}\lib\girepository-1.0";
4159 Source: "share\lua\*.lua"; DestDir: "{app}\share\lua"; Flags: ignoreversion; Components: langs\lua
4160 Source: "share\lua\lgi\*.lua"; DestDir: "{app}\share\lua\lgi"; Flags: ignoreversion; Components: langs\lua
4161 Source: "share\lua\lgi\override\*.lua"; DestDir: "{app}\share\lua\lgi\override"; Flags: ignoreversion; Components: langs\lua
4162 +Source: "plugins\hclua.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Components: langs\lua
4163
4164 Source: "plugins\hcchecksum.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Components: plugins\checksum
4165 Source: "plugins\hcexec.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Components: plugins\exec
4166 @@ -175,11 +176,15 @@ Source: "WinSparkle.dll"; DestDir: "{app}"; Flags: ignoreversion; Components: pl
4167 Source: "plugins\hcwinamp.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Components: plugins\winamp
4168 Source: "share\system.png"; DestDir: "{app}\share"; Flags: ignoreversion; Components: plugins\sysinfo
4169 Source: "plugins\hcsysinfo.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Components: plugins\sysinfo
4170 +Source: "plugins\hcperl.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Components: langs\perl
4171 +
4172 +Source: "python\*.py"; DestDir: "{app}\python"; Flags: ignoreversion; Components: langs\python
4173
4174 Source: "plugins\hcpython2.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Components: langs\python\python2
4175 +Source: "_cffi_backend.pyd"; DestDir: "{app}"; Flags: ignoreversion; Components: langs\python\python2
4176 +
4177 Source: "plugins\hcpython3.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Components: langs\python\python3
4178 -Source: "plugins\hcperl.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Components: langs\perl
4179 -Source: "plugins\hclua.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Components: langs\lua
4180 +Source: "_cffi_backend.cp3*.pyd"; DestDir: "{app}"; Flags: ignoreversion; Components: langs\python\python3
4181
4182 Source: "hexchat.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: libs
4183 Source: "hexchat-text.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: xctext
|