diff options
author | Christopher Davis <loafier@gmail.com> | 2006-06-19 12:25:06 +0000 |
---|---|---|
committer | Christopher Davis <loafier@gmail.com> | 2006-06-19 12:25:06 +0000 |
commit | bb48c914c6239ed1dbcb29eb62d33d3ab91e7215 (patch) | |
tree | 0b6910959f96f683f3aa11a8e2f53572e42b3305 | |
parent | efef73ae301947875602d67d0979a8ce3bd57dd0 (diff) | |
download | irssi-python-bb48c914c6239ed1dbcb29eb62d33d3ab91e7215.tar.gz irssi-python-bb48c914c6239ed1dbcb29eb62d33d3ab91e7215.tar.xz irssi-python-bb48c914c6239ed1dbcb29eb62d33d3ab91e7215.zip |
initial import
git-svn-id: http://svn.irssi.org/repos/irssi-python@4282 dbcabf3a-b0e7-0310-adc4-f8d773084564
68 files changed, 8950 insertions, 0 deletions
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 + @@ -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 <Python>/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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> + +/* 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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#include <structmember.h> +#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 <Python.h> +#include <glib.h> + +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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#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 <Python.h> +#include <string.h> +#include <signal.h> +#include <assert.h> +#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, "<stdin>", 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 <Python.h> +#include <string.h> +#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 <Python.h> +#include <frameobject.h> +#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 <Python.h> + +/* 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 <Python.h> +#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 <Python.h> + +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 <string.h> +#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) |