#!@PYTHON@
# -*- coding: UTF-8 -*-
# -*- python -*-
# Copyright (C) 2005 by Sebastien Estienne
#
# This file may be distributed and/or modified under the terms of
# the GNU General Public License version 2 as published by
# the Free Software Foundation.
# This file is distributed without any warranty; without even the implied
# warranty of merchantability or fitness for a particular purpose.
# See "COPYING" in the source distribution for more information.
#
# $id$
#
# todo
# * dict([el.split('=',1) for el in l ])
import os
import subprocess
import sys
import pygtk
sys.path.append("@actionsdir@")
import services
pygtk.require('2.0')
def error_msg(msg):
d = gtk.MessageDialog(parent=None, flags=gtk.DIALOG_MODAL,
type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_OK)
d.set_markup(msg)
d.show_all()
d.run()
d.destroy()
try:
import gettext
gettext.bindtextdomain("@GETTEXT_PACKAGE@", "@LOCALEDIR@")
gettext.textdomain("@GETTEXT_PACKAGE@")
_ = gettext.gettext
import gobject
import avahi
import dbus
import gtk
import gnomeapplet
import gnome.ui
import gconf
import avahi.ServiceTypeDatabase
except ImportError, e:
error_msg(_("A required python module is missing!\n%s") % (e))
sys.exit()
try:
import dbus.glib
except ImportError, e:
pass
###############################################################################
#
# SERVICES THAT WILL BE ADDED TO GCONF ON THE FIRST RUN
#
# default browsing value
first_run_services = {
"_http._tcp" : True,
"_https._tcp" : False,
"_ssh._tcp" : True,
"_sftp-ssh._tcp" : False,
"_ftp._tcp" : True,
}
###############################################################################
#
# SERVIDE DISCOVERY APPLET MAIN CLASS
#
class ServiceDiscoveryApplet(gnomeapplet.Applet):
def __init__(self, applet, iid):
self.__gobject_init__()
self.applet = applet
self.service_browsers = {}
self.service_menu = gtk.Menu()
self.service_menu.connect("hide", self.on_hide_service_menu)
self.add_no_services_menuitem()
self.zc_types = {}
self.zc_services = {}
self.zc_pretty_name = avahi.ServiceTypeDatabase.ServiceTypeDatabase()
# Gconf Paths
self.gc_options = "/apps/service-discovery-applet/options"
self.gc_services = "/apps/service-discovery-applet/services"
# Gconf
self.gc_client = gconf.client_get_default ()
self.gc_client.add_dir (self.gc_options, gconf.CLIENT_PRELOAD_NONE)
self.show_local_services = self.gc_client.get_bool ("%s/%s" % (self.gc_options,"show_local_services"))
self.show_notifications = self.gc_client.get_bool ("%s/%s" % (self.gc_options,"show_notifications"))
self.gc_client.add_dir (self.gc_services, gconf.CLIENT_PRELOAD_NONE)
self.gc_client.notify_add (self.gc_services, self.gc_services_cb)
self.gc_client.notify_add (self.gc_options, self.gc_options_cb)
# init first run browsing services
if self.gc_client.get_bool ("%s/%s" % (self.gc_options,"first_run")):
self.gc_client.set_bool("%s/%s" % (self.gc_options, "first_run"), False)
for service_type, enabled in first_run_services.iteritems():
self.gc_client.set_bool("%s/%s" % (self.gc_services, service_type), enabled)
applet.connect("button-press-event", self.on_button_press)
applet.connect("size-allocate", self.on_applet_size_allocate)
image = gtk.Image()
image.set_from_file("@iconsdir@/24x24/service-discovery-applet.png")
applet.add(image)
# add tooltip to the applet
tooltip = gtk.Tooltips()
tooltip.set_tip(applet, _("Zeroconf Service Discovery"))
tooltip.enable()
applet.connect("change_background", self.on_change_background)
applet.set_applet_flags(gnomeapplet.EXPAND_MINOR)
# funky right-click menu
menuXml = """
"""
menuXml = menuXml % (_("_About"), _("_Preferences"), _("_Start"), _("S_top"))
applet.setup_menu(menuXml, [
("SDA About", self.on_about),
("SDA Config", self.on_config),
("SDA Start", self.start_service_discovery),
("SDA Stop", self.stop_service_discovery)
], applet)
self.popup = applet.get_popup_component()
self.popup_control = applet.get_control()
#Start Service Discovery
self.domain = ""
self.system_bus = dbus.SystemBus()
self.server = dbus.Interface(self.system_bus.get_object(avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER),
avahi.DBUS_INTERFACE_SERVER)
self.session_bus = dbus.SessionBus()
obj = self.session_bus.get_object("org.freedesktop.Notifications", "/org/freedesktop/Notifications")
self.notif = dbus.Interface(obj, "org.freedesktop.Notifications")
self.start_service_discovery(None, None, None)
# applet.connect("destroy",self.cleanup)
# applet.show_all()
###############################################################################
#
# AVAHI
#
def siocgifname(self, interface):
if interface <= 0:
return "any"
else:
return self.server.GetNetworkInterfaceNameByIndex(interface)
def service_resolved_old(self, interface, protocol, name, type, domain, host, aprotocol, address, port, txt):
self.service_resolved(interface, protocol, name, type, domain, host, aprotocol, address, port, txt, None)
def service_resolved(self, interface, protocol, name, type, domain, host, aprotocol, address, port, txt, flags):
print "Service data for service '%s' of type '%s' in domain '%s' on %s.%i:" % (name, type, domain, self.siocgifname(interface), protocol)
print "\tHost %s (%s), port %i, TXT data: %s" % (host, address, port, avahi.txt_array_to_string_array(txt))
txts = avahi.txt_array_to_string_array(txt)
txts = services.pair_to_dict(txts)
services.handle(name, type, host, address, port, txts)
def print_error(self, err):
# FIXME we should use notifications
print "Error:", str(err)
def menuitem_response(self, widget, interface, protocol, name, type, domain):
if self.avahi_version == "avahi 0.6":
self.server.ResolveService(interface, protocol, name, type, domain, avahi.PROTO_INET, dbus.UInt32(0), reply_handler=self.service_resolved, error_handler=self.print_error)
else:
self.server.ResolveService(interface, protocol, name, type, domain, avahi.PROTO_INET, reply_handler=self.service_resolved_old, error_handler=self.print_error)
def new_service_old(self, interface, protocol, name, type, domain):
self.new_service(interface, protocol, name, type, domain, None)
def new_service(self, interface, protocol, name, type, domain, flags):
print "Found service '%s' of type '%s' in domain '%s' on %s.%i." % (name, type, domain, self.siocgifname(interface), protocol)
try:
if self.show_local_services == False:
if self.avahi_version != "avahi 0.6":
if self.server.IsServiceLocal( interface, protocol, name, type, domain) == True:
return
else:
# FIXME not yet available in python avahi
try:
if flags & avahi.AVAHI_LOOKUP_RESULT_IS_LOCAL:
return
except AttributeError:
pass
except dbus.dbus_bindings.DBusException:
pass
# if we found a service, remove "No service found"
if self.zc_types == {}:
for menuitem in self.service_menu.get_children():
self.service_menu.remove(menuitem)
if self.zc_types.has_key(type) == False:
if self.zc_pretty_name.has_key(type):
pretty_name = self.zc_pretty_name[type]
else:
pretty_name = type
img = gtk.Image()
iconfile = "@iconsdir@/24x24/%s.png" % (type)
if not os.path.exists(iconfile):
iconfile = "@iconsdir@/24x24/service-discovery-applet.png"
img.set_from_file(iconfile)
menuitem = gtk.ImageMenuItem(pretty_name)
menuitem.set_image(img)
menuitem.get_child().set_use_underline(False)
self.service_menu.add(menuitem)
self.zc_types[type] = gtk.Menu()
menuitem.set_submenu(self.zc_types[type])
menuitem.show_all()
menuitem = gtk.MenuItem(name, False)
self.zc_types[type].add(menuitem)
self.zc_services[(interface, protocol, name, type, domain)] = menuitem
menuitem.connect("activate", self.menuitem_response,interface, protocol, name, type, domain)
menuitem.show_all()
self.display_service_notification(True, name, type)
def remove_service_old(self, interface, protocol, name, type, domain):
self.remove_service(interface, protocol, name, type, domain, None)
def remove_service(self, interface, protocol, name, type, domain, flags):
print "Service '%s' of type '%s' in domain '%s' on %s.%i disappeared." % (name, type, domain, self.siocgifname(interface), protocol)
if self.zc_services.has_key((interface, protocol, name, type, domain)):
self.zc_types[type].remove(self.zc_services[(interface, protocol, name, type, domain)])
self.display_service_notification(False, name, type)
if self.zc_types[type].get_children() == []:
self.service_menu.remove(self.zc_types[type].get_attach_widget())
del self.zc_types[type]
if self.zc_types == {}:
self.add_no_services_menuitem()
def add_service_type(self, interface, protocol, type, domain):
# Are we already browsing this domain for this type?
if self.service_browsers.has_key((interface, protocol, type, domain)):
return
print "Browsing for services of type '%s' in domain '%s' on %s.%i ..." % (type, domain, self.siocgifname(interface), protocol)
if self.avahi_version == "avahi 0.6":
b = dbus.Interface(self.system_bus.get_object(avahi.DBUS_NAME,
self.server.ServiceBrowserNew(interface, protocol, type, 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)
else:
b = dbus.Interface(self.system_bus.get_object(avahi.DBUS_NAME,
self.server.ServiceBrowserNew(interface, protocol, type, domain))
, avahi.DBUS_INTERFACE_SERVICE_BROWSER)
b.connect_to_signal('ItemNew', self.new_service_old)
b.connect_to_signal('ItemRemove', self.remove_service_old)
self.service_browsers[(interface, protocol, type, domain)] = b
def del_service_type(self, interface, protocol, type, domain):
service = (interface, protocol, type, domain)
if not self.service_browsers.has_key(service):
return
sb = self.service_browsers[service]
sb.Free()
del self.service_browsers[service]
# delete the sub menu of service_type
if self.zc_types.has_key(type):
self.service_menu.remove(self.zc_types[type].get_attach_widget())
del self.zc_types[type]
if len(self.zc_types) == 0:
self.add_no_services_menuitem()
def add_no_services_menuitem(self):
for menuitem in self.service_menu.get_children():
self.service_menu.remove(menuitem)
menuitem = gtk.MenuItem(_("No services found"))
menuitem.set_sensitive(False)
self.service_menu.add(menuitem)
menuitem.show_all()
###############################################################################
#
# APPLET CONTEXTUAL OPTIONS MENU
#
def on_about(self, component, verb, applet):
icon = gtk.Image()
icon.set_from_file("@iconsdir@/48x48/service-discovery-applet.png")
fullname = _("Zeroconf Service Discovery")
copyright = _("Copyright (C) 2005 Sebastien Estienne")
description = _("Shows Zeroconf Services on your local network and allows accessing them easily")
authors = ["Sebastien Estienne ", "Sebastian Dröge "]
translators = _("translator-credits")
if translators == "translator-credits":
translators = None
about = gnome.ui.About(fullname, "@version@", copyright, description, authors, None, translators, icon.get_pixbuf())
about.set_icon(icon.get_pixbuf())
about.show()
def on_config(self, component, verb, applet):
pid = subprocess.Popen(["service-discovery-config", ""]).pid
def start_service_discovery(self, component, verb, applet):
if len(self.domain) != 0:
print "domain not null %s" % (self.domain)
self.display_notification(_("Already Discovering"),"")
return
try:
self.avahi_version = self.server.GetVersionString()
self.domain = self.server.GetDomainName()
except:
self.display_notification(_("Error Detected!"),_("Check that the Avahi daemon is running!"))
return
self.display_notification(_("Starting discovery"),"")
self.interface = avahi.IF_UNSPEC
self.protocol = avahi.PROTO_INET
gc_entries = self.gc_client.all_entries(self.gc_services)
for gc_entry in gc_entries:
if self.gc_client.get_bool(gc_entry.key) == True:
service_type = os.path.basename(gc_entry.key)
self.add_service_type(self.interface, self.protocol, service_type, self.domain)
# Wait one second before displaying notifications
self.show_notifications = False
gobject.timeout_add(1000, self.start_notifying_cb)
def stop_service_discovery(self, component, verb, applet):
if len(self.domain) == 0:
self.display_notification(_("Discovery already stopped"),"")
return
for service in self.service_browsers.copy():
self.del_service_type(service[0],service[1],service[2],service[3])
self.domain = ""
self.display_notification(_("Discovery stopped"),"")
###############################################################################
#
# NOTIFICATIONS
#
def start_notifying_cb(self):
print "start notifying"
self.show_notifications = self.gc_client.get_bool ("%s/%s" % (self.gc_options,"show_notifications"))
def display_service_notification(self, new, name, type):
# FIXME handle this in avahi.ServiceTypeDatabase
if self.zc_pretty_name.has_key(type):
pretty_name = self.zc_pretty_name[type]
else:
pretty_name = type
iconfile = "@iconsdir@/48x48/%s.png" % (type)
if not os.path.exists(iconfile):
iconfile = "@iconsdir@/48x48/service-discovery-applet.png"
message = _("Name : %s\nType : %s (%s)") % (name, pretty_name, type)
if new == True:
title = _("New service found")
else:
title = _("Service disappeared")
self.display_notification(title, message, iconfile)
def display_notification(self, title, message, iconfile = "@iconsdir@/48x48/service-discovery-applet.png"):
try:
if self.show_notifications == True:
self.notif.Notify(_("Zeroconf Service Discovery"),
iconfile, dbus.UInt32(0),"",dbus.Byte(0),
title,message,
[iconfile],[""],[""],True,dbus.UInt32(3))
except:
print "can't use notification daemon"
pass
###############################################################################
#
# APPLET CALLBACKS
#
def position_popup_cb(self, widget):
x, y = self.applet.window.get_origin()
applet_width = self.applet.allocation.width
applet_height = self.applet.allocation.height
widget_width ,widget_height = widget.size_request()
orientation = self.applet.get_orient()
if orientation == gnomeapplet.ORIENT_UP:
y -= widget_height
elif orientation == gnomeapplet.ORIENT_DOWN:
y += applet_height
elif orientation == gnomeapplet.ORIENT_LEFT:
x -= widget_width
elif orientation == gnomeapplet.ORIENT_RIGHT:
x += applet_width
return (x, y, True)
def on_button_press(self, widget, event):
if event.type == gtk.gdk.BUTTON_PRESS and event.button == 1:
self.service_menu.show_all()
self.service_menu.popup(None, None, self.position_popup_cb, event.button, event.time)
widget.set_state(gtk.STATE_SELECTED)
# if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3:
# self.popup_control.do_popup(event.button,event.time)
return False
def on_hide_service_menu(self,widget):
self.applet.set_state(gtk.STATE_NORMAL)
return False
def on_applet_size_allocate(self, eventbox, rect):
if (rect.x <= 0) or (rect.y <= 0):
return False
rect.x -= 1
rect.y -= 1
rect.width += 2
rect.height += 2
gtk.Widget.size_allocate(eventbox, rect)
return False
def on_change_background(self, panelapplet, backgroundtype, color, pixmap):
panelapplet.modify_bg(gtk.STATE_NORMAL, color)
if backgroundtype == gnomeapplet.PIXMAP_BACKGROUND:
s = panelapplet.get_style()
s.bg_pixmap[gtk.STATE_NORMAL] = pixmap
###############################################################################
#
# GCONF
#
# Callback called when a service is added/removed/enabled/disabled in gconf
def gc_services_cb (self, client, cnxn_id, gc_entry, data):
service_type = os.path.basename(gc_entry.key)
if client.get_bool(gc_entry.key) == True:
# Browse for a new service
self.add_service_type(self.interface, self.protocol, service_type, self.domain)
else:
# Stop browsing for a service
self.del_service_type(self.interface, self.protocol, service_type, self.domain)
def gc_options_cb (self, client, cnxn_id, gc_entry, data):
key = os.path.basename(gc_entry.key)
if key == "show_notifications":
self.show_notifications = client.get_bool(gc_entry.key)
if key == "show_local_services":
self.show_local_services = client.get_bool(gc_entry.key)
self.show_notifications = False
status = self.domain
self.stop_service_discovery(None,None,None)
# only start if it was running before
if len(status) != 0:
self.start_service_discovery(None,None,None)
###############################################################################
#
# STARTING POINT OF THE APPLET
#
def applet_factory(applet, iid):
sda = ServiceDiscoveryApplet(applet, iid)
sda.applet.show_all()
return True
def activate_factory():
gnomeapplet.bonobo_factory("OAFIID:GNOME_ServiceDiscoveryApplet_Factory",
gnomeapplet.Applet.__gtype__,
"Service discovery applet", "0", applet_factory)
def main():
gobject.type_register(ServiceDiscoveryApplet)
if len(sys.argv) == 2 and sys.argv[1] == "-window":
applet_window = gtk.Window(gtk.WINDOW_TOPLEVEL)
applet_window.set_title(_("Zeroconf Service Discovery"))
applet_window.connect("destroy", gtk.main_quit)
gnome.init("Service discovery applet", "@version@")
applet = gnomeapplet.Applet()
applet_factory(applet, None)
applet.reparent(applet_window)
applet_window.show_all()
gtk.main()
else:
activate_factory()
if __name__ == "__main__":
main()