From bb48c914c6239ed1dbcb29eb62d33d3ab91e7215 Mon Sep 17 00:00:00 2001 From: Christopher Davis Date: Mon, 19 Jun 2006 12:25:06 +0000 Subject: initial import git-svn-id: http://svn.irssi.org/repos/irssi-python@4282 dbcabf3a-b0e7-0310-adc4-f8d773084564 --- Makefile | 27 ++ README | 62 ++++ classes.txt | 39 +++ irssi.py | 68 ++++ irssi_startup.py | 202 +++++++++++ objects/Makefile | 24 ++ objects/ban-object.c | 155 ++++++++ objects/ban-object.h | 18 + objects/base-objects.c | 251 +++++++++++++ objects/base-objects.h | 96 +++++ objects/channel-object.c | 331 ++++++++++++++++++ objects/channel-object.h | 22 ++ objects/chatnet-object.c | 182 ++++++++++ objects/chatnet-object.h | 22 ++ objects/connect-object.c | 187 ++++++++++ objects/connect-object.h | 22 ++ objects/dcc-object.c | 321 +++++++++++++++++ objects/dcc-object.h | 24 ++ objects/factory.c | 291 +++++++++++++++ objects/factory.h | 46 +++ objects/ignore-object.c | 275 +++++++++++++++ objects/ignore-object.h | 21 ++ objects/irc-channel-object.c | 166 +++++++++ objects/irc-channel-object.h | 21 ++ objects/irc-connect-object.c | 99 ++++++ objects/irc-connect-object.h | 21 ++ objects/irc-server-object.c | 265 ++++++++++++++ objects/irc-server-object.h | 21 ++ objects/log-object.c | 465 ++++++++++++++++++++++++ objects/log-object.h | 21 ++ objects/logitem-object.c | 156 +++++++++ objects/logitem-object.h | 21 ++ objects/nick-object.c | 216 ++++++++++++ objects/nick-object.h | 22 ++ objects/pyscript-object.c | 225 ++++++++++++ objects/pyscript-object.h | 25 ++ objects/query-object.c | 177 ++++++++++ objects/query-object.h | 21 ++ objects/rawlog-object.c | 306 ++++++++++++++++ objects/rawlog-object.h | 22 ++ objects/reconnect-object.c | 152 ++++++++ objects/reconnect-object.h | 20 ++ objects/server-object.c | 763 ++++++++++++++++++++++++++++++++++++++++ objects/server-object.h | 28 ++ objects/textdest-object.c | 188 ++++++++++ objects/textdest-object.h | 23 ++ objects/window-item-object.c | 307 ++++++++++++++++ objects/window-item-object.h | 26 ++ objects/window-object.c | 646 ++++++++++++++++++++++++++++++++++ objects/window-object.h | 21 ++ pycore.c | 152 ++++++++ pycore.h | 7 + pyirssi.h | 25 ++ pyirssi_irc.h | 16 + pyloader.c | 306 ++++++++++++++++ pyloader.h | 21 ++ pymodule.c | 817 +++++++++++++++++++++++++++++++++++++++++++ pymodule.h | 11 + pysignals.c | 95 +++++ pysignals.h | 14 + pyutils.c | 81 +++++ pyutils.h | 12 + scripts/CVS/Entries | 3 + scripts/CVS/Repository | 1 + scripts/CVS/Root | 1 + scripts/dumper.py | 188 ++++++++++ scripts/hello.py | 20 ++ scripts/test_window.py | 50 +++ 68 files changed, 8950 insertions(+) create mode 100644 Makefile create mode 100644 README create mode 100644 classes.txt create mode 100644 irssi.py create mode 100644 irssi_startup.py create mode 100644 objects/Makefile create mode 100644 objects/ban-object.c create mode 100644 objects/ban-object.h create mode 100644 objects/base-objects.c create mode 100644 objects/base-objects.h create mode 100644 objects/channel-object.c create mode 100644 objects/channel-object.h create mode 100644 objects/chatnet-object.c create mode 100644 objects/chatnet-object.h create mode 100644 objects/connect-object.c create mode 100644 objects/connect-object.h create mode 100644 objects/dcc-object.c create mode 100644 objects/dcc-object.h create mode 100644 objects/factory.c create mode 100644 objects/factory.h create mode 100644 objects/ignore-object.c create mode 100644 objects/ignore-object.h create mode 100644 objects/irc-channel-object.c create mode 100644 objects/irc-channel-object.h create mode 100644 objects/irc-connect-object.c create mode 100644 objects/irc-connect-object.h create mode 100644 objects/irc-server-object.c create mode 100644 objects/irc-server-object.h create mode 100644 objects/log-object.c create mode 100644 objects/log-object.h create mode 100644 objects/logitem-object.c create mode 100644 objects/logitem-object.h create mode 100644 objects/nick-object.c create mode 100644 objects/nick-object.h create mode 100644 objects/pyscript-object.c create mode 100644 objects/pyscript-object.h create mode 100644 objects/query-object.c create mode 100644 objects/query-object.h create mode 100644 objects/rawlog-object.c create mode 100644 objects/rawlog-object.h create mode 100644 objects/reconnect-object.c create mode 100644 objects/reconnect-object.h create mode 100644 objects/server-object.c create mode 100644 objects/server-object.h create mode 100644 objects/textdest-object.c create mode 100644 objects/textdest-object.h create mode 100644 objects/window-item-object.c create mode 100644 objects/window-item-object.h create mode 100644 objects/window-object.c create mode 100644 objects/window-object.h create mode 100644 pycore.c create mode 100644 pycore.h create mode 100644 pyirssi.h create mode 100644 pyirssi_irc.h create mode 100644 pyloader.c create mode 100644 pyloader.h create mode 100644 pymodule.c create mode 100644 pymodule.h create mode 100644 pysignals.c create mode 100644 pysignals.h create mode 100644 pyutils.c create mode 100644 pyutils.h create mode 100644 scripts/CVS/Entries create mode 100644 scripts/CVS/Repository create mode 100644 scripts/CVS/Root create mode 100644 scripts/dumper.py create mode 100644 scripts/hello.py create mode 100644 scripts/test_window.py diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2c2faf8 --- /dev/null +++ b/Makefile @@ -0,0 +1,27 @@ +CC = gcc + +PYTHON = /usr/include/python2.4 +IRSSI = /usr/local/include/irssi + +CFLAGS = -fpic -ggdb -Wall -I$(PYTHON) -I$(IRSSI) -I$(IRSSI)/src \ +-I$(IRSSI)/src/fe-common/core -I$(IRSSI)/src/core -I$(IRSSI)/src/fe-text \ +-I$(IRSSI)/src/irc -I$(IRSSI)/src/irc/core -I$(IRSSI)/src/irc/dcc \ +-I. -Iobjects `pkg-config glib-2.0 --cflags` + +LDFLAGS = -fpic /usr/lib/libpython2.4.so + +OBJ = pycore.o pyutils.o pymodule.o pyloader.o pysignals.o + +pyirssi: pyobjects.a $(OBJ) + $(CC) -shared -o libirssi_python.so $(OBJ) objects/pyobjects.a $(LDFLAGS) + +pyobjects.a: + cd objects/ && make + +%.o: %.c + $(CC) -c $< $(CFLAGS) + +clean: + rm -f *.o *.so + cd objects/ && make clean + diff --git a/README b/README new file mode 100644 index 0000000..ed4201f --- /dev/null +++ b/README @@ -0,0 +1,62 @@ +Pytest for Irssi + +This module embeds Python into an Irssi module. It provides just enough of an +interface to port the hello.pl example from the Perl docs to Python. There are +still a lot of things needed to be done, and some design issues to work out. +See the scripts/ directory for two small example scripts. + +To compile: + So far I've compiled it on Debian i386 and on FreeBSD i386. Haven't tried + elsewhere. The Makefile will need to be edited for it to be compiled. It + requires Python 2.4 installed. Put libpytest.so wherever Irssi recognizes + modules. + +In pytest so far: + -Looks for scripts in ~/.irssi/pyscripts + -Created base object wrappers for structs with type and chat_type members. + -Wrapped members of most of the core structs: + SERVER_REC + WI_ITEM_REC + CHANNEL_REC + QUERY_REC + -Wrapped print methods for the global module, server, and window item. + -Wrapped command methods for server and window item. + -Included a signal handler in the base server and window item types that + seems to prevent crashes when an object is accessed after a server is + disconnected or a window item is removed (channel close, etc). + -Python stdout and stderr are redirected to the Irssi status window. + -The command_bind method of the Script object binds commands. + +TODO: + -Wrap the rest of the *_REC structs. + -Wrap the important methods and functions. + -Signal handling + -Wrapper object factory + -Handle settings + -Handle timeouts + -Bunch of other stuff + +Design issues: + -Threading. Pytest doesn't consider thread usage by scripts at all. Any + thread usage would likely cause a crash, as pytest makes no attempt to + manipulate Python's interpreter lock or thread state. It would be easy to + use PyGILState_* critical sections around important parts where Irssi + calls into Python, but I don't know about the larger impact on Irssi for + allowing threads to run. With the signal system and timeouts, is this + really an important issue? + + -Structure. C modules could use some adjustment, especially as more code + is added. I also noticed how the Perl wrapper, like the rest of Irssi, + seems to be organized around interface type. The Python wrapper may need + some adjustment for this(?) + + -Also, command_bind currently isn't a function in the main namespace like in + the Perl wrapper. Instead, its a method of the Script object, which includes + a list of bound signals for record keeping. That way, the context is always + certain. Currently, the Script object is inserted into the user's script + through a startup hook function (irssi_main). There could be a better way + of doing this (perhaps by inserting the script object directly into the + script's globals and using an import hook to distribute it to modules the + script imports). + + diff --git a/classes.txt b/classes.txt new file mode 100644 index 0000000..e94fd97 --- /dev/null +++ b/classes.txt @@ -0,0 +1,39 @@ +Basic types (source): + IrssiObject/ + IrssiChatObject/ + Chatnet/ (core/chatnets.h) + IrcChatnet (irc/core/irc-chatnets.h) + #ServerSetup/ (core/servers-setup.h) + # IrcServerSetup (irc/core/irc-servers-setup.h) + ServerConnect/ (core/servers.h) + IrcServerConnect (irc/core/irc-servers.h) + Server/ (core/servers.h) + IrcServer (irc/core/irc-servers.h) + WindowItem/ (core/window-item*.h) + Channel/ (core/channels.h) + IrcChannel (irc/core/irc-channels.h) + Query/ (core/queries.h) + IrcQuery (irc/core/irc-queries.h NOTE: only one additional method) + Nick/ (core/nick-rec.h) + Dcc/ (irc/dcc/{dcc,dcc-rec}.h) + DccFile/ (irc/dcc/dcc-file.h) + DccGet (irc/dcc/dcc-get.h) + DccSend (irc/dcc/dcc-send.h) + DccServer (irc/dcc/dcc-server.h NOTE: is this needed?) + DccChat (irc/dcc/dcc-chat.h) + +Final Types (source): + Window (fe-common/core/fe-windows.h) + TextDest (fe-common/core/testdest.h) + Reconnect (core/servers-reconnect.h) + Netsplit (irc/core/netsplit.h) + NetsplitServer (irc/core/netsplit.h) + NetsplitChannel (irc/core/netsplit.h) + Notifylist (irc/notifylist/notifylist.h) + Ban (irc/core/modelists.h) + Ignore (core/ignore.h) + Log (core/log.h) + Logitem (core/log.h) + Rawlog (core/rawlog.h) + Masks ** NO STRUCT ** (core/masks.h) + Process (fe-common/core/fe-exec.h NOTE: what about EXEC_WI_REC sub from WI_ITEM?) diff --git a/irssi.py b/irssi.py new file mode 100644 index 0000000..9b0ef6f --- /dev/null +++ b/irssi.py @@ -0,0 +1,68 @@ +""" +""" + +import sys +import _irssi +from _irssi import * + +#XXX: what if the values change? (better to compile in constants?) +#Level constants (coppied from levels.h valid Irssi 0.8.10) +MSGLEVEL_CRAP = 0x0000001 +MSGLEVEL_MSGS = 0x0000002 +MSGLEVEL_PUBLIC = 0x0000004 +MSGLEVEL_NOTICES = 0x0000008 +MSGLEVEL_SNOTES = 0x0000010 +MSGLEVEL_CTCPS = 0x0000020 +MSGLEVEL_ACTIONS = 0x0000040 +MSGLEVEL_JOINS = 0x0000080 +MSGLEVEL_PARTS = 0x0000100 +MSGLEVEL_QUITS = 0x0000200 +MSGLEVEL_KICKS = 0x0000400 +MSGLEVEL_MODES = 0x0000800 +MSGLEVEL_TOPICS = 0x0001000 +MSGLEVEL_WALLOPS = 0x0002000 +MSGLEVEL_INVITES = 0x0004000 +MSGLEVEL_NICKS = 0x0008000 +MSGLEVEL_DCC = 0x0010000 +MSGLEVEL_DCCMSGS = 0x0020000 +MSGLEVEL_CLIENTNOTICE = 0x0040000 +MSGLEVEL_CLIENTCRAP = 0x0080000 +MSGLEVEL_CLIENTERROR = 0x0100000 +MSGLEVEL_HILIGHT = 0x0200000 +MSGLEVEL_ALL = 0x03fffff +MSGLEVEL_NOHILIGHT = 0x1000000 +MSGLEVEL_NO_ACT = 0x2000000 +MSGLEVEL_NEVER = 0x4000000 +MSGLEVEL_LASTLOG = 0x8000000 + +""" +#Link Irssi functions and objects +get_script = _irssi.get_script +prnt = _irssi.prnt +chatnets = _irssi.chatnets +servers = _irssi.servers +reconnects = _irssi.reconnects +chatnet_find = _irssi.chatnet_find +windows = _irssi.windows + +Script = _irssi.Script +Channel = _irssi.Channel +Query = _irssi.Query +WindowItem = _irssi.WindowItem +Window = _irssi.Window +Server = _irssi.Server +Connect = _irssi.Connect +IrcServer = _irssi.IrcServer +IrcConnect = _irssi.IrcConnect +IrcChannel = _irssi.IrcChannel +Ban = _irssi.Ban +Nick = _irssi.Nick +Chatnet = _irssi.Chatnet +Reconnect = _irssi.Reconnect +""" + +def command_bind(*args, **kwargs): + """ see Script.command_bind """ + get_script().command_bind(*args, **kwargs) + + diff --git a/irssi_startup.py b/irssi_startup.py new file mode 100644 index 0000000..ee8dbdd --- /dev/null +++ b/irssi_startup.py @@ -0,0 +1,202 @@ +import sys, imp, __builtin__ +import _irssi + +class Output: + def __init__(self, level): + self.level = level + self.buf = [] + def write(self, text): + if not text: + return + self.buf.append(text) + if '\n' == text[-1]: + text = ''.join(self.buf)[:-1] + for line in text.split('\\n'): + _irssi.active_win().prnt(line, self.level) + self.buf = [] + +#XXX: hardcode +#try: +sys.stdout = Output(level = 0x0080000) +sys.stderr = Output(level = 0x0100000) +#except Exception, e: +# print 'Cant set output', e +# sys.stdout = sys.__stdout__ +# sys.stderr = sys.__stderr__ + +""" +#Import stuff modified from /Demo/imputil/knee.py +#If a script instance is available, it's module dictionary +#is used in place of sys.modules +#XXX: needs testing +#XXX: copyright? + +# Save the original hooks +original_import = __builtin__.__import__ +original_reload = __builtin__.reload + +# Replacement for __import__() +def import_hook(name, globals=None, locals=None, fromlist=None): + print 'LOADING', name + if name == 'sys': + return sys + #if name == '_irssi': + # return _irssi + + try: + script = _irssi.get_script() + except RuntimeError: + print 'ORIG IMPORT', name + return original_import(name, globals, locals, fromlist) + + parent = determine_parent(globals, script) + q, tail = find_head_package(parent, name, script) + m = load_tail(q, tail, script) + if not fromlist: + return q + + if hasattr(m, "__path__"): + ensure_fromlist(m, fromlist, script) + + recur -= 1 + return m + +def determine_parent(globals, script): + if not globals or not globals.has_key("__name__"): + print 'DP no __name__ in globals' + return None + pname = globals['__name__'] + print 'DP pname', pname + if globals.has_key("__path__"): + parent = script.modules[pname] + assert globals is parent.__dict__ + return parent + if '.' in pname: + i = pname.rfind('.') + pname = pname[:i] + parent = script.modules[pname] + assert parent.__name__ == pname + return parent + return None + +def find_head_package(parent, name, script): + if '.' in name: + i = name.find('.') + head = name[:i] + tail = name[i+1:] + else: + head = name + tail = "" + if parent: + qname = "%s.%s" % (parent.__name__, head) + else: + qname = head + q = import_module(head, qname, parent, script) + if q: + return q, tail + else: + print 'FHP (1) no q module for', name + + if parent: + qname = head + parent = None + q = import_module(head, qname, parent, script) + if q: + return q, tail + else: + print 'FHP (2) no q module for', name + + raise ImportError, "No module named " + qname + +def load_tail(q, tail, script): + m = q + while tail: + i = tail.find('.') + if i < 0: i = len(tail) + head, tail = tail[:i], tail[i+1:] + mname = "%s.%s" % (m.__name__, head) + m = import_module(head, mname, m, script) + if not m: + raise ImportError, "No module named " + mname + return m + +def ensure_fromlist(m, fromlist, script, recursive=0): + for sub in fromlist: + if sub == "*": + if not recursive: + try: + all = m.__all__ + except AttributeError: + pass + else: + assert recursive == 0 + ensure_fromlist(m, all, script, 1) + continue + if sub != "*" and not hasattr(m, sub): + subname = "%s.%s" % (m.__name__, sub) + submod = import_module(sub, subname, m, script) + if not submod: + raise ImportError, "No module named " + subname + +def import_module(partname, fqname, parent, script): + try: + m = script.modules[fqname] + #if hasattr(m, '_script'): + # assert m._script == script + + _irssi.prnt('LOADING CACHED %s script -> %s' % (fqname, repr(script))) + + return m + except KeyError: + print 'IM no cached moddule for', fqname + pass + + try: + fp, pathname, stuff = imp.find_module(partname, + parent and parent.__path__) + except ImportError, e: + print 'IM import error', e + return None + + try: + m = imp.load_module(fqname, fp, pathname, stuff) + finally: + if fp: fp.close() + if parent: + setattr(parent, partname, m) + + #don't load script into builtins, extensions, or this wrapper + if hasattr(m, '__file__') and fqname != 'irssi': + m._script = script + script.modules[fqname] = m + + #if hasattr(m, '__file__') and fqname not in ('__builtin__', 'sys', '__main__', '_irssi'): + # del sys.modules[fqname] + + _irssi.prnt('GOT -> %s, SCRIPT -> %s' % (m, repr(script))) + + return m + + +# Replacement for reload() +def reload_hook(module): + _irssi.prnt('reloading ' + repr(module)) + try: + script = _irssi.get_script() + except RuntimeError: + return original_reload(module) + + name = module.__name__ + if '.' not in name: + return import_module(name, name, None, script) + i = name.rfind('.') + pname = name[:i] + parent = script.modules[pname] + return import_module(name[i+1:], name, parent, script) + + +# Now install our hooks +__builtin__.__import__ = import_hook +__builtin__.reload = reload_hook +""" + diff --git a/objects/Makefile b/objects/Makefile new file mode 100644 index 0000000..b48d47e --- /dev/null +++ b/objects/Makefile @@ -0,0 +1,24 @@ +CC = gcc + +PYTHON = /usr/include/python2.4 +IRSSI = /usr/local/include/irssi +CFLAGS = -fpic -ggdb -Wall -I$(PYTHON) -I$(IRSSI) -I$(IRSSI)/src \ +-I$(IRSSI)/src/fe-common/core -I$(IRSSI)/src/core -I$(IRSSI)/src/fe-text \ +-I$(IRSSI)/src/irc -I$(IRSSI)/src/irc/core -I$(IRSSI)/src/irc/dcc -I.. \ +`pkg-config glib-2.0 --cflags` + +OBJ = pyscript-object.o base-objects.o window-item-object.o channel-object.o \ +query-object.o server-object.o connect-object.o irc-server-object.o \ +irc-connect-object.o irc-channel-object.o ban-object.o nick-object.o \ +chatnet-object.o reconnect-object.o window-object.o textdest-object.o \ +rawlog-object.o log-object.o logitem-object.o ignore-object.o \ +dcc-object.o factory.o + +pyobjects.a: $(OBJ) + ar r pyobjects.a $(OBJ) + +%.o: %.c + $(CC) -c $< $(CFLAGS) + +clean: + rm -f *.o *.so *.a diff --git a/objects/ban-object.c b/objects/ban-object.c new file mode 100644 index 0000000..908bae2 --- /dev/null +++ b/objects/ban-object.c @@ -0,0 +1,155 @@ +#include +#include "pyirssi_irc.h" +#include "pymodule.h" +#include "ban-object.h" +#include "pycore.h" + +/* monitor "ban remove" signal */ +static void ban_cleanup(CHANNEL_REC *chan, BAN_REC *ban) +{ + PyBan *pyban = signal_get_user_data(); + + if (ban == pyban->data) + { + pyban->data = NULL; + pyban->cleanup_installed = 0; + signal_remove_data("ban remove", ban_cleanup, pyban); + } +} + +static void PyBan_dealloc(PyBan *self) +{ + if (self->cleanup_installed) + signal_remove_data("ban remove", ban_cleanup, self); + + self->ob_type->tp_free((PyObject*)self); +} + +static PyObject *PyBan_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyBan *self; + + self = (PyBan *)type->tp_alloc(type, 0); + if (!self) + return NULL; + + return (PyObject *)self; +} + +PyDoc_STRVAR(PyBan_ban_doc, + "The ban" +); +static PyObject *PyBan_ban_get(PyBan *self, void *closure) +{ + BAN_REC *data = self->data; + RET_NULL_IF_INVALID(self->data); + RET_AS_STRING_OR_NONE(data->ban); +} + +PyDoc_STRVAR(PyBan_setby_doc, + "Nick of who set the ban" +); +static PyObject *PyBan_setby_get(PyBan *self, void *closure) +{ + BAN_REC *data = self->data; + RET_NULL_IF_INVALID(self->data); + RET_AS_STRING_OR_NONE(data->setby); +} + +PyDoc_STRVAR(PyBan_time_doc, + "Timestamp when ban was set" +); +static PyObject *PyBan_time_get(PyBan *self, void *closure) +{ + BAN_REC *data = self->data; + RET_NULL_IF_INVALID(self->data); + return PyLong_FromUnsignedLong(data->time); +} + +/* specialized getters/setters */ +static PyGetSetDef PyBan_getseters[] = { + {"ban", (getter)PyBan_ban_get, NULL, + PyBan_ban_doc, NULL}, + {"setby", (getter)PyBan_setby_get, NULL, + PyBan_setby_doc, NULL}, + {"time", (getter)PyBan_time_get, NULL, + PyBan_time_doc, NULL}, + {NULL} +}; + +/* Methods for object */ +static PyMethodDef PyBan_methods[] = { + {NULL} /* Sentinel */ +}; + +PyTypeObject PyBanType = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "Ban", /*tp_name*/ + sizeof(PyBan), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)PyBan_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + "PyBan objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + PyBan_methods, /* tp_methods */ + 0, /* tp_members */ + PyBan_getseters, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + PyBan_new, /* tp_new */ +}; + + +/* window item wrapper factory function */ +PyObject *pyban_new(void *ban) +{ + PyBan *pyban; + + pyban = py_inst(PyBan, PyBanType); + if (!pyban) + return NULL; + + pyban->data = ban; + pyban->cleanup_installed = 1; + signal_add_last_data("ban remove", ban_cleanup, pyban); + + return (PyObject *)pyban; +} + +int ban_object_init(void) +{ + g_return_val_if_fail(py_module != NULL, 0); + + if (PyType_Ready(&PyBanType) < 0) + return 0; + + Py_INCREF(&PyBanType); + PyModule_AddObject(py_module, "Ban", (PyObject *)&PyBanType); + + return 1; +} diff --git a/objects/ban-object.h b/objects/ban-object.h new file mode 100644 index 0000000..59ec9e7 --- /dev/null +++ b/objects/ban-object.h @@ -0,0 +1,18 @@ +#ifndef _BAN_OBJECT_H_ +#define _BAN_OBJECT_H_ + +#include +#include "base-objects.h" + +typedef struct +{ + PyIrssiFinal_HEAD(void) +} PyBan; + +extern PyTypeObject PyBanType; + +int ban_object_init(void); +PyObject *pyban_new(void *ban); +#define pyban_check(op) PyObject_TypeCheck(op, &PyBanType) + +#endif diff --git a/objects/base-objects.c b/objects/base-objects.c new file mode 100644 index 0000000..3601593 --- /dev/null +++ b/objects/base-objects.c @@ -0,0 +1,251 @@ +#include +#include "structmember.h" +#include "pymodule.h" +#include "base-objects.h" +#include "pyirssi.h" + +/* This is the base type for most, if not all, Irssi objects with a type + id. The user can find the type name, type id, and check if the object is + wrapping a valid Irssi record. */ + +/* member IDs */ +enum +{ + M_BASE_TYPE, + M_BASE_NAME, + M_BASE_VALID, +}; + +static void PyIrssiBase_dealloc(PyIrssiBase *self) +{ + self->ob_type->tp_free((PyObject*)self); +} + +static PyObject * +PyIrssiBase_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyIrssiBase *self; + + self = (PyIrssiBase *)type->tp_alloc(type, 0); + if (!self) + return NULL; + + return (PyObject *)self; +} + +static PyObject *PyIrssiBase_get(PyIrssiBase *self, void *closure) +{ + int member = GPOINTER_TO_INT(closure); + + /* If the user passed the valid member, don't trigger an exception */ + if (member != M_BASE_VALID) + RET_NULL_IF_INVALID(self->data); + + switch (member) + { + case M_BASE_TYPE: + return PyInt_FromLong(self->data->type); + case M_BASE_NAME: + RET_AS_STRING_OR_NONE(self->base_name); + case M_BASE_VALID: + if (self->data != NULL) + Py_RETURN_TRUE; + else + Py_RETURN_FALSE; + } + + INVALID_MEMBER(member); +} + +/* specialized getters/setters */ +static PyGetSetDef PyIrssiBase_getseters[] = { + {"type_id", (getter)PyIrssiBase_get, NULL, + "Irssi's type id for object", + GINT_TO_POINTER(M_BASE_TYPE)}, + + {"type", (getter)PyIrssiBase_get, NULL, + "Irssi's name for object", + GINT_TO_POINTER(M_BASE_NAME)}, + + {"valid", (getter)PyIrssiBase_get, NULL, + "True if the object is valid", + GINT_TO_POINTER(M_BASE_VALID)}, + {NULL} +}; + +/* Methods for object */ +static PyMethodDef PyIrssiBase_methods[] = { + /* {"somemeth", (PyCFunction)PyIrssiBase_name, METH_NOARGS, "docstr"}, */ + {NULL} /* Sentinel */ +}; + +PyTypeObject PyIrssiBaseType = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "IrssiBase", /*tp_name*/ + sizeof(PyIrssiBase), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)PyIrssiBase_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + "PyIrssiBase objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + PyIrssiBase_methods, /* tp_methods */ + 0, /* tp_members */ + PyIrssiBase_getseters, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + PyIrssiBase_new, /* tp_new */ +}; + + +/* IrssiChatBase is a base type for any object with a chat type. The user + can find the chat type string name with the chat_type member or + the type id with the chat_type_id member. It inherits from IrssiBase + so the type, valid, and type_id members are visible to the user, too */ + +/* member IDs */ +enum +{ + M_CHAT_CHAT_TYPE, + M_CHAT_CHAT_NAME, +}; + +static void +PyIrssiChatBase_dealloc(PyIrssiChatBase *self) +{ + self->ob_type->tp_free((PyObject*)self); +} + +static PyObject * +PyIrssiChatBase_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyIrssiChatBase *self; + + self = (PyIrssiChatBase *)type->tp_alloc(type, 0); + if (!self) + return NULL; + + return (PyObject *)self; +} + +static PyObject *PyIrssiChatBase_get(PyIrssiChatBase *self, void *closure) +{ + int member = GPOINTER_TO_INT(closure); + + RET_NULL_IF_INVALID(self->data); + + switch (member) + { + case M_CHAT_CHAT_TYPE: + return PyInt_FromLong(self->data->chat_type); + case M_CHAT_CHAT_NAME: + { + CHAT_PROTOCOL_REC *rec = chat_protocol_find_id(self->data->chat_type); + if (rec) + RET_AS_STRING_OR_NONE(rec->name); + else + Py_RETURN_NONE; + } + } + + INVALID_MEMBER(member); +} + +//specialized getters/setters +static PyGetSetDef PyIrssiChatBase_getseters[] = { + {"chat_type_id", (getter)PyIrssiChatBase_get, NULL, + "Chat Type id", + GINT_TO_POINTER(M_CHAT_CHAT_TYPE)}, + + {"chat_type", (getter)PyIrssiChatBase_get, NULL, + "Chat Name", + GINT_TO_POINTER(M_CHAT_CHAT_NAME)}, + {NULL} +}; + +static PyMethodDef PyIrssiChatBase_methods[] = { + {NULL} /* Sentinel */ +}; + +PyTypeObject PyIrssiChatBaseType = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "IrssiChatBase", /*tp_name*/ + sizeof(PyIrssiChatBase), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)PyIrssiChatBase_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + "PyIrssiChatBase objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + PyIrssiChatBase_methods, /* tp_methods */ + 0, /* tp_members */ + PyIrssiChatBase_getseters, /* tp_getset */ + &PyIrssiBaseType, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + PyIrssiChatBase_new, /* tp_new */ +}; + +int base_objects_init(void) +{ + g_return_val_if_fail(py_module != NULL, 0); + + if (PyType_Ready(&PyIrssiBaseType) < 0) + return 0; + if (PyType_Ready(&PyIrssiChatBaseType) < 0) + return 0; + + Py_INCREF(&PyIrssiBaseType); + Py_INCREF(&PyIrssiChatBaseType); + PyModule_AddObject(py_module, "IrssiBase", (PyObject *)&PyIrssiBaseType); + PyModule_AddObject(py_module, "IrssiChatBase", (PyObject *)&PyIrssiChatBaseType); + + return 1; +} diff --git a/objects/base-objects.h b/objects/base-objects.h new file mode 100644 index 0000000..d2f6acb --- /dev/null +++ b/objects/base-objects.h @@ -0,0 +1,96 @@ +#ifndef _BASE_OBJECTS_H_ +#define _BASE_OBJECTS_H_ + +#include + +/* data is a pointer to the underlying Irssi record */ +/* base_name is the type name of the object returned from the type member + it can be SERVER, CHANNEL, QUERY, etc. Note: base_name isn't freed, so + it cannot point to heap memory */ +/* cleanup_installed: 1 = a cleanup signal handler is installed, 0 = not installed */ +#define PyIrssi_HEAD(type) \ + PyObject_HEAD \ + type *data; \ + const char *base_name; \ + int cleanup_installed; + +/* for uninheritable objects without a type id (Ban, Log, etc) */ +#define PyIrssiFinal_HEAD(type) \ + PyObject_HEAD \ + type *data; \ + int cleanup_installed; + +/* base for classes with a type */ +typedef struct +{ + int type; +} IRSSI_BASE_REC; + +typedef struct +{ + PyIrssi_HEAD(IRSSI_BASE_REC) +} PyIrssiBase; + + +/* base for classes with type and a chat type */ +typedef struct +{ + int type; + int chat_type; +} IRSSI_CHAT_REC; + +typedef struct +{ + PyIrssi_HEAD(IRSSI_CHAT_REC) +} PyIrssiChatBase; + +extern PyTypeObject PyIrssiBaseType; +extern PyTypeObject PyIrssiChatBaseType; + +#define pybase_check(op) PyObject_TypeCheck(op, &PyIrssiBaseType) +#define pychatbase_check(op) PyObject_TypeCheck(op, &PyIrssiChatBaseType) +#define py_instp(tp, to) ((tp *) (to)->tp_alloc(to, 0)) +#define py_inst(tp, to) py_instp(tp, &to) + +int base_objects_init(void); + +#define RET_NULL_IF_INVALID(data) \ + if (data == NULL) \ + return PyErr_Format(PyExc_RuntimeError, "wrapped object is invalid") + +#define RET_N1_IF_INVALID(data) \ +do { \ + if (data == NULL) \ + { \ + PyErr_Format(PyExc_RuntimeError, "wrapped object is invalid"); \ + return -1; \ + } \ +} while (0) + +#define RET_AS_STRING_OR_NONE(str) \ +do { \ + if (str) \ + return PyString_FromString(str); \ + else \ + { \ + Py_INCREF(Py_None); \ + return Py_None; \ + } \ +} while (0) + + +#define RET_AS_STRING_OR_EMPTY(str) return PyString_FromString(str? str : "") + +#define RET_AS_OBJ_OR_NONE(obj) \ +do { \ + PyObject *tmp = obj; \ + if (!tmp) \ + tmp = Py_None; \ + Py_INCREF(tmp); \ + return tmp; \ +} while (0) + +#define INVALID_MEMBER(member) \ + return PyErr_Format(PyExc_RuntimeError, "invalid member id, %d", member) + +#endif diff --git a/objects/channel-object.c b/objects/channel-object.c new file mode 100644 index 0000000..7e3758d --- /dev/null +++ b/objects/channel-object.c @@ -0,0 +1,331 @@ +#include +#include "pyirssi.h" +#include "pymodule.h" +#include "factory.h" +#include "channel-object.h" +#include "pycore.h" + +/* member IDs */ +enum +{ + M_CHANNEL_TOPIC, + M_CHANNEL_TOPIC_BY, + M_CHANNEL_TOPIC_TIME, + M_CHANNEL_NO_MODES, + M_CHANNEL_MODE, + M_CHANNEL_LIMIT, + M_CHANNEL_KEY, + M_CHANNEL_CHANOP, + M_CHANNEL_NAMES_GOT, + M_CHANNEL_WHOLIST, + M_CHANNEL_SYNCED, + M_CHANNEL_JOINED, + M_CHANNEL_LEFT, + M_CHANNEL_KICKED, +}; + +/* monitor "channel destroyed" signal */ +static void chan_cleanup(CHANNEL_REC *chan) +{ + PyChannel *pychan = signal_get_user_data(); + + if (chan == pychan->data) + { + pychan->data = NULL; + pychan->cleanup_installed = 0; + signal_remove_data("channel destroyed", chan_cleanup, pychan); + } +} + +static void PyChannel_dealloc(PyChannel *self) +{ + if (self->cleanup_installed) + signal_remove_data("channel destroyed", chan_cleanup, self); + + self->ob_type->tp_free((PyObject*)self); +} + + +static PyObject *PyChannel_get(PyChannel *self, void *closure) +{ + int member = GPOINTER_TO_INT(closure); + + RET_NULL_IF_INVALID(self->data); + + switch (member) + { + case M_CHANNEL_TOPIC: + RET_AS_STRING_OR_NONE(self->data->topic); + case M_CHANNEL_TOPIC_BY: + RET_AS_STRING_OR_NONE(self->data->topic_by); + case M_CHANNEL_TOPIC_TIME: + return PyLong_FromLong(self->data->topic_time); + case M_CHANNEL_NO_MODES: + return PyBool_FromLong(self->data->no_modes); + case M_CHANNEL_MODE: + RET_AS_STRING_OR_NONE(self->data->mode); + case M_CHANNEL_LIMIT: + return PyInt_FromLong(self->data->limit); + case M_CHANNEL_KEY: + RET_AS_STRING_OR_NONE(self->data->key); + case M_CHANNEL_CHANOP: + return PyBool_FromLong(self->data->chanop); + case M_CHANNEL_NAMES_GOT: + return PyBool_FromLong(self->data->names_got); + case M_CHANNEL_WHOLIST: + return PyBool_FromLong(self->data->wholist); + case M_CHANNEL_SYNCED: + return PyBool_FromLong(self->data->synced); + case M_CHANNEL_JOINED: + return PyBool_FromLong(self->data->joined); + case M_CHANNEL_LEFT: + return PyBool_FromLong(self->data->left); + case M_CHANNEL_KICKED: + return PyBool_FromLong(self->data->kicked); + } + + /* This shouldn't be reached... but... */ + return PyErr_Format(PyExc_RuntimeError, "invalid member id, %d", member); +} + +/* specialized getters/setters */ +static PyGetSetDef PyChannel_getseters[] = { + {"topic", (getter)PyChannel_get, NULL, + "Channel topic", + GINT_TO_POINTER(M_CHANNEL_TOPIC)}, + + {"topic_by", (getter)PyChannel_get, NULL, + "Nick who set the topic", + GINT_TO_POINTER(M_CHANNEL_TOPIC_BY)}, + + {"topic_time", (getter)PyChannel_get, NULL, + "Timestamp when the topic was set", + GINT_TO_POINTER(M_CHANNEL_TOPIC_TIME)}, + + {"no_modes", (getter)PyChannel_get, NULL, + "Channel is modeless", + GINT_TO_POINTER(M_CHANNEL_NO_MODES)}, + + {"mode", (getter)PyChannel_get, NULL, + "Channel mode", + GINT_TO_POINTER(M_CHANNEL_MODE)}, + + {"limit", (getter)PyChannel_get, NULL, + "Max. users in channel (+l mode)", + GINT_TO_POINTER(M_CHANNEL_LIMIT)}, + + {"key", (getter)PyChannel_get, NULL, + "Channel key (password)", + GINT_TO_POINTER(M_CHANNEL_KEY)}, + + {"chanop", (getter)PyChannel_get, NULL, + "You are channel operator", + GINT_TO_POINTER(M_CHANNEL_CHANOP)}, + + {"names_got", (getter)PyChannel_get, NULL, + "/NAMES list has been received", + GINT_TO_POINTER(M_CHANNEL_NAMES_GOT)}, + + {"wholist", (getter)PyChannel_get, NULL, + "/WHO list has been received", + GINT_TO_POINTER(M_CHANNEL_WHOLIST)}, + + {"synced", (getter)PyChannel_get, NULL, + "Channel is fully synchronized", + GINT_TO_POINTER(M_CHANNEL_SYNCED)}, + + {"joined", (getter)PyChannel_get, NULL, + "JOIN event for this channel has been received", + GINT_TO_POINTER(M_CHANNEL_JOINED)}, + + {"left", (getter)PyChannel_get, NULL, + "You just left the channel (for 'channel destroyed' event)", + GINT_TO_POINTER(M_CHANNEL_LEFT)}, + + {"kicked", (getter)PyChannel_get, NULL, + "You was just kicked out of the channel (for 'channel destroyed' event)", + GINT_TO_POINTER(M_CHANNEL_KICKED)}, + + {NULL} +}; + +PyDoc_STRVAR(PyChannel_nicks_doc, + "Return a list of nicks in the channel." +); +static PyObject *PyChannel_nicks(PyChannel *self, PyObject *args) +{ + RET_NULL_IF_INVALID(self->data); + + return py_irssi_chatlist_new(nicklist_getnicks(self->data), 1); +} + +PyDoc_STRVAR(PyChannel_nicks_find_mask_doc, + "Find nick mask from nicklist, wildcards allowed." +); +static PyObject *PyChannel_nicks_find_mask(PyChannel *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"mask", NULL}; + char *mask = ""; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &mask)) + return NULL; + + return py_irssi_chat_new(nicklist_find_mask(self->data, mask), 1); +} + +PyDoc_STRVAR(PyChannel_nick_find_doc, + "Find nick from nicklist." +); +static PyObject *PyChannel_nick_find(PyChannel *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"nick", NULL}; + char *nick = ""; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &nick)) + return NULL; + + return py_irssi_chat_new(nicklist_find(self->data, nick), 1); +} + +PyDoc_STRVAR(PyChannel_nick_remove_doc, + "Remove nick from nicklist." +); +static PyObject *PyChannel_nick_remove(PyChannel *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"nick", NULL}; + PyObject *nick = NULL; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, + &nick)) + return NULL; + + if (!pynick_check(nick)) + return PyErr_Format(PyExc_TypeError, "arg must be nick"); + + nicklist_remove(self->data, ((PyNick*)nick)->data); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyChannel_nick_insert_obj_doc, + "Insert nick object into nicklist." +); +static PyObject *PyChannel_nick_insert_obj(PyChannel *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"nick", NULL}; + PyObject *nick = NULL; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, + &nick)) + return NULL; + + if (!pynick_check(nick)) + return PyErr_Format(PyExc_TypeError, "arg must be nick"); + + nicklist_insert(self->data, ((PyNick*)nick)->data); + + Py_RETURN_NONE; +} + +/* Methods for object */ +static PyMethodDef PyChannel_methods[] = { + {"nicks", (PyCFunction)PyChannel_nicks, METH_NOARGS, + PyChannel_nicks_doc}, + {"nicks_find_mask", (PyCFunction)PyChannel_nicks_find_mask, METH_VARARGS | METH_KEYWORDS, + PyChannel_nicks_find_mask_doc}, + {"nick_find", (PyCFunction)PyChannel_nick_find, METH_VARARGS | METH_KEYWORDS, + PyChannel_nick_find_doc}, + {"nick_remove", (PyCFunction)PyChannel_nick_remove, METH_VARARGS | METH_KEYWORDS, + PyChannel_nick_remove_doc}, + {"nick_insert_obj", (PyCFunction)PyChannel_nick_insert_obj, METH_VARARGS | METH_KEYWORDS, + PyChannel_nick_insert_obj_doc}, + {NULL} /* Sentinel */ +}; + +PyTypeObject PyChannelType = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "Channel", /*tp_name*/ + sizeof(PyChannel), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)PyChannel_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + "PyChannel objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + PyChannel_methods, /* tp_methods */ + 0, /* tp_members */ + PyChannel_getseters, /* tp_getset */ + &PyWindowItemType, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ +}; + + +/* window item wrapper factory function */ +PyObject *pychannel_sub_new(void *chan, const char *name, PyTypeObject *type) +{ + PyObject *pychan; + + pychan = pywindow_item_sub_new(chan, name, type); + if (pychan) + { + PyChannel *pych = (PyChannel *)pychan; + signal_add_last_data("channel destroyed", chan_cleanup, pych); + pych->cleanup_installed = 1; + } + + return pychan; +} + +PyObject *pychannel_new(void *chan) +{ + static const char *name = "CHANNEL"; + return pychannel_sub_new(chan, name, &PyChannelType); +} + +int channel_object_init(void) +{ + g_return_val_if_fail(py_module != NULL, 0); + + if (PyType_Ready(&PyChannelType) < 0) + return 0; + + Py_INCREF(&PyChannelType); + PyModule_AddObject(py_module, "Channel", (PyObject *)&PyChannelType); + + return 1; +} diff --git a/objects/channel-object.h b/objects/channel-object.h new file mode 100644 index 0000000..0858e7b --- /dev/null +++ b/objects/channel-object.h @@ -0,0 +1,22 @@ +#ifndef _CHANNEL_OBJECT_H_ +#define _CHANNEL_OBJECT_H_ + +#include +#include "window-item-object.h" + +/* forward */ +struct _CHANNEL_REC; + +typedef struct +{ + PyWindowItem_HEAD(struct _CHANNEL_REC) +} PyChannel; + +extern PyTypeObject PyChannelType; + +int channel_object_init(void); +PyObject *pychannel_sub_new(void *chan, const char *name, PyTypeObject *type); +PyObject *pychannel_new(void *chan); +#define pychannel_check(op) PyObject_TypeCheck(op, &PyChannelType) + +#endif diff --git a/objects/chatnet-object.c b/objects/chatnet-object.c new file mode 100644 index 0000000..c22f0cb --- /dev/null +++ b/objects/chatnet-object.c @@ -0,0 +1,182 @@ +#include +#include "pymodule.h" +#include "base-objects.h" +#include "chatnet-object.h" +#include "pyirssi.h" +#include "pycore.h" +#include "pyutils.h" + +static void chatnet_cleanup(CHATNET_REC *cn) +{ + PyChatnet *pycn = signal_get_user_data(); + + if (cn == pycn->data) + { + pycn->data = NULL; + pycn->cleanup_installed = 0; + signal_remove_data("chatnet destroyed", chatnet_cleanup, pycn); + } +} + +static void PyChatnet_dealloc(PyChatnet *self) +{ + if (self->cleanup_installed) + signal_remove_data("chatnet destroyed", chatnet_cleanup, self); + + self->ob_type->tp_free((PyObject*)self); +} + +/* Getters */ +PyDoc_STRVAR(PyChatnet_name_doc, + "name of chat network" +); +static PyObject *PyChatnet_name_get(PyChatnet *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_STRING_OR_NONE(self->data->name); +} + +PyDoc_STRVAR(PyChatnet_nick_doc, + "if not empty, nick preferred in this network" +); +static PyObject *PyChatnet_nick_get(PyChatnet *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_STRING_OR_NONE(self->data->nick); +} + +PyDoc_STRVAR(PyChatnet_username_doc, + "if not empty, username preferred in this network" +); +static PyObject *PyChatnet_username_get(PyChatnet *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_STRING_OR_NONE(self->data->username); +} + +PyDoc_STRVAR(PyChatnet_realname_doc, + "if not empty, realname preferred in this network" +); +static PyObject *PyChatnet_realname_get(PyChatnet *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_STRING_OR_NONE(self->data->realname); +} + +PyDoc_STRVAR(PyChatnet_own_host_doc, + "address to use when connecting to this network" +); +static PyObject *PyChatnet_own_host_get(PyChatnet *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_STRING_OR_NONE(self->data->own_host); +} + +PyDoc_STRVAR(PyChatnet_autosendcmd_doc, + "command to send after connecting to this network" +); +static PyObject *PyChatnet_autosendcmd_get(PyChatnet *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_STRING_OR_NONE(self->data->autosendcmd); +} + +/* specialized getters/setters */ +static PyGetSetDef PyChatnet_getseters[] = { + {"name", (getter)PyChatnet_name_get, NULL, + PyChatnet_name_doc, NULL}, + {"nick", (getter)PyChatnet_nick_get, NULL, + PyChatnet_nick_doc, NULL}, + {"username", (getter)PyChatnet_username_get, NULL, + PyChatnet_username_doc, NULL}, + {"realname", (getter)PyChatnet_realname_get, NULL, + PyChatnet_realname_doc, NULL}, + {"own_host", (getter)PyChatnet_own_host_get, NULL, + PyChatnet_own_host_doc, NULL}, + {"autosendcmd", (getter)PyChatnet_autosendcmd_get, NULL, + PyChatnet_autosendcmd_doc, NULL}, + {NULL} +}; + +static PyMethodDef PyChatnet_methods[] = { + {NULL} /* Sentinel */ +}; + +PyTypeObject PyChatnetType = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "Chatnet", /*tp_name*/ + sizeof(PyChatnet), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)PyChatnet_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + "PyChatnet objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + PyChatnet_methods, /* tp_methods */ + 0, /* tp_members */ + PyChatnet_getseters, /* tp_getset */ + &PyIrssiChatBaseType, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ +}; + + +/* chatnet factory function */ +PyObject *pychatnet_sub_new(void *cn, PyTypeObject *subclass) +{ + static const char *name = "CHATNET"; + PyChatnet *pycn = NULL; + + pycn = py_instp(PyChatnet, subclass); + if (!pycn) + return NULL; + + pycn->data = cn; + pycn->base_name = name; + signal_add_last_data("chatnet destroyed", chatnet_cleanup, pycn); + pycn->cleanup_installed = 1; + + return (PyObject *)pycn; +} + +PyObject *pychatnet_new(void *cn) +{ + return pychatnet_sub_new(cn, &PyChatnetType); +} + +int chatnet_object_init(void) +{ + g_return_val_if_fail(py_module != NULL, 0); + + if (PyType_Ready(&PyChatnetType) < 0) + return 0; + + Py_INCREF(&PyChatnetType); + PyModule_AddObject(py_module, "Chatnet", (PyObject *)&PyChatnetType); + + return 1; +} diff --git a/objects/chatnet-object.h b/objects/chatnet-object.h new file mode 100644 index 0000000..8a6d1fc --- /dev/null +++ b/objects/chatnet-object.h @@ -0,0 +1,22 @@ +#ifndef _CHATNET_OBJECT_H_ +#define _CHATNET_OBJECT_H_ + +#include +#include "base-objects.h" + +/* forward */ +struct _CHATNET_REC; + +typedef struct +{ + PyIrssi_HEAD(struct _CHATNET_REC) +} PyChatnet; + +extern PyTypeObject PyChatnetType; + +int chatnet_object_init(void); +PyObject *pychatnet_sub_new(void *cn, PyTypeObject *subclass); +PyObject *pychatnet_new(void *cn); +#define pychatnet_check(op) PyObject_TypeCheck(op, &PyChatnetType) + +#endif diff --git a/objects/connect-object.c b/objects/connect-object.c new file mode 100644 index 0000000..2db0e13 --- /dev/null +++ b/objects/connect-object.c @@ -0,0 +1,187 @@ +#include +#include "pymodule.h" +#include "base-objects.h" +#include "connect-object.h" +#include "pyirssi.h" +#include "pycore.h" +#include "pyutils.h" + +/* member IDs */ +enum +{ + M_CONNECT_ADDRESS, + M_CONNECT_PORT, + M_CONNECT_CHATNET, + M_CONNECT_PASSWORD, + M_CONNECT_WANTED_NICK, + M_CONNECT_USERNAME, + M_CONNECT_REALNAME, +}; + +static void connect_cleanup(SERVER_CONNECT_REC *connect) +{ + PyConnect *pyconn = signal_get_user_data(); + + /* + if (server == pyconn->data) + { + pyserver->data = NULL; + pyserver->cleanup_installed = 0; + signal_remove_data("server disconnected", connect_cleanup, pyserver); + } + */ +} + +static void PyConnect_dealloc(PyConnect *self) +{ + if (self->cleanup_installed) + signal_remove_data("server disconnected", connect_cleanup, self); + + self->ob_type->tp_free((PyObject*)self); +} + +static PyObject *PyConnect_get(PyConnect *self, void *closure) +{ + int member = GPOINTER_TO_INT(closure); + + RET_NULL_IF_INVALID(self->data); + + switch (member) + { + case M_CONNECT_ADDRESS: + RET_AS_STRING_OR_NONE(self->data->address); + case M_CONNECT_PORT: + return PyInt_FromLong(self->data->port); + case M_CONNECT_CHATNET: + RET_AS_STRING_OR_NONE(self->data->chatnet); + case M_CONNECT_PASSWORD: + RET_AS_STRING_OR_NONE(self->data->password); + case M_CONNECT_WANTED_NICK: + RET_AS_STRING_OR_NONE(self->data->nick); + case M_CONNECT_USERNAME: + RET_AS_STRING_OR_NONE(self->data->username); + case M_CONNECT_REALNAME: + RET_AS_STRING_OR_NONE(self->data->realname); + } + + /* This shouldn't be reached... but... */ + return PyErr_Format(PyExc_RuntimeError, "invalid member id, %d", member); +} + +static PyGetSetDef PyConnect_getseters[] = { + {"address", (getter)PyConnect_get, NULL, + "Address where we connected (irc.blah.org)", + GINT_TO_POINTER(M_CONNECT_ADDRESS)}, + + {"port", (getter)PyConnect_get, NULL, + "Port where we connected", + GINT_TO_POINTER(M_CONNECT_PORT)}, + + {"chatnet", (getter)PyConnect_get, NULL, + "Chat network", + GINT_TO_POINTER(M_CONNECT_CHATNET)}, + + {"password", (getter)PyConnect_get, NULL, + "Password we used in connection.", + GINT_TO_POINTER(M_CONNECT_PASSWORD)}, + + {"wanted_nick", (getter)PyConnect_get, NULL, + "Nick which we would prefer to use", + GINT_TO_POINTER(M_CONNECT_WANTED_NICK)}, + + {"username", (getter)PyConnect_get, NULL, + "User name", + GINT_TO_POINTER(M_CONNECT_USERNAME)}, + + {"realname", (getter)PyConnect_get, NULL, + "Real name", + GINT_TO_POINTER(M_CONNECT_REALNAME)}, + + {NULL} +}; + +PyTypeObject PyConnectType = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "Connect", /*tp_name*/ + sizeof(PyConnect), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)PyConnect_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + "PyConnect objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + PyConnect_getseters, /* tp_getset */ + &PyIrssiChatBaseType, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ +}; + + +/* server connect factory function (managed == 0, don't do signal cleanup, 1 == do sig cleanup */ +PyObject *pyconnect_sub_new(void *connect, PyTypeObject *subclass, int managed) +{ + static const char *CONNECT_TYPE = "SERVER CONNECT"; + PyConnect *pyconn = NULL; + + g_return_val_if_fail(connect != NULL, NULL); + + pyconn = py_instp(PyConnect, subclass); + if (!pyconn) + return NULL; + + pyconn->base_name = CONNECT_TYPE; + pyconn->data = connect; + + if (managed) + { + //XXX: how to handle cleanup? + //signal_add_last_data("server disconnected", connect_cleanup, pyconn); + //pyconn->cleanup_installed = 1; + } + + return (PyObject *)pyconn; +} + +PyObject *pyconnect_new(void *connect, int managed) +{ + return pyconnect_sub_new(connect, &PyConnectType, managed); +} + +int connect_object_init(void) +{ + g_return_val_if_fail(py_module != NULL, 0); + + if (PyType_Ready(&PyConnectType) < 0) + return 0; + + Py_INCREF(&PyConnectType); + PyModule_AddObject(py_module, "Connect", (PyObject *)&PyConnectType); + + return 1; +} diff --git a/objects/connect-object.h b/objects/connect-object.h new file mode 100644 index 0000000..53bd509 --- /dev/null +++ b/objects/connect-object.h @@ -0,0 +1,22 @@ +#ifndef _CONNECT_OBJECT_H_ +#define _CONNECT_OBJECT_H_ + +#include +#include "base-objects.h" + +/* forward */ +struct _SERVER_CONNECT_REC; + +typedef struct +{ + PyIrssi_HEAD(struct _SERVER_CONNECT_REC) +} PyConnect; + +extern PyTypeObject PyConnectType; + +int connect_object_init(void); +PyObject *pyconnect_sub_new(void *connect, PyTypeObject *subtype, int managed); +PyObject *pyconnect_new(void *connect, int managed); +#define pyconnect_check(op) PyObject_TypeCheck(op, &PyConnectType) + +#endif diff --git a/objects/dcc-object.c b/objects/dcc-object.c new file mode 100644 index 0000000..772c011 --- /dev/null +++ b/objects/dcc-object.c @@ -0,0 +1,321 @@ +#include +#include "pyirssi_irc.h" +#include "pymodule.h" +#include "dcc-object.h" +#include "factory.h" +#include "pycore.h" + +/* monitor "dcc destroyed signal" */ +static void dcc_cleanup(DCC_REC *dcc) +{ + PyDcc *pydcc = signal_get_user_data(); + + if (dcc == pydcc->data) + { + pydcc->data = NULL; + pydcc->cleanup_installed = 0; + signal_remove_data("dcc destroyed", dcc_cleanup, pydcc); + } +} + +static void PyDcc_dealloc(PyDcc *self) +{ + if (self->cleanup_installed) + signal_remove_data("dcc destroyed", dcc_cleanup, self); + + Py_XDECREF(self->server); + Py_XDECREF(self->chat); + + self->ob_type->tp_free((PyObject*)self); +} + +/* Getters */ +PyDoc_STRVAR(PyDcc_orig_type_doc, + "Original DCC type that was sent to us - same as type except GET and SEND are swapped" +); +static PyObject *PyDcc_orig_type_get(PyDcc *self, void *closure) +{ + DCC_REC *data = self->data; + const char *type; + + RET_NULL_IF_INVALID(data); + + type = module_find_id_str("DCC", data->orig_type); + RET_AS_STRING_OR_NONE(type); +} + +PyDoc_STRVAR(PyDcc_created_doc, + "Time stamp when the DCC record was created" +); +static PyObject *PyDcc_created_get(PyDcc *self, void *closure) +{ + DCC_REC *data = self->data; + + RET_NULL_IF_INVALID(data); + return PyLong_FromUnsignedLong(data->created); +} + +PyDoc_STRVAR(PyDcc_server_doc, + "Server record where the DCC was initiated." +); +static PyObject *PyDcc_server_get(PyDcc *self, void *closure) +{ + DCC_REC *data = self->data; + + RET_NULL_IF_INVALID(data); + RET_AS_OBJ_OR_NONE(self->server); +} + +PyDoc_STRVAR(PyDcc_servertag_doc, + "Tag of the server where the DCC was initiated." +); +static PyObject *PyDcc_servertag_get(PyDcc *self, void *closure) +{ + DCC_REC *data = self->data; + + RET_NULL_IF_INVALID(data); + RET_AS_STRING_OR_NONE(data->servertag); +} + +PyDoc_STRVAR(PyDcc_mynick_doc, + "Our nick to use in DCC chat." +); +static PyObject *PyDcc_mynick_get(PyDcc *self, void *closure) +{ + DCC_REC *data = self->data; + + RET_NULL_IF_INVALID(data); + RET_AS_STRING_OR_NONE(data->mynick); +} + +PyDoc_STRVAR(PyDcc_nick_doc, + "Other side's nick name." +); +static PyObject *PyDcc_nick_get(PyDcc *self, void *closure) +{ + DCC_REC *data = self->data; + + RET_NULL_IF_INVALID(data); + RET_AS_STRING_OR_NONE(data->nick); +} + +PyDoc_STRVAR(PyDcc_chat_doc, + "Dcc chat record if the request came through DCC chat" +); +static PyObject *PyDcc_chat_get(PyDcc *self, void *closure) +{ + DCC_REC *data = self->data; + + RET_NULL_IF_INVALID(data); + RET_AS_OBJ_OR_NONE(self->chat); +} + +PyDoc_STRVAR(PyDcc_target_doc, + "Who the request was sent to - your nick, channel or empty if you sent the request" +); +static PyObject *PyDcc_target_get(PyDcc *self, void *closure) +{ + DCC_REC *data = self->data; + + RET_NULL_IF_INVALID(data); + RET_AS_STRING_OR_NONE(data->target); +} + +PyDoc_STRVAR(PyDcc_arg_doc, + "Given argument .. file name usually" +); +static PyObject *PyDcc_arg_get(PyDcc *self, void *closure) +{ + DCC_REC *data = self->data; + + RET_NULL_IF_INVALID(data); + RET_AS_STRING_OR_NONE(data->arg); +} + +PyDoc_STRVAR(PyDcc_addr_doc, + "Other side's IP address." +); +static PyObject *PyDcc_addr_get(PyDcc *self, void *closure) +{ + DCC_REC *data = self->data; + + RET_NULL_IF_INVALID(data); + RET_AS_STRING_OR_NONE(data->addrstr); +} + +PyDoc_STRVAR(PyDcc_port_doc, + "Port we're connecting in." +); +static PyObject *PyDcc_port_get(PyDcc *self, void *closure) +{ + DCC_REC *data = self->data; + + RET_NULL_IF_INVALID(data); + return PyInt_FromLong(data->port); +} + +PyDoc_STRVAR(PyDcc_starttime_doc, + "Unix time stamp when the DCC transfer was started" +); +static PyObject *PyDcc_starttime_get(PyDcc *self, void *closure) +{ + DCC_REC *data = self->data; + + RET_NULL_IF_INVALID(data); + return PyLong_FromUnsignedLong(data->starttime); +} + +PyDoc_STRVAR(PyDcc_transfd_doc, + "Bytes transferred" +); +static PyObject *PyDcc_transfd_get(PyDcc *self, void *closure) +{ + DCC_REC *data = self->data; + + RET_NULL_IF_INVALID(data); + return PyLong_FromUnsignedLong(data->transfd); +} + +/* specialized getters/setters */ +static PyGetSetDef PyDcc_getseters[] = { + {"orig_type", (getter)PyDcc_orig_type_get, NULL, + PyDcc_orig_type_doc, NULL}, + {"created", (getter)PyDcc_created_get, NULL, + PyDcc_created_doc, NULL}, + {"server", (getter)PyDcc_server_get, NULL, + PyDcc_server_doc, NULL}, + {"servertag", (getter)PyDcc_servertag_get, NULL, + PyDcc_servertag_doc, NULL}, + {"mynick", (getter)PyDcc_mynick_get, NULL, + PyDcc_mynick_doc, NULL}, + {"nick", (getter)PyDcc_nick_get, NULL, + PyDcc_nick_doc, NULL}, + {"chat", (getter)PyDcc_chat_get, NULL, + PyDcc_chat_doc, NULL}, + {"target", (getter)PyDcc_target_get, NULL, + PyDcc_target_doc, NULL}, + {"arg", (getter)PyDcc_arg_get, NULL, + PyDcc_arg_doc, NULL}, + {"addr", (getter)PyDcc_addr_get, NULL, + PyDcc_addr_doc, NULL}, + {"port", (getter)PyDcc_port_get, NULL, + PyDcc_port_doc, NULL}, + {"starttime", (getter)PyDcc_starttime_get, NULL, + PyDcc_starttime_doc, NULL}, + {"transfd", (getter)PyDcc_transfd_get, NULL, + PyDcc_transfd_doc, NULL}, + {NULL} +}; + +/* Methods */ +PyDoc_STRVAR(PyDcc_destroy_doc, + "Destroy DCC connection" +); +static PyObject *PyDcc_destroy(PyDcc *self, PyObject *args) +{ + RET_NULL_IF_INVALID(self->data); + + dcc_destroy(DCC(self->data)); + + Py_RETURN_NONE; +} + +/* Methods for object */ +static PyMethodDef PyDcc_methods[] = { + {"destroy", (PyCFunction)PyDcc_destroy, METH_NOARGS, + PyDcc_destroy_doc}, + {NULL} /* Sentinel */ +}; + +PyTypeObject PyDccType = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "Dcc", /*tp_name*/ + sizeof(PyDcc), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)PyDcc_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + "PyDcc objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + PyDcc_methods, /* tp_methods */ + 0, /* tp_members */ + PyDcc_getseters, /* tp_getset */ + &PyIrssiBaseType, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ +}; + + +/* Dcc factory function */ +PyObject *pydcc_sub_new(void *dcc, const char *name, PyTypeObject *subclass) +{ + PyObject *chat = NULL, *server = NULL; + PyDcc *pydcc; + DCC_REC *rec = dcc; + + server = py_irssi_chat_new(rec->server, 1); + if (!server) + return NULL; + + //XXX: do dcc chat + + pydcc = py_instp(PyDcc, subclass); + if (!pydcc) + { + Py_XDECREF(server); + Py_XDECREF(chat); + return NULL; + } + + pydcc->data = dcc; + pydcc->server = server; + pydcc->base_name = name; + + pydcc->cleanup_installed = 1; + signal_add_last_data("dcc destroyed", dcc_cleanup, pydcc); + + return (PyObject *)pydcc; +} + +PyObject *pydcc_new(void *dcc) +{ + static const char *name = "DCC"; + return pydcc_sub_new(dcc, name, &PyDccType); +} + +int dcc_object_init(void) +{ + g_return_val_if_fail(py_module != NULL, 0); + + if (PyType_Ready(&PyDccType) < 0) + return 0; + + Py_INCREF(&PyDccType); + PyModule_AddObject(py_module, "Dcc", (PyObject *)&PyDccType); + + return 1; +} diff --git a/objects/dcc-object.h b/objects/dcc-object.h new file mode 100644 index 0000000..d44d3db --- /dev/null +++ b/objects/dcc-object.h @@ -0,0 +1,24 @@ +#ifndef _DCC_OBJECT_H_ +#define _DCC_OBJECT_H_ + +#include +#include "base-objects.h" + +#define PyDcc_HEAD(type) \ + PyIrssi_HEAD(type) \ + PyObject *server; \ + PyObject *chat; + +typedef struct +{ + PyDcc_HEAD(void) +} PyDcc; + +extern PyTypeObject PyDccType; + +PyObject *pydcc_sub_new(void *dcc, const char *name, PyTypeObject *subclass); +PyObject *pydcc_new(void *dcc); +#define pydcc_check(op) PyObject_TypeCheck(op, &PyDccType) +int dcc_object_init(void); + +#endif diff --git a/objects/factory.c b/objects/factory.c new file mode 100644 index 0000000..bf78688 --- /dev/null +++ b/objects/factory.c @@ -0,0 +1,291 @@ +#include +#include "pyirssi.h" +#include "factory.h" + +/* Irssi object factory works for all items with at least a type member. + * + * Use py_irssi_new() or py_irssi_chat_new() to get a new wrapper for an + * IrssiObject or an IrssiChatObject, respectively. + * + * For objects not descending from IrssiObject or IrssiChatObject, you must + * use the object-specific init function directly. + */ + +#define MAKEKEY(type, chat) ((chat << 16 ) | type) +#define GETTYPE(key) (key & 0xffff) +#define GETCHAT(key) ((key >> 16) & 0xffff) + + +GHashTable *init_map = NULL; + +static int init_objects(void); +static void register_chat(CHAT_PROTOCOL_REC *rec); +static void unregister_chat(CHAT_PROTOCOL_REC *rec); +static void insert_map(int type, int chat_type, InitFunc func); +static int remove_chat(void *key, void *value, void *chat_typep); +static void register_nonchat(void); +static InitFunc find_map(int type, int chat_type); + +static int init_objects(void) +{ + if (!pyscript_init()) + return 0; + + /* order is somewhat important here */ + if (!base_objects_init()) + return 0; + + if (!window_item_object_init()) + return 0; + + if (!channel_object_init()) + return 0; + + if (!query_object_init()) + return 0; + + if (!server_object_init()) + return 0; + + if (!connect_object_init()) + return 0; + + if (!irc_server_object_init()) + return 0; + + if (!irc_connect_object_init()) + return 0; + + if (!irc_channel_object_init()) + return 0; + + if (!ban_object_init()) + return 0; + + if (!nick_object_init()) + return 0; + + if (!chatnet_object_init()) + return 0; + + if (!reconnect_object_init()) + return 0; + + if (!window_object_init()) + return 0; + + if (!textdest_object_init()) + return 0; + + if (!rawlog_object_init()) + return 0; + + if (!log_object_init()) + return 0; + + if (!logitem_object_init()) + return 0; + + if (!ignore_object_init()) + return 0; + + if (!dcc_object_init()) + return 0; + + return 1; +} + +static InitFunc find_map(int type, int chat_type) +{ + unsigned hash; + + g_return_val_if_fail(type <= 0xffff, NULL); + g_return_val_if_fail(chat_type <= 0xffff, NULL); + + hash = MAKEKEY(type, chat_type); + return g_hash_table_lookup(init_map, GUINT_TO_POINTER(hash)); +} + +static void insert_map(int type, int chat_type, InitFunc func) +{ + unsigned hash; + + g_return_if_fail(type <= 0xffff); + g_return_if_fail(chat_type <= 0xffff); + + hash = MAKEKEY(type, chat_type); + g_hash_table_insert(init_map, GUINT_TO_POINTER(hash), func); +} + +static void register_chat(CHAT_PROTOCOL_REC *rec) +{ + int type; + int chat_type; + int is_irc = 0; + + /* chat_type == rec->id ??? */ + chat_type = chat_protocol_lookup(rec->name); + g_return_if_fail(chat_type >= 0 && chat_type < 0xffff); + + if (!g_strcasecmp(rec->name, "IRC")) + is_irc = 1; + + type = module_get_uniq_id("SERVER", 0); + if (is_irc) + insert_map(type, chat_type, (InitFunc)pyirc_server_new); + else + insert_map(type, chat_type, (InitFunc)pyserver_new); + + type = module_get_uniq_id("SERVER CONNECT", 0); + if (is_irc) + insert_map(type, chat_type, (InitFunc)pyirc_connect_new); + else + insert_map(type, chat_type, (InitFunc)pyconnect_new); + + type = module_get_uniq_id_str("WINDOW ITEM TYPE", "CHANNEL"); + if (is_irc) + insert_map(type, chat_type, (InitFunc)pyirc_channel_new); + else + insert_map(type, chat_type, (InitFunc)pychannel_new); + + type = module_get_uniq_id_str("WINDOW ITEM TYPE", "QUERY"); + insert_map(type, chat_type, (InitFunc)pyquery_new); + + type = module_get_uniq_id("CHATNET", 0); + insert_map(type, chat_type, (InitFunc)pychatnet_new); + + type = module_get_uniq_id("NICK", 0); + insert_map(type, chat_type, (InitFunc)pynick_new); +} + +/* register funcs for objects without a chat type */ +static void register_nonchat(void) +{ + int type; + int chat_type = 0xffff; + + //FIXME: specify init funcs + type = module_get_uniq_id_str("DCC", "CHAT"); + insert_map(type, chat_type, (InitFunc)pydcc_new); + + type = module_get_uniq_id_str("DCC", "GET"); + insert_map(type, chat_type, (InitFunc)pydcc_new); + + type = module_get_uniq_id_str("DCC", "SEND"); + insert_map(type, chat_type, (InitFunc)pydcc_new); + + type = module_get_uniq_id_str("DCC", "SERVER"); + insert_map(type, chat_type, (InitFunc)pydcc_new); +} + +static int remove_chat(void *key, void *value, void *chat_typep) +{ + unsigned hash = GPOINTER_TO_UINT(key); + int chat_type = GPOINTER_TO_INT(chat_type); + + if (GETCHAT(hash) == chat_type) + return TRUE; + + return FALSE; +} + +/* remove all items matching chat_type */ +static void unregister_chat(CHAT_PROTOCOL_REC *rec) +{ + /*int chat_type = chat_protocol_lookup(rec->name);*/ + g_hash_table_foreach_remove(init_map, + (GHRFunc)remove_chat, + GINT_TO_POINTER(rec->id)); +} + +PyObject *py_irssi_new(void *typeobj, int managed) +{ + IRSSI_BASE_REC *base = typeobj; + InitFunc ifunc; + + if (!base) + Py_RETURN_NONE; + + ifunc = find_map(base->type, 0xffff); + + if (ifunc) + return ifunc(typeobj, managed); + + return PyErr_Format(PyExc_RuntimeError, "no initfunc for object type %d", base->type); +} + +PyObject *py_irssi_chat_new(void *typeobj, int managed) +{ + IRSSI_CHAT_REC *chat = typeobj; + InitFunc ifunc; + + if (!chat) + Py_RETURN_NONE; + + ifunc = find_map(chat->type, chat->chat_type); + + if (ifunc) + return ifunc(typeobj, managed); + + return PyErr_Format(PyExc_RuntimeError, "no initfunc for object type %d, chat_type %d", + chat->type, chat->chat_type); +} + +PyObject *py_irssi_objlist_new(GSList *node, int managed, InitFunc init) +{ + PyObject *list = NULL; + + list = PyList_New(0); + if (!list) + goto error; + + for (; node != NULL; node = node->next) + { + int ret; + PyObject *obj = init(node->data, managed); + + if (!obj) + goto error; + + ret = PyList_Append(list, obj); + Py_DECREF(obj); + + if (ret != 0) + goto error; + } + + return list; + +error: + Py_XDECREF(list); + return NULL; +} + +int factory_init(void) +{ + g_return_val_if_fail(init_map == NULL, 0); + + if (!init_objects()) + return 0; + + init_map = g_hash_table_new(g_direct_hash, g_direct_equal); + g_slist_foreach(chat_protocols, (GFunc) register_chat, NULL); + register_nonchat(); + + signal_add("chat protocol created", (SIGNAL_FUNC) register_chat); + signal_add("chat protocol destroyed", (SIGNAL_FUNC) unregister_chat); + + return 1; +} + +void factory_deinit(void) +{ + g_return_if_fail(init_map != NULL); + + g_hash_table_destroy(init_map); + init_map = NULL; + + signal_remove("chat protocol created", (SIGNAL_FUNC) register_chat); + signal_remove("chat protocol destroyed", (SIGNAL_FUNC) unregister_chat); +} + diff --git a/objects/factory.h b/objects/factory.h new file mode 100644 index 0000000..1171003 --- /dev/null +++ b/objects/factory.h @@ -0,0 +1,46 @@ +#ifndef _FACTORY_H_ +#define _FACTORY_H_ + +#include +#include "pyscript-object.h" +#include "base-objects.h" +#include "window-item-object.h" +#include "channel-object.h" +#include "query-object.h" +#include "server-object.h" +#include "connect-object.h" +#include "irc-server-object.h" +#include "irc-connect-object.h" +#include "irc-channel-object.h" +#include "ban-object.h" +#include "nick-object.h" +#include "chatnet-object.h" +#include "reconnect-object.h" +#include "window-object.h" +#include "textdest-object.h" +#include "rawlog-object.h" +#include "log-object.h" +#include "logitem-object.h" +#include "ignore-object.h" +#include "dcc-object.h" + +int factory_init(void); +void factory_deinit(void); + +/*managed == 1: object invalidates itself + *managed == 0: caller responsible for invalidating object + *XXX: most objects invalidate themselves, ignoring "managed" switch, + * and some are never managed (Reconnect) + */ + +/* For objects with a type member but no chat_type */ +PyObject *py_irssi_new(void *typeobj, int managed); +/* For objects with both type and chat_type members */ +PyObject *py_irssi_chat_new(void *typeobj, int managed); + +typedef PyObject *(*InitFunc)(void *, int); +PyObject *py_irssi_objlist_new(GSList *node, int managed, InitFunc init); +#define py_irssi_chatlist_new(n, m) py_irssi_objlist_new(n, m, py_irssi_chat_new) +#define py_irssi_list_new(n, m) py_irssi_objlist_new(n, m, py_irssi_new) + +#endif diff --git a/objects/ignore-object.c b/objects/ignore-object.c new file mode 100644 index 0000000..7c6073c --- /dev/null +++ b/objects/ignore-object.c @@ -0,0 +1,275 @@ +#include +#include "pyirssi_irc.h" +#include "pymodule.h" +#include "ignore-object.h" +#include "factory.h" +#include "pycore.h" + +/* monitor "ignore destroy" signal */ +static void ignore_cleanup(IGNORE_REC *ignore) +{ + PyIgnore *pyignore = signal_get_user_data(); + + if (ignore == pyignore->data) + { + pyignore->data = NULL; + pyignore->cleanup_installed = 0; + signal_remove_data("ignore destroy", ignore_cleanup, pyignore); + } +} + +static void PyIgnore_dealloc(PyIgnore *self) +{ + if (self->cleanup_installed) + signal_remove_data("ignore destroy", ignore_cleanup, self); + + self->ob_type->tp_free((PyObject*)self); +} + +static PyObject *PyIgnore_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyIgnore *self; + + self = (PyIgnore *)type->tp_alloc(type, 0); + if (!self) + return NULL; + + return (PyObject *)self; +} + +/* Getters */ +PyDoc_STRVAR(PyIgnore_mask_doc, + "Ignore mask" +); +static PyObject *PyIgnore_mask_get(PyIgnore *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_STRING_OR_NONE(self->data->mask); +} + +PyDoc_STRVAR(PyIgnore_servertag_doc, + "Ignore only in server" +); +static PyObject *PyIgnore_servertag_get(PyIgnore *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_STRING_OR_NONE(self->data->servertag); +} + +PyDoc_STRVAR(PyIgnore_pattern_doc, + "Ignore text patern" +); +static PyObject *PyIgnore_pattern_get(PyIgnore *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_STRING_OR_NONE(self->data->pattern); +} + +PyDoc_STRVAR(PyIgnore_level_doc, + "Ignore level" +); +static PyObject *PyIgnore_level_get(PyIgnore *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyInt_FromLong(self->data->level); +} + +PyDoc_STRVAR(PyIgnore_exception_doc, + "This is an exception ignore" +); +static PyObject *PyIgnore_exception_get(PyIgnore *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyBool_FromLong(self->data->exception); +} + +PyDoc_STRVAR(PyIgnore_regexp_doc, + "Regexp pattern matching" +); +static PyObject *PyIgnore_regexp_get(PyIgnore *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyBool_FromLong(self->data->regexp); +} + +PyDoc_STRVAR(PyIgnore_fullword_doc, + "Pattern matches only full words" +); +static PyObject *PyIgnore_fullword_get(PyIgnore *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyBool_FromLong(self->data->fullword); +} + +PyDoc_STRVAR(PyIgnore_replies_doc, + "Ignore replies to nick in channel" +); +static PyObject *PyIgnore_replies_get(PyIgnore *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyBool_FromLong(self->data->replies); +} + +/* specialized getters/setters */ +static PyGetSetDef PyIgnore_getseters[] = { + {"mask", (getter)PyIgnore_mask_get, NULL, + PyIgnore_mask_doc, NULL}, + {"servertag", (getter)PyIgnore_servertag_get, NULL, + PyIgnore_servertag_doc, NULL}, + {"pattern", (getter)PyIgnore_pattern_get, NULL, + PyIgnore_pattern_doc, NULL}, + {"level", (getter)PyIgnore_level_get, NULL, + PyIgnore_level_doc, NULL}, + {"exception", (getter)PyIgnore_exception_get, NULL, + PyIgnore_exception_doc, NULL}, + {"regexp", (getter)PyIgnore_regexp_get, NULL, + PyIgnore_regexp_doc, NULL}, + {"fullword", (getter)PyIgnore_fullword_get, NULL, + PyIgnore_fullword_doc, NULL}, + {"replies", (getter)PyIgnore_replies_get, NULL, + PyIgnore_replies_doc, NULL}, + {NULL} +}; + +PyDoc_STRVAR(PyIgnore_channels_doc, + "Ignore only in channels (list of names)" +); +static PyObject *PyIgnore_channels(PyIgnore *self, PyObject *args) +{ + char **p; + PyObject *list; + + RET_NULL_IF_INVALID(self->data); + + list = PyList_New(0); + if (!list) + return NULL; + + for (p = self->data->channels; *p; p++) + { + int ret; + PyObject *str = PyString_FromString(*p); + + if (!str) + goto error; + + ret = PyList_Append(list, str); + Py_DECREF(str); + if (ret != 0) + goto error; + } + + return list; + +error: + Py_XDECREF(list); + return NULL; +} + +/* Methods */ +PyDoc_STRVAR(PyIgnore_add_rec_doc, + "Add ignore record" +); +static PyObject *PyIgnore_add_rec(PyIgnore *self, PyObject *args) +{ + RET_NULL_IF_INVALID(self->data); + + ignore_add_rec(self->data); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyIgnore_update_rec_doc, + "Update ignore record in configuration" +); +static PyObject *PyIgnore_update_rec(PyIgnore *self, PyObject *args) +{ + RET_NULL_IF_INVALID(self->data); + + ignore_update_rec(self->data); + + Py_RETURN_NONE; +} + +/* Methods for object */ +static PyMethodDef PyIgnore_methods[] = { + {"add_rec", (PyCFunction)PyIgnore_add_rec, METH_NOARGS, + PyIgnore_add_rec_doc}, + {"update_rec", (PyCFunction)PyIgnore_update_rec, METH_NOARGS, + PyIgnore_update_rec_doc}, + {"channels", (PyCFunction)PyIgnore_channels, METH_NOARGS, + PyIgnore_channels_doc}, + {NULL} /* Sentinel */ +}; + +PyTypeObject PyIgnoreType = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "Ignore", /*tp_name*/ + sizeof(PyIgnore), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)PyIgnore_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + "PyIgnore objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + PyIgnore_methods, /* tp_methods */ + 0, /* tp_members */ + PyIgnore_getseters, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + PyIgnore_new, /* tp_new */ +}; + + +/* ignore factory function */ +PyObject *pyignore_new(void *ignore) +{ + PyIgnore *pyignore; + + pyignore = py_inst(PyIgnore, PyIgnoreType); + if (!pyignore) + return NULL; + + pyignore->data = ignore; + pyignore->cleanup_installed = 1; + signal_add_last_data("ignore destroy", ignore_cleanup, pyignore); + + return (PyObject *)pyignore; +} + +int ignore_object_init(void) +{ + g_return_val_if_fail(py_module != NULL, 0); + + if (PyType_Ready(&PyIgnoreType) < 0) + return 0; + + Py_INCREF(&PyIgnoreType); + PyModule_AddObject(py_module, "Ignore", (PyObject *)&PyIgnoreType); + + return 1; +} diff --git a/objects/ignore-object.h b/objects/ignore-object.h new file mode 100644 index 0000000..39f7425 --- /dev/null +++ b/objects/ignore-object.h @@ -0,0 +1,21 @@ +#ifndef _IGNORE_OBJECT_H_ +#define _IGNORE_OBJECT_H_ + +#include +#include "base-objects.h" + +/* forward */ +struct _IGNORE_REC; + +typedef struct +{ + PyIrssiFinal_HEAD(struct _IGNORE_REC) +} PyIgnore; + +extern PyTypeObject PyIgnoreType; + +int ignore_object_init(void); +PyObject *pyignore_new(void *ignore); +#define pyignore_check(op) PyObject_TypeCheck(op, &PyIgnoreType) + +#endif diff --git a/objects/irc-channel-object.c b/objects/irc-channel-object.c new file mode 100644 index 0000000..daf3329 --- /dev/null +++ b/objects/irc-channel-object.c @@ -0,0 +1,166 @@ +#include +#include "pyirssi_irc.h" +#include "pymodule.h" +#include "pycore.h" +#include "irc-channel-object.h" +#include "factory.h" + +/* PyIrcChannel destructor is inherited from PyChannel */ + +/* specialized getters/setters */ +static PyGetSetDef PyIrcChannel_getseters[] = { + {NULL} +}; + +PyDoc_STRVAR(bans_doc, + "Returns a list of bans in the channel." +); +static PyObject *PyIrcChannel_bans(PyIrcChannel *self, PyObject *args) +{ + return py_irssi_objlist_new(self->data->banlist, 1, (InitFunc)pyban_new); +} + +PyDoc_STRVAR(ban_get_mask_doc, + "Get ban mask for 'nick'." +); +static PyObject *PyIrcChannel_ban_get_mask(PyIrcChannel *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"nick", "ban_type", NULL}; + char *nick, *str; + int ban_type = 0; + PyObject *ret; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|i", kwlist, &nick, &ban_type)) + return NULL; + + str = ban_get_mask(self->data, nick, ban_type); + if (!str) + Py_RETURN_NONE; + + ret = PyString_FromString(str); + g_free(str); + + return ret; +} + +PyDoc_STRVAR(banlist_add_doc, + "Add a new ban to channel. Return None if duplicate." +); +static PyObject *PyIrcChannel_banlist_add(PyIrcChannel *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"ban", "nick", "time", NULL}; + char *ban, *nick; + time_t btime; + BAN_REC *newban; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "ssk", kwlist, &ban, &nick, &btime)) + return NULL; + + newban = banlist_add(self->data, ban, nick, btime); + /* XXX: return none or throw error? */ + if (!newban) + Py_RETURN_NONE; + + return pyban_new(newban); +} + +PyDoc_STRVAR(banlist_remove_doc, + "Remove a new ban from channel." +); +static PyObject *PyIrcChannel_banlist_remove(PyIrcChannel *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"ban", "nick", NULL}; + char *ban, *nick; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "ss", kwlist, &ban, &nick)) + return NULL; + + banlist_remove(self->data, ban, nick); + Py_RETURN_NONE; +} + +/* Methods for object */ +static PyMethodDef PyIrcChannel_methods[] = { + {"bans", (PyCFunction)PyIrcChannel_bans, METH_NOARGS, + bans_doc}, + + {"ban_get_mask", (PyCFunction)PyIrcChannel_ban_get_mask, METH_VARARGS | METH_KEYWORDS, + ban_get_mask_doc}, + + {"banlist_add", (PyCFunction)PyIrcChannel_banlist_add, METH_VARARGS | METH_KEYWORDS, + banlist_add_doc}, + + {"banlist_remove", (PyCFunction)PyIrcChannel_banlist_remove, METH_VARARGS | METH_KEYWORDS, + banlist_remove_doc}, + + {NULL} /* Sentinel */ +}; + +PyTypeObject PyIrcChannelType = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "IrcChannel", /*tp_name*/ + sizeof(PyIrcChannel), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + 0, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + "PyIrcChannel objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + PyIrcChannel_methods, /* tp_methods */ + 0, /* tp_members */ + PyIrcChannel_getseters, /* tp_getset */ + &PyChannelType, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ +}; + + +/* irc channel factory function */ +PyObject *pyirc_channel_new(void *chan) +{ + static const char *BASE_NAME = "CHANNEL"; + return pychannel_sub_new(chan, BASE_NAME, &PyIrcChannelType); +} + +int irc_channel_object_init(void) +{ + g_return_val_if_fail(py_module != NULL, 0); + + if (PyType_Ready(&PyIrcChannelType) < 0) + return 0; + + Py_INCREF(&PyIrcChannelType); + PyModule_AddObject(py_module, "IrcChannel", (PyObject *)&PyIrcChannelType); + + return 1; +} diff --git a/objects/irc-channel-object.h b/objects/irc-channel-object.h new file mode 100644 index 0000000..a1d845a --- /dev/null +++ b/objects/irc-channel-object.h @@ -0,0 +1,21 @@ +#ifndef _IRC_CHANNEL_OBJECT_H_ +#define _IRC_CHANNEL_OBJECT_H_ + +#include +#include "window-item-object.h" + +/* forward */ +struct _IRC_CHANNEL_REC; + +typedef struct +{ + PyWindowItem_HEAD(struct _IRC_CHANNEL_REC) +} PyIrcChannel; + +extern PyTypeObject PyIrcChannelType; + +int irc_channel_object_init(void); +PyObject *pyirc_channel_new(void *chan); +#define pyirc_channel_check(op) PyObject_TypeCheck(op, &PyIrcChannelType) + +#endif diff --git a/objects/irc-connect-object.c b/objects/irc-connect-object.c new file mode 100644 index 0000000..6e2d3a7 --- /dev/null +++ b/objects/irc-connect-object.c @@ -0,0 +1,99 @@ +#include +#include "pymodule.h" +#include "base-objects.h" +#include "irc-connect-object.h" +#include "pyirssi_irc.h" +#include "pycore.h" +#include "pyutils.h" + +/* member IDs */ +enum +{ + M_IRC_CONNECT_ALTERNATE_NICK, +}; + +/* cleanup and deallocation handled by Connect base */ + +static PyObject *PyIrcConnect_get(PyIrcConnect *self, void *closure) +{ + int member = GPOINTER_TO_INT(closure); + + RET_NULL_IF_INVALID(self->data); + + switch (member) + { + case M_IRC_CONNECT_ALTERNATE_NICK: + RET_AS_STRING_OR_NONE(self->data->alternate_nick); + } + + /* This shouldn't be reached... but... */ + return PyErr_Format(PyExc_RuntimeError, "invalid member id, %d", member); +} + +static PyGetSetDef PyIrcConnect_getseters[] = { + {"alternate_nick", (getter)PyIrcConnect_get, NULL, + "Alternate nick to use if default nick is taken", + GINT_TO_POINTER(M_IRC_CONNECT_ALTERNATE_NICK)}, + + {NULL} +}; + +PyTypeObject PyIrcConnectType = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "IrcConnect", /*tp_name*/ + sizeof(PyIrcConnect), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + 0, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + "PyIrcConnect objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + PyIrcConnect_getseters, /* tp_getset */ + &PyConnectType, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ +}; + +PyObject *pyirc_connect_new(void *connect, int managed) +{ + return pyconnect_sub_new(connect, &PyIrcConnectType, managed); +} + +int irc_connect_object_init(void) +{ + g_return_val_if_fail(py_module != NULL, 0); + + if (PyType_Ready(&PyIrcConnectType) < 0) + return 0; + + Py_INCREF(&PyIrcConnectType); + PyModule_AddObject(py_module, "IrcConnect", (PyObject *)&PyIrcConnectType); + + return 1; +} diff --git a/objects/irc-connect-object.h b/objects/irc-connect-object.h new file mode 100644 index 0000000..3f6cad4 --- /dev/null +++ b/objects/irc-connect-object.h @@ -0,0 +1,21 @@ +#ifndef _IRC_CONNECT_OBJECT_H_ +#define _IRC_CONNECT_OBJECT_H_ + +#include +#include "connect-object.h" + +/* forward */ +struct _IRC_SERVER_CONNECT_REC; + +typedef struct +{ + PyIrssi_HEAD(struct _IRC_SERVER_CONNECT_REC) +} PyIrcConnect; + +extern PyTypeObject PyIrcConnectType; + +int irc_connect_object_init(void); +PyObject *pyirc_connect_new(void *connect, int managed); +#define pyirc_connect_check(op) PyObject_TypeCheck(op, &PyIrcConnectType) + +#endif diff --git a/objects/irc-server-object.c b/objects/irc-server-object.c new file mode 100644 index 0000000..0e0ccdc --- /dev/null +++ b/objects/irc-server-object.c @@ -0,0 +1,265 @@ +#include +#include "pymodule.h" +#include "base-objects.h" +#include "server-object.h" +#include "irc-server-object.h" +#include "irc-connect-object.h" +#include "pyirssi_irc.h" +#include "pycore.h" +#include "pyutils.h" + +/* member IDs */ +enum +{ + M_IRC_SERVER_REAL_ADDRESS, + M_IRC_SERVER_USERMODE, + M_IRC_SERVER_USERHOST, +}; + +/* cleanup and dealloc inherited from base Server */ + +static PyObject *PyIrcServer_get(PyIrcServer *self, void *closure) +{ + int member = GPOINTER_TO_INT(closure); + + RET_NULL_IF_INVALID(self->data); + + switch (member) + { + case M_IRC_SERVER_REAL_ADDRESS: + RET_AS_STRING_OR_NONE(self->data->real_address); + case M_IRC_SERVER_USERMODE: + RET_AS_STRING_OR_NONE(self->data->usermode); + case M_IRC_SERVER_USERHOST: + RET_AS_STRING_OR_NONE(self->data->userhost); + } + + /* This shouldn't be reached... but... */ + return PyErr_Format(PyExc_RuntimeError, "invalid member id, %d", member); +} + +static PyGetSetDef PyIrcServer_getseters[] = { + {"real_address", (getter)PyIrcServer_get, NULL, + "Address the IRC server gives", + GINT_TO_POINTER(M_IRC_SERVER_REAL_ADDRESS)}, + + {"usermode", (getter)PyIrcServer_get, NULL, + "User mode in server", + GINT_TO_POINTER(M_IRC_SERVER_USERMODE)}, + + {"userhost", (getter)PyIrcServer_get, NULL, + "Your user host in server", + GINT_TO_POINTER(M_IRC_SERVER_USERHOST)}, + + {NULL} +}; + +PyDoc_STRVAR(get_channels_doc, + "Return a string of all channels (and keys, if any have them) in server,\n" + "like '#a,#b,#c,#d x,b_chan_key,x,x' or just '#e,#f,#g'\n" +); +static PyObject *PyIrcServer_get_channels(PyIrcServer *self, PyObject *args) +{ + char *list; + PyObject *ret; + + RET_NULL_IF_INVALID(self->data); + + list = irc_server_get_channels(self->data); + ret = PyString_FromString(list); + g_free(list); + + return ret; +} + +PyDoc_STRVAR(send_raw_doc, + "Send raw message to server, it will be flood protected so you\n" + "don't need to worry about it.\n" +); +static PyObject *PyIrcServer_send_raw(PyIrcServer *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"cmd", NULL}; + char *cmd; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, &cmd)) + return NULL; + + irc_send_cmd(self->data, cmd); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(send_raw_now_doc, + "Send raw message to server immediately without flood protection.\n" +); +static PyObject *PyIrcServer_send_raw_now(PyIrcServer *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"cmd", NULL}; + char *cmd; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, &cmd)) + return NULL; + + irc_send_cmd_now(self->data, cmd); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(send_raw_split_doc, + "Split the `cmd' into several commands so `nickarg' argument has only\n" + "`max_nicks' number of nicks.\n" + "\n" + "Example:\n" + "server.send_raw_split('KICK #channel nick1,nick2,nick3 :byebye', 3, 2)\n" + "\n" + "Irssi will send commands 'KICK #channel nick1,nick2 :byebye' and\n" + "'KICK #channel nick3 :byebye' to server.\n" +); +static PyObject *PyIrcServer_send_raw_split(PyIrcServer *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"cmd", "nickarg", "max_nicks", NULL}; + char *cmd; + int nickarg; + int max_nicks; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "sii", kwlist, &cmd, &nickarg, &max_nicks)) + return NULL; + + irc_send_cmd_split(self->data, cmd, nickarg, max_nicks); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(ctcp_send_reply_doc, + "Send CTCP reply. This will be 'CTCP flood protected' so if there's too\n" + "many CTCP requests in buffer, this reply might not get sent. The data\n" + "is the full raw command to be sent to server, like\n" + "'NOTICE nick :\001VERSION irssi\001'\n" +); +static PyObject *PyIrcServer_ctcp_send_reply(PyIrcServer *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"data", NULL}; + char *data; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, &data)) + return NULL; + + ctcp_send_reply(self->data, data); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(isupport_doc, + "Returns the value of the named item in the ISUPPORT (005) numeric to the\n" + "script. If the item is not present returns undef, if the item has no value\n" + "then '' is returned use defined server.isupport('name') if you need to\n" + "check whether a property is present.\n" + "See http://www.ietf.org/internet-drafts/draft-brocklesby-irc-isupport-01.txt\n" + "for more information on the ISUPPORT numeric.\n" +); +static PyObject *PyIrcServer_isupport(PyIrcServer *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"name", NULL}; + char *name; + char *found; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, &name)) + return NULL; + + found = g_hash_table_lookup(self->data->isupport, name); + + RET_AS_STRING_OR_NONE(found); +} + +/* Methods for object */ +static PyMethodDef PyIrcServer_methods[] = { + {"get_channels", (PyCFunction)PyIrcServer_get_channels, METH_NOARGS, + get_channels_doc}, + + {"send_raw", (PyCFunction)PyIrcServer_send_raw, METH_VARARGS | METH_KEYWORDS, + send_raw_doc}, + + {"send_raw_now", (PyCFunction)PyIrcServer_send_raw_now, METH_VARARGS | METH_KEYWORDS, + send_raw_now_doc}, + + {"send_raw_split", (PyCFunction)PyIrcServer_send_raw_split, METH_VARARGS | METH_KEYWORDS, + send_raw_split_doc}, + + {"ctcp_send_reply", (PyCFunction)PyIrcServer_ctcp_send_reply, METH_VARARGS | METH_KEYWORDS, + ctcp_send_reply_doc}, + + {"isupport", (PyCFunction)PyIrcServer_isupport, METH_VARARGS | METH_KEYWORDS, + isupport_doc}, + + {NULL} /* Sentinel */ +}; + +PyTypeObject PyIrcServerType = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "IrcServer", /*tp_name*/ + sizeof(PyIrcServer), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + 0, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + "PyIrcServer objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + PyIrcServer_methods, /* tp_methods */ + 0, /* tp_members */ + PyIrcServer_getseters, /* tp_getset */ + &PyServerType, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ +}; + +PyObject *pyirc_server_new(void *server) +{ + return pyserver_sub_new(server, &PyIrcServerType); +} + +int irc_server_object_init(void) +{ + g_return_val_if_fail(py_module != NULL, 0); + + if (PyType_Ready(&PyIrcServerType) < 0) + return 0; + + Py_INCREF(&PyIrcServerType); + PyModule_AddObject(py_module, "IrcServer", (PyObject *)&PyIrcServerType); + + return 1; +} diff --git a/objects/irc-server-object.h b/objects/irc-server-object.h new file mode 100644 index 0000000..b3eb76a --- /dev/null +++ b/objects/irc-server-object.h @@ -0,0 +1,21 @@ +#ifndef _IRC_SERVER_OBJECT_H_ +#define _IRC_SERVER_OBJECT_H_ + +#include +#include "server-object.h" + +/* forward */ +struct _IRC_SERVER_REC; + +typedef struct +{ + PyServer_HEAD(struct _IRC_SERVER_REC) +} PyIrcServer; + +extern PyTypeObject PyIrcServerType; + +int irc_server_object_init(void); +PyObject *pyirc_server_new(void *server); +#define pyirc_server_check(op) PyObject_TypeCheck(op, &PyIrcServerType) + +#endif diff --git a/objects/log-object.c b/objects/log-object.c new file mode 100644 index 0000000..fae9cd4 --- /dev/null +++ b/objects/log-object.c @@ -0,0 +1,465 @@ +#include +#include "pyirssi_irc.h" +#include "pymodule.h" +#include "log-object.h" +#include "factory.h" +#include "pycore.h" + +static LOG_ITEM_REC *find_item(LOG_REC *log, PyLogitem *item); +static void log_cleanup(LOG_REC *log); +static int logtype(int *type, int target, int window); + +/* find/convert a py log item */ +static LOG_ITEM_REC *find_item(LOG_REC *log, PyLogitem *item) +{ + int type; + char *name; + char *servertag = NULL; + + if (!item->type || !item->name) + return NULL; + + type = PyInt_AS_LONG(item->type); + name = PyString_AS_STRING(item->name); + if (item->servertag) + servertag = PyString_AS_STRING(item->servertag); + + return log_item_find(log, type, name, servertag); +} + +/* monitor "log remove" signal */ +static void log_cleanup(LOG_REC *log) +{ + PyLog *pylog = signal_get_user_data(); + + if (log == pylog->data) + { + pylog->data = NULL; + pylog->cleanup_installed = 0; + signal_remove_data("log remove", log_cleanup, pylog); + } +} + +static void PyLog_dealloc(PyLog *self) +{ + if (self->cleanup_installed) + signal_remove_data("log remove", log_cleanup, self); + + if (self->data && !g_slist_find(logs, self->data)) + { + printtext(NULL, NULL, MSGLEVEL_CRAP, "destroying orphan log %s", self->data->fname); + log_close(self->data); + } + + self->ob_type->tp_free((PyObject*)self); +} + +static PyObject *PyLog_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyLog *self; + + self = (PyLog *)type->tp_alloc(type, 0); + if (!self) + return NULL; + + return (PyObject *)self; +} + +/* function to create the log */ +PyDoc_STRVAR(PyLog_doc, + "__init__(fname, level=MSGLEVEL_ALL)\n" + "\n" + "Create a log\n" +); +static int PyLog_init(PyLog *self, PyObject *args, PyObject *kwds) +{ + char *fname; + int level = MSGLEVEL_ALL; + LOG_REC *log; + + static char *kwlist[] = {"fname", "level", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|i", kwlist, + &fname, &level)) + return -1; + + /*XXX: anything better than RuntimeError ? */ + if (self->data || self->cleanup_installed) + { + PyErr_Format(PyExc_RuntimeError, "log already opened; close it first"); + return -1; + } + + log = log_create_rec(fname, level); + if (!log) + { + PyErr_Format(PyExc_RuntimeError, "failed to create log"); + return -1; + } + + self->data = log; + self->cleanup_installed = 1; + signal_add_last_data("log remove", log_cleanup, self); + + return 0; +} + +/* Getters */ +PyDoc_STRVAR(PyLog_fname_doc, + "Log file name" +); +static PyObject *PyLog_fname_get(PyLog *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_STRING_OR_NONE(self->data->fname); +} + +PyDoc_STRVAR(PyLog_real_fname_doc, + "The actual opened log file (after %d.%m.Y etc. are expanded)" +); +static PyObject *PyLog_real_fname_get(PyLog *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_STRING_OR_NONE(self->data->real_fname); +} + +PyDoc_STRVAR(PyLog_opened_doc, + "Log file is open" +); +static PyObject *PyLog_opened_get(PyLog *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyLong_FromUnsignedLong(self->data->opened); +} + +PyDoc_STRVAR(PyLog_level_doc, + "Log only these levels" +); +static PyObject *PyLog_level_get(PyLog *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyInt_FromLong(self->data->level); +} + +PyDoc_STRVAR(PyLog_last_doc, + "Timestamp when last message was written" +); +static PyObject *PyLog_last_get(PyLog *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyLong_FromUnsignedLong(self->data->last); +} + +PyDoc_STRVAR(PyLog_autoopen_doc, + "Automatically open log at startup" +); +static PyObject *PyLog_autoopen_get(PyLog *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyBool_FromLong(self->data->autoopen); +} + +PyDoc_STRVAR(PyLog_failed_doc, + "Opening log failed last time" +); +static PyObject *PyLog_failed_get(PyLog *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyBool_FromLong(self->data->failed); +} + +PyDoc_STRVAR(PyLog_temp_doc, + "Log isn't saved to config file" +); +static PyObject *PyLog_temp_get(PyLog *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyBool_FromLong(self->data->temp); +} + +/* specialized getters/setters */ +static PyGetSetDef PyLog_getseters[] = { + {"fname", (getter)PyLog_fname_get, NULL, + PyLog_fname_doc, NULL}, + {"real_fname", (getter)PyLog_real_fname_get, NULL, + PyLog_real_fname_doc, NULL}, + {"opened", (getter)PyLog_opened_get, NULL, + PyLog_opened_doc, NULL}, + {"level", (getter)PyLog_level_get, NULL, + PyLog_level_doc, NULL}, + {"last", (getter)PyLog_last_get, NULL, + PyLog_last_doc, NULL}, + {"autoopen", (getter)PyLog_autoopen_get, NULL, + PyLog_autoopen_doc, NULL}, + {"failed", (getter)PyLog_failed_get, NULL, + PyLog_failed_doc, NULL}, + {"temp", (getter)PyLog_temp_get, NULL, + PyLog_temp_doc, NULL}, + {NULL} +}; + +/* Methods */ +PyDoc_STRVAR(PyLog_items_doc, + "Return a list of log items" +); +static PyObject *PyLog_items(PyLog *self, PyObject *args) +{ + RET_NULL_IF_INVALID(self->data); + return py_irssi_objlist_new(self->data->items, 1, (InitFunc)pylogitem_new); +} + +PyDoc_STRVAR(PyLog_update_doc, + "Add log to list of logs / save changes to config file." +); +static PyObject *PyLog_update(PyLog *self, PyObject *args) +{ + RET_NULL_IF_INVALID(self->data); + + log_update(self->data); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyLog_close_doc, + "Destroy the log file" +); +static PyObject *PyLog_close(PyLog *self, PyObject *args) +{ + RET_NULL_IF_INVALID(self->data); + + log_close(self->data); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyLog_start_logging_doc, + "Open log file and start logging." +); +static PyObject *PyLog_start_logging(PyLog *self, PyObject *args) +{ + RET_NULL_IF_INVALID(self->data); + + log_start_logging(self->data); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyLog_stop_logging_doc, + "Stop and close the log file." +); +static PyObject *PyLog_stop_logging(PyLog *self, PyObject *args) +{ + RET_NULL_IF_INVALID(self->data); + + log_stop_logging(self->data); + + Py_RETURN_NONE; +} + +static int logtype(int *type, int target, int window) +{ + if (target || window) + { + if (target && window) + { + PyErr_SetString(PyExc_TypeError, "must specify target or window, not both"); + return 0; + } + + *type = target? 0 : 1; + } + else if (*type < 0) + { + PyErr_SetString(PyExc_TypeError, "must specify type, target, or window"); + return 0; + } + + return 1; +} + +PyDoc_STRVAR(PyLog_item_add_doc, + "item_add(item, servertag=None, type=0, target=False, window=False) -> None\n" + "\n" + "Add a log item to log.\n" + "\n" + "Add a target item (nick, chan): \n" + " item_add('#linux', target=True)\n" + " item_add('#linux', type=0)\n" + "\n" + "Add a window ref: \n" + " item_add('2', window=True)\n" + " item_add('2', type=1)\n" +); +static PyObject *PyLog_item_add(PyLog *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"item", "servertag", "type", "target", "window", NULL}; + char *item = ""; + char *servertag = NULL; + int type = 0; + int target = 0; + int window = 0; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|ziii", kwlist, + &item, &servertag, &type, &target, &window)) + return NULL; + + if (!logtype(&type, target, window)) + return NULL; + + log_item_add(self->data, type, item, servertag); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyLog_item_destroy_doc, + "Remove log item from log." +); +static PyObject *PyLog_item_destroy(PyLog *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"item", NULL}; + PyObject *item = NULL; + LOG_ITEM_REC *li; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, + &item)) + return NULL; + + if (!pylogitem_check(item)) + return PyErr_Format(PyExc_TypeError, "arg 1 should be log item"); + + li = find_item(self->data, (PyLogitem *)item); + if (!li) + return PyErr_Format(PyExc_TypeError, "log item invalid or not found"); + + log_item_destroy(self->data, li); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyLog_item_find_doc, + "item_find(item, servertag=None, type=-1, target=False, window=False) -> item or None\n" + "\n" + "Find item from log.\n" +); +static PyObject *PyLog_item_find(PyLog *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"item", "servertag", "type", "target", "window", NULL}; + char *item = ""; + char *server = NULL; + int type = 0; + int target = 0; + int window = 0; + LOG_ITEM_REC *li; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|ziii", kwlist, + &item, &server, &type, &target, &window)) + return NULL; + + if (!logtype(&type, target, window)) + return NULL; + + li = log_item_find(self->data, type, item, server); + if (li) + return pylogitem_new(li); + + Py_RETURN_NONE; +} + +/* Methods for object */ +static PyMethodDef PyLog_methods[] = { + {"items", (PyCFunction)PyLog_items, METH_NOARGS, + PyLog_items_doc}, + {"update", (PyCFunction)PyLog_update, METH_NOARGS, + PyLog_update_doc}, + {"close", (PyCFunction)PyLog_close, METH_NOARGS, + PyLog_close_doc}, + {"start_logging", (PyCFunction)PyLog_start_logging, METH_NOARGS, + PyLog_start_logging_doc}, + {"stop_logging", (PyCFunction)PyLog_stop_logging, METH_NOARGS, + PyLog_stop_logging_doc}, + {"item_add", (PyCFunction)PyLog_item_add, METH_VARARGS | METH_KEYWORDS, + PyLog_item_add_doc}, + {"item_destroy", (PyCFunction)PyLog_item_destroy, METH_VARARGS | METH_KEYWORDS, + PyLog_item_destroy_doc}, + {"item_find", (PyCFunction)PyLog_item_find, METH_VARARGS | METH_KEYWORDS, + PyLog_item_find_doc}, + {NULL} /* Sentinel */ +}; + +PyTypeObject PyLogType = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "Log", /*tp_name*/ + sizeof(PyLog), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)PyLog_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + PyLog_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + PyLog_methods, /* tp_methods */ + 0, /* tp_members */ + PyLog_getseters, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)PyLog_init, /* tp_init */ + 0, /* tp_alloc */ + PyLog_new, /* tp_new */ +}; + + +/* window item wrapper factory function */ +PyObject *pylog_new(void *log) +{ + PyLog *pylog; + + pylog = (PyLog *)PyLogType.tp_alloc(&PyLogType, 0); + if (!pylog) + return NULL; + + pylog->data = log; + pylog->cleanup_installed = 1; + signal_add_last_data("log remove", log_cleanup, pylog); + + return (PyObject *)pylog; +} + +int log_object_init(void) +{ + g_return_val_if_fail(py_module != NULL, 0); + + if (PyType_Ready(&PyLogType) < 0) + return 0; + + Py_INCREF(&PyLogType); + PyModule_AddObject(py_module, "Log", (PyObject *)&PyLogType); + + return 1; +} diff --git a/objects/log-object.h b/objects/log-object.h new file mode 100644 index 0000000..9893bc5 --- /dev/null +++ b/objects/log-object.h @@ -0,0 +1,21 @@ +#ifndef _LOG_OBJECT_H_ +#define _LOG_OBJECT_H_ + +#include +#include "base-objects.h" + +/* forward */ +struct _LOG_REC; + +typedef struct +{ + PyIrssiFinal_HEAD(struct _LOG_REC) +} PyLog; + +extern PyTypeObject PyLogType; + +int log_object_init(void); +PyObject *pylog_new(void *log); +#define pylog_check(op) PyObject_TypeCheck(op, &PyLogType) + +#endif diff --git a/objects/logitem-object.c b/objects/logitem-object.c new file mode 100644 index 0000000..4dc2275 --- /dev/null +++ b/objects/logitem-object.c @@ -0,0 +1,156 @@ +#include +#include "pyirssi_irc.h" +#include "pymodule.h" +#include "logitem-object.h" +#include "pycore.h" + +/* no special cleanup -- value copy is made */ + +static void PyLogitem_dealloc(PyLogitem *self) +{ + Py_XDECREF(self->type); + Py_XDECREF(self->name); + Py_XDECREF(self->servertag); + + self->ob_type->tp_free((PyObject*)self); +} + +static PyObject *PyLogitem_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyLogitem *self; + + self = (PyLogitem *)type->tp_alloc(type, 0); + if (!self) + return NULL; + + return (PyObject *)self; +} + +/* Getters */ +PyDoc_STRVAR(PyLogitem_type_doc, + "0=target, 1=window refnum" +); +static PyObject *PyLogitem_type_get(PyLogitem *self, void *closure) +{ + RET_AS_OBJ_OR_NONE(self->type); +} + +PyDoc_STRVAR(PyLogitem_name_doc, + "Name" +); +static PyObject *PyLogitem_name_get(PyLogitem *self, void *closure) +{ + RET_AS_OBJ_OR_NONE(self->name); +} + +PyDoc_STRVAR(PyLogitem_servertag_doc, + "Server tag" +); +static PyObject *PyLogitem_servertag_get(PyLogitem *self, void *closure) +{ + RET_AS_OBJ_OR_NONE(self->servertag); +} + +/* specialized getters/setters */ +static PyGetSetDef PyLogitem_getseters[] = { + {"type", (getter)PyLogitem_type_get, NULL, + PyLogitem_type_doc, NULL}, + {"name", (getter)PyLogitem_name_get, NULL, + PyLogitem_name_doc, NULL}, + {"servertag", (getter)PyLogitem_servertag_get, NULL, + PyLogitem_servertag_doc, NULL}, + {NULL} +}; + +/* Methods for object */ +static PyMethodDef PyLogitem_methods[] = { + {NULL} /* Sentinel */ +}; + +PyTypeObject PyLogitemType = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "Logitem", /*tp_name*/ + sizeof(PyLogitem), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)PyLogitem_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + "PyLogitem objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + PyLogitem_methods, /* tp_methods */ + 0, /* tp_members */ + PyLogitem_getseters, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + PyLogitem_new, /* tp_new */ +}; + + +/* log item factory function */ +PyObject *pylogitem_new(void *log) +{ + LOG_ITEM_REC *li = log; + PyLogitem *pylog = NULL; + + pylog = py_inst(PyLogitem, PyLogitemType); + if (!pylog) + return NULL; + + pylog->type = PyInt_FromLong(li->type); + if (!pylog->type) + goto error; + + pylog->name = PyString_FromString(li->name); + if (!pylog->name) + goto error; + + if (li->servertag) + { + pylog->servertag = PyString_FromString(li->servertag); + if (!pylog->servertag) + goto error; + } + + return (PyObject *)pylog; + +error: + Py_XDECREF(pylog); + return NULL; +} + +int logitem_object_init(void) +{ + g_return_val_if_fail(py_module != NULL, 0); + + if (PyType_Ready(&PyLogitemType) < 0) + return 0; + + Py_INCREF(&PyLogitemType); + PyModule_AddObject(py_module, "Logitem", (PyObject *)&PyLogitemType); + + return 1; +} diff --git a/objects/logitem-object.h b/objects/logitem-object.h new file mode 100644 index 0000000..b7fd588 --- /dev/null +++ b/objects/logitem-object.h @@ -0,0 +1,21 @@ +#ifndef _LOG_ITEM_OBJECT_H_ +#define _LOG_ITEM_OBJECT_H_ + +#include +#include "base-objects.h" + +typedef struct +{ + PyObject_HEAD + PyObject *type; + PyObject *name; + PyObject *servertag; +} PyLogitem; + +extern PyTypeObject PyLogitemType; + +int logitem_object_init(void); +PyObject *pylogitem_new(void *log); +#define pylogitem_check(op) PyObject_TypeCheck(op, &PyLogitemType) + +#endif diff --git a/objects/nick-object.c b/objects/nick-object.c new file mode 100644 index 0000000..7ad4a65 --- /dev/null +++ b/objects/nick-object.c @@ -0,0 +1,216 @@ +#include +#include "pymodule.h" +#include "base-objects.h" +#include "nick-object.h" +#include "pyirssi.h" +#include "pycore.h" +#include "pyutils.h" + +/* member IDs */ +enum +{ + M_NICK_NICK, + M_NICK_HOST, + M_NICK_REALNAME, + M_NICK_HOPS, + M_NICK_GONE, + M_NICK_SERVEROP, + M_NICK_OP, + M_NICK_VOICE, + M_NICK_HALFOP, + M_NICK_LAST_CHECK, + M_NICK_SEND_MASSJOIN, +}; + +static void nick_cleanup(CHANNEL_REC *chan, NICK_REC *nick) +{ + PyNick *pynick = signal_get_user_data(); + + if (nick == pynick->data) + { + pynick->data = NULL; + pynick->cleanup_installed = 0; + signal_remove_data("nicklist remove", nick_cleanup, pynick); + } +} + +static void PyNick_dealloc(PyNick *self) +{ + if (self->cleanup_installed) + signal_remove_data("nicklist remove", nick_cleanup, self); + + self->ob_type->tp_free((PyObject*)self); +} + +static PyObject *PyNick_get(PyNick *self, void *closure) +{ + int member = GPOINTER_TO_INT(closure); + + RET_NULL_IF_INVALID(self->data); + + switch (member) + { + case M_NICK_NICK: + RET_AS_STRING_OR_NONE(self->data->nick); + case M_NICK_HOST: + RET_AS_STRING_OR_NONE(self->data->host); + case M_NICK_REALNAME: + RET_AS_STRING_OR_NONE(self->data->realname); + case M_NICK_HOPS: + return PyInt_FromLong(self->data->hops); + case M_NICK_GONE: + return PyBool_FromLong(self->data->gone); + case M_NICK_SERVEROP: + return PyBool_FromLong(self->data->serverop); + case M_NICK_OP: + return PyBool_FromLong(self->data->op); + case M_NICK_VOICE: + return PyBool_FromLong(self->data->voice); + case M_NICK_HALFOP: + return PyBool_FromLong(self->data->halfop); + case M_NICK_LAST_CHECK: + return PyLong_FromUnsignedLong(self->data->last_check); + } + + INVALID_MEMBER(member); +} + +PyDoc_STRVAR(PyNick_send_massjoin_doc, + "Waiting to be sent in a 'massjoin' signal, True or False" +); +static PyObject *PyNick_send_massjoin_get(PyNick *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyBool_FromLong(self->data->send_massjoin); +} + + + +/* specialized getters/setters */ +static PyGetSetDef PyNick_getseters[] = { + {"nick", (getter)PyNick_get, NULL, + "Plain nick", + GINT_TO_POINTER(M_NICK_NICK)}, + + {"host", (getter)PyNick_get, NULL, + "Host address", + GINT_TO_POINTER(M_NICK_HOST)}, + + {"realname", (getter)PyNick_get, NULL, + "Real name", + GINT_TO_POINTER(M_NICK_REALNAME)}, + + {"hops", (getter)PyNick_get, NULL, + "Hop count to the server the nick is using", + GINT_TO_POINTER(M_NICK_HOPS)}, + + {"gone", (getter)PyNick_get, NULL, + "User status", + GINT_TO_POINTER(M_NICK_GONE)}, + + {"serverop", (getter)PyNick_get, NULL, + "User status", + GINT_TO_POINTER(M_NICK_SERVEROP)}, + + {"op", (getter)PyNick_get, NULL, + "User status", + GINT_TO_POINTER(M_NICK_OP)}, + + {"voice", (getter)PyNick_get, NULL, + "Channel status", + GINT_TO_POINTER(M_NICK_VOICE)}, + + {"halfop", (getter)PyNick_get, NULL, + "Channel status", + GINT_TO_POINTER(M_NICK_HALFOP)}, + + {"last_check", (getter)PyNick_get, NULL, + "timestamp when last checked gone/ircop status.", + GINT_TO_POINTER(M_NICK_LAST_CHECK)}, + {"send_massjoin", (getter)PyNick_send_massjoin_get, NULL, + PyNick_send_massjoin_doc, NULL}, + {NULL} +}; + +static PyMethodDef PyNick_methods[] = { + {NULL} /* Sentinel */ +}; + +PyTypeObject PyNickType = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "Nick", /*tp_name*/ + sizeof(PyNick), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)PyNick_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + "PyNick objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + PyNick_methods, /* tp_methods */ + 0, /* tp_members */ + PyNick_getseters, /* tp_getset */ + &PyIrssiChatBaseType, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ +}; + + +/* nick factory function */ +PyObject *pynick_sub_new(void *nick, PyTypeObject *subclass) +{ + static const char *name = "NICK"; + PyNick *pynick = NULL; + + pynick = py_instp(PyNick, subclass); + if (!pynick) + return NULL; + + pynick->data = nick; + pynick->base_name = name; + signal_add_last_data("nicklist remove", nick_cleanup, pynick); + pynick->cleanup_installed = 1; + + return (PyObject *)pynick; +} + +PyObject *pynick_new(void *nick) +{ + return pynick_sub_new(nick, &PyNickType); +} + +int nick_object_init(void) +{ + g_return_val_if_fail(py_module != NULL, 0); + + if (PyType_Ready(&PyNickType) < 0) + return 0; + + Py_INCREF(&PyNickType); + PyModule_AddObject(py_module, "Nick", (PyObject *)&PyNickType); + + return 1; +} diff --git a/objects/nick-object.h b/objects/nick-object.h new file mode 100644 index 0000000..6831994 --- /dev/null +++ b/objects/nick-object.h @@ -0,0 +1,22 @@ +#ifndef _NICK_OBJECT_H_ +#define _NICK_OBJECT_H_ + +#include +#include "base-objects.h" + +/* forward */ +struct _NICK_REC; + +typedef struct +{ + PyIrssi_HEAD(struct _NICK_REC) +} PyNick; + +extern PyTypeObject PyNickType; + +int nick_object_init(void); +PyObject *pynick_sub_new(void *nick, PyTypeObject *subclass); +PyObject *pynick_new(void *nick); +#define pynick_check(op) PyObject_TypeCheck(op, &PyNickType) + +#endif diff --git a/objects/pyscript-object.c b/objects/pyscript-object.c new file mode 100644 index 0000000..a874b1b --- /dev/null +++ b/objects/pyscript-object.c @@ -0,0 +1,225 @@ +#include +#include +#include "pyscript-object.h" +#include "pyirssi.h" +#include "pysignals.h" +#include "pymodule.h" + +/* handle cycles... + Can't think of any reason why the user would put script into one of the lists + but who knows. Call GC after unloading module. +*/ +static int PyScript_traverse(PyScript *self, visitproc visit, void *arg) +{ + Py_VISIT(self->module); + Py_VISIT(self->argv); + Py_VISIT(self->modules); + + return 0; +} + +static int PyScript_clear(PyScript *self) +{ + Py_CLEAR(self->module); + Py_CLEAR(self->argv); + Py_CLEAR(self->modules); + + return 0; +} + +static void PyScript_dealloc(PyScript* self) +{ + PyScript_clear(self); + pyscript_remove_signals((PyObject*)self); + + self->ob_type->tp_free((PyObject*)self); +} + +static PyObject *PyScript_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyScript *self; + + self = (PyScript *)type->tp_alloc(type, 0); + if (!self) + return NULL; + + self->argv = PyList_New(0); + if (!self->argv) + goto error; + + self->modules = PyDict_New(); + if (!self->modules) + goto error; + + return (PyObject *)self; + +error: + Py_XDECREF(self->argv); + Py_XDECREF(self->modules); + Py_DECREF(self); + + return NULL; +} + +static PyObject *PyScript_command_bind(PyScript *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"cmd", "func", "category", NULL}; + char *cmd; + PyObject *func; + char *category = NULL; + PY_COMMAND_REC *crec; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "sO|s", kwlist, &cmd, &func, &category)) + return NULL; + + if (!PyCallable_Check(func)) + return PyErr_Format(PyExc_TypeError, "func must be callable"); + + crec = g_new(PY_COMMAND_REC, 1); + crec->name = g_strdup(cmd); + crec->handler = func; + Py_INCREF(func); + + py_command_bind(category, crec); + + /* add record to internal list*/ + self->signals = g_slist_append(self->signals, crec); + + Py_RETURN_NONE; +} + +static PyMemberDef PyScript_members[] = { + {"argv", T_OBJECT, offsetof(PyScript, argv), 0, "Script arguments"}, + {"module", T_OBJECT_EX, offsetof(PyScript, module), RO, "Script module"}, + {"modules", T_OBJECT_EX, offsetof(PyScript, modules), 0, "Imported modules"}, + {NULL} /* Sentinel */ +}; + +/* Methods for object */ +static PyMethodDef PyScript_methods[] = { + {"command_bind", (PyCFunction)PyScript_command_bind, + METH_VARARGS | METH_KEYWORDS, "Bind a command"}, + {NULL} /* Sentinel */ +}; + +static PyTypeObject PyScriptType = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "Script", /*tp_name*/ + sizeof(PyScript), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)PyScript_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /*tp_flags*/ + "PyScript objects", /* tp_doc */ + (traverseproc)PyScript_traverse, /* tp_traverse */ + (inquiry)PyScript_clear, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + PyScript_methods, /* tp_methods */ + PyScript_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + PyScript_new, /* tp_new */ +}; + +/* PyScript factory function */ +PyObject *pyscript_new(PyObject *module, char **argv) +{ + PyObject *script = NULL; + + script = PyObject_CallFunction((PyObject*)&PyScriptType, "()"); + + if (script) + { + PyScript *scr = (PyScript *)script; + + while (*argv) + { + PyObject *str = PyString_FromString(*argv); + if (!str) + { + /* The destructor should DECREF argv */ + Py_DECREF(script); + return NULL; + } + + PyList_Append(scr->argv, str); + Py_DECREF(str); + + *argv++; + } + + Py_INCREF(module); + scr->module = module; + } + + return script; +} + +void pyscript_remove_signals(PyObject *script) +{ + GSList *node; + PyScript *self; + + g_return_if_fail(pyscript_check(script)); + + self = (PyScript *) script; + + for (node = self->signals; node != NULL; node = node->next) + { + PY_COMMAND_REC *crec = node->data; + + py_command_unbind(crec); + g_free(crec->name); + Py_DECREF(crec->handler); + g_free(crec); + } + + g_slist_free(self->signals); + self->signals = NULL; +} + +void pyscript_clear_modules(PyObject *script) +{ + PyScript *self; + + g_return_if_fail(pyscript_check(script)); + + self = (PyScript *) script; + + PyDict_Clear(self->modules); +} + +int pyscript_init(void) +{ + if (PyType_Ready(&PyScriptType) < 0) + return 0; + + Py_INCREF(&PyScriptType); + PyModule_AddObject(py_module, "Script", (PyObject *)&PyScriptType); + + return 1; +} + diff --git a/objects/pyscript-object.h b/objects/pyscript-object.h new file mode 100644 index 0000000..260c6da --- /dev/null +++ b/objects/pyscript-object.h @@ -0,0 +1,25 @@ +#ifndef _PYSCRIPT_OBJECT_H_ +#define _PYSCRIPT_OBJECT_H_ +#include +#include + +typedef struct { + PyObject_HEAD + PyObject *module; /* module object */ + PyObject *argv; /* list of argument strings from the load command */ + PyObject *modules; /* dict of imported modules for script */ + GSList *signals; /* list of bound signals and commands */ +} PyScript; + +extern PyTypeObject PyScriptType; + +int pyscript_init(void); +PyObject *pyscript_new(PyObject *module, char **argv); +void pyscript_remove_signals(PyObject *script); +void pyscript_clear_modules(PyObject *script); +#define pyscript_check(op) PyObject_TypeCheck(op, &PyScriptType) +#define pyscript_get_name(scr) PyModule_GetName(((PyScript*)scr)->module) +#define pyscript_get_filename(scr) PyModule_GetFilename(((PyScript*)scr)->module) +#define pyscript_get_module(scr) ((PyScript*)scr)->module + +#endif diff --git a/objects/query-object.c b/objects/query-object.c new file mode 100644 index 0000000..1370b34 --- /dev/null +++ b/objects/query-object.c @@ -0,0 +1,177 @@ +#include +#include "pyirssi.h" +#include "pymodule.h" +#include "base-objects.h" +#include "window-item-object.h" +#include "query-object.h" +#include "server-object.h" +#include "pycore.h" + +/* member IDs */ +enum +{ + M_QUERY_ADDRESS, + M_QUERY_SERVER_TAG, + M_QUERY_UNWANTED, +}; + +/* monitor "query destroyed" signal */ +static void query_cleanup(QUERY_REC *query) +{ + PyQuery *pyquery = signal_get_user_data(); + + if (query == pyquery->data) + { + pyquery->data = NULL; + pyquery->cleanup_installed = 0; + signal_remove_data("query destroyed", query_cleanup, pyquery); + } +} + +static void PyQuery_dealloc(PyQuery *self) +{ + if (self->cleanup_installed) + signal_remove_data("query destroyed", query_cleanup, self); + + self->ob_type->tp_free((PyObject*)self); +} + + +static PyObject *PyQuery_get(PyQuery *self, void *closure) +{ + int member = GPOINTER_TO_INT(closure); + + RET_NULL_IF_INVALID(self->data); + + switch (member) + { + case M_QUERY_ADDRESS: + RET_AS_STRING_OR_NONE(self->data->address); + case M_QUERY_SERVER_TAG: + RET_AS_STRING_OR_NONE(self->data->server_tag); + case M_QUERY_UNWANTED: + return PyBool_FromLong(self->data->unwanted); + } + + /* This shouldn't be reached... but... */ + return PyErr_Format(PyExc_RuntimeError, "invalid member id, %d", member); +} + +/* specialized getters/setters */ +static PyGetSetDef PyQuery_getseters[] = { + {"address", (getter)PyQuery_get, NULL, + "Host address of the queries nick", + GINT_TO_POINTER(M_QUERY_ADDRESS)}, + + {"server_tag", (getter)PyQuery_get, NULL, + "Server tag used for this nick (doesn't get erased if server gets disconnected)", + GINT_TO_POINTER(M_QUERY_SERVER_TAG)}, + + {"unwanted", (getter)PyQuery_get, NULL, + "1 if the other side closed or some error occured (DCC chats)", + GINT_TO_POINTER(M_QUERY_UNWANTED)}, + + {NULL} +}; + +PyDoc_STRVAR(change_server_doc, + "Change the active server for the query." +); +static PyObject *PyQuery_change_server(PyQuery *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"server", NULL}; + PyObject *server; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, &server)) + return NULL; + + if (!pyserver_check(server)) + return PyErr_Format(PyExc_TypeError, "argument must be server object"); + + query_change_server(self->data, ((PyServer*)server)->data); + + Py_RETURN_NONE; +} + +/* Methods for object */ +static PyMethodDef PyQuery_methods[] = { + {"change_server", (PyCFunction)PyQuery_change_server, METH_VARARGS | METH_KEYWORDS, + change_server_doc}, + + {NULL} /* Sentinel */ +}; + +PyTypeObject PyQueryType = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "Query", /*tp_name*/ + sizeof(PyQuery), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)PyQuery_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + "PyQuery objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + PyQuery_methods, /* tp_methods */ + 0, /* tp_members */ + PyQuery_getseters, /* tp_getset */ + &PyWindowItemType, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ +}; + + +/* query factory function */ +PyObject *pyquery_new(void *query) +{ + static const char *BASE_NAME = "QUERY"; + PyObject *pyquery; + + pyquery = pywindow_item_sub_new(query, BASE_NAME, &PyQueryType); + if (pyquery) + { + PyQuery *pyq = (PyQuery *)pyquery; + signal_add_last_data("query destroyed", query_cleanup, pyq); + pyq->cleanup_installed = 1; + } + + return pyquery; +} + +int query_object_init(void) +{ + g_return_val_if_fail(py_module != NULL, 0); + + if (PyType_Ready(&PyQueryType) < 0) + return 0; + + Py_INCREF(&PyQueryType); + PyModule_AddObject(py_module, "Query", (PyObject *)&PyQueryType); + + return 1; +} diff --git a/objects/query-object.h b/objects/query-object.h new file mode 100644 index 0000000..844ce55 --- /dev/null +++ b/objects/query-object.h @@ -0,0 +1,21 @@ +#ifndef _QUERY_OBJECT_H_ +#define _QUERY_OBJECT_H_ + +#include +#include "window-item-object.h" + +/* forward */ +struct _QUERY_REC; + +typedef struct +{ + PyWindowItem_HEAD(struct _QUERY_REC) +} PyQuery; + +extern PyTypeObject PyQueryType; + +int query_object_init(void); +PyObject *pyquery_new(void *query); +#define pyquery_check(op) PyObject_TypeCheck(op, &PyQueryType) + +#endif diff --git a/objects/rawlog-object.c b/objects/rawlog-object.c new file mode 100644 index 0000000..eed5996 --- /dev/null +++ b/objects/rawlog-object.c @@ -0,0 +1,306 @@ +#include +#include "pyirssi_irc.h" +#include "pymodule.h" +#include "rawlog-object.h" +#include "pycore.h" + +/* monitor "????" signal */ +static void rawlog_cleanup(RAWLOG_REC *ban) +{ + /* XXX */ +} + +static void PyRawlog_dealloc(PyRawlog *self) +{ + self->ob_type->tp_free((PyObject*)self); +} + +static PyObject *PyRawlog_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyRawlog *self; + + self = (PyRawlog *)type->tp_alloc(type, 0); + if (!self) + return NULL; + + return (PyObject *)self; +} + +/* XXX: Need function to create the rawlog */ + +/* Getters */ +PyDoc_STRVAR(PyRawlog_logging_doc, + "The raw log is being written to file currently." +); +static PyObject *PyRawlog_logging_get(PyRawlog *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyBool_FromLong(self->data->logging); +} + +PyDoc_STRVAR(PyRawlog_nlines_doc, + "Number of lines in rawlog." +); +static PyObject *PyRawlog_nlines_get(PyRawlog *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyInt_FromLong(self->data->nlines); +} + +/* specialized getters/setters */ +static PyGetSetDef PyRawlog_getseters[] = { + {"logging", (getter)PyRawlog_logging_get, NULL, + PyRawlog_logging_doc, NULL}, + {"nlines", (getter)PyRawlog_nlines_get, NULL, + PyRawlog_nlines_doc, NULL}, + {NULL} +}; + +/* Methods */ +PyDoc_STRVAR(PyRawlog_get_lines_doc, + "Return a list of lines for rawlog." +); +static PyObject *PyRawlog_get_lines(PyRawlog *self, PyObject *args) +{ + PyObject *lines = NULL; + GSList *node; + + RET_NULL_IF_INVALID(self->data); + + lines = PyList_New(0); + if (!lines) + return NULL; + + for (node = self->data->lines; node; node = node->next) + { + int ret; + PyObject *line = PyString_FromString(node->data); + + if (!line) + goto error; + + ret = PyList_Append(lines, line); + Py_DECREF(line); + if (ret != 0) + goto error; + } + + return lines; + +error: + Py_XDECREF(lines); + return NULL; +} + +PyDoc_STRVAR(PyRawlog_destroy_doc, + "Destroy rawlog" +); +static PyObject *PyRawlog_destroy(PyRawlog *self, PyObject *args) +{ + RET_NULL_IF_INVALID(self->data); + + rawlog_destroy(self->data); + + /*XXX: what about signal handler ? */ + self->data = NULL; + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyRawlog_input_doc, + "Send str to rawlog as input text." +); +static PyObject *PyRawlog_input(PyRawlog *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"str", NULL}; + char *str = ""; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &str)) + return NULL; + + rawlog_input(self->data, str); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyRawlog_output_doc, + "Send str to rawlog as output text." +); +static PyObject *PyRawlog_output(PyRawlog *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"str", NULL}; + char *str = ""; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &str)) + return NULL; + + rawlog_output(self->data, str); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyRawlog_redirect_doc, + "Send str to rawlog as redirection text." +); +static PyObject *PyRawlog_redirect(PyRawlog *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"str", NULL}; + char *str = ""; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &str)) + return NULL; + + rawlog_redirect(self->data, str); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyRawlog_open_doc, + "Start logging new messages in rawlog to specified file." +); +static PyObject *PyRawlog_open(PyRawlog *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"fname", NULL}; + char *fname = ""; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &fname)) + return NULL; + + rawlog_open(self->data, fname); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyRawlog_close_doc, + "Stop logging to file" +); +static PyObject *PyRawlog_close(PyRawlog *self, PyObject *args) +{ + RET_NULL_IF_INVALID(self->data); + + rawlog_close(self->data); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyRawlog_save_doc, + "Save the current rawlog history to specified file." +); +static PyObject *PyRawlog_save(PyRawlog *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"fname", NULL}; + char *fname = ""; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &fname)) + return NULL; + + rawlog_save(self->data, fname); + + Py_RETURN_NONE; +} + +/* Methods for object */ +static PyMethodDef PyRawlog_methods[] = { + {"get_lines", (PyCFunction)PyRawlog_get_lines, METH_NOARGS, + PyRawlog_get_lines_doc}, + {"destroy", (PyCFunction)PyRawlog_destroy, METH_NOARGS, + PyRawlog_destroy_doc}, + {"input", (PyCFunction)PyRawlog_input, METH_VARARGS | METH_KEYWORDS, + PyRawlog_input_doc}, + {"output", (PyCFunction)PyRawlog_output, METH_VARARGS | METH_KEYWORDS, + PyRawlog_output_doc}, + {"redirect", (PyCFunction)PyRawlog_redirect, METH_VARARGS | METH_KEYWORDS, + PyRawlog_redirect_doc}, + {"open", (PyCFunction)PyRawlog_open, METH_VARARGS | METH_KEYWORDS, + PyRawlog_open_doc}, + {"close", (PyCFunction)PyRawlog_close, METH_NOARGS, + PyRawlog_close_doc}, + {"save", (PyCFunction)PyRawlog_save, METH_VARARGS | METH_KEYWORDS, + PyRawlog_save_doc}, + {NULL} /* Sentinel */ +}; + +PyTypeObject PyRawlogType = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "Rawlog", /*tp_name*/ + sizeof(PyRawlog), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)PyRawlog_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + "PyRawlog objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + PyRawlog_methods, /* tp_methods */ + 0, /* tp_members */ + PyRawlog_getseters, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + PyRawlog_new, /* tp_new */ +}; + + +/* window item wrapper factory function */ +PyObject *pyrawlog_new(void *rlog) +{ + PyRawlog *pyrlog; + + pyrlog = py_inst(PyRawlog, PyRawlogType); + if (!pyrlog) + return NULL; + + pyrlog->data = rlog; + + return (PyObject *)pyrlog; +} + +int rawlog_object_init(void) +{ + g_return_val_if_fail(py_module != NULL, 0); + + if (PyType_Ready(&PyRawlogType) < 0) + return 0; + + Py_INCREF(&PyRawlogType); + PyModule_AddObject(py_module, "Rawlog", (PyObject *)&PyRawlogType); + + return 1; +} diff --git a/objects/rawlog-object.h b/objects/rawlog-object.h new file mode 100644 index 0000000..d2a9a2d --- /dev/null +++ b/objects/rawlog-object.h @@ -0,0 +1,22 @@ +#ifndef _RAWLOG_OBJECT_H_ +#define _RAWLOG_OBJECT_H_ + +#include +#include "base-objects.h" + +/* forward */ +struct _RAWLOG_REC; + +typedef struct +{ + PyIrssiFinal_HEAD(struct _RAWLOG_REC) + int owned; +} PyRawlog; + +extern PyTypeObject PyRawlogType; + +int rawlog_object_init(void); +PyObject *pyrawlog_new(void *rlog); +#define pyrawlog_check(op) PyObject_TypeCheck(op, &PyRawlogType) + +#endif diff --git a/objects/reconnect-object.c b/objects/reconnect-object.c new file mode 100644 index 0000000..4e60599 --- /dev/null +++ b/objects/reconnect-object.c @@ -0,0 +1,152 @@ +#include +#include "pyirssi.h" +#include "pymodule.h" +#include "pycore.h" +#include "factory.h" +#include "reconnect-object.h" + +/*XXX: no Reconnect cleanup/destroy sig. Maybe value copy the two members? */ + +static void PyReconnect_dealloc(PyReconnect *self) +{ + Py_XDECREF(self->connect); + self->ob_type->tp_free((PyObject*)self); +} + +static PyObject *PyReconnect_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyReconnect *self; + + self = (PyReconnect *)type->tp_alloc(type, 0); + if (!self) + return NULL; + + return (PyObject *)self; +} + +/* Getters */ + +PyDoc_STRVAR(PyReconnect_tag_doc, + "Unique numeric tag" +); +static PyObject *PyReconnect_tag_get(PyReconnect *self, void *closure) +{ + RECONNECT_REC *data = self->data; + RET_NULL_IF_INVALID(self->data); + + return PyInt_FromLong(data->tag); +} + +PyDoc_STRVAR(PyReconnect_next_connect_doc, + "Unix time stamp when the next connection occurs" +); +static PyObject *PyReconnect_next_connect_get(PyReconnect *self, void *closure) +{ + RECONNECT_REC *data = self->data; + RET_NULL_IF_INVALID(self->data); + + return PyLong_FromUnsignedLong(data->next_connect); +} + +PyDoc_STRVAR(PyReconnect_connect_doc, + "Connection object" +); +static PyObject *PyReconnect_connect_get(PyReconnect *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_OBJ_OR_NONE(self->connect); +} + +/* specialized getters/setters */ +static PyGetSetDef PyReconnect_getseters[] = { + {"tag", (getter)PyReconnect_tag_get, NULL, + PyReconnect_tag_doc, NULL}, + {"next_connect", (getter)PyReconnect_next_connect_get, NULL, + PyReconnect_next_connect_doc, NULL}, + {"connect", (getter)PyReconnect_connect_get, NULL, + PyReconnect_connect_doc, NULL}, + {NULL} +}; + +/* Methods for object */ +static PyMethodDef PyReconnect_methods[] = { + {NULL} /* Sentinel */ +}; + +PyTypeObject PyReconnectType = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "Reconnect", /*tp_name*/ + sizeof(PyReconnect), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)PyReconnect_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + "PyReconnect objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + PyReconnect_methods, /* tp_methods */ + 0, /* tp_members */ + PyReconnect_getseters, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + PyReconnect_new, /* tp_new */ +}; + + +/* window item wrapper factory function */ +PyObject *pyreconnect_new(void *recon) +{ + RECONNECT_REC *rec = recon; + PyObject *connect; + PyReconnect *pyrecon; + + /* XXX: get a managed connect because there's no signals to manage reconnect */ + connect = py_irssi_chat_new(rec->conn, 1); + if (!connect) + return NULL; + + pyrecon = py_inst(PyReconnect, PyReconnectType); + if (!pyrecon) + return NULL; + + pyrecon->data = recon; + pyrecon->connect = connect; + + return (PyObject *)pyrecon; +} + +int reconnect_object_init(void) +{ + g_return_val_if_fail(py_module != NULL, 0); + + if (PyType_Ready(&PyReconnectType) < 0) + return 0; + + Py_INCREF(&PyReconnectType); + PyModule_AddObject(py_module, "Reconnect", (PyObject *)&PyReconnectType); + + return 1; +} diff --git a/objects/reconnect-object.h b/objects/reconnect-object.h new file mode 100644 index 0000000..ec0b094 --- /dev/null +++ b/objects/reconnect-object.h @@ -0,0 +1,20 @@ +#ifndef _RECONNECT_OBJECT_H_ +#define _RECONNECT_OBJECT_H_ + +#include +#include "base-objects.h" + +/*XXX: no Reconnect cleanup/destroy sig. Maybe value copy the two members? */ +typedef struct +{ + PyIrssiFinal_HEAD(void) + PyObject *connect; +} PyReconnect; + +extern PyTypeObject PyReconnectType; + +int reconnect_object_init(void); +PyObject *pyreconnect_new(void *recon); +#define pyreconnect_check(op) PyObject_TypeCheck(op, &PyReconnectType) + +#endif diff --git a/objects/server-object.c b/objects/server-object.c new file mode 100644 index 0000000..23a33b0 --- /dev/null +++ b/objects/server-object.c @@ -0,0 +1,763 @@ +#include +#include "pymodule.h" +#include "factory.h" +#include "pyirssi.h" +#include "pycore.h" +#include "pyutils.h" + +static void server_cleanup(SERVER_REC *server) +{ + PyServer *pyserver = signal_get_user_data(); + + if (server == pyserver->data) + { + if (pyserver->connect) + ((PyConnect *)pyserver->connect)->data = NULL; + + if (pyserver->rawlog) + ((PyRawlog *)pyserver->rawlog)->data = NULL; + + pyserver->data = NULL; + pyserver->cleanup_installed = 0; + signal_remove_data("server disconnected", server_cleanup, pyserver); + } +} + +static void PyServer_dealloc(PyServer *self) +{ + if (self->cleanup_installed) + signal_remove_data("server disconnected", server_cleanup, self); + + Py_XDECREF(self->connect); + Py_XDECREF(self->rawlog); + + self->ob_type->tp_free((PyObject*)self); +} + +PyDoc_STRVAR(PyServer_connect_time_doc, + "Time when connect() to server finished" +); +static PyObject *PyServer_connect_time_get(PyServer *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyLong_FromLong(self->data->connect_time); +} + +PyDoc_STRVAR(PyServer_real_connect_time_doc, + "Time when server sent 'connected' message" +); +static PyObject *PyServer_real_connect_time_get(PyServer *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyLong_FromLong(self->data->real_connect_time); +} + +PyDoc_STRVAR(PyServer_tag_doc, + "Unique server tag" +); +static PyObject *PyServer_tag_get(PyServer *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_STRING_OR_NONE(self->data->tag); +} + +PyDoc_STRVAR(PyServer_nick_doc, + "Current nick" +); +static PyObject *PyServer_nick_get(PyServer *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_STRING_OR_NONE(self->data->nick); +} + +PyDoc_STRVAR(PyServer_connected_doc, + "Is connection finished? 1|0" +); +static PyObject *PyServer_connected_get(PyServer *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyBool_FromLong(self->data->connected); +} + +PyDoc_STRVAR(PyServer_connection_lost_doc, + "Did we lose the connection (1) or was the connection just /DISCONNECTed (0)" +); +static PyObject *PyServer_connection_lost_get(PyServer *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyBool_FromLong(self->data->connection_lost); +} + +PyDoc_STRVAR(PyServer_rawlog_doc, + "Rawlog object for the server" +); +static PyObject *PyServer_rawlog_get(PyServer *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_OBJ_OR_NONE(self->rawlog); +} + +PyDoc_STRVAR(PyServer_connect_doc, + "Connect object for the server" +); +static PyObject *PyServer_connect_get(PyServer *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_OBJ_OR_NONE(self->connect); +} + +PyDoc_STRVAR(PyServer_version_doc, + "Server version" +); +static PyObject *PyServer_version_get(PyServer *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_STRING_OR_NONE(self->data->version); +} + +PyDoc_STRVAR(PyServer_last_invite_doc, + "Last channel we were invited to" +); +static PyObject *PyServer_last_invite_get(PyServer *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_STRING_OR_NONE(self->data->last_invite); +} + +PyDoc_STRVAR(PyServer_server_operator_doc, + "Are we server operator (IRC op) 1|0" +); +static PyObject *PyServer_server_operator_get(PyServer *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyBool_FromLong(self->data->server_operator); +} + +PyDoc_STRVAR(PyServer_usermode_away_doc, + "Are we marked as away? 1|0" +); +static PyObject *PyServer_usermode_away_get(PyServer *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyBool_FromLong(self->data->usermode_away); +} + +PyDoc_STRVAR(PyServer_away_reason_doc, + "Away reason message" +); +static PyObject *PyServer_away_reason_get(PyServer *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_STRING_OR_NONE(self->data->away_reason); +} + +PyDoc_STRVAR(PyServer_banned_doc, + "Were we banned from this server? 1|0" +); +static PyObject *PyServer_banned_get(PyServer *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyBool_FromLong(self->data->banned); +} + +PyDoc_STRVAR(PyServer_lag_doc, + "Current lag to server in milliseconds" +); +static PyObject *PyServer_lag_get(PyServer *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyInt_FromLong(self->data->lag); +} + +static PyGetSetDef PyServer_getseters[] = { + {"connect_time", (getter)PyServer_connect_time_get, NULL, + PyServer_connect_time_doc, NULL}, + {"real_connect_time", (getter)PyServer_real_connect_time_get, NULL, + PyServer_real_connect_time_doc, NULL}, + {"tag", (getter)PyServer_tag_get, NULL, + PyServer_tag_doc, NULL}, + {"nick", (getter)PyServer_nick_get, NULL, + PyServer_nick_doc, NULL}, + {"connected", (getter)PyServer_connected_get, NULL, + PyServer_connected_doc, NULL}, + {"connection_lost", (getter)PyServer_connection_lost_get, NULL, + PyServer_connection_lost_doc, NULL}, + {"rawlog", (getter)PyServer_rawlog_get, NULL, + PyServer_rawlog_doc, NULL}, + {"connect", (getter)PyServer_connect_get, NULL, + PyServer_connect_doc, NULL}, + {"version", (getter)PyServer_version_get, NULL, + PyServer_version_doc, NULL}, + {"last_invite", (getter)PyServer_last_invite_get, NULL, + PyServer_last_invite_doc, NULL}, + {"server_operator", (getter)PyServer_server_operator_get, NULL, + PyServer_server_operator_doc, NULL}, + {"usermode_away", (getter)PyServer_usermode_away_get, NULL, + PyServer_usermode_away_doc, NULL}, + {"away_reason", (getter)PyServer_away_reason_get, NULL, + PyServer_away_reason_doc, NULL}, + {"banned", (getter)PyServer_banned_get, NULL, + PyServer_banned_doc, NULL}, + {"lag", (getter)PyServer_lag_get, NULL, + PyServer_lag_doc, NULL}, + {NULL} +}; + +PyDoc_STRVAR(print_doc, + "Print to server\n" +); +static PyObject *PyServer_prnt(PyServer *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"channel", "str", "level", NULL}; + char *str, *channel; + int level = MSGLEVEL_CLIENTNOTICE; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "ss|i", kwlist, &channel, &str, &level)) + return NULL; + + printtext_string(self->data, channel, level, str); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(command_doc, + "Send command\n" +); +static PyObject *PyServer_command(PyServer *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"cmd", NULL}; + char *cmd; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, &cmd)) + return NULL; + + py_command(cmd, self->data, NULL); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(disconnect_doc, + "Disconnect from server\n" +); +static PyObject *PyServer_disconnect(PyServer *self, PyObject *args) +{ + RET_NULL_IF_INVALID(self->data); + + server_disconnect(self->data); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(isnickflag_doc, + "Returns True if flag is a nick mode flag (@, + or % in IRC)\n" +); +static PyObject *PyServer_isnickflag(PyServer *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"flag", NULL}; + char flag; + int ret; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "c", kwlist, &flag)) + return NULL; + + ret = self->data->isnickflag(self->data, flag); + + return PyBool_FromLong(ret); +} + +PyDoc_STRVAR(ischannel_doc, + "Returns True if start of `data' seems to mean channel.\n" +); +static PyObject *PyServer_ischannel(PyServer *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"data", NULL}; + char *data; + int ret; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, &data)) + return NULL; + + ret = self->data->ischannel(self->data, data); + + return PyBool_FromLong(ret); +} + +PyDoc_STRVAR(get_nick_flags_doc, + "Returns nick flag characters in order: op, voice, halfop (\"@+%\") in IRC\n" +); +static PyObject *PyServer_get_nick_flags(PyServer *self, PyObject *args) +{ + char *ret; + + RET_NULL_IF_INVALID(self->data); + + ret = (char *)self->data->get_nick_flags(self->data); + + return PyString_FromString(ret); +} + +PyDoc_STRVAR(send_message_doc, + "Sends a message to nick/channel. target_type 0 = channel, 1 = nick\n" +); +static PyObject *PyServer_send_message(PyServer *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"target", "msg", "target_type", NULL}; + char *target, *msg; + int target_type; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "ssi", kwlist, &target, &msg, &target_type)) + return NULL; + + self->data->send_message(self->data, target, msg, target_type); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(channels_join_doc, + " channels_join(channels, automatic=False) -> None\n" + "\n" + "Join to channels in server. `channels' may also contain keys for\n" + "channels just like with /JOIN command. `automatic' specifies if this\n" + "channel was joined 'automatically' or if it was joined because join\n" + "was requested by user. If channel join is 'automatic', irssi doesn't\n" + "jump to the window where the channel was joined.\n" +); +static PyObject *PyServer_channels_join(PyServer *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"channels", "automatic", NULL}; + char *channels; + int automatic = 0; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|i", kwlist, &channels, &automatic)) + return NULL; + + self->data->channels_join(self->data, channels, automatic); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyServer_window_item_find_doc, + "Find window item that matches best to given arguments" +); +static PyObject *PyServer_window_item_find(PyServer *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"name", NULL}; + char *name = ""; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &name)) + return NULL; + + return py_irssi_chat_new(window_item_find(self->data, name), 1); +} + +PyDoc_STRVAR(PyServer_window_find_item_doc, + "Find window which contains window item with specified name/server" +); +static PyObject *PyServer_window_find_item(PyServer *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"name", NULL}; + char *name = ""; + WINDOW_REC *win; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &name)) + return NULL; + + win = window_find_item(self->data, name); + if (win) + return pywindow_new(win); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyServer_window_find_level_doc, + "Find window with level" +); +static PyObject *PyServer_window_find_level(PyServer *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"level", NULL}; + int level = 0; + WINDOW_REC *win; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "i", kwlist, + &level)) + return NULL; + + win = window_find_level(self->data, level); + if (win) + return pywindow_new(win); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyServer_window_find_closest_doc, + "Find window that matches best to given arguments. `name' can be either\n" + "window name or name of one of the window items.\n" +); +static PyObject *PyServer_window_find_closest(PyServer *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"name", "level", NULL}; + char *name = ""; + int level = 0; + WINDOW_REC *win; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "si", kwlist, + &name, &level)) + return NULL; + + win = window_find_closest(self->data, name, level); + if (win) + return pywindow_new(win); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyServer_channels_doc, + "Return list of channels for server" +); +static PyObject *PyServer_channels(PyServer *self, PyObject *args) +{ + RET_NULL_IF_INVALID(self->data); + + return py_irssi_chatlist_new(self->data->channels, 1); +} + +PyDoc_STRVAR(PyServer_channel_find_doc, + "Find channel from this server" +); +static PyObject *PyServer_channel_find(PyServer *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"name", NULL}; + char *name = ""; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &name)) + return NULL; + + return py_irssi_chat_new(channel_find(self->data, name), 1); +} + +PyDoc_STRVAR(PyServer_nicks_get_same_doc, + "Return all nick objects in all channels in server. List is in format:\n" + "[(Channel, Nick), (Channel, Nick), ...]\n" +); +static PyObject *PyServer_nicks_get_same(PyServer *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"nick", NULL}; + char *nick = ""; + GSList *list, *node; + PyObject *pylist = NULL; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &nick)) + return NULL; + + pylist = PyList_New(0); + if (!pylist) + return NULL; + + list = nicklist_get_same(self->data, nick); + for (node = list; node != NULL; node = node->next->next) + { + int ret; + PyObject *tup; + + tup = Py_BuildValue("(NN)", + py_irssi_chat_new(node->data, 1), + py_irssi_chat_new(node->next->data, 1)); + if (!tup) + goto error; + + ret = PyList_Append(pylist, tup); + Py_DECREF(tup); + if (ret != 0) + goto error; + } + + return pylist; + +error: + Py_XDECREF(pylist); + return NULL; +} + +PyDoc_STRVAR(PyServer_queries_doc, + "Return a list of queries for server." +); +static PyObject *PyServer_queries(PyServer *self, PyObject *args) +{ + RET_NULL_IF_INVALID(self->data); + + return py_irssi_chatlist_new(self->data->queries, 1); +} + +PyDoc_STRVAR(PyServer_query_find_doc, + "Find a query on this server." +); +static PyObject *PyServer_query_find(PyServer *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"nick", NULL}; + char *nick = ""; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &nick)) + return NULL; + + return py_irssi_chat_new(query_find(self->data, nick), 1); +} + +PyDoc_STRVAR(PyServer_mask_match_doc, + "Return true if mask matches nick!user@host" +); +static PyObject *PyServer_mask_match(PyServer *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"mask", "nick", "user", "host", NULL}; + char *mask = ""; + char *nick = ""; + char *user = ""; + char *host = ""; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "ssss", kwlist, + &mask, &nick, &user, &host)) + return NULL; + + return PyBool_FromLong(mask_match(self->data, mask, nick, user, host)); +} + +PyDoc_STRVAR(PyServer_mask_match_address_doc, + "Return True if mask matches nick!address" +); +static PyObject *PyServer_mask_match_address(PyServer *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"mask", "nick", "address", NULL}; + char *mask = ""; + char *nick = ""; + char *address = ""; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "sss", kwlist, + &mask, &nick, &address)) + return NULL; + + return PyBool_FromLong(mask_match_address(self->data, mask, nick, address)); +} + +PyDoc_STRVAR(PyServer_masks_match_doc, + "Return True if any mask in the masks (string separated by spaces)\n" + "matches nick!address\n" +); +static PyObject *PyServer_masks_match(PyServer *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"masks", "nick", "address", NULL}; + char *masks = ""; + char *nick = ""; + char *address = ""; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "sss", kwlist, + &masks, &nick, &address)) + return NULL; + + return PyBool_FromLong(masks_match(self->data, masks, nick, address)); +} + +PyDoc_STRVAR(PyServer_ignore_check_doc, + "Return True if ignore matches" +); +static PyObject *PyServer_ignore_check(PyServer *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"nick", "host", "channel", "text", "level", NULL}; + char *nick = ""; + char *host = ""; + char *channel = ""; + char *text = ""; + int level = 0; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "ssssi", kwlist, + &nick, &host, &channel, &text, &level)) + return NULL; + + return PyBool_FromLong(ignore_check(self->data, + nick, host, channel, text, level)); +} + +/* Methods for object */ +static PyMethodDef PyServer_methods[] = { + {"prnt", (PyCFunction)PyServer_prnt, METH_VARARGS | METH_KEYWORDS, + print_doc}, + {"command", (PyCFunction)PyServer_command, METH_VARARGS | METH_KEYWORDS, + command_doc}, + {"disconnect", (PyCFunction)PyServer_disconnect, METH_NOARGS, + disconnect_doc}, + {"isnickflag", (PyCFunction)PyServer_isnickflag, METH_VARARGS | METH_KEYWORDS, + isnickflag_doc}, + {"ischannel", (PyCFunction)PyServer_ischannel, METH_VARARGS | METH_KEYWORDS, + ischannel_doc}, + {"get_nick_flags", (PyCFunction)PyServer_get_nick_flags, METH_NOARGS, + get_nick_flags_doc}, + {"send_message", (PyCFunction)PyServer_send_message, METH_VARARGS | METH_KEYWORDS, + send_message_doc}, + {"channels_join", (PyCFunction)PyServer_channels_join, METH_VARARGS | METH_KEYWORDS, + channels_join_doc}, + {"window_item_find", (PyCFunction)PyServer_window_item_find, METH_VARARGS | METH_KEYWORDS, + PyServer_window_item_find_doc}, + {"window_find_item", (PyCFunction)PyServer_window_find_item, METH_VARARGS | METH_KEYWORDS, + PyServer_window_find_item_doc}, + {"window_find_level", (PyCFunction)PyServer_window_find_level, METH_VARARGS | METH_KEYWORDS, + PyServer_window_find_level_doc}, + {"window_find_closest", (PyCFunction)PyServer_window_find_closest, METH_VARARGS | METH_KEYWORDS, + PyServer_window_find_closest_doc}, + {"channels", (PyCFunction)PyServer_channels, METH_NOARGS, + PyServer_channels_doc}, + {"channel_find", (PyCFunction)PyServer_channel_find, METH_VARARGS | METH_KEYWORDS, + PyServer_channel_find_doc}, + {"nicks_get_same", (PyCFunction)PyServer_nicks_get_same, METH_VARARGS | METH_KEYWORDS, + PyServer_nicks_get_same_doc}, + {"queries", (PyCFunction)PyServer_queries, METH_NOARGS, + PyServer_queries_doc}, + {"query_find", (PyCFunction)PyServer_query_find, METH_VARARGS | METH_KEYWORDS, + PyServer_query_find_doc}, + {"mask_match", (PyCFunction)PyServer_mask_match, METH_VARARGS | METH_KEYWORDS, + PyServer_mask_match_doc}, + {"mask_match_address", (PyCFunction)PyServer_mask_match_address, METH_VARARGS | METH_KEYWORDS, + PyServer_mask_match_address_doc}, + {"masks_match", (PyCFunction)PyServer_masks_match, METH_VARARGS | METH_KEYWORDS, + PyServer_masks_match_doc}, + {"ignore_check", (PyCFunction)PyServer_ignore_check, METH_VARARGS | METH_KEYWORDS, + PyServer_ignore_check_doc}, + {NULL} /* Sentinel */ +}; + +PyTypeObject PyServerType = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "Server", /*tp_name*/ + sizeof(PyServer), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)PyServer_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + "PyServer objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + PyServer_methods, /* tp_methods */ + 0, /* tp_members */ + PyServer_getseters, /* tp_getset */ + &PyIrssiChatBaseType, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ +}; + + +/* server factory function + connect arg should point to a wrapped SERVER_CONNECT */ +PyObject *pyserver_sub_new(void *server, PyTypeObject *subclass) +{ + static const char *SERVER_TYPE = "SERVER"; + SERVER_REC *srec = server; + PyServer *pyserver = NULL; + PyObject *rawlog = NULL; + PyObject *connect = NULL; + + g_return_val_if_fail(server != NULL, NULL); + + connect = py_irssi_chat_new(srec->connrec, 0); + if (!connect) + return NULL; + + /* FIXME */ + /* + if (srec->rawlog) + { + rawlog = pyrawlog_new(srec->rawlog); + if (!rawlog) + return NULL; + } + */ + + pyserver = py_instp(PyServer, subclass); + if (!pyserver) + return NULL; + + pyserver->base_name = SERVER_TYPE; + pyserver->data = server; + signal_add_last_data("server disconnected", server_cleanup, pyserver); + pyserver->cleanup_installed = 1; + pyserver->rawlog = rawlog; + pyserver->connect = connect; + + return (PyObject *)pyserver; +} + +PyObject *pyserver_new(void *server) +{ + return pyserver_sub_new(server, &PyServerType); +} + +int server_object_init(void) +{ + g_return_val_if_fail(py_module != NULL, 0); + + if (PyType_Ready(&PyServerType) < 0) + return 0; + + Py_INCREF(&PyServerType); + PyModule_AddObject(py_module, "Server", (PyObject *)&PyServerType); + + return 1; +} diff --git a/objects/server-object.h b/objects/server-object.h new file mode 100644 index 0000000..0703f47 --- /dev/null +++ b/objects/server-object.h @@ -0,0 +1,28 @@ +#ifndef _SERVER_OBJECT_H_ +#define _SERVER_OBJECT_H_ + +#include +#include "base-objects.h" + +/* forward */ +struct _SERVER_REC; + +#define PyServer_HEAD(type) \ + PyIrssi_HEAD(type) \ + PyObject *connect; \ + PyObject *rawlog; + +typedef struct +{ + PyServer_HEAD(struct _SERVER_REC) +} PyServer; + +extern PyTypeObject PyServerType; + +int server_object_init(void); +PyObject *pyserver_sub_new(void *server, PyTypeObject *subclass); +PyObject *pyserver_new(void *server); + +#define pyserver_check(op) PyObject_TypeCheck(op, &PyServerType) + +#endif diff --git a/objects/textdest-object.c b/objects/textdest-object.c new file mode 100644 index 0000000..9dd94d6 --- /dev/null +++ b/objects/textdest-object.c @@ -0,0 +1,188 @@ +#include +#include "pyirssi_irc.h" +#include "pymodule.h" +#include "textdest-object.h" +#include "factory.h" +#include "pycore.h" + +/* XXX: no cleanup signal for textdest */ + +static void PyTextDest_dealloc(PyTextDest *self) +{ + Py_XDECREF(self->window); + Py_XDECREF(self->server); + + self->ob_type->tp_free((PyObject*)self); +} + +static PyObject *PyTextDest_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyTextDest *self; + + self = (PyTextDest *)type->tp_alloc(type, 0); + if (!self) + return NULL; + + return (PyObject *)self; +} + +PyDoc_STRVAR(PyTextDest_window_doc, + "Window where the text will be written" +); +static PyObject *PyTextDest_window_get(PyTextDest *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_OBJ_OR_NONE(self->window); +} + +PyDoc_STRVAR(PyTextDest_server_doc, + "Target server" +); +static PyObject *PyTextDest_server_get(PyTextDest *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_OBJ_OR_NONE(self->server); +} + +PyDoc_STRVAR(PyTextDest_target_doc, + "Target channel/query/etc name" +); +static PyObject *PyTextDest_target_get(PyTextDest *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_STRING_OR_NONE(self->data->target); +} + +PyDoc_STRVAR(PyTextDest_level_doc, + "Text level" +); +static PyObject *PyTextDest_level_get(PyTextDest *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyInt_FromLong(self->data->level); +} + +PyDoc_STRVAR(PyTextDest_hilight_priority_doc, + "Priority for the hilighted text" +); +static PyObject *PyTextDest_hilight_priority_get(PyTextDest *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyInt_FromLong(self->data->hilight_priority); +} + +PyDoc_STRVAR(PyTextDest_hilight_color_doc, + "Color for the hilighted text" +); +static PyObject *PyTextDest_hilight_color_get(PyTextDest *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_STRING_OR_NONE(self->data->hilight_color); +} + +/* specialized getters/setters */ +static PyGetSetDef PyTextDest_getseters[] = { + {"window", (getter)PyTextDest_window_get, NULL, + PyTextDest_window_doc, NULL}, + {"server", (getter)PyTextDest_server_get, NULL, + PyTextDest_server_doc, NULL}, + {"target", (getter)PyTextDest_target_get, NULL, + PyTextDest_target_doc, NULL}, + {"level", (getter)PyTextDest_level_get, NULL, + PyTextDest_level_doc, NULL}, + {"hilight_priority", (getter)PyTextDest_hilight_priority_get, NULL, + PyTextDest_hilight_priority_doc, NULL}, + {"hilight_color", (getter)PyTextDest_hilight_color_get, NULL, + PyTextDest_hilight_color_doc, NULL}, + {NULL} +}; + +/* Methods for object */ +static PyMethodDef PyTextDest_methods[] = { + {NULL} /* Sentinel */ +}; + +PyTypeObject PyTextDestType = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "TextDest", /*tp_name*/ + sizeof(PyTextDest), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)PyTextDest_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + "PyTextDest objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + PyTextDest_methods, /* tp_methods */ + 0, /* tp_members */ + PyTextDest_getseters, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + PyTextDest_new, /* tp_new */ +}; + + +/* TextDest factory function */ +PyObject *pytextdest_new(void *td) +{ + PyObject *window, *server; + PyTextDest *pytdest; + TEXT_DEST_REC *tdest = td; + + window = py_irssi_chat_new(tdest->window, 1); + if (!window) + return NULL; + + server = py_irssi_chat_new(tdest->server, 1); + if (!server) + { + Py_DECREF(window); + return NULL; + } + + pytdest = py_inst(PyTextDest, PyTextDestType); + if (!pytdest) + return NULL; + + pytdest->data = td; + pytdest->window = window; + pytdest->server = server; + + return (PyObject *)pytdest; +} + +int textdest_object_init(void) +{ + g_return_val_if_fail(py_module != NULL, 0); + + if (PyType_Ready(&PyTextDestType) < 0) + return 0; + + Py_INCREF(&PyTextDestType); + PyModule_AddObject(py_module, "TextDest", (PyObject *)&PyTextDestType); + + return 1; +} diff --git a/objects/textdest-object.h b/objects/textdest-object.h new file mode 100644 index 0000000..366914a --- /dev/null +++ b/objects/textdest-object.h @@ -0,0 +1,23 @@ +#ifndef _TEXTDEST_OBJECT_H_ +#define _TEXTDEST_OBJECT_H_ + +#include +#include "base-objects.h" + +/* forward */ +struct _TEXT_DEST_REC; + +typedef struct +{ + PyIrssiFinal_HEAD(struct _TEXT_DEST_REC) + PyObject *window; + PyObject *server; +} PyTextDest; + +extern PyTypeObject PyTextDestType; + +int textdest_object_init(void); +PyObject *pytextdest_new(void *td); +#define pytextdest_check(op) PyObject_TypeCheck(op, &PyTextDestType) + +#endif diff --git a/objects/window-item-object.c b/objects/window-item-object.c new file mode 100644 index 0000000..96181fc --- /dev/null +++ b/objects/window-item-object.c @@ -0,0 +1,307 @@ +#include +#include "pymodule.h" +#include "base-objects.h" +#include "window-item-object.h" +#include "pyirssi.h" +#include "pycore.h" +#include "pyutils.h" +#include "factory.h" + +/* Dealloc is overridden by sub types */ + +PyDoc_STRVAR(PyWindowItem_server_doc, + "Active name for item" +); +static PyObject *PyWindowItem_server_get(PyWindowItem *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_OBJ_OR_NONE(self->server); +} + +PyDoc_STRVAR(PyWindowItem_name_doc, + "Name of the item" +); +static PyObject *PyWindowItem_name_get(PyWindowItem *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_STRING_OR_NONE(self->data->visible_name); +} + +PyDoc_STRVAR(PyWindowItem_createtime_doc, + "Time the witem was created" +); +static PyObject *PyWindowItem_createtime_get(PyWindowItem *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyLong_FromLong(self->data->createtime); +} + +PyDoc_STRVAR(PyWindowItem_data_level_doc, + "0=no new data, 1=text, 2=msg, 3=highlighted text" +); +static PyObject *PyWindowItem_data_level_get(PyWindowItem *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyInt_FromLong(self->data->data_level); +} + +PyDoc_STRVAR(PyWindowItem_hilight_color_doc, + "Color of the last highlighted text" +); +static PyObject *PyWindowItem_hilight_color_get(PyWindowItem *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_STRING_OR_NONE(self->data->hilight_color); +} + +/* specialized getters/setters */ +static PyGetSetDef PyWindowItem_getseters[] = { + {"server", (getter)PyWindowItem_server_get, NULL, + PyWindowItem_server_doc, NULL}, + {"name", (getter)PyWindowItem_name_get, NULL, + PyWindowItem_name_doc, NULL}, + {"createtime", (getter)PyWindowItem_createtime_get, NULL, + PyWindowItem_createtime_doc, NULL}, + {"data_level", (getter)PyWindowItem_data_level_get, NULL, + PyWindowItem_data_level_doc, NULL}, + {"hilight_color", (getter)PyWindowItem_hilight_color_get, NULL, + PyWindowItem_hilight_color_doc, NULL}, + {NULL} +}; + +PyDoc_STRVAR(PyWindowItem_prnt_doc, + "Print to window item" +); +static PyObject *PyWindowItem_prnt(PyWindowItem *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"str", "level", NULL}; + char *str; + int level = MSGLEVEL_CLIENTNOTICE; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|i", kwlist, &str, &level)) + return NULL; + + printtext_string(self->data->server, self->data->visible_name, level, str); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyWindowItem_command_doc, + "Send command to window item" +); +static PyObject *PyWindowItem_command(PyWindowItem *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"cmd", NULL}; + char *cmd; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, &cmd)) + return NULL; + + py_command(cmd, self->data->server, self->data); + + Py_RETURN_NONE; +} + +/* Methods */ + +PyDoc_STRVAR(PyWindowItem_window_doc, + "Return parent window for window item" +); +static PyObject *PyWindowItem_window(PyWindowItem *self, PyObject *args) +{ + WINDOW_REC *win; + + RET_NULL_IF_INVALID(self->data); + + win = window_item_window(self->data); + if (win) + return pywindow_new(win); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyWindowItem_change_server_doc, + "Change server for window item" +); +static PyObject *PyWindowItem_change_server(PyWindowItem *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"server", NULL}; + PyObject *server = NULL; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, + &server)) + return NULL; + + if (!pyserver_check(server)) + return PyErr_Format(PyExc_TypeError, "arg must be server"); + + window_item_change_server(self->data, ((PyServer*)server)->data); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyWindowItem_is_active_doc, + "Returns true if window item is active" +); +static PyObject *PyWindowItem_is_active(PyWindowItem *self, PyObject *args) +{ + RET_NULL_IF_INVALID(self->data); + + return PyBool_FromLong(window_item_is_active(self->data)); +} + +PyDoc_STRVAR(PyWindowItem_set_active_doc, + "Set window item active" +); +static PyObject *PyWindowItem_set_active(PyWindowItem *self, PyObject *args) +{ + RET_NULL_IF_INVALID(self->data); + + window_item_set_active(window_item_window(self->data), self->data); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyWindowItem_activity_doc, + "Unknown/untested" +); +static PyObject *PyWindowItem_activity(PyWindowItem *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"data_level", "hilight_color", NULL}; + int data_level = 0; + char *hilight_color = NULL; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "is", kwlist, + &data_level, &hilight_color)) + return NULL; + + window_item_activity(self->data, data_level, hilight_color); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyWindowItem_destroy_doc, + "Destroy channel or query" +); +static PyObject *PyWindowItem_destroy(PyWindowItem *self, PyObject *args) +{ + RET_NULL_IF_INVALID(self->data); + + window_item_destroy(self->data); + + Py_RETURN_NONE; +} + +/* Methods for object */ +static PyMethodDef PyWindowItem_methods[] = { + {"prnt", (PyCFunction)PyWindowItem_prnt, METH_VARARGS | METH_KEYWORDS, + PyWindowItem_prnt_doc}, + {"command", (PyCFunction)PyWindowItem_command, METH_VARARGS | METH_KEYWORDS, + PyWindowItem_command_doc}, + {"window", (PyCFunction)PyWindowItem_window, METH_NOARGS, + PyWindowItem_window_doc}, + {"change_server", (PyCFunction)PyWindowItem_change_server, METH_VARARGS | METH_KEYWORDS, + PyWindowItem_change_server_doc}, + {"is_active", (PyCFunction)PyWindowItem_is_active, METH_NOARGS, + PyWindowItem_is_active_doc}, + {"set_active", (PyCFunction)PyWindowItem_set_active, METH_NOARGS, + PyWindowItem_set_active_doc}, + {"activity", (PyCFunction)PyWindowItem_activity, METH_VARARGS | METH_KEYWORDS, + PyWindowItem_activity_doc}, + {"destroy", (PyCFunction)PyWindowItem_destroy, METH_NOARGS, + PyWindowItem_destroy_doc}, + {NULL} /* Sentinel */ +}; + +PyTypeObject PyWindowItemType = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "WindowItem", /*tp_name*/ + sizeof(PyWindowItem), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + 0, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + "PyWindowItem objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + PyWindowItem_methods, /* tp_methods */ + 0, /* tp_members */ + PyWindowItem_getseters, /* tp_getset */ + &PyIrssiChatBaseType, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ +}; + + +/* window item wrapper factory function */ +PyObject *pywindow_item_sub_new(void *witem, const char *name, PyTypeObject *subclass) +{ + WI_ITEM_REC *rec = witem; + PyWindowItem *pywitem = NULL; + PyObject *server; + + g_return_val_if_fail(witem != NULL, NULL); + + server = py_irssi_chat_new(rec->server, 1); + if (!server) + return NULL; + + pywitem = py_instp(PyWindowItem, subclass); + if (!pywitem) + return NULL; + + pywitem->data = witem; + pywitem->base_name = name; + pywitem->server = server; + + return (PyObject *)pywitem; +} + +PyObject *pywindow_item_new(void *witem) +{ + return pywindow_item_sub_new(witem, NULL, &PyWindowItemType); +} + +int window_item_object_init(void) +{ + g_return_val_if_fail(py_module != NULL, 0); + + if (PyType_Ready(&PyWindowItemType) < 0) + return 0; + + Py_INCREF(&PyWindowItemType); + PyModule_AddObject(py_module, "WindowItem", (PyObject *)&PyWindowItemType); + + return 1; +} diff --git a/objects/window-item-object.h b/objects/window-item-object.h new file mode 100644 index 0000000..e205ebe --- /dev/null +++ b/objects/window-item-object.h @@ -0,0 +1,26 @@ +#ifndef _WITEM_OBJECT_H_ +#define _WITEM_OBJECT_H_ + +#include +#include "base-objects.h" + +#define PyWindowItem_HEAD(type) \ + PyIrssi_HEAD(type) \ + PyObject *server; + +/* forward */ +struct _WI_ITEM_REC; + +typedef struct +{ + PyWindowItem_HEAD(struct _WI_ITEM_REC) +} PyWindowItem; + +extern PyTypeObject PyWindowItemType; + +int window_item_object_init(void); +PyObject *pywindow_item_sub_new(void *witem, const char *name, PyTypeObject *subclass); +PyObject *pywindow_item_new(void *witem); +#define pywindow_item_check(op) PyObject_TypeCheck(op, &PyWindowItemType) + +#endif diff --git a/objects/window-object.c b/objects/window-object.c new file mode 100644 index 0000000..df74766 --- /dev/null +++ b/objects/window-object.c @@ -0,0 +1,646 @@ +#include +#include "pyirssi_irc.h" +#include "pymodule.h" +#include "window-object.h" +#include "factory.h" +#include "pycore.h" +#include "pyutils.h" + +/* monitor "window destroyed" signal */ +static void window_cleanup(WINDOW_REC *win) +{ + PyWindow *pywindow = signal_get_user_data(); + + if (win == pywindow->data) + { + pywindow->data = NULL; + pywindow->cleanup_installed = 0; + signal_remove_data("window destroyed", window_cleanup, pywindow); + } +} + +static void PyWindow_dealloc(PyWindow *self) +{ + if (self->cleanup_installed) + signal_remove_data("window destroyed", window_cleanup, self); + + self->ob_type->tp_free((PyObject*)self); +} + +static PyObject *PyWindow_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyWindow *self; + + self = (PyWindow *)type->tp_alloc(type, 0); + if (!self) + return NULL; + + return (PyObject *)self; +} + +/* Getters */ +PyDoc_STRVAR(PyWindow_refnum_doc, + "Reference number" +); +static PyObject *PyWindow_refnum_get(PyWindow *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyInt_FromLong(self->data->refnum); +} + +PyDoc_STRVAR(PyWindow_name_doc, + "Name" +); +static PyObject *PyWindow_name_get(PyWindow *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_STRING_OR_NONE(self->data->name); +} + +PyDoc_STRVAR(PyWindow_width_doc, + "Width" +); +static PyObject *PyWindow_width_get(PyWindow *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyInt_FromLong(self->data->width); +} + +PyDoc_STRVAR(PyWindow_height_doc, + "Height" +); +static PyObject *PyWindow_height_get(PyWindow *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyInt_FromLong(self->data->height); +} + +PyDoc_STRVAR(PyWindow_history_name_doc, + "Name of named historylist for this window" +); +static PyObject *PyWindow_history_name_get(PyWindow *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_STRING_OR_NONE(self->data->history_name); +} + +PyDoc_STRVAR(PyWindow_active_doc, + "Active window item" +); +static PyObject *PyWindow_active_get(PyWindow *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return py_irssi_chat_new(self->data->active, 1); +} + +PyDoc_STRVAR(PyWindow_active_server_doc, + "Active server" +); +static PyObject *PyWindow_active_server_get(PyWindow *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return py_irssi_chat_new(self->data->active_server, 1); +} + +PyDoc_STRVAR(PyWindow_servertag_doc, + "active_server must be either None or have this same tag" + "(unless there's items in this window). This is used by" + "/WINDOW SERVER -sticky" +); +static PyObject *PyWindow_servertag_get(PyWindow *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_STRING_OR_NONE(self->data->servertag); +} + +PyDoc_STRVAR(PyWindow_level_doc, + "Current window level" +); +static PyObject *PyWindow_level_get(PyWindow *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyInt_FromLong(self->data->level); +} + +PyDoc_STRVAR(PyWindow_sticky_refnum_doc, + "True if reference number is sticky" +); +static PyObject *PyWindow_sticky_refnum_get(PyWindow *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyBool_FromLong(self->data->sticky_refnum); +} + +PyDoc_STRVAR(PyWindow_data_level_doc, + "Current data level" +); +static PyObject *PyWindow_data_level_get(PyWindow *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyInt_FromLong(self->data->data_level); +} + +PyDoc_STRVAR(PyWindow_hilight_color_doc, + "Current activity hilight color" +); +static PyObject *PyWindow_hilight_color_get(PyWindow *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_STRING_OR_NONE(self->data->hilight_color); +} + +PyDoc_STRVAR(PyWindow_last_timestamp_doc, + "Last time timestamp was written in window" +); +static PyObject *PyWindow_last_timestamp_get(PyWindow *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyLong_FromUnsignedLong(self->data->last_timestamp); +} + +PyDoc_STRVAR(PyWindow_last_line_doc, + "Last time text was written in window" +); +static PyObject *PyWindow_last_line_get(PyWindow *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + return PyLong_FromUnsignedLong(self->data->last_line); +} + +PyDoc_STRVAR(PyWindow_theme_name_doc, + "Active theme in window, None = default" +); +static PyObject *PyWindow_theme_name_get(PyWindow *self, void *closure) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_STRING_OR_NONE(self->data->theme_name); +} + +/* specialized getters/setters */ +static PyGetSetDef PyWindow_getseters[] = { + {"refnum", (getter)PyWindow_refnum_get, NULL, + PyWindow_refnum_doc, NULL}, + {"name", (getter)PyWindow_name_get, NULL, + PyWindow_name_doc, NULL}, + {"width", (getter)PyWindow_width_get, NULL, + PyWindow_width_doc, NULL}, + {"height", (getter)PyWindow_height_get, NULL, + PyWindow_height_doc, NULL}, + {"history_name", (getter)PyWindow_history_name_get, NULL, + PyWindow_history_name_doc, NULL}, + {"active", (getter)PyWindow_active_get, NULL, + PyWindow_active_doc, NULL}, + {"active_server", (getter)PyWindow_active_server_get, NULL, + PyWindow_active_server_doc, NULL}, + {"servertag", (getter)PyWindow_servertag_get, NULL, + PyWindow_servertag_doc, NULL}, + {"level", (getter)PyWindow_level_get, NULL, + PyWindow_level_doc, NULL}, + {"sticky_refnum", (getter)PyWindow_sticky_refnum_get, NULL, + PyWindow_sticky_refnum_doc, NULL}, + {"data_level", (getter)PyWindow_data_level_get, NULL, + PyWindow_data_level_doc, NULL}, + {"hilight_color", (getter)PyWindow_hilight_color_get, NULL, + PyWindow_hilight_color_doc, NULL}, + {"last_timestamp", (getter)PyWindow_last_timestamp_get, NULL, + PyWindow_last_timestamp_doc, NULL}, + {"last_line", (getter)PyWindow_last_line_get, NULL, + PyWindow_last_line_doc, NULL}, + {"theme_name", (getter)PyWindow_theme_name_get, NULL, + PyWindow_theme_name_doc, NULL}, + {NULL} +}; + +/* Methods */ +PyDoc_STRVAR(PyWindow_items_doc, + "Return a list of items in window." +); +static PyObject *PyWindow_items(PyWindow *self, PyObject *args) +{ + RET_NULL_IF_INVALID(self->data); + return py_irssi_chatlist_new(self->data->items, 1); +} + +PyDoc_STRVAR(PyWindow_prnt_doc, + "Print to window" +); +static PyObject *PyWindow_prnt(PyWindow *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"str", "level", NULL}; + char *str = ""; + int level = MSGLEVEL_CLIENTNOTICE; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|i", kwlist, + &str, &level)) + return NULL; + + printtext_string_window(self->data, level, str); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyWindow_command_doc, + "Send command to window" +); +static PyObject *PyWindow_command(PyWindow *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"cmd", NULL}; + char *cmd = ""; + WINDOW_REC *old; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &cmd)) + return NULL; + + old = active_win; + active_win = self->data; + py_command(cmd, active_win->active_server, active_win->active); + if (g_slist_find(windows, old) != NULL) + active_win = old; + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyWindow_item_add_doc, + " item_add(item, automatic=False) -> None\n" + "\n" + "Add window item\n" +); +static PyObject *PyWindow_item_add(PyWindow *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"item", "automatic", NULL}; + PyObject *item = NULL; + int automatic = 0; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|i", kwlist, + &item, &automatic)) + return NULL; + + if (!pywindow_item_check(item)) + return PyErr_Format(PyExc_TypeError, "item must be window item"); + + window_item_add(self->data, ((PyWindowItem*)item)->data, automatic); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyWindow_item_remove_doc, + "Remove window item" +); +static PyObject *PyWindow_item_remove(PyWindow *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"item", NULL}; + PyObject *item = NULL; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, + &item)) + return NULL; + + if (!pywindow_item_check(item)) + return PyErr_Format(PyExc_TypeError, "item must be window item"); + + window_item_remove(((PyWindowItem*)item)->data); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyWindow_item_destroy_doc, + "Destroy window item" +); +static PyObject *PyWindow_item_destroy(PyWindow *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"item", NULL}; + PyObject *item = NULL; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, + &item)) + return NULL; + + if (!pywindow_item_check(item)) + return PyErr_Format(PyExc_TypeError, "item must be window item"); + + window_item_destroy(((PyWindowItem*)item)->data); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyWindow_item_prev_doc, + "Change to previous window item" +); +static PyObject *PyWindow_item_prev(PyWindow *self, PyObject *args) +{ + RET_NULL_IF_INVALID(self->data); + + window_item_prev(self->data); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyWindow_item_next_doc, + "Change to next window item" +); +static PyObject *PyWindow_item_next(PyWindow *self, PyObject *args) +{ + RET_NULL_IF_INVALID(self->data); + + window_item_next(self->data); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyWindow_destroy_doc, + "Destroy the window." +); +static PyObject *PyWindow_destroy(PyWindow *self, PyObject *args) +{ + RET_NULL_IF_INVALID(self->data); + + window_destroy(self->data); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyWindow_set_active_doc, + "Set window active." +); +static PyObject *PyWindow_set_active(PyWindow *self, PyObject *args) +{ + RET_NULL_IF_INVALID(self->data); + + window_set_active(self->data); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyWindow_change_server_doc, + "Change server in window" +); +static PyObject *PyWindow_change_server(PyWindow *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"server", NULL}; + PyObject *server = NULL; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, + &server)) + return NULL; + + if (!pyserver_check(server)) + return PyErr_Format(PyExc_TypeError, "arg must be server"); + + window_change_server(self->data, ((PyServer*)server)->data); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyWindow_set_refnum_doc, + "Set window refnum" +); +static PyObject *PyWindow_set_refnum(PyWindow *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"refnum", NULL}; + int refnum = 0; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "i", kwlist, + &refnum)) + return NULL; + + window_set_refnum(self->data, refnum); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyWindow_set_name_doc, + "Set window name" +); +static PyObject *PyWindow_set_name(PyWindow *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"name", NULL}; + char *name = ""; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &name)) + return NULL; + + window_set_name(self->data, name); + + Py_RETURN_NONE; +} + +/* Methods for object */ +PyDoc_STRVAR(PyWindow_set_history_doc, + "Set window history" +); +static PyObject *PyWindow_set_history(PyWindow *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"history", NULL}; + char *history = ""; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &history)) + return NULL; + + window_set_history(self->data, history); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyWindow_set_level_doc, + "Set window level" +); +static PyObject *PyWindow_set_level(PyWindow *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"level", NULL}; + int level = 0; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "i", kwlist, + &level)) + return NULL; + + window_set_level(self->data, level); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyWindow_activity_doc, + "Unknown/Untested" +); +static PyObject *PyWindow_activity(PyWindow *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"data_level", "hilight_color", NULL}; + int data_level = 0; + char *hilight_color = NULL; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "i|s", kwlist, + &data_level, &hilight_color)) + return NULL; + + window_activity(self->data, data_level, hilight_color); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(PyWindow_get_active_name_doc, + "Return active item's name, or if none is active, window's name." +); +static PyObject *PyWindow_get_active_name(PyWindow *self, PyObject *args) +{ + RET_NULL_IF_INVALID(self->data); + RET_AS_STRING_OR_NONE(window_get_active_name(self->data)); +} + +PyDoc_STRVAR(PyWindow_item_find_doc, + "Find window item that matches best to given arguments" +); +static PyObject *PyWindow_item_find(PyWindow *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"server", "name", NULL}; + PyObject *server = NULL; + char *name = ""; + WI_ITEM_REC *witem; + + RET_NULL_IF_INVALID(self->data); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "Os", kwlist, + &server, &name)) + return NULL; + + if (!pyserver_check(server)) + return PyErr_Format(PyExc_TypeError, "arg 1 must be server"); + + witem = window_item_find_window(self->data, ((PyServer*)server)->data, name); + return py_irssi_chat_new(witem, 1); +} + +static PyMethodDef PyWindow_methods[] = { + {"items", (PyCFunction)PyWindow_items, METH_NOARGS, + PyWindow_items_doc}, + {"prnt", (PyCFunction)PyWindow_prnt, METH_VARARGS | METH_KEYWORDS, + PyWindow_prnt_doc}, + {"command", (PyCFunction)PyWindow_command, METH_VARARGS | METH_KEYWORDS, + PyWindow_command_doc}, + {"item_add", (PyCFunction)PyWindow_item_add, METH_VARARGS | METH_KEYWORDS, + PyWindow_item_add_doc}, + {"item_remove", (PyCFunction)PyWindow_item_remove, METH_VARARGS | METH_KEYWORDS, + PyWindow_item_remove_doc}, + {"item_destroy", (PyCFunction)PyWindow_item_destroy, METH_VARARGS | METH_KEYWORDS, + PyWindow_item_destroy_doc}, + {"item_prev", (PyCFunction)PyWindow_item_prev, METH_NOARGS, + PyWindow_item_prev_doc}, + {"item_next", (PyCFunction)PyWindow_item_next, METH_NOARGS, + PyWindow_item_next_doc}, + {"destroy", (PyCFunction)PyWindow_destroy, METH_NOARGS, + PyWindow_destroy_doc}, + {"set_active", (PyCFunction)PyWindow_set_active, METH_NOARGS, + PyWindow_set_active_doc}, + {"change_server", (PyCFunction)PyWindow_change_server, METH_VARARGS | METH_KEYWORDS, + PyWindow_change_server_doc}, + {"set_refnum", (PyCFunction)PyWindow_set_refnum, METH_VARARGS | METH_KEYWORDS, + PyWindow_set_refnum_doc}, + {"set_name", (PyCFunction)PyWindow_set_name, METH_VARARGS | METH_KEYWORDS, + PyWindow_set_name_doc}, + {"set_history", (PyCFunction)PyWindow_set_history, METH_VARARGS | METH_KEYWORDS, + PyWindow_set_history_doc}, + {"set_level", (PyCFunction)PyWindow_set_level, METH_VARARGS | METH_KEYWORDS, + PyWindow_set_level_doc}, + {"activity", (PyCFunction)PyWindow_activity, METH_VARARGS | METH_KEYWORDS, + PyWindow_activity_doc}, + {"get_active_name", (PyCFunction)PyWindow_get_active_name, METH_NOARGS, + PyWindow_get_active_name_doc}, + {"item_find", (PyCFunction)PyWindow_item_find, METH_VARARGS | METH_KEYWORDS, + PyWindow_item_find_doc}, + {NULL} /* Sentinel */ +}; + +PyTypeObject PyWindowType = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "Window", /*tp_name*/ + sizeof(PyWindow), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)PyWindow_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + "PyWindow objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + PyWindow_methods, /* tp_methods */ + 0, /* tp_members */ + PyWindow_getseters, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + PyWindow_new, /* tp_new */ +}; + + +/* window item wrapper factory function */ +PyObject *pywindow_new(void *win) +{ + PyWindow *pywindow; + + pywindow = py_inst(PyWindow, PyWindowType); + if (!pywindow) + return NULL; + + pywindow->data = win; + pywindow->cleanup_installed = 1; + signal_add_last_data("window destroyed", window_cleanup, pywindow); + + return (PyObject *)pywindow; +} + +int window_object_init(void) +{ + g_return_val_if_fail(py_module != NULL, 0); + + if (PyType_Ready(&PyWindowType) < 0) + return 0; + + Py_INCREF(&PyWindowType); + PyModule_AddObject(py_module, "Window", (PyObject *)&PyWindowType); + + return 1; +} diff --git a/objects/window-object.h b/objects/window-object.h new file mode 100644 index 0000000..b9860af --- /dev/null +++ b/objects/window-object.h @@ -0,0 +1,21 @@ +#ifndef _WINDOW_OBJECT_H_ +#define _WINDOW_OBJECT_H_ + +#include +#include "base-objects.h" + +/* forward */ +struct _WINDOW_REC; + +typedef struct +{ + PyIrssiFinal_HEAD(struct _WINDOW_REC) +} PyWindow; + +extern PyTypeObject PyWindowType; + +int window_object_init(void); +PyObject *pywindow_new(void *win); +#define pywindow_check(op) PyObject_TypeCheck(op, &PyWindowType) + +#endif diff --git a/pycore.c b/pycore.c new file mode 100644 index 0000000..63cd2c6 --- /dev/null +++ b/pycore.c @@ -0,0 +1,152 @@ +#include +#include +#include +#include +#include "pyirssi.h" +#include "pycore.h" +#include "pyloader.h" +#include "pymodule.h" +#include "factory.h" + + +/*XXX: copy parse into utils */ +static void cmd_exec(const char *data) +{ + PyObject *co; + PyObject *ret; + PyObject *d; + PyObject *m; + char *cmd; + + if (!*data) + cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS); + + cmd = g_strconcat(data, "\n", NULL); + + m = PyImport_AddModule("__main__"); + if (!m) + goto error; + + d = PyModule_GetDict(m); + if (!d) + goto error; + + co = Py_CompileString(cmd, "", Py_single_input); + if (!co) + goto error; + + ret = PyEval_EvalCode((PyCodeObject *)co, d, d); + Py_DECREF(co); + Py_XDECREF(ret); + +error: + g_free(cmd); + if (PyErr_Occurred()) + PyErr_Print(); +} + +static void cmd_load(const char *data) +{ + char **argv; + + argv = g_strsplit(data, " ", -1); + if (*argv == NULL || **argv == '\0') + { + g_strfreev(argv); + cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS); + } + + pyloader_load_script_argv(argv); + g_strfreev(argv); +} + +static void cmd_unload(const char *data) +{ + void *free_arg; + char *script; + + if (!cmd_get_params(data, &free_arg, 1, &script)) + return; + + if (*script == '\0') + cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + pyloader_unload_script(script); + + cmd_params_free(free_arg); +} + +static void cmd_list() +{ + char buf[128]; + GSList *list; + + list = pyloader_list(); + + g_snprintf(buf, sizeof(buf), "%-15s %s", "Name", "File"); + + if (list != NULL) + { + GSList *node; + + printtext_string(NULL, NULL, MSGLEVEL_CLIENTCRAP, buf); + for (node = list; node != NULL; node = node->next) + { + PY_LIST_REC *item = node->data; + g_snprintf(buf, sizeof(buf), "%-15s %s", item->name, item->file); + + printtext_string(NULL, NULL, MSGLEVEL_CLIENTCRAP, buf); + } + } + else + printtext_string(NULL, NULL, MSGLEVEL_CLIENTERROR, "No python scripts are loaded"); + + pyloader_list_destroy(&list); +} + +#if 0 +/* why doesn't this get called? */ +static void intr_catch(int sig) +{ + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "got sig %d", sig); + PyErr_SetInterrupt(); +} +#endif + +void irssi_python_init(void) +{ + Py_InitializeEx(0); + + if (!pyloader_init() || !pymodule_init() || !factory_init()) + { + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "Failed to load Python"); + return; + } + + /*PyImport_ImportModule("irssi_startup");*/ + /* Install the custom output handlers, import hook and reload function */ + /* XXX: handle import error */ + PyRun_SimpleString( + "import irssi_startup\n" + ); + + //assert(signal(SIGINT, intr_catch) != SIG_ERR); + + command_bind("pyload", NULL, (SIGNAL_FUNC) cmd_load); + command_bind("pyunload", NULL, (SIGNAL_FUNC) cmd_unload); + command_bind("pylist", NULL, (SIGNAL_FUNC) cmd_list); + command_bind("pyexec", NULL, (SIGNAL_FUNC) cmd_exec); + module_register(MODULE_NAME, "core"); +} + +void irssi_python_deinit(void) +{ + command_unbind("pyload", (SIGNAL_FUNC) cmd_load); + command_unbind("pyunload", (SIGNAL_FUNC) cmd_unload); + command_unbind("pylist", (SIGNAL_FUNC) cmd_list); + command_unbind("pyexec", (SIGNAL_FUNC) cmd_exec); + + pymodule_deinit(); + pyloader_deinit(); + Py_Finalize(); +} diff --git a/pycore.h b/pycore.h new file mode 100644 index 0000000..ae51924 --- /dev/null +++ b/pycore.h @@ -0,0 +1,7 @@ +#ifndef _PYCORE_H +#define _PYCORE_H + +void irssi_python_init(void); +void irssi_python_deinit(void); + +#endif diff --git a/pyirssi.h b/pyirssi.h new file mode 100644 index 0000000..045557e --- /dev/null +++ b/pyirssi.h @@ -0,0 +1,25 @@ +#ifndef _PYIRSSI_H_ +#define _PYIRSSI_H_ + +#define MODULE_NAME "irssi_python" +#include "config.h" +#include "common.h" +#include "modules.h" +#include "commands.h" +#include "printtext.h" +#include "window-items.h" +#include "window-activity.h" +#include "levels.h" +#include "servers.h" +#include "chat-protocols.h" +#include "channels.h" +#include "queries.h" +#include "nicklist.h" +#include "chatnets.h" +#include "servers-reconnect.h" +#include "masks.h" +#include "rawlog.h" +#include "log.h" +#include "ignore.h" + +#endif diff --git a/pyirssi_irc.h b/pyirssi_irc.h new file mode 100644 index 0000000..31b0908 --- /dev/null +++ b/pyirssi_irc.h @@ -0,0 +1,16 @@ +#ifndef _PY_IRSSI_IRC_H_ +#define _PY_IRSSI_IRC_H_ + +#include "pyirssi.h" +#include "irc.h" +#include "irc-servers.h" +#include "irc-channels.h" +#include "ctcp.h" +#include "mode-lists.h" +#include "bans.h" +#include "dcc.h" +#include "dcc-get.h" +#include "dcc-send.h" +#include "dcc-chat.h" + +#endif diff --git a/pyloader.c b/pyloader.c new file mode 100644 index 0000000..6c7fdfe --- /dev/null +++ b/pyloader.c @@ -0,0 +1,306 @@ +#include +#include +#include "pyirssi.h" +#include "pyloader.h" +#include "pyutils.h" +#include "pyscript-object.h" + +/* List of loaded modules */ +static PyObject *script_modules; + +/* List of load paths for scripts */ +static GSList *script_paths = NULL; + +static PyObject *py_get_script(const char *name, int *id); +static int py_load_module(PyObject *module, const char *path); +static char *py_find_script(const char *name); + +/* Add to the list of script load paths */ +void pyloader_add_script_path(const char *path) +{ + PyObject *ppath = PySys_GetObject("path"); + if (ppath) + { + PyList_Append(ppath, PyString_FromString(path)); + script_paths = g_slist_append(script_paths, g_strdup(path)); + } +} + +/* Loads a file into a module; it is not inserted into sys.modules */ +static int py_load_module(PyObject *module, const char *path) +{ + PyObject *dict, *ret, *fp; + + if (PyModule_AddStringConstant(module, "__file__", (char *)path) < 0) + return 0; + + dict = PyModule_GetDict(module); + + if (PyDict_SetItemString(dict, "__builtins__", PyEval_GetBuiltins()) < 0) + return 0; + + /* Dont use the standard library to avoid incompatabilities with + the FILE structure and Python */ + fp = PyFile_FromString((char *)path, "r"); + if (!fp) + return 0; + + ret = PyRun_File(PyFile_AsFile(fp), path, Py_file_input, dict, dict); + Py_DECREF(fp); /* XXX: I assume that the file is closed when refs drop to zero? */ + if (!ret) + return 0; + + Py_DECREF(ret); + return 1; + +} + +/* looks up name in Irssi script directories + returns full path or NULL if not found */ +static char *py_find_script(const char *name) +{ + GSList *node; + char *fname; + char *path = NULL; + + //XXX: what if there's another ext? + if (!file_has_ext(name, "py")) + fname = g_strdup_printf("%s.py", name); + else + fname = (char *)name; + + /*XXX: use case insensitive path search? */ + for (node = script_paths; node != NULL && !path; node = node->next) + { + path = g_strdup_printf("%s/%s", (char *)node->data, fname); + + if (!g_file_test(path, G_FILE_TEST_IS_REGULAR)) + { + g_free(path); + path = NULL; + } + } + + if (fname != name) + g_free(fname); + + return path; +} + +/* Load a script manually using PyRun_File. + * This expects a null terminated array of strings + * (such as from g_strsplit) of the command line. + * The array needs at least one item + */ +int pyloader_load_script_argv(char **argv) +{ + PyObject *module = NULL, *script = NULL; + char *name = NULL, *path = NULL; + + if (py_get_script(argv[0], NULL) != NULL) + { + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "script %s already loaded", argv[0]); + return 0; + } + + path = py_find_script(argv[0]); + if (!path) + { + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "script %s does not exist", argv[0]); + return 0; + } + + name = file_get_filename(path); + module = PyModule_New(name); + g_free(name); + + if (!module) + goto error; + + script = pyscript_new(module, argv); + Py_DECREF(module); + + if (!script) + goto error; + + /* insert script obj into module dict, load file */ + if (PyModule_AddObject(module, "_script", script) < 0) + goto error; + Py_INCREF(script); + + if (!py_load_module(module, path)) + goto error; + + PyList_Append(script_modules, script); + Py_DECREF(script); + g_free(path); + + /* PySys_WriteStdout("load %s, script -> 0x%x\n", argv[0], script); */ + + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "loaded script %s", argv[0]); + return 1; + +error: + if (PyErr_Occurred()) + PyErr_Print(); + else + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "error loading script %s", argv[0]); + + Py_XDECREF(script); + g_free(path); + + return 0; +} + +int pyloader_load_script(char *name) +{ + char *argv[2]; + + argv[0] = name; + argv[1] = NULL; + + return pyloader_load_script_argv(argv); +} + +static PyObject *py_get_script(const char *name, int *id) +{ + int i; + + g_return_val_if_fail(script_modules != NULL, NULL); + + for (i = 0; i < PyList_Size(script_modules); i++) + { + PyObject *script; + char *sname; + + script = PyList_GET_ITEM(script_modules, i); + sname = pyscript_get_name(script); + + if (sname && !strcmp(sname, name)) + { + if (id) + *id = i; + return script; + } + } + + return NULL; +} + +int pyloader_unload_script(const char *name) +{ + int id; + PyObject *script = py_get_script(name, &id); + + if (!script) + { + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "%s is not loaded", name); + return 0; + } + + PySys_WriteStdout("unload %s, script -> 0x%x\n", name, script); + + pyscript_remove_signals(script); + pyscript_clear_modules(script); + + if (PySequence_DelItem(script_modules, id) < 0) + { + PyErr_Print(); + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "error unloading script %s", name); + return 0; + } + + /* Probably a good time to call the garbage collecter to clean up reference cycles */ + PyGC_Collect(); + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "unloaded script %s", name); + + return 1; +} + +GSList *pyloader_list(void) +{ + int i; + GSList *list = NULL; + + g_return_val_if_fail(script_modules != NULL, NULL); + + for (i = 0; i < PyList_Size(script_modules); i++) + { + PyObject *scr; + char *name, *file; + + scr = PyList_GET_ITEM(script_modules, i); + name = pyscript_get_name(scr); + file = pyscript_get_filename(scr); + + if (name && file) + { + PY_LIST_REC *rec; + rec = g_new0(PY_LIST_REC, 1); + + rec->name = g_strdup(name); + rec->file = g_strdup(file); + list = g_slist_append(list, rec); + } + } + + return list; +} + +void pyloader_list_destroy(GSList **list) +{ + GSList *node; + + if (*list == NULL) + return; + + for (node = *list; node != NULL; node = node->next) + { + PY_LIST_REC *rec = node->data; + + g_free(rec->name); + g_free(rec->file); + g_free(rec); + } + + g_slist_free(*list); + + *list = NULL; +} + +int pyloader_init(void) +{ + char *pyhome; + + g_return_val_if_fail(script_paths == NULL, 0); + g_return_val_if_fail(script_modules == NULL, 0); + + script_modules = PyList_New(0); + if (!script_modules) + return 0; + + /* XXX: load autorun scripts here */ + /* Add script location to the load path (add more paths later) */ + pyhome = g_strdup_printf("%s/scripts", get_irssi_dir()); + pyloader_add_script_path(pyhome); + g_free(pyhome); + + return 1; +} + +void pyloader_deinit(void) +{ + GSList *node; + + g_return_if_fail(script_paths != NULL); + g_return_if_fail(script_modules != NULL); + + for (node = script_paths; node != NULL; node = node->next) + g_free(node->data); + g_slist_free(script_paths); + script_paths = NULL; + + /* script specific resources (cmd + signal handlers) should be removed + by the object's destructor */ + Py_DECREF(script_modules); +} diff --git a/pyloader.h b/pyloader.h new file mode 100644 index 0000000..f7e19fb --- /dev/null +++ b/pyloader.h @@ -0,0 +1,21 @@ +#ifndef _PYLOADER_H_ +#define _PYLOADER_H_ + +typedef struct +{ + char *name; + char *file; +} PY_LIST_REC; + +void pyloader_add_script_path(const char *path); +int pyloader_load_script_argv(char **argv); +int pyloader_load_script(char *name); +int pyloader_unload_script(const char *name); + +GSList *pyloader_list(void); +void pyloader_list_destroy(GSList **list); + +int pyloader_init(void); +void pyloader_deinit(void); + +#endif diff --git a/pymodule.c b/pymodule.c new file mode 100644 index 0000000..c29940b --- /dev/null +++ b/pymodule.c @@ -0,0 +1,817 @@ +#include +#include +#include "pymodule.h" +#include "pyirssi_irc.h" +#include "pyscript-object.h" +#include "factory.h" +#include "pyutils.h" + +/* + * This module is some what different than the Perl's. + * Script specific operations are handled by the Script object + * instead of by a function in this module. command_bind, + * signal_bind, etc require data to be saved about the script + * for cleanup purposes, so I moved those functions to the script + * object. + */ + +/* Main embedded module */ +PyObject *py_module = NULL; + +static PyObject *find_script(void); + +/* Module functions */ +/*XXX: prefix PY to avoid ambiguity with py_command function */ +PyDoc_STRVAR(PY_command_doc, + "Execute command" +); +static PyObject *PY_command(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"cmd", NULL}; + char *cmd = ""; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &cmd)) + return NULL; + + py_command(cmd, NULL, NULL); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(py_prnt_doc, + "print output" +); +/*XXX: print is a python keyword, so abbreviate it */ +static PyObject *py_prnt(PyObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = {"text", "msglvl", NULL}; + int msglvl = MSGLEVEL_CLIENTNOTICE; + char *text = NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|i:prnt", kwlist, + &text, &msglvl)) + return NULL; + + printtext_string(NULL, NULL, msglvl, text); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(py_get_script_doc, + "Get Irssi script object" +); +static PyObject *py_get_script(PyObject *self, PyObject *args) +{ + PyObject *ret = find_script(); + + /* XXX: type check */ + + if (!ret) + PyErr_SetString(PyExc_RuntimeError, "unable to find script object"); + else + Py_INCREF(ret); + + return ret; +} + +PyDoc_STRVAR(py_chatnet_find_doc, + "Find chat network with name" +); +static PyObject *py_chatnet_find(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"name", NULL}; + char *name = ""; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &name)) + return NULL; + + return py_irssi_chat_new(chatnet_find(name), 1); +} + +PyDoc_STRVAR(py_chatnets_doc, + "Return a list of all chatnets" +); +static PyObject *py_chatnets(PyObject *self, PyObject *args) +{ + return py_irssi_chatlist_new(chatnets, 1); +} + +PyDoc_STRVAR(py_reconnects_doc, + "Return a list of all reconnects" +); +static PyObject *py_reconnects(PyObject *self, PyObject *args) +{ + return py_irssi_objlist_new(reconnects, 1, (InitFunc)pyreconnect_new); +} + +PyDoc_STRVAR(py_servers_doc, + "Return a list of all servers" +); +static PyObject *py_servers(PyObject *self, PyObject *args) +{ + return py_irssi_chatlist_new(servers, 1); +} + +PyDoc_STRVAR(py_channels_doc, + "Return channel list" +); +static PyObject *py_channels(PyObject *self, PyObject *args) +{ + return py_irssi_chatlist_new(channels, 1); +} + +PyDoc_STRVAR(py_channel_find_doc, + "Find channel from any server" +); +static PyObject *py_channel_find(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"name", NULL}; + char *name = ""; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &name)) + return NULL; + + return py_irssi_chat_new(channel_find(NULL, name), 1); +} + +PyDoc_STRVAR(py_windows_doc, + "Get a list of all windows" +); +static PyObject *py_windows(PyObject *self, PyObject *args) +{ + return py_irssi_objlist_new(windows, 1, (InitFunc)pywindow_new); +} + +PyDoc_STRVAR(py_active_win_doc, + "Return active window" +); +static PyObject *py_active_win(PyObject *self, PyObject *args) +{ + if (active_win) + return pywindow_new(active_win); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(py_active_server_doc, + "Return active server" +); +static PyObject *py_active_server(PyObject *self, PyObject *args) +{ + if (active_win) + return py_irssi_chat_new(active_win->active_server, 1); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(py_window_find_name_doc, + "Find window with name" +); +static PyObject *py_window_find_name(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"name", NULL}; + char *name = ""; + WINDOW_REC *win; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &name)) + return NULL; + + win = window_find_name(name); + if (win) + return pywindow_new(win); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(py_window_find_refnum_doc, + "Find window with reference number" +); +static PyObject *py_window_find_refnum(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"refnum", NULL}; + int refnum = 0; + WINDOW_REC *win; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "i", kwlist, + &refnum)) + return NULL; + + win = window_find_refnum(refnum); + if (win) + return pywindow_new(win); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(py_window_refnum_prev_doc, + "Return refnum for window that's previous in window list" +); +static PyObject *py_window_refnum_prev(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"refnum", "wrap", NULL}; + int refnum = 0; + int wrap = 0; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "ii", kwlist, + &refnum, &wrap)) + return NULL; + + return PyInt_FromLong(window_refnum_prev(refnum, wrap)); +} + +PyDoc_STRVAR(py_window_refnum_next_doc, + "Return refnum for window that's next in window list" +); +static PyObject *py_window_refnum_next(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"refnum", "wrap", NULL}; + int refnum = 0; + int wrap = 0; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "ii", kwlist, + &refnum, &wrap)) + return NULL; + + return PyInt_FromLong(window_refnum_next(refnum, wrap)); +} + +PyDoc_STRVAR(py_windows_refnum_last_doc, + "Return refnum for last window." +); +static PyObject *py_windows_refnum_last(PyObject *self, PyObject *args) +{ + return PyInt_FromLong(windows_refnum_last()); +} + +PyDoc_STRVAR(py_window_find_level_doc, + "Find window with level." +); +static PyObject *py_window_find_level(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"level", NULL}; + int level = 0; + WINDOW_REC *win; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "i", kwlist, + &level)) + return NULL; + + win = window_find_level(NULL, level); + if (win) + return pywindow_new(win); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(py_window_find_item_doc, + "Find window which contains window item with specified name." +); +static PyObject *py_window_find_item(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"name", NULL}; + char *name = ""; + WINDOW_REC *win; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &name)) + return NULL; + + win = window_find_item(NULL, name); + if (win) + return pywindow_new(win); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(py_window_find_closest_doc, + "Find window that matches best to given arguments. `name' can be either" + "window name or name of one of the window items." +); +static PyObject *py_window_find_closest(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"name", "level", NULL}; + char *name = ""; + int level = 0; + WINDOW_REC *win; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "si", kwlist, + &name, &level)) + return NULL; + + win = window_find_closest(NULL, name, level); + if (win) + return pywindow_new(win); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(py_window_item_find_doc, + "Find window item that matches best to given arguments." +); +static PyObject *py_window_item_find(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"name", NULL}; + char *name = ""; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &name)) + return NULL; + + return py_irssi_chat_new(window_item_find(NULL, name), 1); +} + +/*XXX: this could be __init__ for Window */ +PyDoc_STRVAR(py_window_create_doc, + "window_create(item=None, automatic=False) -> Window object\n" + "\n" + "Create a new window\n" +); +static PyObject *py_window_create(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"item", "automatic", NULL}; + PyObject *item = NULL; + int automatic = 0; + WI_ITEM_REC *witem = NULL; + WINDOW_REC *win; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Oi", kwlist, + &item, &automatic)) + return NULL; + + if (item) + { + if (pywindow_item_check(item)) + { + witem = ((PyWindowItem*)item)->data; + if (!witem) + return PyErr_Format(PyExc_TypeError, "invalid window item"); + else if (witem->server != NULL) + return PyErr_Format(PyExc_TypeError, "window item already assigned to window"); + } + else if (item == Py_None) + ; + else + return PyErr_Format(PyExc_TypeError, "item must be window item or None"); + } + + win = window_create(witem, automatic); + if (win) + return pywindow_new(win); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(py_server_find_tag_doc, + "Find server with tag" +); +static PyObject *py_server_find_tag(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"tag", NULL}; + char *tag = ""; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &tag)) + return NULL; + + return py_irssi_chat_new(server_find_tag(tag), 1); +} + +PyDoc_STRVAR(py_server_find_chatnet_doc, + "Find first server that is in chatnet" +); +static PyObject *py_server_find_chatnet(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"chatnet", NULL}; + char *chatnet = ""; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &chatnet)) + return NULL; + + return py_irssi_chat_new(server_find_chatnet(chatnet), 1); +} + +PyDoc_STRVAR(py_queries_doc, + "Return a list of open queries." +); +static PyObject *py_queries(PyObject *self, PyObject *args) +{ + return py_irssi_chatlist_new(queries, 1); +} + +PyDoc_STRVAR(py_query_find_doc, + "Find a query from any server." +); +static PyObject *py_query_find(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"nick", NULL}; + char *nick = ""; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &nick)) + return NULL; + + return py_irssi_chat_new(query_find(NULL, nick), 1); +} + +PyDoc_STRVAR(py_mask_match_doc, + "Return true if mask matches nick!user@host" +); +static PyObject *py_mask_match(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"mask", "nick", "user", "host", NULL}; + char *mask = ""; + char *nick = ""; + char *user = ""; + char *host = ""; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "ssss", kwlist, + &mask, &nick, &user, &host)) + return NULL; + + return PyBool_FromLong(mask_match(NULL, mask, nick, user, host)); +} + +PyDoc_STRVAR(py_mask_match_address_doc, + "Return True if mask matches nick!address" +); +static PyObject *py_mask_match_address(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"mask", "nick", "address", NULL}; + char *mask = ""; + char *nick = ""; + char *address = ""; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "sss", kwlist, + &mask, &nick, &address)) + return NULL; + + return PyBool_FromLong(mask_match_address(NULL, mask, nick, address)); +} + +PyDoc_STRVAR(py_masks_match_doc, + "Return True if any mask in the masks (string separated by spaces)\n" + "matches nick!address\n" +); +static PyObject *py_masks_match(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"masks", "nick", "address", NULL}; + char *masks = ""; + char *nick = ""; + char *address = ""; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "sss", kwlist, + &masks, &nick, &address)) + return NULL; + + return PyBool_FromLong(masks_match(NULL, masks, nick, address)); +} + +PyDoc_STRVAR(py_rawlog_set_size_doc, + "Set the default rawlog size for new rawlogs." +); +static PyObject *py_rawlog_set_size(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"lines", NULL}; + int lines = 0; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "i", kwlist, + &lines)) + return NULL; + + rawlog_set_size(lines); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(py_logs_doc, + "Return list of logs." +); +static PyObject *py_logs(PyObject *self, PyObject *args) +{ + return py_irssi_objlist_new(logs, 1, (InitFunc)pylog_new); +} + +PyDoc_STRVAR(py_log_find_doc, + "Find log by file name." +); +static PyObject *py_log_find(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"fname", NULL}; + char *fname = ""; + LOG_REC *log; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &fname)) + return NULL; + + log = log_find(fname); + if (log) + return pylog_new(log); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(py_ignores_doc, + "Return a list of ignore entries" +); +static PyObject *py_ignores(PyObject *self, PyObject *args) +{ + return py_irssi_objlist_new(ignores, 1, (InitFunc)pyignore_new); +} + +PyDoc_STRVAR(py_ignore_check_doc, + "Return True if ignore matches" +); +static PyObject *py_ignore_check(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"nick", "host", "channel", "text", "level", NULL}; + char *nick = ""; + char *host = ""; + char *channel = ""; + char *text = ""; + int level = 0; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "ssssi", kwlist, + &nick, &host, &channel, &text, &level)) + return NULL; + + return PyBool_FromLong(ignore_check(NULL, nick, host, channel, text, level)); +} + +PyDoc_STRVAR(py_dccs_doc, + "Return list of active DCCs" +); +static PyObject *py_dccs(PyObject *self, PyObject *args) +{ + return py_irssi_list_new(dcc_conns, 1); +} + +PyDoc_STRVAR(py_dcc_register_type_doc, + "???" +); +static PyObject *py_dcc_register_type(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"type", NULL}; + char *type = ""; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &type)) + return NULL; + + dcc_register_type(type); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(py_dcc_unregister_type_doc, + "???" +); +static PyObject *py_dcc_unregister_type(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"type", NULL}; + char *type = ""; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &type)) + return NULL; + + dcc_unregister_type(type); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(py_dcc_find_request_latest_doc, + "???" +); +static PyObject *py_dcc_find_request_latest(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"type", NULL}; + int type = 0; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "i", kwlist, + &type)) + return NULL; + + return py_irssi_new(dcc_find_request_latest(type), 1); +} + +PyDoc_STRVAR(py_dcc_find_request_doc, + "???" +); +static PyObject *py_dcc_find_request(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"type", "nick", "arg", NULL}; + int type = 0; + char *nick = ""; + char *arg = ""; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "iss", kwlist, + &type, &nick, &arg)) + return NULL; + + return py_irssi_new(dcc_find_request(type, nick, arg), 1); +} + +PyDoc_STRVAR(py_dcc_chat_find_id_doc, + "???" +); +static PyObject *py_dcc_chat_find_id(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"id", NULL}; + char *id = ""; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &id)) + return NULL; + + return py_irssi_new(dcc_chat_find_id(id), 1); +} + +PyDoc_STRVAR(py_dcc_str2type_doc, + "???" +); +static PyObject *py_dcc_str2type(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"type", NULL}; + char *type = ""; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &type)) + return NULL; + + return PyInt_FromLong(dcc_str2type(type)); +} + +PyDoc_STRVAR(py_dcc_type2str_doc, + "???" +); +static PyObject *py_dcc_type2str(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"type", NULL}; + int type = 0; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "i", kwlist, + &type)) + return NULL; + + RET_AS_STRING_OR_NONE(dcc_type2str(type)); +} + +PyDoc_STRVAR(py_dcc_get_download_path_doc, + "???" +); +static PyObject *py_dcc_get_download_path(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"fname", NULL}; + char *fname = ""; + char *path; + PyObject *pypath; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &fname)) + return NULL; + + path = dcc_get_download_path(fname); + if (!path) + Py_RETURN_NONE; /*XXX: how to handle? */ + + pypath = PyString_FromString(path); + g_free(path); + + return pypath; +} + +static PyMethodDef ModuleMethods[] = { + {"prnt", (PyCFunction)py_prnt, METH_VARARGS|METH_KEYWORDS, py_prnt_doc}, + {"get_script", (PyCFunction)py_get_script, METH_NOARGS, py_get_script_doc}, + {"chatnet_find", (PyCFunction)py_chatnet_find, METH_VARARGS | METH_KEYWORDS, + py_chatnet_find_doc}, + {"chatnets", (PyCFunction)py_chatnets, METH_NOARGS, + py_chatnets_doc}, + {"reconnects", (PyCFunction)py_reconnects, METH_NOARGS, + py_reconnects_doc}, + {"servers", (PyCFunction)py_servers, METH_NOARGS, + py_servers_doc}, + {"windows", (PyCFunction)py_windows, METH_NOARGS, + py_windows_doc}, + {"active_win", (PyCFunction)py_active_win, METH_NOARGS, + py_active_win_doc}, + {"active_server", (PyCFunction)py_active_server, METH_NOARGS, + py_active_server_doc}, + {"window_find_name", (PyCFunction)py_window_find_name, METH_VARARGS | METH_KEYWORDS, + py_window_find_name_doc}, + {"window_find_refnum", (PyCFunction)py_window_find_refnum, METH_VARARGS | METH_KEYWORDS, + py_window_find_refnum_doc}, + {"window_refnum_prev", (PyCFunction)py_window_refnum_prev, METH_VARARGS | METH_KEYWORDS, + py_window_refnum_prev_doc}, + {"window_refnum_next", (PyCFunction)py_window_refnum_next, METH_VARARGS | METH_KEYWORDS, + py_window_refnum_next_doc}, + {"windows_refnum_last", (PyCFunction)py_windows_refnum_last, METH_NOARGS, + py_windows_refnum_last_doc}, + {"window_find_level", (PyCFunction)py_window_find_level, METH_VARARGS | METH_KEYWORDS, + py_window_find_level_doc}, + {"window_find_item", (PyCFunction)py_window_find_item, METH_VARARGS | METH_KEYWORDS, + py_window_find_item_doc}, + {"window_find_closest", (PyCFunction)py_window_find_closest, METH_VARARGS | METH_KEYWORDS, + py_window_find_closest_doc}, + {"window_item_find", (PyCFunction)py_window_item_find, METH_VARARGS | METH_KEYWORDS, + py_window_item_find_doc}, + {"window_create", (PyCFunction)py_window_create, METH_VARARGS | METH_KEYWORDS, + py_window_create_doc}, + {"server_find_tag", (PyCFunction)py_server_find_tag, METH_VARARGS | METH_KEYWORDS, + py_server_find_tag_doc}, + {"server_find_chatnet", (PyCFunction)py_server_find_chatnet, METH_VARARGS | METH_KEYWORDS, + py_server_find_chatnet_doc}, + {"command", (PyCFunction)PY_command, METH_VARARGS | METH_KEYWORDS, + PY_command_doc}, + {"channels", (PyCFunction)py_channels, METH_NOARGS, + py_channels_doc}, + {"channel_find", (PyCFunction)py_channel_find, METH_VARARGS | METH_KEYWORDS, + py_channel_find_doc}, + {"query_find", (PyCFunction)py_query_find, METH_VARARGS | METH_KEYWORDS, + py_query_find_doc}, + {"queries", (PyCFunction)py_queries, METH_NOARGS, + py_queries_doc}, + {"mask_match", (PyCFunction)py_mask_match, METH_VARARGS | METH_KEYWORDS, + py_mask_match_doc}, + {"mask_match_address", (PyCFunction)py_mask_match_address, METH_VARARGS | METH_KEYWORDS, + py_mask_match_address_doc}, + {"masks_match", (PyCFunction)py_masks_match, METH_VARARGS | METH_KEYWORDS, + py_masks_match_doc}, + {"rawlog_set_size", (PyCFunction)py_rawlog_set_size, METH_VARARGS | METH_KEYWORDS, + py_rawlog_set_size_doc}, + {"logs", (PyCFunction)py_logs, METH_NOARGS, + py_logs_doc}, + {"log_find", (PyCFunction)py_log_find, METH_VARARGS | METH_KEYWORDS, + py_log_find_doc}, + {"ignores", (PyCFunction)py_ignores, METH_NOARGS, + py_ignores_doc}, + {"ignore_check", (PyCFunction)py_ignore_check, METH_VARARGS | METH_KEYWORDS, + py_ignore_check_doc}, + {"dccs", (PyCFunction)py_dccs, METH_NOARGS, + py_dccs_doc}, + {"dcc_register_type", (PyCFunction)py_dcc_register_type, METH_VARARGS | METH_KEYWORDS, + py_dcc_register_type_doc}, + {"dcc_unregister_type", (PyCFunction)py_dcc_unregister_type, METH_VARARGS | METH_KEYWORDS, + py_dcc_unregister_type_doc}, + {"dcc_find_request_latest", (PyCFunction)py_dcc_find_request_latest, METH_VARARGS | METH_KEYWORDS, + py_dcc_find_request_latest_doc}, + {"dcc_find_request", (PyCFunction)py_dcc_find_request, METH_VARARGS | METH_KEYWORDS, + py_dcc_find_request_doc}, + {"dcc_chat_find_id", (PyCFunction)py_dcc_chat_find_id, METH_VARARGS | METH_KEYWORDS, + py_dcc_chat_find_id_doc}, + {"dcc_str2type", (PyCFunction)py_dcc_str2type, METH_VARARGS | METH_KEYWORDS, + py_dcc_str2type_doc}, + {"dcc_type2str", (PyCFunction)py_dcc_type2str, METH_VARARGS | METH_KEYWORDS, + py_dcc_type2str_doc}, + {"dcc_get_download_path", (PyCFunction)py_dcc_get_download_path, METH_VARARGS | METH_KEYWORDS, + py_dcc_get_download_path_doc}, + {NULL, NULL, 0, NULL} /* Sentinel */ +}; + +/* Traverse stack backwards to find the nearest _script object in globals */ +static PyObject *find_script(void) +{ + PyFrameObject *frame; + + for (frame = PyEval_GetFrame(); frame != NULL; frame = frame->f_back) + { + g_return_val_if_fail(frame->f_globals != NULL, NULL); + PyObject *script = PyDict_GetItemString(frame->f_globals, "_script"); + if (script) + { + /* + PySys_WriteStdout("Found script at %s in %s, script -> 0x%x\n", + PyString_AS_STRING(frame->f_code->co_name), + PyString_AS_STRING(frame->f_code->co_filename), script); + */ + return script; + } + } + + return NULL; +} + +int pymodule_init(void) +{ + g_return_val_if_fail(py_module == NULL, 0); + + py_module = Py_InitModule("_irssi", ModuleMethods); + if (!py_module) + return 0; + + return 1; +} + +void pymodule_deinit(void) +{ + g_return_if_fail(py_module != NULL); + + Py_DECREF(py_module); + py_module = NULL; +} diff --git a/pymodule.h b/pymodule.h new file mode 100644 index 0000000..242eecf --- /dev/null +++ b/pymodule.h @@ -0,0 +1,11 @@ +#ifndef _PY_MODULE_H_ +#define _PY_MODULE_H_ + +#include + +/* This is global so that type objects and such can be easily attached */ +extern PyObject *py_module; +int pymodule_init(void); +void pymodule_deinit(void); + +#endif diff --git a/pysignals.c b/pysignals.c new file mode 100644 index 0000000..c3764fb --- /dev/null +++ b/pysignals.c @@ -0,0 +1,95 @@ +#include +#include "pyirssi.h" +#include "pysignals.h" +#include "factory.h" + +static void py_command_proxy(char *data, SERVER_REC *server, WI_ITEM_REC *witem); + +/* crec should be owned by a Script object */ +void py_command_bind(const char *category, PY_COMMAND_REC *crec) +{ + command_bind_full(MODULE_NAME, SIGNAL_PRIORITY_DEFAULT, crec->name, + -1, category, (SIGNAL_FUNC)py_command_proxy, crec); +} + +void py_command_unbind(PY_COMMAND_REC *crec) +{ + command_unbind_full(crec->name, (SIGNAL_FUNC)py_command_proxy, crec); +} + +/* This is just for testing. A complete version would use a signal map, a better + wrapper object factory system, and a generic signal handler like in the Perl + bindings */ +static void py_command_proxy(char *data, SERVER_REC *server, WI_ITEM_REC *witem) +{ + PY_COMMAND_REC *crec; + PyObject *ret, *pyserver, *pywitem; + +#if 0 + if (server) + { + CHAT_PROTOCOL_REC *chat = chat_protocol_find_id(server->chat_type); + if (chat && !strcmp(chat->name, "IRC")) + pyserver = pyirc_server_new(server); + else + pyserver = pyserver_new(server); + g_assert(pyserver != NULL); + } + else + { + pyserver = Py_None; + Py_INCREF(pyserver); + } + if (witem) + { + char *type = module_find_id_str("WINDOW ITEM TYPE", witem->type); + g_assert(type != NULL); + + if (!strcmp("CHANNEL", type)) + pywitem = pychannel_new(witem); + else if (!strcmp("QUERY", type)) + pywitem = pyquery_new(witem); + else + pywitem = pywindow_item_new(witem); + + g_assert(pywitem != NULL); + } + else + { + pywitem = Py_None; + Py_INCREF(pywitem); + } +#endif + + if (server) + { + pyserver = py_irssi_chat_new(server, 1); + g_assert(pyserver != NULL); + } + else + { + pyserver = Py_None; + Py_INCREF(Py_None); + } + + if (witem) + { + pywitem = py_irssi_chat_new(witem, 1); + g_assert(pywitem != NULL); + } + else + { + pywitem = Py_None; + Py_INCREF(Py_None); + } + + crec = signal_get_user_data(); + ret = PyObject_CallFunction(crec->handler, "(sOO)", data, pyserver, pywitem); + if (!ret) + PyErr_Print(); + else + Py_DECREF(ret); + + Py_DECREF(pyserver); + Py_DECREF(pywitem); +} diff --git a/pysignals.h b/pysignals.h new file mode 100644 index 0000000..823472e --- /dev/null +++ b/pysignals.h @@ -0,0 +1,14 @@ +#ifndef _PYSIGNALS_H_ +#define _PYSIGNALS_H_ +#include + +typedef struct +{ + char *name; + PyObject *handler; +} PY_COMMAND_REC; + +void py_command_bind(const char *category, PY_COMMAND_REC *crec); +void py_command_unbind(PY_COMMAND_REC *crec); + +#endif diff --git a/pyutils.c b/pyutils.c new file mode 100644 index 0000000..05e1f31 --- /dev/null +++ b/pyutils.c @@ -0,0 +1,81 @@ +#include +#include "pyirssi.h" +#include "pyutils.h" +#include "settings.h" +#include "servers.h" + +/* copy paste from perl bindings */ +void py_command(const char *cmd, SERVER_REC *server, WI_ITEM_REC *item) +{ + const char *cmdchars; + char *sendcmd = (char *) cmd; + + if (*cmd == '\0') + return; + + cmdchars = settings_get_str("cmdchars"); + if (strchr(cmdchars, *cmd) == NULL) { + /* no command char - let's put it there.. */ + sendcmd = g_strdup_printf("%c%s", *cmdchars, cmd); + } + + signal_emit("send command", 3, sendcmd, server, item); + if (sendcmd != cmd) g_free(sendcmd); +} + +/* return the file extension for a file, or empty string + don't free result */ +char *file_get_ext(const char *file) +{ + const char *dot = NULL; + + while (*file) + { + if (*file == '.') + dot = file; + + file++; + } + + if (dot) + return (char *) dot + 1; + + return (char *) file; +} + +int file_has_ext(const char *file, const char *ext) +{ + const char *fext = file_get_ext(file); + + return !strcmp(fext, ext); +} + + +/* return whats in the braces -> /path/to/{filename}.py + result must be freed */ +char *file_get_filename(const char *path) +{ + const char *begin; + const char *end; + char *name; + size_t len; + + begin = strrchr(path, '/'); + if (!begin) + begin = path; + else + begin++; + + end = strrchr(begin, '.'); + if (end != NULL && end > begin) + len = end - begin; + else + len = strlen(begin); + + name = g_strnfill(len, 0); + + strncpy(name, begin, len); + + return name; +} + diff --git a/pyutils.h b/pyutils.h new file mode 100644 index 0000000..7b2ca7b --- /dev/null +++ b/pyutils.h @@ -0,0 +1,12 @@ +#ifndef _PYUTILS_H_ +#define _PYUTILS_H_ + +#include "servers.h" + +void py_command(const char *cmd, SERVER_REC *server, WI_ITEM_REC *item); +char *file_get_ext(const char *file); +int file_has_ext(const char *file, const char *ext); +char *file_get_filename(const char *path); + + +#endif diff --git a/scripts/CVS/Entries b/scripts/CVS/Entries new file mode 100644 index 0000000..c13e17d --- /dev/null +++ b/scripts/CVS/Entries @@ -0,0 +1,3 @@ +/hello.py/1.3/Thu Jun 8 03:08:39 2006// +/dumper.py/1.8/Mon Jun 12 11:07:19 2006// +D diff --git a/scripts/CVS/Repository b/scripts/CVS/Repository new file mode 100644 index 0000000..51ee3e5 --- /dev/null +++ b/scripts/CVS/Repository @@ -0,0 +1 @@ +irssi-python/scripts diff --git a/scripts/CVS/Root b/scripts/CVS/Root new file mode 100644 index 0000000..de78dd2 --- /dev/null +++ b/scripts/CVS/Root @@ -0,0 +1 @@ +:ext:loafier@sverige.freeshell.org/arpa/gm/l/loafier/cvsroot diff --git a/scripts/dumper.py b/scripts/dumper.py new file mode 100644 index 0000000..b11e24c --- /dev/null +++ b/scripts/dumper.py @@ -0,0 +1,188 @@ +# type /pyload dumper + +import sys +import irssi + +__script = None +__last_witem = None +__last_server = None + +def cmd_pydumper(data, server, witem): + assert isinstance(server, irssi.Server), "This should be a Server" + assert isinstance(witem, irssi.WindowItem), "This should be a WindowItem" + assert isinstance(witem, irssi.Query) or \ + isinstance(witem, irssi.Channel), \ + "... and be a Query or Channel" + + server.channels_join("#neblooh") + #server.disconnect() + sc = server.connect + + print 'witem.server', witem.server + + print 'Server.Connect', sc + print 'connect.type', sc.type + print 'connect.type_id', sc.type_id + print 'connect.chat_type', sc.chat_type + print 'connect.chat_type_id', sc.chat_type_id + print 'connect.address', sc.address + print 'connect.port', sc.port + print 'connect.chatnet', sc.chatnet + print 'connect.password', sc.password + print 'connect.wanted_nick', sc.wanted_nick + print 'connect.username', sc.username + print 'connect.realname', sc.realname + if isinstance(sc, irssi.IrcConnect): + print 'IRC Connect items:' + print 'connect.alternate_nick', sc.alternate_nick + + print + print + print 'Server', server + print 'server.type', server.type + print 'server.type_id', server.type_id + print 'server.chat_type', server.chat_type + print 'server.chat_type_id', server.chat_type_id + print 'server.connect_time', server.connect_time + print 'server.real_connect_time', server.real_connect_time + print 'server.tag', server.tag + print 'server.nick', server.nick + print 'server.connected', server.connected + print 'server.connection_lost', server.connection_lost + print 'server.rawlog', server.rawlog + print 'server.version', server.version + print 'server.last_invite', server.server_operator + print 'server.usermode_away', server.usermode_away + print 'server.away_reason', server.away_reason + print 'server.banned', server.banned + print 'server.lag', server.lag + if isinstance(server, irssi.IrcServer): + print 'IRC Server items:' + print 'server.real_address', server.real_address + print 'server.usermode', server.usermode + print 'server.userhost', server.userhost + + print + print + print 'Witem', witem + print 'witem.type', witem.type + print 'witem.type_id', witem.type_id + print 'witem.chat_type', witem.chat_type + print 'witem.chat_type_id', witem.chat_type_id + print 'witem.server', witem.server + print 'witem.name', witem.name + print 'witem.createtime', witem.createtime + print 'witem.data_level', witem.data_level + print 'witem.hilight_color', witem.hilight_color + + #if witem.type == "CHANNEL": + if isinstance(witem, irssi.Channel): + print 'channel items:' + print 'witem.topic', witem.topic + print 'witem.topic_by', witem.topic_by + print 'witem.topic_time', witem.topic_time + print 'witem.no_modes', witem.no_modes + print 'witem.mode', witem.mode + print 'witem.limit', witem.limit + print 'witem.key', witem.key + print 'witem.chanop', witem.chanop + print 'witem.names_got', witem.names_got + print 'witem.wholist', witem.wholist + print 'witem.synced', witem.synced + #witem.destroy() + print 'witem.joined', witem.joined + print 'witem.left', witem.left + print 'witem.kicked', witem.kicked + if isinstance(witem, irssi.IrcChannel): + print 'IRC channel:' + print 'witem.bans', witem.bans() + for ban in witem.bans(): + print 'ban.ban', ban.ban + print 'ban.setby', ban.setby + print 'ban.time', ban.time + + #elif witem.type == "QUERY": + elif isinstance(witem, irssi.Query): + print 'query items:' + print 'witem.address', witem.address + witem.change_server(server) + #witem.change_server(witem) + print 'witem.server_tag', witem.server_tag + print 'witem.unwanted', witem.unwanted + + print + print + print 'is nick flag "@"?', server.isnickflag('@') + print 'is nick flag "+"?', server.isnickflag('+') + print 'is nick flag "%"?', server.isnickflag('%') + + print 'is channel "#fuggerd"', server.ischannel('#fuggerd') + print 'is channel "&booh"', server.ischannel('&booh') + print 'is channel "xbooh"', server.ischannel('xbooh') + + print 'nick flags', server.get_nick_flags() + + print irssi.chatnets() + for cn in irssi.chatnets(): + print 'cn.type', cn.type + print 'cn.chat_type', cn.chat_type + print 'cn.name', cn.name + print 'cn.nick', cn.nick + print 'cn.username', cn.username + print 'cn.realname', cn.realname + print 'cn.own_host', cn.own_host + print 'cn.autosendcmd', cn.autosendcmd + print + + print irssi.chatnet_find('ircnet') + print irssi.servers() + print irssi.reconnects() + + print irssi.windows() + for win in irssi.windows(): + print 'win.refnum', win.refnum + print 'win.name', win.name + print 'win.width', win.width + print 'win.height', win.height + print 'win.history_name', win.history_name + print 'win.active', win.active + print 'win.active_server', win.active_server + print 'win.servertag', win.servertag + print 'win.level', win.level + print 'win.sticky_refnum', win.sticky_refnum + print 'win.data_level', win.data_level + print 'win.hilight_color', win.hilight_color + print 'win.last_timestamp', win.last_timestamp + print 'win.last_line', win.last_line + print 'win.theme_name', win.theme_name + print + + """ + print 'printing to channel' + server.send_message('#booh', 'test msg chan', 0) + server.send_message('#booh', 'test msg chan ER', 1) + + print 'printing to nick' + server.send_message('melbo', 'test msg nick', 1) + server.send_message('melbo', 'test msg nick ER', 0) + """ + + witem.prnt('hello there') + global __last_witem + __last_witem = witem + global __last_server + __last_server = server + + #new = irssi.IrssiChatBase() + #print 'New', new.type_id + +def cmd_crashme(data, server, witem): + __last_server.prnt('#booh', 'what up??') + __last_witem.prnt('imma crash mebbe?') + +print dir(_script) +print _script.module +print _script.argv + +irssi.command_bind('pydumper', cmd_pydumper) +irssi.command_bind('crashme', cmd_crashme) diff --git a/scripts/hello.py b/scripts/hello.py new file mode 100644 index 0000000..fd5a814 --- /dev/null +++ b/scripts/hello.py @@ -0,0 +1,20 @@ +# type /pyload hello + +import irssi + +# data - contains the parameters for /HELLO +# server - the active server in window +# witem - the active window item (eg. channel, query) +# or None if the window is empty +def cmd_hello(data, server, witem): + if not server or not server.connected: + irssi.prnt("Not connected to server") + + if data: + server.command("MSG %s Hello!" % data) + elif isinstance(witem, irssi.Channel) or isinstance(witem, irssi.Query): + witem.command("MSG %s Hello!" % witem.name) + else: + irssi.prnt("Nick not given, and no active channel/query in window") + +irssi.command_bind('hello', cmd_hello) diff --git a/scripts/test_window.py b/scripts/test_window.py new file mode 100644 index 0000000..69521d5 --- /dev/null +++ b/scripts/test_window.py @@ -0,0 +1,50 @@ +import irssi + +win0 = None +win1 = None + +def cmd_wintest(data, server, witem): + act_win = irssi.active_win() + act_server = irssi.active_server() + + print 'active_win', act_win, 'ref', act_win.refnum + print 'active_server', act_server + + items = act_win.items() + print 'win.items()', items + + for i in items: + print i, 'window ref', i.window().refnum, 'window name', i.window().name + + print + print 'all windows' + for i in irssi.windows(): + print 'window refnum', i.refnum, 'window name', i.name + print + + f0 = irssi.window_find_name('melbo') + f1 = irssi.window_find_name('(status)') + print 'irssi.window_find_name(melbo)', f0 + print 'irssi.window_find_name(status)', f1 + +def cmd_opentest(data, server, witem): + global win0, win1 + win0 = irssi.window_create(True) + print 'window_create(True) ->', win0 + win1 = irssi.window_create(False) + print 'window_create(False) ->', win1 + +def cmd_closetest(data, server, witem): + print 'destroy win0 && win1' + win0.destroy() + win1.destroy() + +def cmd_postclose(*args): + print 'post-close access' + print win0.items() + print win1.items() + +irssi.command_bind('wintest', cmd_wintest) +irssi.command_bind('closetest', cmd_closetest) +irssi.command_bind('postclose', cmd_postclose) +irssi.command_bind('opentest', cmd_opentest) -- cgit