#!@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$ # import os import subprocess import sys import pygtk import pprint import sdapplet.pluginloader import sdapplet.pluginutils 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 pynotify 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 ############################################################################### # # ServiceTypeDatabase # class ServiceTypeDatabase: def __init__(self): self.pretty_name = avahi.ServiceTypeDatabase.ServiceTypeDatabase() def get_human_type(self, type): if self.pretty_name.has_key(type): return self.pretty_name[type] else: return type ############################################################################### # # GCONF # class SDAGconf: def __init__(self, applet): self.applet = applet # 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.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) for (stype, desc) in avahi.ServiceTypeDatabase.ServiceTypeDatabase().items(): if not self.gc_client.get("%s/%s" % (self.gc_services, stype)): enabled = self.applet.plugin.plugins.has_key(stype) self.gc_client.set_bool("%s/%s" % (self.gc_services, stype), enabled) def get_services(self): services = [] 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) services.append(service_type) return services def get_option(self, key): return self.gc_client.get_bool ("%s/%s" % (self.gc_options, key)) # 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.applet.add_service_type(self.applet.interface, self.applet.protocol, service_type, self.applet.domain) else: # Stop browsing for a service self.applet.del_service_type(self.applet.interface, self.applet.protocol, service_type, self.applet.domain) def gc_options_cb (self, client, cnxn_id, gc_entry, data): key = os.path.basename(gc_entry.key) if key == "show_notifications": self.applet.show_notifications = client.get_bool(gc_entry.key) if key == "show_local_services": self.applet.show_local_services = client.get_bool(gc_entry.key) self.applet.show_notifications = False status = self.applet.domain self.applet.stop_service_discovery(None,None,None) # only start if it was running before if len(status) != 0: self.applet.start_service_discovery(None,None,None) ############################################################################### # # 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 = {} # Plugins self.plugin = sdapplet.pluginloader.PluginLoader("@pluginsdir@") self.sdaGconf = SDAGconf(self) self.show_local_services = self.sdaGconf.get_option("show_local_services") self.show_notifications = self.sdaGconf.get_option("show_notifications") 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")) applet.setup_menu(menuXml, [ ("SDA About", self.on_about), ("SDA Config", self.on_config) ], applet) self.popup = applet.get_popup_component() self.popup_control = applet.get_control() #Start Service Discovery self.domain = "" try: self.system_bus = dbus.SystemBus() self.system_bus.add_signal_receiver(self.avahi_dbus_connect_cb, "NameOwnerChanged", "org.freedesktop.DBus", arg0="org.freedesktop.Avahi") except dbus.DBusException, e: error_msg(_("DBus Error:\nCan't contact the system bus.\n\n Exiting...")) pprint.pprint(e) sys.exit(1) if not pynotify.init(_("Zeroconf Service Discovery")): error_msg(_("Notification Daemon Error:\n Notifications won't work.")) self.applet.show_notifications = False self.start_service_discovery(None, None, None) # applet.connect("destroy",self.cleanup) # applet.show_all() def avahi_dbus_connect_cb(self, a, connect, disconnect): if connect != "": print "We are disconnected from avahi-daemon" self.stop_service_discovery(None, None, None) else: print "We are connected to avahi-daemon" self.start_service_discovery(None, None, None) def start_notifying_cb(self): print "start notifying" self.show_notifications = self.sdaGconf.get_option("show_notifications") ############################################################################### # # NOTIFICATIONS # def display_service_notification(self, new, name, type): iconfile_path = "@iconsdir@/48x48/%s.png" % (type) print iconfile_path if not os.path.exists(iconfile_path): iconfile = "file://@iconsdir@/48x48/service-discovery-applet.png" else: iconfile ="file://%s" % iconfile_path stdb = ServiceTypeDatabase() h_type = stdb.get_human_type(type) message = _("Name : %s\nType : %s (%s)") % (name, h_type, 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 = "file://@iconsdir@/48x48/service-discovery-applet.png"): try: if self.show_notifications == True: n = pynotify.Notification(title, message, iconfile) # x,y = self.applet.window.get_origin() # n.set_hint("x",x) # n.set_hint("y",y) # n.attach_to_widget(self.applet.window) n.show() except: print "can't use notification daemon" pass ############################################################################### # # AVAHI # def siocgifname(self, interface): if interface <= 0: return "any" else: return self.server.GetNetworkInterfaceNameByIndex(interface) 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 = sdapplet.pluginutils.pair_to_dict(txts) try: self.plugin.plugins[type][0].connect(self.use_host_names, name, type, host, address, port, txts) except KeyError,e: error_msg("No plugin to handle %s" % type) def print_error(self, err): # FIXME we should use notifications print "Error:", str(err) def menuitem_response(self, widget, interface, protocol, name, type, domain): self.server.ResolveService(interface, protocol, name, type, domain, avahi.PROTO_INET, dbus.UInt32(0), reply_handler=self.service_resolved, error_handler=self.print_error) 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 flags & avahi.LOOKUP_RESULT_LOCAL: return except dbus.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: 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) stdb = ServiceTypeDatabase() h_type = stdb.get_human_type(type) menuitem = gtk.ImageMenuItem(h_type) 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(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.has_key(type) and 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) 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) 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] try: sb.Free() except dbus.DBusException: pass 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.server = dbus.Interface(self.system_bus.get_object(avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER) self.domain = self.server.GetDomainName() except: self.display_notification(_("Error Detected!"),_("Check that the Avahi daemon is running!")) return try: self.use_host_names = self.server.IsNSSSupportAvailable() except: self.use_host_names = False self.display_notification(_("Starting discovery"),"") self.interface = avahi.IF_UNSPEC self.protocol = avahi.PROTO_INET services = self.sdaGconf.get_services() for service_type in services: 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"),"") ############################################################################### # # 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 ############################################################################### # # 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()