Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/chat
diff options
context:
space:
mode:
authorDan Williams <dcbw@redhat.com>2006-04-19 18:43:40 (GMT)
committer Dan Williams <dcbw@redhat.com>2006-04-19 18:43:40 (GMT)
commitb4706c0db67d937ddccb57dbc20e2f09afd55df2 (patch)
treed66e13ae229dc99e454185cd719be7b2d4f181b2 /chat
parent2cc103db83ed1550e2a9e92bf3306e4476c1297a (diff)
Initial standalone chat thingy
Diffstat (limited to 'chat')
-rw-r--r--chat/SimpleGladeApp.py341
-rw-r--r--chat/chat.glade118
-rwxr-xr-xchat/main.py115
-rw-r--r--chat/network.py60
-rw-r--r--chat/presence.py96
5 files changed, 730 insertions, 0 deletions
diff --git a/chat/SimpleGladeApp.py b/chat/SimpleGladeApp.py
new file mode 100644
index 0000000..90c598c
--- /dev/null
+++ b/chat/SimpleGladeApp.py
@@ -0,0 +1,341 @@
+"""
+ SimpleGladeApp.py
+ Module that provides an object oriented abstraction to pygtk and libglade.
+ Copyright (C) 2004 Sandino Flores Moreno
+"""
+
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+# USA
+
+import os
+import sys
+import re
+
+import tokenize
+import gtk
+import gtk.glade
+import weakref
+import inspect
+
+__version__ = "1.0"
+__author__ = 'Sandino "tigrux" Flores-Moreno'
+
+def bindtextdomain(app_name, locale_dir=None):
+ """
+ Bind the domain represented by app_name to the locale directory locale_dir.
+ It has the effect of loading translations, enabling applications for different
+ languages.
+
+ app_name:
+ a domain to look for translations, tipically the name of an application.
+
+ locale_dir:
+ a directory with locales like locale_dir/lang_isocode/LC_MESSAGES/app_name.mo
+ If omitted or None, then the current binding for app_name is used.
+ """
+ try:
+ import locale
+ import gettext
+ locale.setlocale(locale.LC_ALL, "")
+ gtk.glade.bindtextdomain(app_name, locale_dir)
+ gettext.install(app_name, locale_dir, unicode=1)
+ except (IOError,locale.Error), e:
+ print "Warning", app_name, e
+ __builtins__.__dict__["_"] = lambda x : x
+
+
+class SimpleGladeApp:
+
+ def __init__(self, path, root=None, domain=None, **kwargs):
+ """
+ Load a glade file specified by glade_filename, using root as
+ root widget and domain as the domain for translations.
+
+ If it receives extra named arguments (argname=value), then they are used
+ as attributes of the instance.
+
+ path:
+ path to a glade filename.
+ If glade_filename cannot be found, then it will be searched in the
+ same directory of the program (sys.argv[0])
+
+ root:
+ the name of the widget that is the root of the user interface,
+ usually a window or dialog (a top level widget).
+ If None or ommited, the full user interface is loaded.
+
+ domain:
+ A domain to use for loading translations.
+ If None or ommited, no translation is loaded.
+
+ **kwargs:
+ a dictionary representing the named extra arguments.
+ It is useful to set attributes of new instances, for example:
+ glade_app = SimpleGladeApp("ui.glade", foo="some value", bar="another value")
+ sets two attributes (foo and bar) to glade_app.
+ """
+ if os.path.isfile(path):
+ self.glade_path = path
+ else:
+ glade_dir = os.path.dirname( sys.argv[0] )
+ self.glade_path = os.path.join(glade_dir, path)
+ for key, value in kwargs.items():
+ try:
+ setattr(self, key, weakref.proxy(value) )
+ except TypeError:
+ setattr(self, key, value)
+ self.glade = None
+ self.install_custom_handler(self.custom_handler)
+ self.glade = self.create_glade(self.glade_path, root, domain)
+ if root:
+ self.main_widget = self.get_widget(root)
+ else:
+ self.main_widget = None
+ self.normalize_names()
+ self.add_callbacks(self)
+ self.new()
+
+ def __repr__(self):
+ class_name = self.__class__.__name__
+ if self.main_widget:
+ root = gtk.Widget.get_name(self.main_widget)
+ repr = '%s(path="%s", root="%s")' % (class_name, self.glade_path, root)
+ else:
+ repr = '%s(path="%s")' % (class_name, self.glade_path)
+ return repr
+
+ def new(self):
+ """
+ Method called when the user interface is loaded and ready to be used.
+ At this moment, the widgets are loaded and can be refered as self.widget_name
+ """
+ pass
+
+ def add_callbacks(self, callbacks_proxy):
+ """
+ It uses the methods of callbacks_proxy as callbacks.
+ The callbacks are specified by using:
+ Properties window -> Signals tab
+ in glade-2 (or any other gui designer like gazpacho).
+
+ Methods of classes inheriting from SimpleGladeApp are used as
+ callbacks automatically.
+
+ callbacks_proxy:
+ an instance with methods as code of callbacks.
+ It means it has methods like on_button1_clicked, on_entry1_activate, etc.
+ """
+ self.glade.signal_autoconnect(callbacks_proxy)
+
+ def normalize_names(self):
+ """
+ It is internally used to normalize the name of the widgets.
+ It means a widget named foo:vbox-dialog in glade
+ is refered self.vbox_dialog in the code.
+
+ It also sets a data "prefixes" with the list of
+ prefixes a widget has for each widget.
+ """
+ for widget in self.get_widgets():
+ widget_name = gtk.Widget.get_name(widget)
+ prefixes_name_l = widget_name.split(":")
+ prefixes = prefixes_name_l[ : -1]
+ widget_api_name = prefixes_name_l[-1]
+ widget_api_name = "_".join( re.findall(tokenize.Name, widget_api_name) )
+ gtk.Widget.set_name(widget, widget_api_name)
+ if hasattr(self, widget_api_name):
+ raise AttributeError("instance %s already has an attribute %s" % (self,widget_api_name))
+ else:
+ setattr(self, widget_api_name, widget)
+ if prefixes:
+ gtk.Widget.set_data(widget, "prefixes", prefixes)
+
+ def add_prefix_actions(self, prefix_actions_proxy):
+ """
+ By using a gui designer (glade-2, gazpacho, etc)
+ widgets can have a prefix in theirs names
+ like foo:entry1 or foo:label3
+ It means entry1 and label3 has a prefix action named foo.
+
+ Then, prefix_actions_proxy must have a method named prefix_foo which
+ is called everytime a widget with prefix foo is found, using the found widget
+ as argument.
+
+ prefix_actions_proxy:
+ An instance with methods as prefix actions.
+ It means it has methods like prefix_foo, prefix_bar, etc.
+ """
+ prefix_s = "prefix_"
+ prefix_pos = len(prefix_s)
+
+ is_method = lambda t : callable( t[1] )
+ is_prefix_action = lambda t : t[0].startswith(prefix_s)
+ drop_prefix = lambda (k,w): (k[prefix_pos:],w)
+
+ members_t = inspect.getmembers(prefix_actions_proxy)
+ methods_t = filter(is_method, members_t)
+ prefix_actions_t = filter(is_prefix_action, methods_t)
+ prefix_actions_d = dict( map(drop_prefix, prefix_actions_t) )
+
+ for widget in self.get_widgets():
+ prefixes = gtk.Widget.get_data(widget, "prefixes")
+ if prefixes:
+ for prefix in prefixes:
+ if prefix in prefix_actions_d:
+ prefix_action = prefix_actions_d[prefix]
+ prefix_action(widget)
+
+ def custom_handler(self,
+ glade, function_name, widget_name,
+ str1, str2, int1, int2):
+ """
+ Generic handler for creating custom widgets, internally used to
+ enable custom widgets (custom widgets of glade).
+
+ The custom widgets have a creation function specified in design time.
+ Those creation functions are always called with str1,str2,int1,int2 as
+ arguments, that are values specified in design time.
+
+ Methods of classes inheriting from SimpleGladeApp are used as
+ creation functions automatically.
+
+ If a custom widget has create_foo as creation function, then the
+ method named create_foo is called with str1,str2,int1,int2 as arguments.
+ """
+ try:
+ handler = getattr(self, function_name)
+ return handler(str1, str2, int1, int2)
+ except AttributeError:
+ return None
+
+ def gtk_widget_show(self, widget, *args):
+ """
+ Predefined callback.
+ The widget is showed.
+ Equivalent to widget.show()
+ """
+ widget.show()
+
+ def gtk_widget_hide(self, widget, *args):
+ """
+ Predefined callback.
+ The widget is hidden.
+ Equivalent to widget.hide()
+ """
+ widget.hide()
+
+ def gtk_widget_grab_focus(self, widget, *args):
+ """
+ Predefined callback.
+ The widget grabs the focus.
+ Equivalent to widget.grab_focus()
+ """
+ widget.grab_focus()
+
+ def gtk_widget_destroy(self, widget, *args):
+ """
+ Predefined callback.
+ The widget is destroyed.
+ Equivalent to widget.destroy()
+ """
+ widget.destroy()
+
+ def gtk_window_activate_default(self, window, *args):
+ """
+ Predefined callback.
+ The default widget of the window is activated.
+ Equivalent to window.activate_default()
+ """
+ widget.activate_default()
+
+ def gtk_true(self, *args):
+ """
+ Predefined callback.
+ Equivalent to return True in a callback.
+ Useful for stopping propagation of signals.
+ """
+ return True
+
+ def gtk_false(self, *args):
+ """
+ Predefined callback.
+ Equivalent to return False in a callback.
+ """
+ return False
+
+ def gtk_main_quit(self, *args):
+ """
+ Predefined callback.
+ Equivalent to self.quit()
+ """
+ self.quit()
+
+ def main(self):
+ """
+ Starts the main loop of processing events.
+ The default implementation calls gtk.main()
+
+ Useful for applications that needs a non gtk main loop.
+ For example, applications based on gstreamer needs to override
+ this method with gst.main()
+
+ Do not directly call this method in your programs.
+ Use the method run() instead.
+ """
+ gtk.main()
+
+ def quit(self):
+ """
+ Quit processing events.
+ The default implementation calls gtk.main_quit()
+
+ Useful for applications that needs a non gtk main loop.
+ For example, applications based on gstreamer needs to override
+ this method with gst.main_quit()
+ """
+ gtk.main_quit()
+
+ def run(self):
+ """
+ Starts the main loop of processing events checking for Control-C.
+
+ The default implementation checks wheter a Control-C is pressed,
+ then calls on_keyboard_interrupt().
+
+ Use this method for starting programs.
+ """
+ try:
+ self.main()
+ except KeyboardInterrupt:
+ self.on_keyboard_interrupt()
+
+ def on_keyboard_interrupt(self):
+ """
+ This method is called by the default implementation of run()
+ after a program is finished by pressing Control-C.
+ """
+ pass
+
+ def install_custom_handler(self, custom_handler):
+ gtk.glade.set_custom_handler(custom_handler)
+
+ def create_glade(self, glade_path, root, domain):
+ return gtk.glade.XML(self.glade_path, root, domain)
+
+ def get_widget(self, widget_name):
+ return self.glade.get_widget(widget_name)
+
+ def get_widgets(self):
+ return self.glade.get_widget_prefix("")
diff --git a/chat/chat.glade b/chat/chat.glade
new file mode 100644
index 0000000..69f58c8
--- /dev/null
+++ b/chat/chat.glade
@@ -0,0 +1,118 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkWindow" id="mainWindow">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Chat</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="default_width">440</property>
+ <property name="default_height">250</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+
+ <child>
+ <widget class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">0</property>
+ <property name="column_spacing">0</property>
+
+ <child>
+ <widget class="GtkTextView" id="chatView">
+ <property name="visible">True</property>
+ <property name="editable">True</property>
+ <property name="overwrite">False</property>
+ <property name="accepts_tab">True</property>
+ <property name="justification">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap_mode">GTK_WRAP_NONE</property>
+ <property name="cursor_visible">True</property>
+ <property name="pixels_above_lines">0</property>
+ <property name="pixels_below_lines">0</property>
+ <property name="pixels_inside_wrap">0</property>
+ <property name="left_margin">0</property>
+ <property name="right_margin">0</property>
+ <property name="indent">0</property>
+ <property name="text" translatable="yes"></property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="entry">
+ <property name="visible">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">•</property>
+ <property name="activates_default">False</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options">shrink|fill</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHBox" id="hbox1">
+ <property name="border_width">6</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkTreeView" id="buddyList">
+ <property name="border_width">2</property>
+ <property name="width_request">167</property>
+ <property name="visible">True</property>
+ <property name="headers_visible">False</property>
+ <property name="rules_hint">False</property>
+ <property name="reorderable">False</property>
+ <property name="enable_search">True</property>
+ <property name="fixed_height_mode">False</property>
+ <property name="hover_selection">False</property>
+ <property name="hover_expand">False</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
diff --git a/chat/main.py b/chat/main.py
new file mode 100755
index 0000000..5eff742
--- /dev/null
+++ b/chat/main.py
@@ -0,0 +1,115 @@
+#!/usr/bin/python -t
+
+import os, sys, pwd
+sys.path.append(os.getcwd())
+import gtk, gobject
+
+from SimpleGladeApp import SimpleGladeApp
+import presence
+import network
+import avahi
+
+glade_dir = os.getcwd()
+
+class ChatApp(SimpleGladeApp):
+ def __init__(self, glade_file="chat.glade", root="mainWindow", domain=None, **kwargs):
+
+ self._pdiscovery = presence.PresenceDiscovery()
+ self._pannounce = presence.PresenceAnnounce()
+
+ (self._nick, self._realname) = self._get_name()
+
+ path = os.path.join(glade_dir, glade_file)
+ gtk.window_set_default_icon_name("config-users")
+ SimpleGladeApp.__init__(self, path, root, domain, **kwargs)
+
+ def _get_name(self):
+ ent = pwd.getpwuid(os.getuid())
+ nick = ent[0]
+ if not nick or not len(nick):
+ nick = "n00b"
+ realname = ent[4]
+ if not realname or not len(realname):
+ realname = "Some Clueless User"
+ return (nick, realname)
+
+ def new_service(self, action, interface, protocol, name, stype, domain, flags):
+ if action != 'added' or stype != presence.OLPC_CHAT_SERVICE:
+ return
+ self._pdiscovery.resolve_service(interface, protocol, name, stype, domain, self.service_resolved)
+
+ def on_buddyList_buddy_selected(self, widget, *args):
+ (model, aniter) = widget.get_selection().get_selected()
+ name = self.treemodel.get(aniter,0)
+ print "Selected %s" % name
+
+ def on_buddyList_buddy_double_clicked(self, widget, *args):
+ (model, aniter) = widget.get_selection().get_selected()
+ name = self.treemodel.get(aniter,0)
+ print "Double-clicked %s" % name
+
+ def on_main_window_delete(self, widget, *args):
+ self.quit()
+
+ def _recv_group_message(self, msg):
+ aniter = self._group_chat_buffer.get_end_iter()
+ self._group_chat_buffer.insert(aniter, msg['data'] + "\n")
+# print "Message: %s" % msg['data']
+
+ def _send_group_message(self, widget, *args):
+ text = widget.get_text()
+ if len(text) > 0:
+ self._gc_controller.send_msg(text)
+ widget.set_text("")
+
+ def _pair_to_dict(self, l):
+ res = {}
+ for el in l:
+ tmp = el.split('=', 1)
+ if len(tmp) > 1:
+ res[tmp[0]] = tmp[1]
+ else:
+ res[tmp[0]] = ''
+ return res
+
+ def service_resolved(self, interface, protocol, name, stype, domain, host, aprotocol, address, port, txt, flags):
+ data = self._pair_to_dict(avahi.txt_array_to_string_array(txt))
+ if len(data) > 0 and 'name' in data.keys():
+ aniter = self.treemodel.insert_after(None,None)
+ self.treemodel.set(aniter, 0, data['name'])
+
+ def new(self):
+ self._group_chat_buffer = gtk.TextBuffer()
+ self.chatView.set_buffer(self._group_chat_buffer)
+
+ self.treemodel = gtk.TreeStore(gobject.TYPE_STRING)
+ self.buddyList.set_model(self.treemodel)
+ self.buddyList.connect("cursor-changed", self.on_buddyList_buddy_selected)
+ self.buddyList.connect("row-activated", self.on_buddyList_buddy_double_clicked)
+ self.mainWindow.connect("delete-event", self.on_main_window_delete)
+ self.entry.connect("activate", self._send_group_message)
+
+ renderer = gtk.CellRendererText()
+ column = gtk.TreeViewColumn("", renderer, text=0)
+ column.set_resizable(True)
+ column.set_sizing("GTK_TREE_VIEW_COLUMN_GROW_ONLY");
+ column.set_expand(True);
+ self.buddyList.append_column(column)
+
+ self._pannounce.register_service(self._realname, 6666, presence.OLPC_CHAT_SERVICE, name=self._nick)
+ self._pdiscovery.add_service_listener(self.new_service)
+ self._pdiscovery.start()
+
+ self._gc_controller = network.GroupChatController('224.0.0.221', 6666, self._recv_group_message)
+ self._gc_controller.start()
+
+ def cleanup(self):
+ pass
+
+def main():
+ app = ChatApp()
+ app.run()
+ app.cleanup()
+
+if __name__ == "__main__":
+ main()
diff --git a/chat/network.py b/chat/network.py
new file mode 100644
index 0000000..a685a38
--- /dev/null
+++ b/chat/network.py
@@ -0,0 +1,60 @@
+import socket
+import threading
+import traceback
+import select
+import time
+import gobject
+
+class GroupChatController(object):
+
+ _MAX_MSG_SIZE = 500
+
+ def __init__(self, address, port, data_cb):
+ self._address = address
+ self._port = port
+ self._data_cb = data_cb
+
+ self._setup_sender()
+ self._setup_listener()
+
+ def _setup_sender(self):
+ self._send_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ # Make the socket multicast-aware, and set TTL.
+ self._send_sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 20) # Change TTL (=20) to suit
+
+ def _setup_listener(self):
+ # Listener socket
+ self._listen_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+
+ # Set some options to make it multicast-friendly
+ self._listen_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ try:
+ self._listen_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
+ except:
+ pass
+ self._listen_sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, 20)
+ self._listen_sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
+
+ def start(self):
+ # Set some more multicast options
+ self._listen_sock.bind(('', self._port))
+ self._listen_sock.settimeout(2)
+ intf = socket.gethostbyname(socket.gethostname())
+ self._listen_sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_IF, socket.inet_aton(intf) + socket.inet_aton('0.0.0.0'))
+ self._listen_sock.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(self._address) + socket.inet_aton('0.0.0.0'))
+
+ # Watch the listener socket for data
+ gobject.io_add_watch(self._listen_sock, gobject.IO_IN, self._handle_incoming_data)
+
+ def _handle_incoming_data(self, source, condition):
+ if not (condition & gobject.IO_IN):
+ return
+ msg = {}
+ msg['data'], (msg['addr'], msg['port']) = source.recvfrom(self._MAX_MSG_SIZE)
+ if self._data_cb:
+ self._data_cb(msg)
+ return True
+
+ def send_msg(self, data):
+ self._send_sock.sendto(data, (self._address, self._port))
+
diff --git a/chat/presence.py b/chat/presence.py
new file mode 100644
index 0000000..55d8011
--- /dev/null
+++ b/chat/presence.py
@@ -0,0 +1,96 @@
+#!/usr/bin/python -t
+
+
+import avahi, dbus, dbus.glib
+
+OLPC_CHAT_SERVICE = "_olpc_chat._udp"
+
+
+class PresenceDiscovery(object):
+ def __init__(self):
+ self.bus = dbus.SystemBus()
+ self.server = dbus.Interface(self.bus.get_object(avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER)
+ self._service_browsers = {}
+ self._service_type_browsers = {}
+ self._service_listeners = []
+
+ def add_service_listener(self, listener):
+ self._service_listeners.append(listener)
+
+ def start(self):
+ # Always browse .local
+ self.browse_domain(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, "local")
+ db = dbus.Interface(self.bus.get_object(avahi.DBUS_NAME, self.server.DomainBrowserNew(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, "", avahi.DOMAIN_BROWSER_BROWSE, dbus.UInt32(0))), avahi.DBUS_INTERFACE_DOMAIN_BROWSER)
+ db.connect_to_signal('ItemNew', self.new_domain)
+
+ def _error_handler(self, err):
+ print "Error resolving: %s" % err
+
+ def resolve_service(self, interface, protocol, name, stype, domain, reply_handler, error_handler=None):
+ if not error_handler:
+ error_handler = self._error_handler
+ self.server.ResolveService(int(interface), int(protocol), name, stype, domain, avahi.PROTO_UNSPEC, dbus.UInt32(0), reply_handler=reply_handler, error_handler=error_handler)
+
+ def new_service(self, interface, protocol, name, stype, domain, flags):
+# print "Found service '%s' (%d) of type '%s' in domain '%s' on %i.%i." % (name, flags, stype, domain, interface, protocol)
+
+ for listener in self._service_listeners:
+ listener('added', interface, protocol, name, stype, domain, flags)
+
+ def remove_service(self, interface, protocol, name, stype, domain, flags):
+# print "Service '%s' of type '%s' in domain '%s' on %i.%i disappeared." % (name, stype, domain, interface, protocol)
+
+ for listener in self._service_listeners:
+ listener('removed', interface, protocol, name, stype, domain, flags)
+
+ def new_service_type(self, interface, protocol, stype, domain, flags):
+ # Are we already browsing this domain for this type?
+ if self._service_browsers.has_key((interface, protocol, stype, domain)):
+ return
+
+# print "Browsing for services of type '%s' in domain '%s' on %i.%i ..." % (stype, domain, interface, protocol)
+
+ b = dbus.Interface(self.bus.get_object(avahi.DBUS_NAME, self.server.ServiceBrowserNew(interface, protocol, stype, domain, dbus.UInt32(0))), avahi.DBUS_INTERFACE_SERVICE_BROWSER)
+ b.connect_to_signal('ItemNew', self.new_service)
+ b.connect_to_signal('ItemRemove', self.remove_service)
+
+ self._service_browsers[(interface, protocol, stype, domain)] = b
+
+ def browse_domain(self, interface, protocol, domain):
+ # Are we already browsing this domain?
+ if self._service_type_browsers.has_key((interface, protocol, domain)):
+ return
+
+# print "Browsing domain '%s' on %i.%i ..." % (domain, interface, protocol)
+
+ b = dbus.Interface(self.bus.get_object(avahi.DBUS_NAME, self.server.ServiceTypeBrowserNew(interface, protocol, domain, dbus.UInt32(0))), avahi.DBUS_INTERFACE_SERVICE_TYPE_BROWSER)
+ b.connect_to_signal('ItemNew', self.new_service_type)
+
+ self._service_type_browsers[(interface, protocol, domain)] = b
+
+ def new_domain(self,interface, protocol, domain, flags):
+ if domain != "local":
+ return
+ self.browse_domain(interface, protocol, domain)
+
+
+class PresenceAnnounce(object):
+ def __init__(self):
+ self.bus = dbus.SystemBus()
+ self.server = dbus.Interface(self.bus.get_object(avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER)
+ self._hostname = None
+
+ def register_service(self, rs_name, rs_port, rs_service, **kwargs):
+ g = dbus.Interface(self.bus.get_object(avahi.DBUS_NAME, self.server.EntryGroupNew()), avahi.DBUS_INTERFACE_ENTRY_GROUP)
+
+ if rs_name is None:
+ if self._hostname is None:
+ self._hostname = "%s:%s" % (self.server.GetHostName(), rs_port)
+ rs_name = self._hostname
+
+ info = ["%s=%s" % (k,v) for k,v in kwargs.items()]
+ g.AddService(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, 0, rs_name, rs_service,
+ "", "", # domain, host (let the system figure it out)
+ dbus.UInt16(rs_port), info,)
+ g.Commit()
+ return g