#!@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 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 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, "_webdav._tcp" : False, "_webdavs._tcp": False } ############################################################################### # # 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) # 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) 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) ############################################################################### # # NOTIFICATIONS # class Notifications: def __init__(self, applet): 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.applet = applet def display_service_notification(self, new, name, type): iconfile = "file://@iconsdir@/48x48/%s.png" % (type) if not os.path.exists(iconfile): iconfile = "file://@iconsdir@/48x48/service-discovery-applet.png" 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.applet.show_notifications == True: self.notif.Notify(_("Zeroconf Service Discovery"), iconfile, dbus.UInt32(0), title, message, [], [], dbus.Int32(3000), dbus.UInt32(0)) except: print "can't use notification daemon" pass ############################################################################### # # 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"), _("_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) try: self.use_host_names = self.server.IsNSSSupportAvailable() except: self.use_host_names = False self.sdaNotifications = Notifications(self) self.start_service_discovery(None, None, None) # applet.connect("destroy",self.cleanup) # applet.show_all() def start_notifying_cb(self): print "start notifying" self.show_notifications = self.sdaGconf.get_option("show_notifications") ############################################################################### # # 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 = sdapplet.pluginutils.pair_to_dict(txts) self.plugin.plugins[type][0].connect(self.use_host_names, 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 >= 2: self.server.ResolveService(interface, protocol, name, type, domain, avahi.PROTO_INET, dbus.UInt32(0), reply_handler=self.service_resolved, error_handler=self.print_error) elif self.avahi_version == 0: 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 >= 2: try: if flags & avahi.LOOKUP_RESULT_LOCAL: return except AttributeError: pass elif self.avahi_version == 0: if self.server.IsServiceLocal( interface, protocol, name, type, domain) == True: return 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: 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.sdaNotifications.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.sdaNotifications.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 >= 2: 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) elif self.avahi_version == 0: 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.sdaNotifications.display_notification(_("Already Discovering"),"") return try: try: self.avahi_version = self.server.GetAPIVersion() >> 8 except: if self.server.GetVersionString() == "avahi 0.6": self.avahi_version = 0x0201 >> 8 else: self.avahi_version = 0x0000 >> 8 self.domain = self.server.GetDomainName() except: self.sdaNotifications.display_notification(_("Error Detected!"),_("Check that the Avahi daemon is running!")) return self.sdaNotifications.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.sdaNotifications.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.sdaNotifications.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()