Initial standalone chat thingy
This commit is contained in:
parent
2cc103db83
commit
b4706c0db6
341
chat/SimpleGladeApp.py
Normal file
341
chat/SimpleGladeApp.py
Normal file
@ -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("")
|
118
chat/chat.glade
Normal file
118
chat/chat.glade
Normal file
@ -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>
|
115
chat/main.py
Executable file
115
chat/main.py
Executable file
@ -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()
|
60
chat/network.py
Normal file
60
chat/network.py
Normal file
@ -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))
|
||||||
|
|
96
chat/presence.py
Normal file
96
chat/presence.py
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user