Rick Spencer pulled me over today to help with an IRC client that he’s working on. He’s using python irclib to talk to IRC and Gtk/Webkit for the UI. The trouble with that combination is that the two are not using the same mainloop.
We sat down for a while and finally figured out that the ‘IRC’ object in irclib has three hooks for user-provided functions to help with this:
- add fd watch
- remove fd watch
- add timeout
The fd watch functions are passed a python socket object to add or remove a watch for. The intent is that you will watch for the socket becoming readable. As far as I can tell, irclib always performs blocking writes on the assumption that it won’t be a problem.
GLib lacks functionality for easily hooking up watches for fds (although we have some proposals for that in bug #658020 which I will be looking at more closely soon). You can use GIOChannel but that’s always somewhat annoying and as far as I can tell cannot be used from Python with a unix fd (possibly due to a binding issue?). The remaining solution is to implement a GSource from python, which is tricky. Rick made me promise I’d blog about the code that I came up with to help with that. Here it is:
class SocketSource(GLib.Source): def __init__(self, callback): GLib.Source.__init__(self) self.callback = callback self.pollfds = [] def prepare(self): return False def check(self): for pollfd in self.pollfds: if pollfd.revents: return True return False def dispatch(self, callback, args): self.callback() return True def add_socket(self, socket): pollfd = GLib.PollFD(socket.fileno(), GLib.IO_IN) self.pollfds.append(pollfd) self.add_poll(pollfd) def rm_socket(self, socket): fd = socket.fileno() for pollfd in self.pollfds: if pollfd.fd == fd: self.remove_poll(pollfd) self.pollfds.remove(pollfd)
The callback function provided to the constructor is called when one of the sockets becomes ready for reading. That maps nicely for irclib’s process_once
function. The add_socket
and rm_socket
fit nicely with irclib’s fn_to_add_socket
and fn_to_remove_socket
. Using the code looks something like so:
simple = irclib.SimpleIRCClient() source = Socketsource(simple.ircobj.process_once) simple.ircobj.fn_to_add_socket = source.add_socket simple.ircobj.fn_to_remove_socket = source.rm_socket source.attach()
Timeouts are left as an exercise to the reader.