UPnP-Inspector-0.2.2/0000755000175000017500000000000011233422032012467 5ustar poxpoxUPnP-Inspector-0.2.2/bin/0000755000175000017500000000000011204600210013231 5ustar poxpoxUPnP-Inspector-0.2.2/bin/upnp-inspector0000755000175000017500000000272011204600003016146 0ustar poxpox#! /usr/bin/env python # -*- coding: utf-8 -*- # # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2009 - Frank Scholz """ Inspector is a UPnP device inspector, or device spy Based on the Coherence UPnP/DLNA framework http://coherence.beebits.net """ import os, sys from twisted.internet import gtk2reactor gtk2reactor.install() from twisted.internet import reactor from twisted.python import usage from upnp_inspector import __version__ from coherence import __version__ as coherence_version from upnp_inspector.base import Inspector class Options(usage.Options): optFlags = [ ['version','v', 'print out version'] ] optParameters = [['logfile', 'l', None, 'logfile'], ] def __init__(self): usage.Options.__init__(self) self['options'] = {} def opt_version(self): print "UPnP-Inspector version:", __version__ print "using Coherence:", coherence_version sys.exit(0) def opt_help(self): sys.argv.remove('--help') print self.__str__() sys.exit(0) if __name__ == '__main__': options = Options() try: options.parseOptions() except usage.UsageError, errortext: print '%s: %s' % (sys.argv[0], errortext) print '%s: Try --help for usage details.' % (sys.argv[0]) sys.exit(0) i = Inspector(logfile=options['logfile']) reactor.run() UPnP-Inspector-0.2.2/README0000644000175000017500000000034611204600003013344 0ustar poxpoxUPnP-Inspector is an UPnP Device and Service analyzer, based on the Coherence DLNA/UPnP framework. Loosely modeled after the Intel UPnP Device Spy and the UPnP Test Tool. More info @ http://coherence.beebits.net/UPnP-Inspector. UPnP-Inspector-0.2.2/setup.cfg0000644000175000017500000000007311204600210014302 0ustar poxpox[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 UPnP-Inspector-0.2.2/upnp_inspector/0000755000175000017500000000000011204600210015531 5ustar poxpoxUPnP-Inspector-0.2.2/upnp_inspector/mediaserver.py0000644000175000017500000006311211204600003020414 0ustar poxpox# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2009 - Frank Scholz import os.path import mimetypes mimetypes.init() import pygtk pygtk.require("2.0") import gtk from twisted.internet import reactor from coherence import log from coherence.upnp.core.utils import parse_xml # gtk store defines NAME_COLUMN = 0 ID_COLUMN = 1 UPNP_CLASS_COLUMN = 2 CHILD_COUNT_COLUMN = 3 UDN_COLUMN = 4 SERVICE_COLUMN = 5 ICON_COLUMN = 6 DIDL_COLUMN = 7 TOOLTIP_ICON_COLUMN = 8 from pkg_resources import resource_filename namespaces = {'{http://purl.org/dc/elements/1.1/}':'dc:', '{urn:schemas-upnp-org:metadata-1-0/upnp/}': 'upnp:', '{urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/}': 'DIDL-Lite:', '{urn:schemas-dlna-org:metadata-1-0}': 'dlna:', '{http://www.pv.com/pvns/}': 'pv:'} class ItemDetailsWidget(object): def __init__(self): self.window = gtk.ScrolledWindow() self.window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.window.set_border_width(2) self.window.set_shadow_type(gtk.SHADOW_ETCHED_IN) self.store = gtk.TreeStore(str,str) self.treeview = gtk.TreeView(self.store) self.column = gtk.TreeViewColumn() self.treeview.append_column(self.column) self.treeview.set_headers_visible(False) self.treeview.connect("button_press_event", self.button_action) text_cell = gtk.CellRendererText() self.column.pack_start(text_cell, False) self.column.set_attributes(text_cell, text=0) text_cell = gtk.CellRendererText() self.column.pack_start(text_cell, True) self.column.set_attributes(text_cell, text=1) self.window.set_size_request(400, 300) self.window.add(self.treeview) def open_url(self,url): import webbrowser webbrowser.open(url) def button_action(self, widget, event): #print "ItemDetailsWidget button_action", widget, self x = int(event.x) y = int(event.y) path = widget.get_path_at_pos(x, y) if path == None: return True row_path,column,_,_ = path if event.button == 3: store = widget.get_model() iter = store.get_iter(row_path) menu = gtk.Menu() key,= store.get(iter,0) value,= store.get(iter,1) clipboard = gtk.clipboard_get(gtk.gdk.SELECTION_CLIPBOARD) if key in ['DIDL-Lite:res','upnp:albumArtURI']: item = gtk.MenuItem("copy URL") item.connect("activate", lambda w: clipboard.set_text(value)) menu.append(item) item = gtk.MenuItem("open URL") item.connect("activate", lambda w: self.open_url(value)) menu.append(item) else: item = gtk.MenuItem("copy value") item.connect("activate", lambda w: clipboard.set_text(value)) menu.append(item) menu.show_all() menu.popup(None,None,None,event.button,event.time) return True return False class TreeWidget(object): def __init__(self,coherence,device, details_store=None, cb_item_dbl_click=None, cb_resource_chooser=None): self.details_store = details_store self.cb_item_dbl_click = cb_item_dbl_click self.cb_item_right_click = None self.cb_resource_chooser = cb_resource_chooser self.build_ui() self.coherence = coherence self.device = device self.mediaserver_found(device) def build_ui(self): self.window = gtk.ScrolledWindow() self.window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) icon = resource_filename(__name__, os.path.join('icons','folder.png')) self.folder_icon = gtk.gdk.pixbuf_new_from_file(icon) icon = resource_filename(__name__, os.path.join('icons','audio-x-generic.png')) self.audio_icon = gtk.gdk.pixbuf_new_from_file(icon) icon = resource_filename(__name__, os.path.join('icons','video-x-generic.png')) self.video_icon = gtk.gdk.pixbuf_new_from_file(icon) icon = resource_filename(__name__, os.path.join('icons','image-x-generic.png')) self.image_icon = gtk.gdk.pixbuf_new_from_file(icon) self.store = gtk.TreeStore(str, # 0: name or title str, # 1: id, '0' for the device str, # 2: upnp_class, 'root' for the device int, # 3: child count, -1 if not available str, # 4: device udn, '' for an item str, # 5: service path, '' for a non container item gtk.gdk.Pixbuf, str, # 7: DIDLLite fragment, '' for a non upnp item gtk.gdk.Pixbuf ) self.treeview = gtk.TreeView(self.store) self.column = gtk.TreeViewColumn('Items') self.treeview.append_column(self.column) self.treeview.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, [('upnp/metadata', 0, 1)], gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_PRIVATE) self.treeview.connect("drag_data_get", self.drag_data_get_cb) # create a CellRenderers to render the data icon_cell = gtk.CellRendererPixbuf() text_cell = gtk.CellRendererText() self.column.pack_start(icon_cell, False) self.column.pack_start(text_cell, True) self.column.set_attributes(text_cell, text=0) self.column.add_attribute(icon_cell, "pixbuf",6) self.treeview.connect("row-activated", self.browse) self.treeview.connect("row-expanded", self.row_expanded) self.treeview.connect("button_press_event", self.button_action) #self.treeview.set_property("has-tooltip", True) #self.treeview.connect("query-tooltip", self.show_tooltip) #self.tooltip_path = None self.we_are_scrolling = None #def end_scrolling(): # self.we_are_scrolling = None #def start_scrolling(w,e): # if self.we_are_scrolling != None: # self.we_are_scrolling.reset(800) # else: # self.we_are_scrolling = reactor.callLater(800, end_scrolling) #self.treeview.connect('scroll-event', start_scrolling) self.window.set_size_request(400, 300) self.window.add(self.treeview) def drag_data_get_cb(self,treeview, context, selection, info, timestamp): treeselection = treeview.get_selection() model, iter = treeselection.get_selected() didl = model.get_value(iter, DIDL_COLUMN) #print "drag_data_get_cb", didl selection.set('upnp/metadata', 8, didl) return def show_tooltip(self, widget, x, y, keyboard_mode, tooltip): if self.we_are_scrolling != None: return False ret = False try: path = self.treeview.get_dest_row_at_pos(x, y) iter = self.store.get_iter(path[0]) title,object_id,upnp_class,item = self.store.get(iter,NAME_COLUMN,ID_COLUMN,UPNP_CLASS_COLUMN,DIDL_COLUMN) from coherence.upnp.core import DIDLLite if upnp_class == 'object.item.videoItem': self.tooltip_path = object_id item = DIDLLite.DIDLElement.fromString(item).getItems()[0] tooltip_icon, = self.store.get(iter,TOOLTIP_ICON_COLUMN) if tooltip_icon != None: tooltip.set_icon(tooltip_icon) else: tooltip.set_icon(self.video_icon) for res in item.res: protocol,network,content_format,additional_info = res.protocolInfo.split(':') if(content_format == 'image/jpeg' and 'DLNA.ORG_PN=JPEG_TN' in additional_info.split(';')): icon_loader = gtk.gdk.PixbufLoader() icon_loader.write(urllib.urlopen(str(res.data)).read()) icon_loader.close() icon = icon_loader.get_pixbuf() tooltip.set_icon(icon) self.store.set_value(iter, TOOLTIP_ICON_COLUMN, icon) #print "got poster", icon break title = title.replace('&','&') try: director = item.director.replace('&','&') except AttributeError: director = "" try: description = item.description.replace('&','&') except AttributeError: description = "" tooltip.set_markup("%s\n" "Director: %s\n" "Description: %s" % (title, director, description)) ret = True except TypeError: #print traceback.format_exc() pass except Exception: #print traceback.format_exc() #print "something wrong" pass return ret def button_action(self, widget, event): #print "TreeWidget button_action", widget, event, event.button x = int(event.x) y = int(event.y) path = widget.get_path_at_pos(x, y) if path == None: return True row_path,column,_,_ = path if event.button == 1 and self.details_store != None: store = widget.get_model() iter = store.get_iter(row_path) didl,= store.get(iter,DIDL_COLUMN) self.details_store.clear() #print didl et = parse_xml(didl, 'utf-8') et = et.getroot() def un_namespace(text): for k,v in namespaces.items(): if text.startswith(k): return text.replace(k,v) return text def append(item,row=None): for k,v in item.attrib.items(): self.details_store.append(row,(un_namespace(k),v)) for child in item: new_row=self.details_store.append(row,(un_namespace(child.tag),child.text)) if un_namespace(child.tag) == 'DIDL-Lite:res': append(child,new_row) for item in et: append(item) if event.button == 3: if self.cb_item_right_click != None: return self.cb_item_right_click(widget, event) else: store = widget.get_model() iter = store.get_iter(row_path) title,object_id,upnp_class = self.store.get(iter,NAME_COLUMN,ID_COLUMN,UPNP_CLASS_COLUMN) menu = None if upnp_class == 'root' or upnp_class.startswith('object.container'): def refresh(treeview,path): expanded = treeview.row_expanded(path) store = treeview.get_model() iter = store.get_iter(row_path) child = store.iter_children(iter) while child: store.remove(child) child = store.iter_children(iter) self.browse(treeview,path,None, starting_index=0,requested_count=0,force=True,expand=expanded) menu = gtk.Menu() item = gtk.MenuItem("refresh container") item.connect("activate", lambda x: refresh(widget,row_path)) menu.append(item) if upnp_class != 'root': url,didl = self.store.get(iter,SERVICE_COLUMN,DIDL_COLUMN) if upnp_class.startswith('object.container'): from coherence.upnp.core import DIDLLite url = '' item = DIDLLite.DIDLElement.fromString(didl).getItems()[0] res = item.res.get_matching(['*:*:*:*'], protocol_type='http-get') if len(res) > 0: for r in res: if r.data.startswith('dlna-playcontainer://'): url = r.data break if url != '': print "prepare to play", url def handle_error(e): print 'we have an error', e def handle_result(r): print "done", r def start(r,service): print "call start", service action = service.get_action('Play') d = action.call(InstanceID=0,Speed=1) d.addCallback(handle_result) d.addErrback(handle_error) def set_uri(r,service,url,didl): print "call set", service,url,didl action = service.get_action('SetAVTransportURI') d = action.call(InstanceID=0,CurrentURI=url, CurrentURIMetaData=didl) d.addCallback(start,service) d.addErrback(handle_error) return d def play(service,url,didl): print "call stop", service action = service.get_action('Stop') print action d = action.call(InstanceID=0) d.addCallback(set_uri,service,url,didl) d.addErrback(handle_error) if menu == None: menu = gtk.Menu() else: menu.append(gtk.SeparatorMenuItem()) item = gtk.MenuItem("play on MediaRenderer...") item.set_sensitive(False) menu.append(item) menu.append(gtk.SeparatorMenuItem()) for device in self.coherence.devices: if device.get_device_type().split(':')[3].lower() == 'mediarenderer': item = gtk.MenuItem(device.get_friendly_name()) service = device.get_service_by_type('AVTransport') item.connect("activate", lambda x: play(service,url,didl)) menu.append(item) if menu != None: menu.show_all() menu.popup(None,None,None,event.button,event.time) return True return 0 def handle_error(self,error): print error def device_has_action(self,udn,service,action): try: self.devices[udn][service]['actions'].index(action) return True except: return False def state_variable_change( self, variable): #print variable.name, 'changed to', variable.value name = variable.name value = variable.value if name == 'ContainerUpdateIDs': changes = value.split(',') while len(changes) > 1: container = changes.pop(0).strip() update_id = changes.pop(0).strip() def match_func(model, iter, data): column, key = data # data is a tuple containing column number, key value = model.get_value(iter, column) return value == key def search(model, iter, func, data): #print "search", model, iter, data while iter: if func(model, iter, data): return iter result = search(model, model.iter_children(iter), func, data) if result: return result iter = model.iter_next(iter) return None row_count = 0 for row in self.store: iter = self.store.get_iter(row_count) match_iter = search(self.store, self.store.iter_children(iter), match_func, (ID_COLUMN, container)) if match_iter: print "heureka, we have a change in ", container, ", container needs a reload" path = self.store.get_path(match_iter) expanded = self.treeview.row_expanded(path) child = self.store.iter_children(match_iter) while child: self.store.remove(child) child = self.store.iter_children(match_iter) self.browse(self.treeview,path,None, starting_index=0,requested_count=0,force=True,expand=expanded) break row_count += 1 def mediaserver_found(self,device): service = device.get_service_by_type('ContentDirectory') def reply(response): item = self.store.append(None) self.store.set_value(item, NAME_COLUMN, 'root') self.store.set_value(item, ID_COLUMN, '0') self.store.set_value(item, UPNP_CLASS_COLUMN, 'root') self.store.set_value(item, CHILD_COUNT_COLUMN, -1) self.store.set_value(item, UDN_COLUMN, device.get_usn()) self.store.set_value(item, ICON_COLUMN, self.folder_icon) self.store.set_value(item, DIDL_COLUMN, response['Result']) self.store.set_value(item, SERVICE_COLUMN, service) self.store.set_value(item, TOOLTIP_ICON_COLUMN, None) self.store.append(item, ('...loading...','','placeholder',-1,'','',None,'',None)) action = service.get_action('Browse') d = action.call(ObjectID='0',BrowseFlag='BrowseMetadata', StartingIndex=str(0),RequestedCount=str(0), Filter='*',SortCriteria='') d.addCallback(reply) d.addErrback(self.handle_error) service.subscribe_for_variable('ContainerUpdateIDs',callback=self.state_variable_change) service.subscribe_for_variable('SystemUpdateID',callback=self.state_variable_change) def row_expanded(self,view,iter,row_path): #print "row_expanded", view,iter,row_path child = self.store.iter_children(iter) if child: upnp_class, = self.store.get(child,UPNP_CLASS_COLUMN) if upnp_class == 'placeholder': self.browse(view,row_path,None) def browse(self,view,row_path,column,starting_index=0,requested_count=0,force=False,expand=False): #print "browse", view,row_path,column,starting_index,requested_count,force iter = self.store.get_iter(row_path) child = self.store.iter_children(iter) if child: upnp_class, = self.store.get(child,UPNP_CLASS_COLUMN) if upnp_class != 'placeholder': if force == False: if view.row_expanded(row_path): view.collapse_row(row_path) else: view.expand_row(row_path, False) return title,object_id,upnp_class = self.store.get(iter,NAME_COLUMN,ID_COLUMN,UPNP_CLASS_COLUMN) if(not upnp_class.startswith('object.container') and not upnp_class == 'root'): url, = self.store.get(iter,SERVICE_COLUMN) if url == '': return print "request to play:", title,object_id,url if self.cb_item_dbl_click != None: self.cb_item_dbl_click(url) return def reply(r): #print "browse_reply - %s of %s returned" % (r['NumberReturned'],r['TotalMatches']) from coherence.upnp.core import DIDLLite from coherence.extern.et import ET child = self.store.iter_children(iter) if child: upnp_class, = self.store.get(child,UPNP_CLASS_COLUMN) if upnp_class == 'placeholder': self.store.remove(child) title, = self.store.get(iter,NAME_COLUMN) try: title = title[:title.rindex('(')] self.store.set_value(iter,NAME_COLUMN, "%s(%d)" % (title,int(r['TotalMatches']))) except ValueError: pass elt = parse_xml(r['Result'], 'utf-8') elt = elt.getroot() for child in elt: #stored_didl_string = DIDLLite.element_to_didl(child) stored_didl_string = DIDLLite.element_to_didl(ET.tostring(child)) didl = DIDLLite.DIDLElement.fromString(stored_didl_string) item = didl.getItems()[0] #print item.title, item.id, item.upnp_class if item.upnp_class.startswith('object.container'): icon = self.folder_icon service, = self.store.get(iter,SERVICE_COLUMN) child_count = item.childCount try: title = "%s (%d)" % (item.title,item.childCount) except TypeError: title = "%s (n/a)" % item.title child_count = -1 else: icon=None service = '' child_count = -1 title = item.title if item.upnp_class.startswith('object.item.audioItem'): icon = self.audio_icon elif item.upnp_class.startswith('object.item.videoItem'): icon = self.video_icon elif item.upnp_class.startswith('object.item.imageItem'): icon = self.image_icon res = item.res.get_matching(['*:*:*:*'], protocol_type='http-get') if len(res) > 0: res = res[0] service = res.data new_iter = self.store.append(iter, (title,item.id,item.upnp_class,child_count,'',service,icon,stored_didl_string,None)) if item.upnp_class.startswith('object.container'): self.store.append(new_iter, ('...loading...','','placeholder',-1,'','',None,'',None)) if((int(r['TotalMatches']) > 0 and force==False) or expand==True): view.expand_row(row_path, False) if(requested_count != int(r['NumberReturned']) and int(r['NumberReturned']) < (int(r['TotalMatches'])-starting_index)): print "seems we have been returned only a part of the result" print "requested %d, starting at %d" % (requested_count,starting_index) print "got %d out of %d" % (int(r['NumberReturned']), int(r['TotalMatches'])) print "requesting more starting now at %d" % (starting_index+int(r['NumberReturned'])) self.browse(view,row_path,column, starting_index=starting_index+int(r['NumberReturned']), force=True) service = self.device.get_service_by_type('ContentDirectory') action = service.get_action('Browse') d = action.call(ObjectID=object_id,BrowseFlag='BrowseDirectChildren', StartingIndex=str(starting_index),RequestedCount=str(requested_count), Filter='*',SortCriteria='') d.addCallback(reply) d.addErrback(self.handle_error) def destroy_object(self, row_path): #print "destroy_object", row_path iter = self.store.get_iter(row_path) object_id, = self.store.get(iter,ID_COLUMN) parent_iter = self.store.iter_parent(iter) service, = self.store.get(parent_iter,SERVICE_COLUMN) if service == '': return def reply(r): #print "destroy_object reply", r pass s = self.bus.get_object(BUS_NAME+'.service',service) s.action('destroy_object', {'object_id':object_id}, reply_handler=reply,error_handler=self.handle_error) class MediaServerWidget(log.Loggable): logCategory = 'inspector' def __init__(self,coherence,device): self.coherence = coherence self.device = device self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.hide) self.window.set_default_size(400,600) self.window.set_title('Browse MediaServer %s' % device.get_friendly_name()) self.item_details = ItemDetailsWidget() self.ui = TreeWidget(coherence,device,self.item_details.store) vpane = gtk.VPaned() vpane.add1(self.ui.window) vpane.add2(self.item_details.window) self.window.add(vpane) self.window.show_all() def hide(self,w,e): w.hide() self.ui.store.clear() self.ui.mediaserver_found(self.device) return TrueUPnP-Inspector-0.2.2/upnp_inspector/icons/0000755000175000017500000000000011204600210016644 5ustar poxpoxUPnP-Inspector-0.2.2/upnp_inspector/icons/media-skip-backward.png0000644000175000017500000000232411204600003023152 0ustar poxpoxPNG  IHDR szzsBIT|dIDATX[lTEs3gK!HL&U$o"@hbbՐvHA}E.xReUbƺ@lS(]텲rr_f~7\2{HJdʜ`$e pAxyBU$,B ]/YR\8{"0+/Oz cs f[SZZT>o~Ƒ#c)Ç3!]{ hтG]xeuD~pX @.,q2|eeOOXIQ)DH&g.7+<OZ `ڿ'*O:BZ~ w݈F#H$$ d2|JpTt[PJ'w6lbKYFc‘0 HܩLɊϭ5]oĞ-[JÑqܸ9 I", .` ι%鴄.wbw B>\ B,$"B&/Ly׮/bx/I^kB#1H.RM-+ ;. vJe(UUqB`FHP?~>Йp9Yy 0 K4-kC}޸}k$pP2JFXº4koھ~<8 lD.`,K)paCU;>k8s=Ua!RuBdOOOw†?U;wny]4Tp!ӈF#Pdv]M958p=@)YRw˕4BiV0AIN$[wW؂&iZ 4MT  gNĘ=\ׁivl[soaIlnmme[ C:Ku=7ooT*$BQHX,ˁc€Rlƅ_DLfE+WƦ_!EQ4m.|e󣗗L.P[=7[y.x}v%EQfA¸s/tIR-I|QџLSg+fgm$=iYId29gke%(b E#>JTTDq_BY(}"gIENDB`UPnP-Inspector-0.2.2/upnp_inspector/icons/camera-web.png0000644000175000017500000000213011204600003021351 0ustar poxpoxPNG  IHDRysBIT|d pHYs"utEXtSoftwarewww.inkscape.org<IDAT8oEMW8nVԴNPU8pH="UBrM DQHU6q7^k;Y{wCBK@Q2ѧ|bܗsc+׏vbmST*j6oJW^RT؋ @\f~~~7o>rSS7\::Tm <<:5]1W,vNLL>/BRʁd2~]OL?p``Im0uVoK~ygccc7sN.>۷'kq-H`:Kb1抳s @48%TV&'?is L|G=Fµk9|kuPq|p ҶuR ihdzuL]J%zQ`'nMӈf9}]Lˤ/7D+AZZuY.;,;.ǩUL~gQJd()23=qUeKf puGV Ftuuq{v];DJE( p]rL`&\љ/.x.ҲǪW;ՍWob-nqoϿ8-SZZ$jIԼ:O Ꞌ R_ @֛XZ;RHgy^mfq8ua!>zu h?BRRb[BבeY ۋRl6jS0MSj \H4͍ tzn? Ck M#R.tSSSH)^Qa:>6167WΡߓ?l]@Jgd ,zOvw?Z[]1r`d NeY9 3Pss[vA=i gUUgYIl[J U|oՇMԀXHX5FT*)8Ys :!Stq 8+ҟ'\(F3jLcd gŊu=Eȳ+,N7};YW9dD4ZKbs4ԕo[5 {E\[=\Ok,RfH[qΘu% FN೏ގ/޺6Lْ3ez,O|"X&PLyW Z2Z$QPXHRC9L͇9DCC8l?O^,RR2lE,CJR|eeRI秷M˲Գ3H)Ci4Mu/}寋KEP Ͽ+t:mqh@f2B`xfZH*uߚ{9WX^^^ ommy }edpčB!Sypn%05Fyg {[-8湮#cx1P{2;+_n;;pG@6c30ַp]Wݺ!̰mŇ KWͥe`mT~Z!u/9:)SHH) \ݖ"hqǷƈADL&,=RU?m<8::SVw` f0g45IENDB`UPnP-Inspector-0.2.2/upnp_inspector/icons/upnp-device.png0000644000175000017500000000141411204600003021571 0ustar poxpoxPNG  IHDR&sBIT|d pHYs"utEXtSoftwarewww.inkscape.org<IDAT8jA{61b BZכT!ED^ I?@&qpcPlsI!rRd`ۯwvѶxGe`f8â>:::cd8>ւUJ8! ^۬%ޗ(py|pp1sЖBu΁ ! ;;;#T7RAkc+`/A('"$I$IPUN"ZjE]ZkiY-} cPeH$2hOGe4̨ eYL}? ;B =}|1fIwk-& ⫫+t:y4Mq>H!,pww3 766P~gEpssS%IEQP 94MJ { iEO?Dԍɠ(9o 2ۋ~l6]\\| ݅W+?{@2sTԯIENDB`UPnP-Inspector-0.2.2/upnp_inspector/icons/media-seek-backward.png0000644000175000017500000000206211204600003023132 0ustar poxpoxPNG  IHDR szzsBIT|dIDATX]L[U{{o[J[m1)l8B0q0I!B/<%t!!X:RJoYZe Ȃ&/ܗ9wrq,+ @ Yaym/~ғ1x]]];=}~wog\.^p{ikgXl[Ŗw\w&TU}~@]E{{[P5H4I@(~uEElm(2!`8cY8⪩zl7}f 6ӛ407(Ks: ͗{onj VA4 g| NAt:}0d2577t z/aڑX[A&% C0 ΎꋅO=W>y7 KTVV*v[jXf#e2B`Yvt7GmE|x"Q$IXLN'nom~+FNtWr&34h'#ݷeyk1qyjwV۷bZ2<rcFyDp14±܌wvQes,Ą86h$ HJ=o,g䏏}(Y@@EYJĮg/?9%(J(~PbTR1*tQB u9$AЀ]~vUUR0Lxx3}Z=B)pS @0y.?iv2̥gn:RHaf'us1RC}X?<5_8 &3 \ _KM`e "(DZah}H,C+ \t]D#1Y~uڜ>{_w"a/_8hyʛ,‡ʧIENDB`UPnP-Inspector-0.2.2/upnp_inspector/icons/audio-x-generic.png0000644000175000017500000000064311204600003022335 0ustar poxpoxPNG  IHDRabKGD pHYs B(xtIME 69$G0IDAT8c`0b,5g`Z@H3^2_  ھvzg&l3~# ,l*ÿ@&0000t7^:  Ld?xpa\xIt@V, ˳0000dZH2w |k'ӧ:5- 3U~^u~ne{O2s2ga```aȰf33;'k.[ɿzOg`a{p[wo^<}C>''L"b,* \axCWy9uo)JIENDB`UPnP-Inspector-0.2.2/upnp_inspector/icons/audio-volume-medium.png0000644000175000017500000000347611204600003023250 0ustar poxpoxPNG  IHDR szzsBIT|dIDATXmTW{;/23˲ah6TشQRc/1Q11i~~0M&BLmԚl R ]fwٷٙrvP<朜yN%߆.`>m u}_-&h͵شi#uu)Ky CCc"!O}CGOO\cw|?ξq5D];۶8BVkuyVruGʷw(֑kkO~ *5m458PWŗ^OuD3Ǿ}d*8BV˕1# sW$D +iOc`ǣBġܟxaaνown{ b ۓ ̇zpU{댎r{.S^(#?QeK91, !%x & spH|{Ͼt]{~<gjRlģ[:{rQ'ղeT+U([8=8DbӺ,[IR(BOƼ2W-Νb]f~~Hk֮Yv8Wtb6M"@:@R0 f޶->;;Ga--.i]嫗:čhL7Τ$T( 1={(m!):ă5X@kM&TRm[i:;;dyrd4R UG)U.;@" D RjC_9-QJdhiicu;FR-B[LGh4BJBŒp'a5@SsSEiCFS*-vGqnJ9XV#hjab) B eYss+-x*Je8NI$F1F`+{݄8N c BJؖER^Qʹ[cJDdM c~ЮBJI204T [YdY:Va$ 'FRpTu+\}d"E,bLk;rєmFvȥm uq R0>9I6J8 9adG3hyk+RZ,P$)Ƙc Q}0 Z0>1?HKWNhA'~z{fMq:&i/+BM'˕/hdd|}C ॗy\nR@qDRauden9Q:gk`"4F\{zGv!^2xkYV{XqᭋtsF3;3ϥKA[7t5 l>^8,yVsX%L=bKVڜmneYrEn9w]C&FJ|+hB[Nxvf n1w,nݰA/}Ėv&zShBs;~7$cZ271@b*@u]_݊-榸%39IENDB`UPnP-Inspector-0.2.2/upnp_inspector/icons/media-skip-forward.png0000644000175000017500000000231111204600003023034 0ustar poxpoxPNG  IHDR szzsBIT|dIDATX[LUƿ3Ι2]cz@/5\0nj՗FC^H&m4B%%E!MII%5-T]hrۋ ̅ekZ|$g~?|fhEwu5 C{T@X)>]pm75ݨ 5%;)hQEQl-zs!ػoIхW0@`=$I"t H%G&7{K_ܴaÆg<[ߒZZ[=s)õΏޭ {$=P0L86p'vUm#?S9j88`:sx[jjr-[JYڜ )1_f!>C,} M8I hp\8vQ \yeOE>QB=@kI HY4y)#PxF~=fwL6HEx]UMCkc_ڳK[0 w 0-Pd'd此w\PmO,_yw=Cew]0tP.\M.omv/(z9C'qH)mUW;ݿo/%Icq{&C^C]6vF]?9Sn\(+1t\z\oD"!Z-t]u`znEEgC֌yK|yŶ +sO?,D?YzX&DX _Hw!D_7/~OE" _NWu4",t=$|6sFfշGf&zԛ_QVZ heŧEBBtaբ߶_(ecp\bӧN0h@ŌO/T„,YBBu TT2{# vU,Y6>&+skFa#97٥;I"(*! 9Hme5ݛo|YlnOjD#yŗ_.6mMZW_ZP_ՔDX5iBS[[<%.; Ԁ_a]h.t]Z($}(-BhBhZ@l߾^\xRM}N'. B7 !qs鴵5PK{k$$4td`]1[tቅ/c1 ]EM?ΝRw=$# IV `_Xr񱉌{ )Rq9 $YDF9MsͫBׅU!n!KESspۄE]MhmnzD !4!m.wKSgEIQQ^::nG֮^]ⷕļGg?~%/"j8,jP^[44 ]BUbͺkE"DDS'_/^&Bׅх9qz}~['**B5غyp\tICU*4UB\_(ؾYD>T]]pxsNzThn*B4ϸs\@ KeahmB+ #"VŎmE_/n,TU* v![<0A~j!ԫU5q`O,=s׋_%T!aiaaqa,뚯f.е8z@=(4MpP[hjhk+jV[|dغaz8_[?K߱J|sbSw2moTkwBs-ykػU!;"ω>Ytk.$"Ţ+EsӗwK|ǝ'ɋmgth49wd~uf=hv'f8&Z/@1*D"*_~MMdbRЕ@q85j4׌EZFt݄ Ξ=ǤիݻS]]Jyy586EoL$QZR. &gUtJK :6`0Ν ſgw ƍQd3e@f$$|~/=zd#PAm1HHT=D9{5P[۝t+_5%9 P0YHBF6ix~zj 翊UϫEBR* ѱ.^:sfcE| .Qy~d$%w;}|6}" 2֮]IPv IV|$ kHJcsG3y Hڊ;UΠ~#1(6sB!g"O.F46o{$^Y/aF5ain-j#;sdj&ӸiZ6c #$ŷP8wd͜99g46N/$ѹs:pikQU6W3Ǐ)Nk $0FOIُnƱ?=|Nt͖;0y.rA{]{Cݲط onm&i<YѸim\H8ƠXRSgN2p`$$rm:r,W6q"6t$YD #;+GFLRR  k,Y1)LFJK1LdtI#-%22oP5.͓&0{PE g/o!{P5S1<]O])DFeE%$ I7!xtJ^q)x^}~Bj+ފbA†uGH/8c ';n'9%] P9Wtn=)-iﵱrE! e:&gX|ֆV2z.j+$&o&$Ljj6"b C p\tԃ:0IX n~F j:QHjZ';l$ƞQ;]/6aqӧG^q٥>c:uUG!蚎$P edIP?$weO[xPSkQQ\bh%s"kO"$b1dDtHmA<./5 <4¥UK}]9?\2&x^~mf~Z?& Hm wPЅ̙WMffpkڂ$go[d-MMfPPpnY70Y̓sjA*k˵ # ]_Pqz5u^w8SD<ԔYuahIAg!1!X )N|ܾ=k$D,Eн~eG'q5.\ xy|;L֯EuUDej, yhokw'9;}J;￟n.羇nbk*ٱu7O8ʊjd Rw" =8o MVf̺HIYg P\Vj<…$R*bgnƬxqiB3qhii #V2S55>Rɕ+#t@N ,ǺuX{\W)fus0xZf_-ذ~=лW9A8lLs![,guL?mw~+ynf̜ΰQE 㮟%ѥk(A|֑-ng458u(:(+9ݳ0YEp`o1qvH'=OlɓMhD10)M")قTWaLc)zf0Ɖ%@F$$d:Orl?֭܈7RF\b.}1}MHأMſۯKppLr͈θk3z -qd4B@ӡsF\8!tɊGR$BBdgZ~TRoaHߞLZA?+X{@5""'@sr-aE[Tc&!s sHzJ*m ,|g5@/.#J Uk"&16Qߠ,R3HꆬZvM;vD-HAD2"f,1n#M{>|lc? ..閧Ў K8x޼93uk׬vՄ$~G,(JK4&NIwo3uSSH) 4ID9rkD\y|zȮ %w^r>n4Twy_1:xjz{a2{Q anZ3o݋AP#r{0IܛþkDc4'op%  tc=gΌ{^զp\mc,AT-:5Ñx^w\ixogFfDFVd+_'w:9-M20juw jL!Dlt*M~,щ6repI1Xe3 %=UٔDPwޏn];㌎f#I1xIh‹>} :m0O>188~85MxO~$5/w߽>ptmq1]`z /;k0U?ΨkGc0(IC$!7 AdI cDPqHB$kz"#>>|o A=)8ۘ5\($13f_SϽGtˍG'COoryRTp\7sF2dRuB3SZQFm+\8в~~*4D@ѻߣVgv7ZJ1mho&4“شW>J[{1)=ydDP$F̛4 7^~hve{>`UvcX𖧟y:B;ӻ!\8뾐RkR]AϞ9̙ސc&5'f,e6`Mnd4{r*iKAvmr8Cy;PqDB D:/ޝGi$"pW[rtRҩ:͝w.*m ,nm2jwOH'3hOִHH(ŏ.`ذ7WE=aGSL,j$B̏~Ⱦ}u_3PQ[pc/8e3u߰qu,E6rx;w`/_Iv#I]х +5_Nzݜ/`[tOgD;xPZȱϸRUD^YD9آ:І?.λ^7wO+?-uA0eXc[#**(^]$wRyĉUe7s9v O{:qN =AQi5߬Fw ¦)ơ!K@ ,j>cweF_z!<5Jxj]J|BϜ;CB!8 UA\>^V !ZhmhB61wٞBs{56ke/'EȃPniH@aϏc6ZXzdu7O'i$%KUqRO<;k9>SQلN  7|&"{7yaRo-L7o8|'>mMحnF@f^r| T&|ىTW{Edxfy_|;2$ePRJtb1A^znf+|IZMDGU7Re=6~n]H< rDZPLU8}dwNZj? $ ΕSx-l$X3: 9َ&-iӍN ϗp;Mlm|ݛiHA2*N^79Y|kL~4Ք9L31^B_qf>SZ:Զx0ȱ !d?ņ?95s$nbHŦ˧]xp;Ȑ,}#䠑 M~oC҈tMOb>(M=|!Ca0F]5kRY]5Jpopl3aÖ2f88s5" _ Ux-f^JrǶvdf@N IDAT&&A>0)hg'OVqpiTE8"#aaw`;hn0*2#!PJKS5ѠcTT¾zZ\D9$zF.blٲKXV2CX; 7x?RٔEFߞ6JZzv)+Մngnbcu/LLrcXG .buytÇ%}$`B:|b6zbMøsƭ\<_Go.-vn(cDe[P\,w~$ IH&o9)409,,[VξSdJUm)3G1l}mm(.)&[J#)ΆPKQemaFqm;sINmfoAI=71*VNUQ :Q$tWo$(B8ҊT[\tNӫW q=᧟ynjj}j,tIA h߃,_ ډ2r5|"FBZzwk,|Y})ְh]0~0vT ^h@WTa$LEc`oIJJ2n-`Іv81x^vhU3dET%,xC󯌪bFa 00Ȫ p ȒE"G-H d̞} KE4зk7]rq{w Θ$4шfظe -aHx}|6(L{f>ZЩs,ΗK$rǦ2~4nc:qŒE[c-]rd王K E 8qJəO3ׁ'PêImms/(lFf<;o~ٜE}{͂jj1_Q/*USx38{EuݘN#AL'4bsبl3ց'f9>D"ad %~tpՠ>N"⤱6HFZW>*$q4u%)UpFf?q;O>[7mLw>UDH݃(bjvr`!Iʫ3'vbRsۨkTK\l6W$.NמXb{ v$]~( [l5ϼ;vL<6ou2]6_6^5M2.?Í%8%СCG9{{nL.#8|oNr#G5Q]o$ޑD݃f Ruӥk/>L8bdX$`L 6E t-REfB>ΞݍQgM&\U!KPJb4&_50!F BFW+)FSK{Q0 *Z M4 O (Gvg[{ kIO߁d3эi&Ιo蒓O-.`K!hp#@P5i[s9q/"MCsD[Cog>Lh\"ȦfNbL/m/Nѣi­O<:w:dqSMHFZ}-[iY &J֋/ܷ䙌n h;#%\;z=_}Q5q$~A'A%P-OI>z4h csc6J,`%zeMJNddd݀j.RbSqfӆ-{9?#0aadƭ?ٗ3?=)o܋Q2 MYѐJF{C3h'|QC)%Z$B@k*(PT;݄"Wm1}Y=Ss_d`)$1!`ÖeO;{n9ԅ$'n*e3&ӈGTFtI AhD6>_ WMnRnidW߳r4C2ح 8k76S&^UJF6ug7%:H$[vz,Ɓ r3 )1WO%0-4%5!$Ft! +=_͐UO~:A AK{2`3jX_ibhEg3.KTU$^0hzq96!Q51{z 9dJnw =S۬b#ab yxxⓓ .sq岏/?>'{y$~&8{]T{o3\5)>:sc ϝ3|nVكb0`X.dK09?IaƁɠ C<&M/,>Lc}"6[Wb;um1Kb0J Whi"&*shACD0BCMGe㚝l}DL֫q`-GpjM|h0WE!LDR1'Dj/{m^ّ:F2 $ MD¸=>BaM f$+/RRhkۘ٢T6 LUأ1RݳE~AL w8xO#I0CnLz ^z)qMw/>xX{4770dH&MԷ\"%#HS7MuM?n3`[iAo<3JѺ'jȊ+`$>5F4h( NUF('i"tF2K4TLtrb3OӯOO}iQN]Yx[`^-LSIG aMPӬ@-*VIE%bH1}a~!S_tš 'Є )(8i{bY8'OMuU q8b ZlKtM0~T?ϽFkK &e0ԡ]sس7ʫ~܂q3kRJ$ #>&$WtKa?-Z7sh%VT7_P\ZKKVˬ,8bv+F4I"0]R m(}4U1Z^3ZB y *c%(Q^;CNw)UBUZY/?ב4@ 44!r$$jHJISRnëP@սo$$ wA# ":n9D0FȊ$@`'('ٶ9~ťXPs#qP ]x0#N;fe4\lOqG8skO޳g' _^m 89@{,}; 1LQr0 %Sύ$T]"Aд a=@DуFhw40ppw 1qf"A<%|H k<dx㹤G!h֯,䳏Aa 4Ua Q]]EJzDp.hN{0P B(DȲI2le7g5: St<Rqy%xr_ߟϏ?7B]M9F4Մ#iHT7g/}󍯖h&$iifɧKRI4J'ƙ{yA5m _%&F'~Kpާ7{ܣַFk5\Vsqx<&Ǐr}50HHߴ"r3SxI¢O΋Wq#gZmm|0&Ba>4wz,r=f Q@Ao%   ȒJH2Dnr#R]g߼7͇X; [aȇ?˾ IRRRhz {.T_ ̛ץ, niz”3 I&2L?\mQcO-&*7uu%Ưӛ~z :]\ ]*J̃(!'xd\0:-/Vׅ6Us!1dFJRoˀ'sx6fKx}9T/ϾT䫬,Z|/bD>\{]'TpK)+-ŗaD cEHɌљ9t { bNIaxx:死YկLhr*]og!:M3"yYĔ* MQ1T!lFVPKc #f2PrLG ,aRii?mf7F[#ׂPňq}2hONs 0% jG "neA  x$ghPZma'Q:Vʉf>Skfaw8#VKF|7$q؊UXW#F:CjR =>)Wd}|6/dC[舶 :L&+&sN#ȉUs#I"ABKxTtS,d5ځ0/WHDHg#fՉ-nRSEL\: ˵xne:I7V9LN=*A^:Of-Ù2C<fe<2u_Wt/ְ'^lG6iO?lEF.y}:d0 ?GbT7gh~ߟm`Ob#9-\0[̈Ṅ;S]Doա* Dӹ!a ˄#& H%, 5#$UT&H%trylջ6b߁R vtлEwlY8ߺ'?ŲNz{w݉NɷJy5t{8K,խ GHeok1ݷۣ{>B 9z9/q~٪'%7嵕#<lr8)FINt^$pcu$g[g/G9z9]kOAMC'-b>QYOXU# /zQH<FYDEdCfDdéE*f+ 5*yW돱?hnQLzKNċ/ blpVV:ŧkՂ<Lͨ9]r-~g;zk7qh7,xѦV7Tϟ=_bƌ4v!U;קrĶoOADLjXёA_Ozhi:Msc ^t6\G]}sykF h9A FRІEcQMx|r'!zI25c alr-&qVF5X:7_}SEydKlW;ߏv јa rfb IDAT6Ąx<>ܰqݣ3C\8ׄ(sCHL9wеjWM=6m~xHNY灇8_B3Go~`*u[wikQK'$$q{6~û Gۍ|N4M&qfڛHGw掤rj(tʞCSZ9ʙ gB/h0{; 7еW"%BZR"9dd$gw0nxFNlQ=>EYYԤr=n6zr;(4590Yz gLv^}ּw/oqC[^mwQLDӉQ9{^&c #5[+"ĺcM|XS{(UFOÐp ^z=<u2הUƕf#!H&rqDɀfdތu}SΒ=Ơ WoTgyy4X42zHP8HRTJ׍3a NvZW"j0bo(b7d">N%AyutxVžD"gE%jlȪ:h*YNzUlNuzͪwcw>ܼ䘑VW>}㴓W?ԃϘ:`7->z폦@ bcESOՂDCȡTFK]cz0o@Η5SWsW^ʂ&"kmP'!E(GپYy|!9>}im#,Љ2) E%!`X(63!ڋ5|1o0G%rR\”^(![O߰/5J2/{E|' p$6SVVǻヒƾ=;xٌDVb"!O '!]K!7e Wy_⃯3)<2"J2h FgJOݻؤ(bJO=%}7-9_Ͼ2vh\s b|ìz7c&Nj< A#/ĉ:c91%DHD1ND-G՝@o.cG\( aΫpTTu?v(&LB Jz1Lm۾&)7.{]u}èx4#~Aq+-c@^-6~^  'Ӷ4 &nG~Đ@Ɗ:c@vp#줵?lF^PMT/ZDQo禹ҿkz` WM DHPMaSR,Q1ifA m&鏤%3{1mrOkN?q>F Z?>pyZ!0\ðGm$!)P mQcy筯pw1VO3w>~S|.7|y#EQдZ6ܼd2R)_i}#\( MfMg;SinaL(ī/mb]̝ѕ,p&[;,"3+o=7^{AƫEiTv##0`ԕ,}>nn^ǟT֧ MGf!F}0%u0j^8J3:t$+ vkXNjLLQ+ct#>EOs{WQ[PtS;/ >7q5R:pyrL=߾F[AZF˿*VV *<=~% 5Urif"V3en~ !)10PЌ?\̫oAKE>3dqX ZBgiZtD,Y*9iټg_؎jhC%' [BJ±N$Y0#GHKIDKT Л;!D-K'TTUFI&&'j8oK+ 6[f838]?3(4\35 y恥lc7Nc Sжc>ư}|c 噧odΝn@JXMA,RH-&7[wWөuɫ FlG fڪUz夰9tv滋{3B RI1KHEn1<2vIޣ%kGٹ{/Fq0qx&)#sDFE N^{f_ɸIM}O|廂6,F#NYq2/=M?UsML5͛PVQ?_}YVy7{Q" ? [ҳo?z$ QW'3&Lqľ=e㸽~,DlR2D(ԁM̥I ))BEkHj,@V aޮt~`-6<>7A|j;o 8JfOJ 6k 1H*1ϼ#Ǐ#'80gX7̡ggj">b!Bx0 ø< G)_̉oѱ|>QgP|U\O~'>eƴ:xUGVT9{<\?Rp#T.{#ՈRdYB$$rĸtw:8~%g zK8Bni!#:zREoR0}M &YdoEp$Qd̨b9)+L:4a0)xS8/W޻/>x]?W>G܃ޔ3LTo')Kdalq-z71zz)z5 @Ed^c 7x YpQ3w ٙ>osOng(Sa(᠉V?x6:;F`Đ~L;?CqQ)R0FED<̍΋ϠbRMF QHI)n]q?͙ 5n=hbu7?NgDD?&>}Vspbz]ǫ\UtXrͭLqVN:Iz4׶a3u/?"u[ǡ׵nm&6^`k^Uذo=E?|ˤh.-mr߲+Qf~~a<5k# 72f(TGA);ܶh!udtL0 K^|sgγng|ݻLçY,3pX74Y%dKu;ILj'˔C$cdB#/Z 2iHD ɌB0#Eb[󧛒 E4"%,9MNj3!80!,[ɈʨK_#E@E/X0C:+N_ n)r` ;_XCuM'AU'bh!9MTq} ^[oǩCvW6o31!@SщN44T%[>|%Eؽ7NDEE%+Fѕ7}:l4ǡ "VD2{/40T0>7ؼq5 qitQi-DBA+:sH45*TPT=X.aILȂEF#B(>tϔit-|6GW5=c+6yiOIKعssZkছu~=¹w8Ww# X ~4q޺¿7ۗt^͝wCmEttx*#? *PჇ8$F~u;_--bC$+#78r%qI,]q/1z~>Dc[o[Ȼo=KSB$ϒOv\3KZ0qou;zZ4!.q|Ŏ$gxlkZNڎ r˯}Mc[ [#(<~ݹw7USU]N,cq̚11# SN!7css| T_EՇC=F;AtsnWl-ݺR\%"qmA2fgZլYS_2)/S2>h()/o`˖_k`70h@"vv-bY5:LbbWϞ@C=fa0AFNt:/G1zA.b|qW'Ŋø:r̘xaUkسX|l}%NWqp.wTQ^W-dϖrzx 6JORvRI6|WJLаx4!N.:UAQQQAAQK:"TVTQnՃ3& pCwpJLm+>ȃ\ŪJ{ b)'2z<;|MDXj]~vJ1j4N5g%><c<#zwD>f+y}(.CM$!9J:LF;pDTPCKѿo^܀oChi돗/@g'K%'7;jN$k_OEy ?Юԓy\\:2 ] Y~peEK9̼nE'0-ƙf ¿2_Ԑ\ݹ9b= j ZNB4&j P*ah~$JXKtP6P+y9^tPfq8-(,䠉Ha>:[Ċ>fϑ:v:Ö? id&*L>u;~">Ejt@PAB 7@$+ɉ#Xl*IqěӈCuxrՄdXY}_3_o]?@"c$C]eZ7.nߙSM }twW^kPIDATb QPˎ:A\,i^HOj"(]!އǽ""fpmB3{`b8&YCO`DzV{R?< S?Arx53g CPYo,y2>%Ie脞 h r$%_OAu]3&9lj 47jh64MCDbE4Ad!"k46Q\T,2Tw0׌sDnvIDZ^tzk+GM'E_Bn=uԶIёԼx~Q+Mz'_} + סD$% *)it dYI c2).½٨`4#;/55?AFfO:Cք>Թ<~FD}d9}M,?oF5 1d.!>EcqF W~m'K<>z{U GN-S憫FsD''t'HQVY-+A_9FӖb ȑuqw8~z?Uimi8 Gҽ׋`N%`%QV}VCbj.bI{<<"YR2 V#Y6HKz=jG_O+̣+>{пO,VD4$ $6ih(" nb@k6΂E0Y ͧk[Fmmf`H΂%M;uT }ףc]?~Kޜ:gD t q4\9&w#IqǓ.o9&"yk?x*:D0i\2#Jq7Q1"サQ1`HKɥo|wkM"N;Vd L$HGFLja ڃAZ:YzE8犖>ywLlQ(੓78)xGO׼NoBG˥YbA᢭N EslYlkʫ )l_w؟=x`K ?, ;WnkA<) =>?oımK(k5r:Hg'.Sk#?Rb> 6'q):[ 6z紐w?7Qv4u.U:~Cj|N:ń)^8|"Q[H}MzC$*lܡc&q͛Pu[̑Sv.r43u6pdg7IFv fl]RYՓR.*LE|Naano⁓:K-9` a!\1`/cL3gh9]y*$"kns$D#LfAbX_ }ߩ7l%\Df^ȻBq7= ?Ad9S˕ ngwvENކc]>wŞ{V|nNGi}:.q506!!&3wH:TVujJ+zM܇d}1|TMm;q V>G+DGMibIJ,{f~f4A^Gz~gƳ+2eJ^V>1Ry:]LCɋ7ߔ@M.\ QU@BA{>&f] >ou#IWMZpb̫gڿzr<.~wYC)uڑAZK 8FtzKd$:p:0k֔js\P[Ĕ>$Azpl!p]xJJo*5Y̝w <a)=ʱ$;ʞ:pC )eu{^OjTVJ72q4Bv4~r`\=er7ɇ"+#V77ABJTC9 ѓ6 `;zP6n &$wG\Qe][(-)FR/H\0%HWih(A9h02ltڃ@kFghN~(8g;%^;!APJAA*!B"TB\$.) @UԡA Mq6q5x<p@H8bs|OޟGϫw&ć%CCk&T2()z5N>rSee 0RI/:zlb\uYCƶ-@CRi+^74UB S_ϲVq*M-C 3qJ{WK(Wrq|z~ MP$zTUf.c.W{.H]P/R6+t&$LgO` |/iP zSI?zg6S)XXlhH-ZnI\?W}Ǎ""sX#"^ X͗ ];:7EWz׳r(I"b aXVw֛QȲo<]mddgqpFLu%lvS[6܎g_J>xbDU$ %F!Q1#uPozTSBo֩&Av/K*G dY{ѢA_95qwߙ˥huazRaUVRC(`SEC/P̔N)Gx~h,Fw YjU ,Z.W2&7'?|w.Ǘ/]<1oRJ\]=ܻbϑR#@1.bXkV2U#ZGw&Zk8&"©},axa$!s8듩yΣG:NqQF: T"p}cdG  iQ(b O\|Z q{w;=窼{r)%^{BQT <ERXbyyzH)1PVy&>ǎ ˲|aa 6'aEJI%\YH^GkMߧVa\z9{NHwޚXzl=Vi{j,ÐlooYZZ1$@H9PJ+:"jETbqqRD&"?wVVV^ٳoެ[ĸ֊ڹs|qt:8nk7m?IZ2ZGE h4N]~}ƍgfggCk۷w׿nZZckm6V7i}e]JϧW1ӧO{7iS7*IENDB`UPnP-Inspector-0.2.2/upnp_inspector/icons/audio-volume-low.png0000644000175000017500000000326011204600003022560 0ustar poxpoxPNG  IHDR szzsBIT|dgIDATX_lG?3w;ʱc7F@jSJ *TPR%@}@*-P!JUAj$OBB8g&/gߝvgxH\򕾚YHO;ۺy;x _Hla6t*7FByXZPUrgg1Bѓ>0,} G.#n3gZ'}>4D6my'ZSӷ BJ=}+l#Z4M=|Ip$VX)lVPZ#0uaHk@_~H}vەj7se}NNR[CQeKH8 , !%9KJ%f|Q͊7={e ]{x^vmz\ʖ8yxN:nngtTKcj[⣏nNΏl۪Nq%b8 97ta6  @&N^'Fl3aK}ڹ/^=_~+=k8k&?֚l6C[[;33y Za CCC w0\ W I(j2Bo`7Re"m8KZ%J),]Z6b, !jF$>($7J8@kT*e*KKDQD5qBي8i!Jc{ض1f[G1qZHRF˶+wRqmb-E\z(|C)1Fe5!"KnK9-,˺XÒ+rZCk$+Jp@ޥK#}[6z.5eQ.GSJ9wkL+iy97=]@)T%2RA, n{&L8 |\G T+U0CX/R'NRɀDŶ-N6 19u籦 !uU۶8C!P,i"f-^8thR]zzJ }}}Li Rd2j0ǚ0 RnwW-Okm$ɶz˲PJa 1 sT5j JWg.LN'3~T^xΩ5J.7}Mxwp?ixl5`r%VX3W(QXnjg^ $ wZ:2O?@&R,248@[:h)-\ܹI͈ Hq)Xyllw#@j5\ j[gW-B}kx#l)%yrqts[:N)eQ?b 1hLLH_&h@P S ҊPhowft^}~wjC'?Y9+k^k}vc^HLxL{?1zR$pZBtܹ& "r\.G~j u(x[ ~|b^{/?j=I ȹ_h%O>O"l[co<Tꖇ~Z-Xo}U{DHu㱟})n뺻{ǰ{L'ٔdKO7 ^}45%xhmm1e /%BR;o8=) = `9~D-۶?#JW˷8e eZqTJp~>qu=zg꜊zwmh澢1bdȚk[tя|Ӧϝ;ǡG8R`[˔XEĎ`Y&a $2sn% {=5Z.jJh퟽g`Gp.jY,Sb6F](XFlSY*w( CqlP%뮝}5cP(*-[' Lq\ǹK,V'LPVinj"Z^ =T/X ݈uwd]:~ysstuvp p"Z-$SI )D"~D"' hhRB] `gAoYB@ܔNqRRIZȓH$(˘A-I__}}}^O]D"Fa[U.{A m/ Dit@Ѷ,VVVftR 5ҔXE*bݺz7КlE6 Q\^@+);+f"ղF$LT*P\]X(Ac[i!5ϣX,*TQ4Z>NLa)P=hN(\!ePTL!ꚴRC0 VVVOY_`yy˶.eyXaj" +Kuegw@WB"aӫ+ YWRR ێ0?@<Ƕ-iWql;!%Z)LàXce_%V` bq.e!$295M5I< RmmnaˉbvhulBi(Kxa)6x@TBO]6> V?x,A$`T۶;Cuio@qdLql-,("RJ G -hBN-Gsgs.4%N3>Ոb 5a< [V27 e!%=Ti+#/;8281;;hk#-Ve݅i(?;漺}McORJo%ã/yP6o/ e#q'$ ;E/{J2IENDB`UPnP-Inspector-0.2.2/upnp_inspector/icons/network-server.png0000644000175000017500000000133711204600003022353 0ustar poxpoxPNG  IHDR|0sBIT|d pHYsEtEXtSoftwarewww.inkscape.org<\IDAT81k@t>Y&qŐ~Pp̝ [(C||  Iv 2eR/QPH[:u(NmdJܠ{~z_{!"5{AD8::zvv['Y^p8L&|{{\777v.N~sss-se\LnU] l6h4B$1PJbAc/q_/2PF@#W1pQEmTIENDB`UPnP-Inspector-0.2.2/upnp_inspector/icons/upnp-action-arg-out.png0000644000175000017500000000125411204600003023165 0ustar poxpoxPNG  IHDR 64usBIT|d pHYs ^]:tEXtSoftwarewww.inkscape.org<)IDAT(]HQ'e &N("2&EWMtE7Aya Aw]}E!SAǠ\Vmm|ona??~ĶmlMv#V,SM]/,\N=/6[c=ODwJ_f '---S퇶[X_\n˽F*;8Yg4?@ \ί sG\SW)(!VMGMŰWukB sU7*݈ !3R8eB[!e`U3;In9#J5޵i?g:vV0~ ~ 5N'/K0*2&,ӂe ‚0-‚YT&[8>?|Tcʆ= em}ss淏qYQV(lZ,ǂ^*5bzyosG࡟ޯy' /?kc+/Si988.{SJ(4Ģvlm33O~;}xxϹeo[ɏ/|u"8KBpԶ=\}]gC~zZ( *F:.jf^0BƻYkYuuvéj9tW5YL[.X|}^(Q&QmX%Lϑ!^H t78XARj(K(SYx4RTCU}Az=04`Q2VR`7GvQ̦(y=otݓB0ۤ}u6k-n^2d"F3㫯C+pWg<C̄jLzenXՄ\ H7v3`w '#;˴u / .lD# Sa{zk%Ɠ-a3ѫ hLyrfߡc su5J:Rb)n^v9姟Rײp=d7^ ;>w2lТ`Aa tRg`6u&&$ENn"Bz..o0o`h(LMkr$u&W.E#9jtw"a eLBɨ*.wCi$]zh IX*\vFq}̦u&$Z gHd6d޷Jwed2A͞$٤tūuXXN[0yz=po019cKƩ 3|S<:+C2ıՕb벤12锌,hob7hoM r{ffoy齹;vK{d?1ffɾ,~(J"+qf4 盱7ŗiЈ XJ{}gR45^z6?UkDy2μliiR4=|DҀǞ & [$%KE%*=s8Z:;tKضmuŬ'7URcBNq^o7dUs`qudN&/"Q.+? (c0imӢ 9O蠳UO*DճRˠ.EI'Թ$^zgWt 6=.ߩqRzu$N jH@2HWETM΢gں8wcGP;iB6 MHᨓ:sZ,P+"j*bB%" U2R5h_r03Qڮv7{zX f hE;DC4JQ J*Y@ "nc:5!Ld!g=L7ftlK[T-j$MsI[ՔǙ0H1I%>zOڙg xliZ}Fq e;xWl].y> F5FTbL|79ZPf2<bѠo_et)Kd>9װHkZ\Ʊsw?H={,owZ0U(-K+׉C?5k>+/!T.g0rrFQܮCD7}eʈ [i̙OM>V 85:"jqsSA9}lMUQ۶?aIENDB`UPnP-Inspector-0.2.2/upnp_inspector/icons/upnp-service.png0000644000175000017500000000237011204600003021774 0ustar poxpoxPNG  IHDRysBIT|d pHYs tEXtSoftwarewww.inkscape.org<uIDAT8T_H[g=zIM.1Lhi@Zڎv:Ok1B`>IXAA 4:v8P;lZKl_nbs{&^ڮ>?;?B<kvv61>>~IG100rFd޽T.[HAr*H9r;<YzٯqfsWcccH$4Q.dZmfǎ`?$UQ!I#p'&&ҥKk׮v…]޼ysruqH$ukkkB jnoo_ c,v644w^9֒7kFh{E5Ki* U L%Ba_mm-p8i4h4;vl*ĕG>gˢ(*t:-QfŜNg ˲`XUU+v !WWW?㑑z}cXDPV#eYV9E[(Ph4zڳgϫXWQN>ܿmzKtu}}l6GR6Ht+mmm=(\q.;+Jf`_0v[`0$I*ebYp8{<4Mvܹsgt:odY`xgss.2E??~5jkktebT*\.q\V5T*R{333>6EQz555;GFF&5 _.dYh\.twwwM]*van l>6BJ"uuu+'Nd2I2 7288 @?wŋ(EͽҞf!r\.w,6ЃI(l?`4_(p݀N{\.777"~F($ !((!ɴommӧyQ]PdFc/YBȭgx@Q4,[^>*yz1[LIENDB`UPnP-Inspector-0.2.2/upnp_inspector/icons/upnp-action-arg-in.png0000644000175000017500000000122011204600003022755 0ustar poxpoxPNG  IHDR 64usBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org< IDAT(=hSa}I{㽷^CJ mIJ,RPqp"":3R׊TpuJQ`'A\b+D1-CZD%ƛ&77?xyH4i{u}GpauG}ۃ8h.{nO>@U<򢞸vz+XDpD[(hek?|sH̰!89Z*'c@O5Ծ&c U) Z0Z}݈#7 @ĠʄL&+^*Fe9̇e6Ca:lAEp(5GW0-9@:T1[Sf7e"좉,[&v`<2 `xɈQoX!W9eL!a5m-1ivAUKPwfZ~~Gv?[2o qķq{gF ҽLQò縛ٝiԄ_VR[IENDB`UPnP-Inspector-0.2.2/upnp_inspector/icons/media-playback-pause.png0000644000175000017500000000074111204600003023332 0ustar poxpoxPNG  IHDR szzsBIT|dIDATX݊@I&!_R&m+R\@!/iꪰ6zPl950as"'@s|dGϯ}>W||#4m~P/hV.,V@Wnn6t1LiڳLt0*] ,\.u8e8N&Y`X HY~N&Y`e o-A,Ḁ<8u]*(p $z—xlfao,K̎#$ }/*p=!*W,,u]^\Y@3$ (3Cf5kcG2MȬW'fM(my~i~$ H:;ιfΜ9H '0IENDB`UPnP-Inspector-0.2.2/upnp_inspector/icons/media-playback-start.png0000644000175000017500000000200411204600003023344 0ustar poxpoxPNG  IHDR szzsBIT|dIDATXOh\Eǿ&MYfw4Cbƞm%5 [$j4-,Z`( ^Z(BfKnm43a76ۘƃœ}w~iOx}=f.-MP2zc?f'OOM}[ZO,.Ο47aa~qN!Qr CCtл " u(%9_CSJ̚沋mGWH)%8g>SL&3k~4c6 rG@(`CR ~S93{"dw}>DZ5@2 CQE0"R Ģ1aT7AX Z[S(LӬTiz X37|Z{Iv]wifffSGXh8sJ( W`;6VV wѱu%HaRmv,ضuy(BH (Fkj, X:cmmm(*b14  PP5&&]q?}3~|>oNOOoz2*DQ"k c Bs LNNxw2?'϶=[0VL-u1c sX]һ~l1|ԩWB人V*]@*  uEógM#IDUQU( D#{.^U |,,=T ÀRC +W.{~sppUet]~Fۙox'n(Wm 7f_x.BUlmzT*`Qa' ob+rRJ)])բ{Oz*|^IENDB`UPnP-Inspector-0.2.2/upnp_inspector/icons/video-x-generic.png0000644000175000017500000000146711204600003022347 0ustar poxpoxPNG  IHDRabKGD pHYs  tIME :'3y5tEXtComment(c) 2004 Jakub Steiner Created with The GIMPًoIDAT8˝KSa?g6ě bcZjj*D ګ.Z;ABl+@Н i1v]IMܼ y8}=@\@X] /5յWEAΗ=UUqx S< F 3 }F%yNm5 3H 40&k "?ہQS]߾O(x˼LNLr91nb}fzvY14-n_7[@F EQe~tQ8>83qI%,HX,ܹ{MV@w "F$Q"J1>6App̈h J!R=kn;l6TUcdxQTUf57c 3\@PUUr>_9-MA2-M|TUH3ӳ˕UAL! WEsD6#mxd"ɻ3]O$7>Uv{(,,KerH&|5D--Vڅ_Oy^ve90o# \. QI2!IENDB`UPnP-Inspector-0.2.2/upnp_inspector/icons/upnp-action.png0000644000175000017500000000221311204600003021605 0ustar poxpoxPNG  IHDR_%.-sBIT|d pHYsȠtEXtSoftwarewww.inkscape.org<IDAT8mLW@<¢-ʛc1ф} /1ō12"ĈuDtAE7I03MD!Q`BK }}ݗb:vܜsr Dk&E*ufjXݷ;DvVR,,\N1w# IMU#m]_999hS[Ngve}0L(H)RWGwSscOS+R^ (Ķ<,AJ}#`$L+RLjkᴻ^ϳ?ML\pX-p:\eSMrZ7­[rV*(@)xcJTjT_|+ܭ?X>HTt]Ԓze,/6@ Jq֝Y9ޚ b8yZWVP*,+7 b劸_;88^I_*uaYFBѝEݪx&1=ᶺw{/7բJ+zkSEψS?n, 9 6(==㇦Ơ%Xz,/3U*RƋHO¤u-n<\ۚΦFKih&Xy;~%=pp|ۘQiTxׁ=Ql捝ջ@]YY9:&a !={pm/~%oέٱ9%\}ѱ`궘-{ָ*/ ~b4.N'KFJORX.0L$nC$%SvEwG}fY18@C BOӋ0-B$e Ou~>!9!!BB!2BHLyЪ2061z|6BBYL!,P4L>>{9V6.!$s I/I򎠕i߅gR*x ȷS^oɒqLIENDB`UPnP-Inspector-0.2.2/upnp_inspector/icons/audio-volume-muted.png0000644000175000017500000000337511204600003023104 0ustar poxpoxPNG  IHDR szzsBIT|dIDATXlUg?9=sRnQ:Dnj?dmbLLX3d11Ѱ2m uDI+a26,嶽?{9m`sϾɓ''}?K䮮]>* ,J" Ou>Mh %hmK~4b EJLkh}!g={vLF5:Nƺ:V],KYons?~nNc-TH;ײìj[Κ˚MOEyWhjj xX {Dض[R+Tu]'#&i -1I$7 i:T{4sydMhSoOyo[H@ڒ@J 48399_xW vnкuO`dd7T*d2WmCAQr[Yg&?c=ڼIKBk׬v_x{XE0$  9!'D88x#sss4bZ=ZoP@]N>?C0-~r9Z'r JyqL 8 hКMuٖj3(H$b eFe,$VVPVkEh4LRŖRXcKI>S/P,ZcXRJ--I%b|bq0Ms=aJE^.TZQ*() xR"-TU"|E aYZk\%X.{>QƼ% ÛI(QgnB0LY&'F'BJ?g~D23J>_cK \а LӼ>34K2J)l;Hۖ#ׯXO0ĶL꥽)N˧>08\Jtt(1Kkjfpp_WA,$h澵\! 9b1ZXI0D+0 ",Gw@X|a0^:vbX>Wb&c1RVDQֵ5Vo87SXӳ'Oz,맺--ɤ7REkqRyTQR_^kNrϞ]@OWUKo;Ag}i뺼ʟm%b\X(Vtk ۻxCCn M oڶѱOl{Ko](O,],d>_>s?|4UBJukאH1 I2,y>yZׁō~}V:Ο'ϣ% !1{6`< Z,!Mn|޼7:YZrJjAZZZ1Gu6`BL-«ww>~!|,2$DbPsf7>>>oO}{"QFE_>k͟fX5k֬sKYi`oo n݄~ޙ8)E^f>ϡwZнܻ9BY/_<sPȮ+sMwLwmߛ7)c7zdӽTliqo,=ziCKӖ F+u}Q2S>B$!ТɰM=@ApXyzzZR3i )0XuD7eqz4ByX3AKKsaw'**mИ9s қ3ќ ۋl7v3 m] k=]sԝ5b&%ޤׁޮX=8訧:e!`@"ëĈx4bh7ǎpB x8*LPxp/C]m~<=Pg? |<58AuWǏ ?JO]` vc8> kw 4OMP {OAylq"а`#XQ j'$_B[M,%n:Nl >A46у%t 083mː_8 Y  $CAs0!,"B7М % UUBTa{SK{".$xt`pX(:>ў-=& \9ˆNk+a)RKm%@mhtԇOr\$c.Ap{x&SáUw.^_HFrA İܸqdq NHA iMN@JiOAT|@¾*!939μ_NpR& %/Gv BJ@ح5OŸ< )$$$&!DĂc{ 9o%.^x 6Q"T-N?ݨ DG*""O”p zKiC|?r7@ӬA2ZR/sN7! tIc[qG.IR 1*"\D~琮߸SO>ݑdRRB\ó]%P-Mܟ9w+sIuܨd;e}~C+2Hk!iN^u\hh6r)&p(H9m||nS@[QǯZLIO@$6B5e1jN댚)11'xƣaZjU$t.GQME ĜHM9-.`X:thxasV!eF:J@ut|RjxS7D`f^$*5^adܦDw[qs5U$2mnZ:mII= ^2e ( aF>zu#Gnu[Uhc32zfġ:cQfӈe# *8RR B o2q8Ƹx]xx ijL>xho`tW#~a#ܢa~QXiWxG4OIquʔ)iiSPQ6@2"%1Z g5?sU9%Ή NNqd@] Qč6;&]F/vd33a+Wӝ3ddy]3X r-Y+pc^-gsJONNLIG_3vFRaẍ́1 4ǡ6#sl%uiӟт,O1nTͩzS5:5:.ֽdɹzx>Cԫ9IfY}ܑC7̴7BlLm3ݱtАܱC9IL1,R |rsrQsw;Itw s 6Tjfձ6Wd X @ijN^^^<-5j_"({G4*0]W޷|| FYS#\CSɩĚkK?wlK!>Ts{5 <LV`>y2)1 {_\55*, zAqg|^;Gi  &O>xqueuuՄ Օ&CI@901b]‚PXUݫɱ NՄ UU8M ;M)=܌gC$Vndx~wyyIy >G4FVm$бNhlԌB3~1jˌVVV.?@B+`KS{vTM˰aÚbN4$lM7o }vM...E.(UiKQъ^,Ɲ1#lϓ3x߾|{fvv4Ј77׮e'z[CގgEj*xNSV~#L՟%>{K3YXT\|Z7>žkP(`}xšvs'Ș@̃Ɗ5@eݴViŴTRRBlL| i4>#ˇ-OTFϞ,66myIi QRB4{/Mx([8ǐ})A%QXF@ekTN k36V]jm̠40 Vm+xWlَcV x u0^561ެii^cMPlhXӸhEGx:>ӛCQϮߓR&4GWфӥN˄@MoﴫXssIHMŤfˀaNBǎcqJܓrutt n18krdf2p|ؤLNj{{mY?ϥ(#5ܑ0;.Y1U;/`5m{|eIt~68Z3 ***+(WrJDe˨)Qr K,J\-ď:WZ&#m*ԮЮXj{K+*jkWT7$S//OMO#3A/j5bꊊ!0E6 pccNO?X}2<k+J -y\6r țW1'clL9UbZ0̔pZ' CX'EF:foqcG͌<{QM(;5i\(&+Uf8g,[u˭rVUs7]*" ϟ]Zt94]nj&܄UU{r'L[wBWM<~[Э~ JJ pe}k媂i.SsQd5Ϫ^@)x)|. ӚLo 5R~ d6J^ˌ_Tƚ|4W1I]D%eBϡ}㵴[R݈ÅCk U08:a(666\_C\m,Flt5SU|$x wvU צO/:cWyu ŋacU%`bf˿!qBA |oө1)~÷XZV \s)7&Ǜ`Ub4+NA}]&@ 8 0 jTv\cLbV8P,د4UqBCB V!гk"$!n,V5rPީ5eg9<$ե7Y8XN(!&Zzvzz7=l77Tj#ҀgPV/^A#Xx TNVB=)3 ,JJ>8PU7FF5e؁y=x|*;㟿p(-.Vƥ\W»t44`e72 S&b֪FL{x:'~V3:!`Bډy6q=ac>(@VÑ2U9"Q "'A[6C@{'<X9~,89C .4 0YmU钤Kc7`Gߋz7:CfLDყ^c_}4TDJL8XoP}F( iWmyu}\.e˗P%2SPJP N4Ohl*dTknhWV.֮\ J<.5Ԯ4E=$]]Vd IkCPtՋ_ks}q%`AX˗=ָ&Jm9`}DοUߐw!譵Vg"fX΀?cbԪDiϚV[}=զ9[=>UgMMV0,T X,ݲeזvhemaSC~3#@ g Wɋ+3˭Zu5.䢚hQ.D;vK=PCwKjF[`e[;v c+h JEJ 0D/sd_$RXPS_1V-B.;̪\rn$kfj?~֬簴r,`S͵@Q_/Ox\Xz&U 'E~ AAZJB$?d!-7TlVSεYm2y I }Y#f-E6,pamfxlw+,;$91YAKA@@lp192ĨYgeef~W>fY#G~ (!Ef&!T[e@፬~xZ]]R8RԼ[-~#CF ׊-jP )qҽkf#{ux5(,&F{za_ΐ Ȱ YXm⻸۽mJ*n d]Fk% Ơ -f<vhTJ:Rz%%q >0We)fFڏ^ɤ-VTLp.2dPޗr =O(~JNg/y>;Q ]n#]2 v:dʛ+72+j uҝӝ9bt8XtNW0krIDATPn]Pjwf 3vRa^ ׭s&͙ import os import pygtk pygtk.require("2.0") import gtk if __name__ == '__main__': from twisted.internet import gtk2reactor gtk2reactor.install() from twisted.internet import reactor from twisted.internet import task from coherence import log from coherence.upnp.core.utils import parse_xml, getPage, means_true from pkg_resources import resource_filename class MediaRendererWidget(log.Loggable): logCategory = 'inspector' def __init__(self,coherence,device): self.coherence = coherence self.device = device self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.hide) self.window.set_default_size(480,200) try: title = 'MediaRenderer %s' % device.get_friendly_name() except: title = 'MediaRenderer' self.window.set_title(title) self.window.drag_dest_set(gtk.DEST_DEFAULT_DROP, [('upnp/metadata', 0, 1)], gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_PRIVATE) self.window.connect('drag_motion', self.motion_cb) self.window.connect('drag_drop', self.drop_cb) self.window.connect("drag_data_received", self.received_cb) vbox = gtk.VBox(homogeneous=False, spacing=10) hbox = gtk.HBox(homogeneous=False, spacing=10) hbox.set_border_width(2) self.album_art_image = gtk.Image() icon = resource_filename(__name__, os.path.join('icons','blankalbum.png')) self.blank_icon = gtk.gdk.pixbuf_new_from_file(icon) self.album_art_image.set_from_pixbuf(self.blank_icon) hbox.pack_start(self.album_art_image,False,False,2) #icon_loader = gtk.gdk.PixbufLoader() #icon_loader.write(urllib.urlopen(str(res.data)).read()) #icon_loader.close() vbox.pack_start(hbox,False,False,2) textbox = gtk.VBox(homogeneous=False, spacing=10) self.title_text = gtk.Label("title") self.title_text.set_use_markup(True) textbox.pack_start(self.title_text,False,False,2) self.album_text = gtk.Label("album") self.album_text.set_use_markup(True) textbox.pack_start(self.album_text,False,False,2) self.artist_text = gtk.Label("artist") self.artist_text.set_use_markup(True) textbox.pack_start(self.artist_text,False,False,2) hbox.pack_start(textbox,False,False,2) seekbox = gtk.HBox(homogeneous=False, spacing=10) self.position_min_text = gtk.Label("0:00") self.position_min_text.set_use_markup(True) seekbox.pack_start(self.position_min_text,False,False,2) adjustment=gtk.Adjustment(value=0, lower=0, upper=240, step_incr=1,page_incr=20)#, page_size=20) self.position_scale = gtk.HScale(adjustment=adjustment) self.position_scale.set_draw_value(True) self.position_scale.set_value_pos(gtk.POS_BOTTOM) self.position_scale.set_sensitive(False) self.position_scale.connect("format-value", self.format_position) self.position_scale.connect('change-value',self.position_changed) seekbox.pack_start(self.position_scale,True,True,2) self.position_max_text = gtk.Label("0:00") self.position_max_text.set_use_markup(True) seekbox.pack_end(self.position_max_text,False,False,2) vbox.pack_start(seekbox,False,False,2) buttonbox = gtk.HBox(homogeneous=False, spacing=10) self.prev_button = self.make_button('media-skip-backward.png',self.skip_backward,sensitive=False) buttonbox.pack_start(self.prev_button,False,False,2) self.seek_backward_button = self.make_button('media-seek-backward.png',callback=self.seek_backward,sensitive=False) buttonbox.pack_start(self.seek_backward_button,False,False,2) self.stop_button = self.make_button('media-playback-stop.png',callback=self.stop,sensitive=False) buttonbox.pack_start(self.stop_button,False,False,2) self.start_button = self.make_button('media-playback-start.png',callback=self.play_or_pause,sensitive=False) buttonbox.pack_start(self.start_button,False,False,2) self.seek_forward_button = self.make_button('media-seek-forward.png',callback=self.seek_forward,sensitive=False) buttonbox.pack_start(self.seek_forward_button,False,False,2) self.next_button = self.make_button('media-skip-forward.png',self.skip_forward,sensitive=False) buttonbox.pack_start(self.next_button,False,False,2) hbox = gtk.HBox(homogeneous=False, spacing=10) #hbox.set_size_request(240,-1) adjustment=gtk.Adjustment(value=0, lower=0, upper=100, step_incr=1,page_incr=20)#, page_size=20) self.volume_scale = gtk.HScale(adjustment=adjustment) self.volume_scale.set_size_request(140,-1) self.volume_scale.set_draw_value(False) self.volume_scale.connect('change-value',self.volume_changed) hbox.pack_start(self.volume_scale,False,False,2) button = gtk.Button() self.volume_image = gtk.Image() icon = resource_filename(__name__, os.path.join('icons','audio-volume-low.png')) self.volume_low_icon = gtk.gdk.pixbuf_new_from_file(icon) self.volume_image.set_from_pixbuf(self.volume_low_icon) button.set_image(self.volume_image) button.connect("clicked", self.mute) icon = resource_filename(__name__, os.path.join('icons','audio-volume-medium.png')) self.volume_medium_icon = gtk.gdk.pixbuf_new_from_file(icon) icon = resource_filename(__name__, os.path.join('icons','audio-volume-high.png')) self.volume_high_icon = gtk.gdk.pixbuf_new_from_file(icon) icon = resource_filename(__name__, os.path.join('icons','audio-volume-muted.png')) self.volume_muted_icon = gtk.gdk.pixbuf_new_from_file(icon) hbox.pack_end(button,False,False,2) buttonbox.pack_end(hbox,False,False,2) vbox.pack_start(buttonbox,False,False,2) self.pause_button_image = gtk.Image() icon = resource_filename(__name__, os.path.join('icons','media-playback-pause.png')) icon = gtk.gdk.pixbuf_new_from_file(icon) self.pause_button_image.set_from_pixbuf(icon) self.start_button_image = self.start_button.get_image() self.status_bar = gtk.Statusbar() context_id = self.status_bar.get_context_id("Statusbar") vbox.pack_end(self.status_bar,False,False,2) self.window.add(vbox) self.window.show_all() self.position_loop = task.LoopingCall(self.get_position) service = self.device.get_service_by_type('RenderingControl') #volume_variable = service.get_state_variable('Volume') #print "volume_variable",volume_variable.value #try: # volume = int(volume_variable.value) # if int(scale.get_value()) != volume: # self.volume_scale.set_value(volume) #except: # pass service.subscribe_for_variable('Volume', callback=self.state_variable_change) service.subscribe_for_variable('Mute', callback=self.state_variable_change) service = self.device.get_service_by_type('AVTransport') service.subscribe_for_variable('AVTransportURI', callback=self.state_variable_change) service.subscribe_for_variable('CurrentTrackMetaData', callback=self.state_variable_change) service.subscribe_for_variable('TransportState', callback=self.state_variable_change) service.subscribe_for_variable('CurrentTransportActions', callback=self.state_variable_change) service.subscribe_for_variable('AbsTime', callback=self.state_variable_change) service.subscribe_for_variable('TrackDuration', callback=self.state_variable_change) self.get_position() def motion_cb(self,wid, context, x, y, time): #print 'drag_motion' context.drag_status(gtk.gdk.ACTION_COPY, time) return True def drop_cb(self,wid, context, x, y, time): #print('\n'.join([str(t) for t in context.targets])) context.finish(True, False, time) return True def received_cb(self, widget, context, x, y, selection, targetType, time): #print "received_cb", targetType if targetType == 1: metadata = selection.data #print "got metadata", metadata from coherence.upnp.core import DIDLLite elt = DIDLLite.DIDLElement.fromString(metadata) if elt.numItems() == 1: service = self.device.get_service_by_type('ConnectionManager') local_protocol_infos=service.get_state_variable('SinkProtocolInfo').value.split(',') #print local_protocol_infos item = elt.getItems()[0] try: res = item.res.get_matching(local_protocol_infos, protocol_type='internal') if len(res) == 0: res = item.res.get_matching(local_protocol_infos) if len(res) > 0: res = res[0] remote_protocol,remote_network,remote_content_format,_ = res.protocolInfo.split(':') d = self.stop() d.addCallback(lambda x: self.set_uri(res.data,metadata)) d.addCallback(lambda x: self.play_or_pause(force_play=True)) d.addErrback(self.handle_error) d.addErrback(self.handle_error) except AttributeError: print "Sorry, we currently support only single items!" def make_button(self,icon,callback=None,sensitive=True): icon = resource_filename(__name__, os.path.join('icons',icon)) icon = gtk.gdk.pixbuf_new_from_file(icon) button = gtk.Button() image = gtk.Image() image.set_from_pixbuf(icon) button.set_image(image) button.connect("clicked", lambda x: callback()) button.set_sensitive(sensitive) return button def hide(self,w,e): w.hide() return True def state_variable_change(self,variable): print "%s %r" % (variable.name, variable.value) if variable.name == 'CurrentTrackMetaData': if variable.value != None and len(variable.value)>0: try: from coherence.upnp.core import DIDLLite elt = DIDLLite.DIDLElement.fromString(variable.value) for item in elt.getItems(): print "now playing: %r - %r (%s/%r)" % (item.artist, item.title, item.id, item.upnp_class) self.title_text.set_markup("%s" % item.title) if item.album != None: self.album_text.set_markup(item.album) else: self.album_text.set_markup('') if item.artist != None: self.artist_text.set_markup("%s" % item.artist) else: self.artist_text.set_markup("") if item.albumArtURI != None: def got_icon(icon): icon = icon[0] icon_loader = gtk.gdk.PixbufLoader() icon_loader.write(icon) icon_loader.close() icon = icon_loader.get_pixbuf() icon = icon.scale_simple(128,128,gtk.gdk.INTERP_BILINEAR) self.album_art_image.set_from_pixbuf(icon) d = getPage(item.albumArtURI) d.addCallback(got_icon) else: self.album_art_image.set_from_pixbuf(self.blank_icon) except SyntaxError: #print "seems we haven't got an XML string" return else: self.title_text.set_markup('') self.album_text.set_markup('') self.artist_text.set_markup('') self.album_art_image.set_from_pixbuf(self.blank_icon) elif variable.name == 'TransportState': print variable.name, 'changed from', variable.old_value, 'to', variable.value if variable.value == 'PLAYING': self.start_button.set_image(self.pause_button_image) try: self.position_loop.start(1.0, now=True) except: pass elif variable.value != 'TRANSITIONING': self.start_button.set_image(self.start_button_image) try: self.position_loop.stop() except: pass if variable.value == 'STOPPED': self.get_position() context_id = self.status_bar.get_context_id("Statusbar") self.status_bar.pop(context_id) self.status_bar.push(context_id,"%s" % variable.value) elif variable.name == 'CurrentTransportActions': try: actions = map(lambda x: x.upper(),variable.value.split(',')) if 'SEEK' in actions: self.position_scale.set_sensitive(True) self.seek_forward_button.set_sensitive(True) self.seek_backward_button.set_sensitive(True) else: self.position_scale.set_sensitive(False) self.seek_forward_button.set_sensitive(False) self.seek_backward_button.set_sensitive(False) self.start_button.set_sensitive('PLAY' in actions) self.stop_button.set_sensitive('STOP' in actions) self.prev_button.set_sensitive('PREVIOUS' in actions) self.next_button.set_sensitive('NEXT' in actions) except: #very unlikely to happen import traceback print traceback.format_exc() elif variable.name == 'AVTransportURI': print variable.name, 'changed from', variable.old_value, 'to', variable.value if variable.value != '': pass #self.seek_backward_button.set_sensitive(True) #self.stop_button.set_sensitive(True) #self.start_button.set_sensitive(True) #self.seek_forward_button.set_sensitive(True) else: #self.seek_backward_button.set_sensitive(False) #self.stop_button.set_sensitive(False) #self.start_button.set_sensitive(False) #self.seek_forward_button.set_sensitive(False) self.album_art_image.set_from_pixbuf(self.blank_icon) self.title_text.set_markup('') self.album_text.set_markup('') self.artist_text.set_markup('') elif variable.name == 'Volume': try: volume = int(variable.value) print "volume value", volume if int(self.volume_scale.get_value()) != volume: self.volume_scale.set_value(volume) service = self.device.get_service_by_type('RenderingControl') mute_variable = service.get_state_variable('Mute') if means_true(mute_variable.value) == True: self.volume_image.set_from_pixbuf(self.volume_muted_icon) elif volume < 34: self.volume_image.set_from_pixbuf(self.volume_low_icon) elif volume < 67: self.volume_image.set_from_pixbuf(self.volume_medium_icon) else: self.volume_image.set_from_pixbuf(self.volume_high_icon) except: import traceback print traceback.format_exc() pass elif variable.name == 'Mute': service = self.device.get_service_by_type('RenderingControl') volume_variable = service.get_state_variable('Volume') volume = volume_variable.value if means_true(variable.value) == True: self.volume_image.set_from_pixbuf(self.volume_muted_icon) elif volume < 34: self.volume_image.set_from_pixbuf(self.volume_low_icon) elif volume < 67: self.volume_image.set_from_pixbuf(self.volume_medium_icon) else: self.volume_image.set_from_pixbuf(self.volume_high_icon) def seek_backward(self): value = self.position_scale.get_value() value = int(value) seconds = max(0,value-20) hours = seconds / 3600 seconds = seconds - hours * 3600 minutes = seconds / 60 seconds = seconds - minutes * 60 target = "%d:%02d:%02d" % (hours,minutes,seconds) def handle_result(r): self.get_position() service = self.device.get_service_by_type('AVTransport') action = service.get_action('Seek') d = action.call(InstanceID=0,Unit='ABS_TIME',Target=target) d.addCallback(handle_result) d.addErrback(self.handle_error) return d def seek_forward(self): value = self.position_scale.get_value() value = int(value) max = int(self.position_scale.get_adjustment().upper) seconds = min(max,value+20) hours = seconds / 3600 seconds = seconds - hours * 3600 minutes = seconds / 60 seconds = seconds - minutes * 60 target = "%d:%02d:%02d" % (hours,minutes,seconds) def handle_result(r): self.get_position() service = self.device.get_service_by_type('AVTransport') action = service.get_action('Seek') d = action.call(InstanceID=0,Unit='ABS_TIME',Target=target) d.addCallback(handle_result) d.addErrback(self.handle_error) return d def play_or_pause(self,force_play=False): print "play_or_pause" service = self.device.get_service_by_type('AVTransport') variable = service.get_state_variable('TransportState', instance=0) print variable.value if force_play == True or variable.value != 'PLAYING': action = service.get_action('Play') d = action.call(InstanceID=0,Speed=1) else: action = service.get_action('Pause') d = action.call(InstanceID=0) d.addCallback(self.handle_result) d.addErrback(self.handle_error) return d def stop(self): print "stop" service = self.device.get_service_by_type('AVTransport') action = service.get_action('Stop') d = action.call(InstanceID=0) d.addCallback(self.handle_result) d.addErrback(self.handle_error) return d def skip_backward(self): service = self.device.get_service_by_type('AVTransport') action = service.get_action('Previous') d = action.call(InstanceID=0) d.addCallback(self.handle_result) d.addErrback(self.handle_error) return d def skip_forward(self): service = self.device.get_service_by_type('AVTransport') action = service.get_action('Next') d = action.call(InstanceID=0) d.addCallback(self.handle_result) d.addErrback(self.handle_error) return d def set_uri(self,url,didl): print "set_uri %s %r" % (url,didl) service = self.device.get_service_by_type('AVTransport') action = service.get_action('SetAVTransportURI') d = action.call(InstanceID=0,CurrentURI=url, CurrentURIMetaData=didl) d.addCallback(self.handle_result) d.addErrback(self.handle_error) return d def position_changed(self,range,scroll,value): adjustment = range.get_adjustment() value = int(value) max = int(adjustment.upper) seconds = min(max,value) hours = seconds / 3600 seconds = seconds - hours * 3600 minutes = seconds / 60 seconds = seconds - minutes * 60 target = "%d:%02d:%02d" % (hours,minutes,seconds) service = self.device.get_service_by_type('AVTransport') action = service.get_action('Seek') d = action.call(InstanceID=0,Unit='ABS_TIME',Target=target) d.addCallback(self.handle_result) d.addErrback(self.handle_error) def format_position(self,scale,value): seconds = int(value) hours = seconds / 3600 seconds = seconds - hours * 3600 minutes = seconds / 60 seconds = seconds - minutes * 60 if hours > 0: return "%d:%02d:%02d" % (hours,minutes,seconds) else: return "%d:%02d" % (minutes,seconds) def get_position(self): def handle_result(r,service): try: duration = r['TrackDuration'] h,m,s = duration.split(':') if int(h) > 0: duration = '%d:%02d:%02d' % (int(h),int(m),int(s)) else: duration = '%d:%02d' % (int(m),int(s)) max = (int(h) * 3600) + (int(m)*60) + int(s) self.position_scale.set_range(0,max) self.position_max_text.set_markup(duration) actions = service.get_state_variable('CurrentTransportActions') try: actions = actions.value.split(',') if 'SEEK' in actions: self.position_scale.set_sensitive(True) except AttributeError: pass except: import traceback print traceback.format_exc() try: self.position_scale.set_range(0,0) except: pass self.position_max_text.set_markup('0:00') self.position_scale.set_sensitive(False) pass try: position = r['AbsTime'] h,m,s = position.split(':') position = (int(h) * 3600) + (int(m)*60) + int(s) self.position_scale.set_value(position) except: #import traceback #print traceback.format_exc() pass service = self.device.get_service_by_type('AVTransport') action = service.get_action('GetPositionInfo') d = action.call(InstanceID=0) d.addCallback(handle_result,service) d.addErrback(self.handle_error) return d def volume_changed(self,range,scroll,value): value = int(value) if value > 100: value = 100 print "volume changed", value service = self.device.get_service_by_type('RenderingControl') action = service.get_action('SetVolume') d = action.call(InstanceID=0, Channel='Master', DesiredVolume=value) d.addCallback(self.handle_result) d.addErrback(self.handle_error) return d def mute(self,w): service = self.device.get_service_by_type('RenderingControl') action = service.get_action('SetMute') mute_variable = service.get_state_variable('Mute') if means_true(mute_variable.value) == False: new_mute = '1' else: new_mute = '0' print "Mute new:", new_mute d = action.call(InstanceID=0, Channel='Master', DesiredMute=new_mute) d.addCallback(self.handle_result) d.addErrback(self.handle_error) return d def handle_error(self,e): print 'we have an error', e return e def handle_result(self,r): print "done", r return r if __name__ == '__main__': MediaRendererWidget.hide = lambda x,y,z: reactor.stop() i = MediaRendererWidget(None,None) reactor.run()UPnP-Inspector-0.2.2/upnp_inspector/__init__.py0000644000175000017500000000021611204600003017641 0ustar poxpox# -*- coding: utf-8 -*- __version_info__ = (0,2,2) __version__ = '%d.%d.%d' % (__version_info__[0],__version_info__[1],__version_info__[2],) UPnP-Inspector-0.2.2/upnp_inspector/extract.py0000644000175000017500000001617311204600003017565 0ustar poxpox# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2009 - Frank Scholz import os import tempfile from twisted.internet import defer from twisted.internet import protocol from twisted.python.filepath import FilePath try: from twisted.mail import smtp from twisted.names import client as namesclient from twisted.names import dns import StringIO EMAIL_RECIPIENT = 'upnp.fingerprint@googlemail.com' class SMTPClient(smtp.ESMTPClient): """ build an email message and send it to our googlemail account """ def __init__(self, mail_from, mail_to, mail_subject, mail_file, *args, **kwargs): smtp.ESMTPClient.__init__(self, *args, **kwargs) self.mailFrom = mail_from self.mailTo = mail_to self.mailSubject = mail_subject self.mail_file = mail_file self.mail_from = mail_from def getMailFrom(self): result = self.mailFrom self.mailFrom = None return result def getMailTo(self): return [self.mailTo] def getMailData(self): from email.mime.application import MIMEApplication from email.mime.multipart import MIMEMultipart msg = MIMEMultipart() msg['Subject'] = self.mailSubject msg['From'] = self.mail_from msg['To'] = self.mailTo fp = open(self.mail_file, 'rb') tar = MIMEApplication(fp.read(),'x-tar') fp.close() tar.add_header('Content-Disposition', 'attachment', filename=os.path.basename(self.mail_file)) msg.attach(tar) return StringIO.StringIO(msg.as_string()) def sentMail(self, code, resp, numOk, addresses, log): print 'Sent', numOk, 'messages' class SMTPClientFactory(protocol.ClientFactory): protocol = SMTPClient def __init__(self, mail_from, mail_to, mail_subject, mail_file, *args, **kwargs): self.mail_from = mail_from self.mail_to = mail_to self.mail_subject = mail_subject self.mail_file = mail_file def buildProtocol(self, addr): return self.protocol(self.mail_from, self.mail_to, self.mail_subject, self.mail_file, secret=None, identity='localhost') haz_smtp = True except ImportError: haz_smtp = False from coherence.upnp.core.utils import downloadPage import pygtk pygtk.require("2.0") import gtk class Extract(object): def __init__(self,device): self.device = device self.window = gtk.Dialog(title="Extracting XMl descriptions", parent=None,flags=0,buttons=None) self.window.connect("delete_event", self.hide) label = gtk.Label("Extracting XMl device and service descriptions\nfrom %s @ %s" % (device.friendly_name, device.host)) self.window.vbox.pack_start(label, True, True, 10) tar_button = gtk.CheckButton("tar.gz them") tar_button.connect("toggled", self._toggle_tar) self.window.vbox.pack_start(tar_button, True, True, 5) if haz_smtp == True: self.email_button = gtk.CheckButton("email them to Coherence HQ (%s)" % EMAIL_RECIPIENT) self.email_button.set_sensitive(False) self.window.vbox.pack_start(self.email_button, True, True, 5) align = gtk.Alignment(0.5, 0.5, 0.9, 0) self.window.vbox.pack_start(align, False, False, 5) self.progressbar = gtk.ProgressBar() align.add(self.progressbar) button = gtk.Button(stock=gtk.STOCK_CANCEL) self.window.action_area.pack_start(button, True, True, 5) button.connect("clicked", lambda w: self.window.destroy()) button = gtk.Button(stock=gtk.STOCK_OK) self.window.action_area.pack_start(button, True, True, 5) button.connect("clicked", lambda w: self.extract(w,tar_button.get_active())) self.window.show_all() def _toggle_tar(self,w): self.email_button.set_sensitive(w.get_active()) def hide(self,w,e): w.hide() return True def extract(self,w,make_tar): print w, make_tar self.progressbar.pulse() try: l = [] path = FilePath(tempfile.gettempdir()) def device_extract(workdevice, workpath): tmp_dir = workpath.child(workdevice.get_uuid()) if tmp_dir.exists(): tmp_dir.remove() tmp_dir.createDirectory() target = tmp_dir.child('device-description.xml') print "d",target,target.path d = downloadPage(workdevice.get_location(),target.path) l.append(d) for service in workdevice.services: target = tmp_dir.child('%s-description.xml'%service.service_type.split(':',3)[3]) print "s",target,target.path d = downloadPage(service.get_scpd_url(),target.path) l.append(d) for ed in workdevice.devices: device_extract(ed, tmp_dir) def finished(result): uuid = self.device.get_uuid() print "extraction of device %s finished" % uuid print "files have been saved to %s" % os.path.join(tempfile.gettempdir(),uuid) if make_tar == True: tgz_file = self.create_tgz(path.child(uuid)) if haz_smtp == True and self.email_button.get_active() == True: self.send_email(tgz_file) path.child(uuid).remove() self.progressbar.set_fraction(0.0) self.window.hide() device_extract(self.device,path) dl = defer.DeferredList(l) dl.addCallback(finished) except Exception, msg: print "problem creating download directory: %r (%s)" % (Exception,msg) self.progressbar.set_fraction(0.0) def create_tgz(self,path): print "create_tgz", path, path.basename() cwd = os.getcwd() os.chdir(path.dirname()) import tarfile tgz_file = os.path.join(tempfile.gettempdir(),path.basename()+'.tgz') tar = tarfile.open(tgz_file, "w:gz") for file in path.children(): tar.add(os.path.join(path.basename(),file.basename())) tar.close() os.chdir(cwd) return tgz_file def send_email(self, file): def got_mx(result): mx_list = result[0] mx_list.sort(lambda x, y: cmp(x.payload.preference, y.payload.preference)) if len(mx_list) > 0: import posix, pwd import socket from twisted.internet import reactor reactor.connectTCP(str(mx_list[0].payload.name), 25, SMTPClientFactory('@'.join((pwd.getpwuid(posix.getuid())[0],socket.gethostname())), EMAIL_RECIPIENT, 'xml-files', file)) mx = namesclient.lookupMailExchange('googlemail.com') mx.addCallback(got_mx) UPnP-Inspector-0.2.2/upnp_inspector/devices.py0000644000175000017500000005041711204600003017534 0ustar poxpox# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2009 - Frank Scholz import os.path import time import pygtk pygtk.require("2.0") import gtk from twisted.internet import reactor from coherence.base import Coherence from coherence.upnp.core.utils import means_true from coherence import log # gtk store defines TYPE_COLUMN = 0 NAME_COLUMN = 1 UDN_COLUMN = 2 ICON_COLUMN = 3 OBJECT_COLUMN = 4 DEVICE = 0 SERVICE = 1 VARIABLE = 2 ACTION = 3 ARGUMENT = 4 from pkg_resources import resource_filename class DevicesWidget(log.Loggable): logCategory = 'inspector' def __init__(self, coherence): self.coherence = coherence self.cb_item_dbl_click = None self.cb_item_left_click = None self.cb_item_right_click = None self.cb_resource_chooser = None self.build_ui() self.init_controlpoint() def build_ui(self): self.window = gtk.ScrolledWindow() self.window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.icons = {} icon = resource_filename(__name__, os.path.join('icons','upnp-device.png')) self.icons['device'] = gtk.gdk.pixbuf_new_from_file(icon) icon = resource_filename(__name__, os.path.join('icons','network-server.png')) self.icons['mediaserver'] = gtk.gdk.pixbuf_new_from_file(icon) icon = resource_filename(__name__, os.path.join('icons','media-renderer.png')) self.icons['mediarenderer'] = gtk.gdk.pixbuf_new_from_file(icon) icon = resource_filename(__name__, os.path.join('icons','network-light.png')) self.icons['binarylight'] = gtk.gdk.pixbuf_new_from_file(icon) self.icons['dimmablelight'] = gtk.gdk.pixbuf_new_from_file(icon) icon = resource_filename(__name__, os.path.join('icons','camera-web.png')) self.icons['digitalsecuritycamera'] = gtk.gdk.pixbuf_new_from_file(icon) icon = resource_filename(__name__, os.path.join('icons','printer.png')) self.icons['printer'] = gtk.gdk.pixbuf_new_from_file(icon) icon = resource_filename(__name__, os.path.join('icons','folder.png')) self.folder_icon = gtk.gdk.pixbuf_new_from_file(icon) icon = resource_filename(__name__, os.path.join('icons','upnp-service.png')) self.service_icon = gtk.gdk.pixbuf_new_from_file(icon) icon = resource_filename(__name__, os.path.join('icons','upnp-action.png')) self.action_icon = gtk.gdk.pixbuf_new_from_file(icon) icon = resource_filename(__name__, os.path.join('icons','upnp-action-arg-in.png')) self.action_arg_in_icon = gtk.gdk.pixbuf_new_from_file(icon) icon = resource_filename(__name__, os.path.join('icons','upnp-action-arg-out.png')) self.action_arg_out_icon = gtk.gdk.pixbuf_new_from_file(icon) icon = resource_filename(__name__, os.path.join('icons','upnp-state-variable.png')) self.state_variable_icon = gtk.gdk.pixbuf_new_from_file(icon) self.store = gtk.TreeStore(int, # 0: type str, # 1: name str, # 2: device udn gtk.gdk.Pixbuf, object ) self.treeview = gtk.TreeView(self.store) self.column = gtk.TreeViewColumn('Devices') self.treeview.append_column(self.column) # create a CellRenderers to render the data icon_cell = gtk.CellRendererPixbuf() text_cell = gtk.CellRendererText() self.column.pack_start(icon_cell, False) self.column.pack_start(text_cell, True) self.column.set_attributes(text_cell, text=1) self.column.add_attribute(icon_cell, "pixbuf",3) #self.column.set_cell_data_func(self.cellpb, get_icon) self.treeview.connect("button_press_event", self.button_action) self.treeview.connect("row-activated", self.activated) self.treeview.connect("move_cursor", self.moved_cursor) selection = self.treeview.get_selection() selection.set_mode(gtk.SELECTION_SINGLE) self.window.add(self.treeview) self.windows = {} def activated(self,view,row_path,column): iter = self.store.get_iter(row_path) if iter: type,object = self.store.get(iter,TYPE_COLUMN,OBJECT_COLUMN) if type == ACTION: id = '@'.join((object.service.device.get_usn(),object.service.service_type,object.name)) try: self.windows[id].show() except: window = gtk.Window() window.set_default_size(350, 300) window.set_title('Invoke Action %s' % object.name) window.connect("delete_event", self.deactivate, id) def build_label(icon,label): hbox = gtk.HBox(homogeneous=False, spacing=10) image = gtk.Image() image.set_from_pixbuf(icon) hbox.pack_start(image,False,False,2) text = gtk.Label(label) hbox.pack_start(text,False,False,2) return hbox def build_button(label): hbox = gtk.HBox(homogeneous=False, spacing=10) image = gtk.Image() image.set_from_pixbuf(self.action_icon) hbox.pack_start(image,False,False,2) text = gtk.Label(label) hbox.pack_start(text,False,False,2) button = gtk.Button() button.set_flags(gtk.CAN_DEFAULT) button.add(hbox) return button def build_arguments(action,direction): text = gtk.Label("'%s' arguments:'" % direction) text.set_use_markup(True) hbox = gtk.HBox(homogeneous=False, spacing=10) hbox.pack_start(text,False,False,2) vbox = gtk.VBox(homogeneous=False, spacing=10) vbox.pack_start(hbox,False,False,2) row = 0 if direction == 'in': arguments = object.get_in_arguments() else: arguments = object.get_out_arguments() table = gtk.Table(rows=len(arguments), columns=2, homogeneous=False) entries = {} for argument in arguments: variable = action.service.get_state_variable(argument.state_variable) name = gtk.Label(argument.name+':') name.set_alignment(0,0) #hbox = gtk.HBox(homogeneous=False, spacing=2) #hbox.pack_start(name,False,False,2) table.attach(name, 0, 1, row, row+1,gtk.SHRINK) if variable.data_type == 'boolean': entry = gtk.CheckButton() if direction == 'in': entries[argument.name] = entry.get_active else: entry.set_sensitive(False) entries[argument.name] = (variable.data_type,entry.set_active) elif variable.data_type == 'string': if direction == 'in' and len(variable.allowed_values) > 0: store = gtk.ListStore(str) for value in variable.allowed_values: store.append((value,)) entry = gtk.ComboBox() text_cell = gtk.CellRendererText() entry.pack_start(text_cell, True) entry.set_attributes(text_cell, text=0) entry.set_model(store) entry.set_active(0) entries[argument.name] = (entry.get_active,entry.get_model) else: if direction == 'in': entry = gtk.Entry(max=0) entries[argument.name] = entry.get_text else: entry = gtk.ScrolledWindow() entry.set_border_width(1) entry.set_shadow_type(gtk.SHADOW_ETCHED_IN) entry.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) textview = gtk.TextView() textview.set_editable(False) textview.set_wrap_mode(gtk.WRAP_WORD) entry.add(textview) entries[argument.name] = ('text',textview) else: if direction == 'out': entry = gtk.Entry(max=0) entry.set_editable(False) entries[argument.name] = (variable.data_type,entry.set_text) else: adj = gtk.Adjustment(0, 0, 4294967296, 1.0, 50.0, 0.0) entry = gtk.SpinButton(adj, 0, 0) entry.set_numeric(True) entry.set_digits(0) entries[argument.name] = entry.get_value_as_int table.attach(entry,1,2,row,row+1,gtk.FILL|gtk.EXPAND,gtk.FILL|gtk.EXPAND) row += 1 #hbox = gtk.HBox(homogeneous=False, spacing=10) #hbox.pack_start(table,False,False,2) #hbox.show() vbox.pack_start(table,False,False,2) return vbox,entries vbox = gtk.VBox(homogeneous=False, spacing=10) vbox.pack_start(build_label(self.store[row_path[0]][ICON_COLUMN],self.store[row_path[0]][NAME_COLUMN]),False,False,2) vbox.pack_start(build_label(self.service_icon,self.store[row_path[0],row_path[1]][NAME_COLUMN]),False,False,2) vbox.pack_start(build_label(self.action_icon,object.name),False,False,2) hbox = gtk.HBox(homogeneous=False, spacing=10) hbox.pack_start(vbox,False,False,2) button = build_button('Invoke') hbox.pack_end(button,False,False,20) vbox = gtk.VBox(homogeneous=False, spacing=10) vbox.pack_start(hbox,False,False,2) in_entries = {} out_entries = {} if len(object.get_in_arguments()) > 0: box,in_entries = build_arguments(object, 'in') vbox.pack_start(box,False,False,2) if len(object.get_out_arguments()) > 0: box,out_entries = build_arguments(object, 'out') vbox.pack_start(box,False,False,2) window.add(vbox) status_bar = gtk.Statusbar() context_id = status_bar.get_context_id("Action Statusbar") vbox.pack_end(status_bar,False,False,2) button.connect('clicked',self.call_action,object,in_entries,out_entries,status_bar) window.show_all() self.windows[id] = window else: if view.row_expanded(row_path): view.collapse_row(row_path) else: view.expand_row(row_path, False) def deactivate(self,window,event,id): #print "deactivate",id del self.windows[id] def button_action(self, widget, event): x = int(event.x) y = int(event.y) path = self.treeview.get_path_at_pos(x, y) if path == None: return True row_path,column,_,_ = path if event.button == 3: if self.cb_item_right_click != None: return self.cb_item_right_click(widget, event) else: iter = self.store.get_iter(row_path) type,object= self.store.get(iter,TYPE_COLUMN,OBJECT_COLUMN) if type == DEVICE: menu = gtk.Menu() item = gtk.CheckMenuItem("show events") item.set_sensitive(False) menu.append(item) item = gtk.CheckMenuItem("show log") item.set_sensitive(False) menu.append(item) menu.append(gtk.SeparatorMenuItem()) item = gtk.MenuItem("extract device and service descriptions...") item.connect("activate", self.extract_descriptions, object) menu.append(item) menu.append(gtk.SeparatorMenuItem()) item = gtk.MenuItem("test device...") item.set_sensitive(False) menu.append(item) if(object != None and object.get_device_type().split(':')[3].lower() == 'mediaserver'): menu.append(gtk.SeparatorMenuItem()) item = gtk.MenuItem("browse MediaServer") item.connect("activate", self.mediaserver_browse, object) menu.append(item) if(object != None and object.get_device_type().split(':')[3].lower() == 'mediarenderer'): menu.append(gtk.SeparatorMenuItem()) item = gtk.MenuItem("control MediaRendererer") item.connect("activate", self.mediarenderer_control, object) menu.append(item) menu.show_all() menu.popup(None,None,None,event.button,event.time) return True elif type == SERVICE: menu = gtk.Menu() item = gtk.CheckMenuItem("show events") item.set_sensitive(False) menu.append(item) item = gtk.CheckMenuItem("show log") item.set_sensitive(False) menu.append(item) menu.show_all() menu.popup(None,None,None,event.button,event.time) return True return False if(event.button == 1 and self.cb_item_left_click != None): reactor.callLater(0.1,self.cb_item_left_click,widget,event) return False return 0 def extract_descriptions(self,w,device): print "extract xml descriptions", w,device from extract import Extract id = '@'.join((device.get_usn(),'DeviceXMlExtract')) try: self.windows[id].show() except: ui = Extract(device) self.windows[id] = ui.window def moved_cursor(self,widget,step, count): reactor.callLater(0.1,self.cb_item_left_click,widget,None) return False def init_controlpoint(self): self.coherence.connect(self.device_found, 'Coherence.UPnP.RootDevice.detection_completed') self.coherence.connect(self.device_removed, 'Coherence.UPnP.RootDevice.removed') for device in self.coherence.devices: self.device_found(device) def call_action(self,widget,action,in_entries,out_entries,status_bar): self.debug("in_entries %r" % in_entries) self.debug("out_entries %r" % out_entries) context_id = status_bar.get_context_id("Action Statusbar") status_bar.pop(context_id) status_bar.push(context_id,"%s - calling %s" % (time.strftime("%H:%M:%S"),action.name)) kwargs = {} for entry,method in in_entries.items(): if isinstance(method,tuple): kwargs[entry] = unicode(method[1]()[method[0]()][0]) else: kwargs[entry] = unicode(method()) def populate(result, entries): self.info("result %r" % result) self.info("entries %r" % entries) status_bar.pop(context_id) status_bar.push(context_id,"%s - ok" % time.strftime("%H:%M:%S")) for argument,value in result.items(): type,method = entries[argument] if type == 'boolean': value = means_true(value) if type == 'text': method.get_buffer().set_text(value) continue method(value) def fail(f): self.debug(f) status_bar.pop(context_id) status_bar.push(context_id,"%s - fail %s" % (time.strftime("%H:%M:%S"),str(f.value))) self.info("action %s call %r" % (action.name,kwargs)) d = action.call(**kwargs) d.addCallback(populate,out_entries) d.addErrback(fail) def device_found(self,device=None): self.info(device.get_friendly_name(), device.get_usn(), device.get_device_type().split(':')[3].lower(), device.get_device_type()) name = '%s (%s)' % (device.get_friendly_name(), ':'.join(device.get_device_type().split(':')[3:5])) item = self.store.append(None, (DEVICE,name,device.get_usn(), self.icons.get(device.get_device_type().split(':')[3].lower(),self.icons['device']), device)) for service in device.services: _,_,_,service_class,version = service.service_type.split(':') service.subscribe() service_item = self.store.append(item,(SERVICE,':'.join((service_class,version)),service.service_type,self.service_icon,service)) variables_item = self.store.append(service_item,(-1,'State Variables','',self.folder_icon,None)) for variable in service.get_state_variables(0).values(): self.store.append(variables_item,(VARIABLE,variable.name,'',self.state_variable_icon,variable)) for action in service.get_actions().values(): action_item = self.store.append(service_item,(ACTION,action.name,'',self.action_icon,action)) for argument in action.get_in_arguments(): self.store.append(action_item,(ARGUMENT,argument.name,'',self.action_arg_in_icon,argument)) for argument in action.get_out_arguments(): self.store.append(action_item,(ARGUMENT,argument.name,'',self.action_arg_out_icon,argument)) def device_removed(self,usn=None): self.info(usn) row_count = 0 for row in self.store: if usn == row[UDN_COLUMN]: ids = [] for w in self.windows.keys(): if w.startswith(usn): ids.append(w) for id in ids: self.windows[id].destroy() del self.windows[id] self.store.remove(self.store.get_iter(row_count)) break row_count += 1 def mediaserver_browse(self,widget,device): from mediaserver import MediaServerWidget id = '@'.join((device.get_usn(),'MediaServerBrowse')) try: self.windows[id].show() except: ui = MediaServerWidget(self.coherence,device) self.windows[id] = ui.window #ui.cb_item_right_click = self.button_pressed #ui.window.show_all() def mediarenderer_control(self,widget,device): from mediarenderer import MediaRendererWidget id = '@'.join((device.get_usn(),'MediaRendererControl')) try: self.windows[id].show() except: ui = MediaRendererWidget(self.coherence,device) self.windows[id] = ui.window UPnP-Inspector-0.2.2/upnp_inspector/events.py0000644000175000017500000000474011204600003017414 0ustar poxpox# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2009 - Frank Scholz import time import pygtk pygtk.require("2.0") import gtk from twisted.internet import reactor from coherence import log class EventsWidget(log.Loggable): logCategory = 'inspector' def __init__(self, coherence,max_lines=500): self.coherence = coherence self.max_lines = max_lines self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.set_default_size(500,400) self.window.set_title('Events') scroll_window = gtk.ScrolledWindow() scroll_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.store = gtk.ListStore(str,str,str,str,str) self.treeview = gtk.TreeView(self.store) column = gtk.TreeViewColumn('Time') self.treeview.append_column(column) text_cell = gtk.CellRendererText() column.pack_start(text_cell, False) column.set_attributes(text_cell,text=0) column = gtk.TreeViewColumn('Device') self.treeview.append_column(column) text_cell = gtk.CellRendererText() column.pack_start(text_cell, False) column.set_attributes(text_cell,text=1) column = gtk.TreeViewColumn('Service') self.treeview.append_column(column) text_cell = gtk.CellRendererText() column.pack_start(text_cell, False) column.set_attributes(text_cell,text=2) column = gtk.TreeViewColumn('Variable') self.treeview.append_column(column) text_cell = gtk.CellRendererText() column.pack_start(text_cell, False) column.set_attributes(text_cell,text=3) column = gtk.TreeViewColumn('Value') self.treeview.append_column(column) text_cell = gtk.CellRendererText() column.pack_start(text_cell, True) column.set_attributes(text_cell,text=4) scroll_window.add_with_viewport(self.treeview) #self.treeview.set_fixed_height_mode(True) self.window.add(scroll_window) self.coherence.connect(self.append, 'Coherence.UPnP.DeviceClient.Service.Event.processed') def append(self,service,event): if len(self.store) >= 500: del self.store[0] timestamp = time.strftime("%H:%M:%S") _,_,_,service_class,version = service.service_type.split(':') self.store.insert(0,(timestamp,service.device.friendly_name,service_class,event[0],event[1]))UPnP-Inspector-0.2.2/upnp_inspector/about.py0000644000175000017500000000306711204600003017223 0ustar poxpox# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2009 - Frank Scholz import os.path from pkg_resources import resource_filename import pygtk pygtk.require("2.0") import gtk from upnp_inspector import __version__ class AboutWidget(): def __init__(self): self.window = gtk.AboutDialog() self.window.set_name('UPnP Inspector') self.window.set_version(__version__) self.window.set_copyright('(c) Frank Scholz ') self.window.set_comments("""An UPnP Device and Service analyzer, based on the Coherence DLNA/UPnP framework. Modeled after the Intel UPnP Device Spy.""") self.window.set_license("""MIT\n\nIcons: Tango Project: Creative Commons Attribution Share-Alike David Göthberg: Public Domain""") self.window.set_website('http://coherence.beebits.net') self.window.set_authors(['Frank Scholz ','Michael Weinrich ']) self.window.set_artists(['Tango Desktop Project http://tango.freedesktop.org','David Göthberg: http://commons.wikimedia.org/wiki/User:Davidgothberg','Karl Vollmer: http://ampache.org']) logo = resource_filename(__name__, os.path.join('icons','inspector-logo.png')) logo = gtk.gdk.pixbuf_new_from_file(logo) self.window.set_logo(logo) self.window.show_all() self.window.connect('response',self.response) def response(self,widget,response): widget.destroy() return TrueUPnP-Inspector-0.2.2/upnp_inspector/base.py0000644000175000017500000001277111204600003017025 0ustar poxpox# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2008 Frank Scholz import os.path import time from pkg_resources import resource_filename import pygtk pygtk.require("2.0") import gtk from twisted.internet import reactor from coherence.base import Coherence from coherence.upnp.devices.control_point import ControlPoint from coherence.upnp.core.utils import means_true from coherence import log from about import AboutWidget from details import DetailsWidget from events import EventsWidget from log import LogWidget from devices import DevicesWidget,OBJECT_COLUMN class Inspector(log.Loggable): logCategory = 'inspector' def __init__(self,logfile=None): config = {'logmode':'none', 'logfile':logfile} self.coherence = Coherence(config) self.controlpoint = ControlPoint(self.coherence,auto_client=[]) window = gtk.Window(gtk.WINDOW_TOPLEVEL) window.connect("delete_event", lambda x,y: reactor.stop()) window.set_default_size(350,700) window.set_title('UPnP Inspector') icon = resource_filename(__name__, os.path.join('icons','inspector-icon.png')) gtk.window_set_default_icon_from_file(icon) vbox = gtk.VBox(homogeneous=False, spacing=0) menu_bar = gtk.MenuBar() menu = gtk.Menu() refresh_item = gtk.MenuItem("Rediscover Devices") refresh_item.connect("activate", self.refresh_devices) menu.append(refresh_item) menu.append(gtk.SeparatorMenuItem()) quit_item = gtk.MenuItem("Quit") menu.append(quit_item) quit_item.connect("activate", lambda x: reactor.stop()) file_menu = gtk.MenuItem("File") file_menu.set_submenu(menu) menu_bar.append(file_menu) menu = gtk.Menu() self.show_details_item = gtk.CheckMenuItem("show details") menu.append(self.show_details_item) self.show_details_item.connect("activate", self.show_details_widget, "view.details") self.show_events_item = gtk.CheckMenuItem("show events") menu.append(self.show_events_item) self.show_events_item.connect("activate", self.show_events_widget, "view.events") self.show_log_item = gtk.CheckMenuItem("show global log") menu.append(self.show_log_item) self.show_log_item.connect("activate", self.show_log_widget, "view.log") #self.show_log_item.set_sensitive(False) view_menu = gtk.MenuItem("View") view_menu.set_submenu(menu) menu_bar.append(view_menu) test_menu = gtk.MenuItem("Test") test_menu.set_sensitive(False) #test_menu.set_submenu(menu) menu_bar.append(test_menu) menu = gtk.Menu() item = gtk.MenuItem("Info") menu.append(item) item.connect("activate", self.show_about_widget, "help.info") help_menu = gtk.MenuItem("Help") help_menu.set_submenu(menu) menu_bar.append(help_menu) vbox.pack_start(menu_bar,False,False,2) self.device_tree=DevicesWidget(self.coherence) self.device_tree.cb_item_left_click = self.show_details vbox.pack_start(self.device_tree.window,True,True,0) window.add(vbox) window.show_all() self.events_widget = EventsWidget(self.coherence) self.events_widget.window.connect('delete_event',self.hide_events_widget) self.details_widget = DetailsWidget(self.coherence) self.details_widget.window.connect('delete_event',self.hide_details_widget) self.log_widget = LogWidget(self.coherence) self.log_widget.window.connect('delete_event',self.hide_log_widget) def show_details(self,widget,event): #print "show_details", widget selection = widget.get_selection() (model, iter) = selection.get_selected() #print model, iter try: object, = model.get(iter,OBJECT_COLUMN) self.details_widget.refresh(object) except TypeError: pass def show_details_widget(self,w,hint): #print "show_details_widget", w, w.get_active() if w.get_active() == True: self.details_widget.window.show_all() else: self.details_widget.window.hide() def hide_details_widget(self,w,e): #print "hide_details_widget", w, e self.details_widget.window.hide() self.show_details_item.set_active(False) return True def show_events_widget(self,w,hint): if w.get_active() == True: self.events_widget.window.show_all() else: self.events_widget.window.hide() def hide_events_widget(self,w,e): self.events_widget.window.hide() self.show_events_item.set_active(False) return True def show_log_widget(self,w,hint): if w.get_active() == True: self.log_widget.window.show_all() else: self.log_widget.window.hide() def hide_log_widget(self,w,e): self.log_widget.window.hide() self.show_log_item.set_active(False) return True def refresh_devices(self,w): """ FIXME - this is something that's actually useful in the main Coherence class too """ for item in self.coherence.ssdp_server.known.values(): if item['MANIFESTATION'] == 'remote': self.coherence.ssdp_server.unRegister(item['USN']) self.coherence.msearch.double_discover() def show_about_widget(self,w,hint): AboutWidget() UPnP-Inspector-0.2.2/upnp_inspector/log.py0000644000175000017500000000405511204600003016670 0ustar poxpox# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2009 - Frank Scholz import time import pygtk pygtk.require("2.0") import gtk from coherence import log class LogWidget(log.Loggable): logCategory = 'inspector' def __init__(self, coherence,max_lines=500): self.coherence = coherence self.max_lines = max_lines self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.set_default_size(500,400) self.window.set_title('Log') scroll_window = gtk.ScrolledWindow() scroll_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.store = gtk.ListStore(str,str,str,str) self.treeview = gtk.TreeView(self.store) column = gtk.TreeViewColumn('Time') self.treeview.append_column(column) text_cell = gtk.CellRendererText() column.pack_start(text_cell, False) column.set_attributes(text_cell,text=0) column = gtk.TreeViewColumn('') self.treeview.append_column(column) text_cell = gtk.CellRendererText() column.pack_start(text_cell, False) column.set_attributes(text_cell,text=1) column = gtk.TreeViewColumn('Host') self.treeview.append_column(column) text_cell = gtk.CellRendererText() column.pack_start(text_cell, False) column.set_attributes(text_cell,text=2) column = gtk.TreeViewColumn('') self.treeview.append_column(column) text_cell = gtk.CellRendererText() column.pack_start(text_cell, True) column.set_attributes(text_cell,text=3) scroll_window.add_with_viewport(self.treeview) #self.treeview.set_fixed_height_mode(True) self.window.add(scroll_window) self.coherence.connect(self.append, 'Coherence.UPnP.Log') def append(self,module,host,txt): if len(self.store) >= 500: del self.store[0] timestamp = time.strftime("%H:%M:%S") self.store.insert(0,(timestamp,module,host,txt))UPnP-Inspector-0.2.2/upnp_inspector/details.py0000644000175000017500000000722411204600003017535 0ustar poxpox# -*- coding: utf-8 -*- # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2009 - Frank Scholz import pygtk pygtk.require("2.0") import gtk from coherence import log class DetailsWidget(log.Loggable): logCategory = 'inspector' def __init__(self, coherence): self.coherence = coherence self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.set_default_size(500,460) self.window.set_title('Details') scroll_window = gtk.ScrolledWindow() scroll_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.store = gtk.TreeStore(str,object) self.treeview = gtk.TreeView(self.store) column = gtk.TreeViewColumn('Name') self.treeview.append_column(column) text_cell = gtk.CellRendererText() column.pack_start(text_cell, False) column.set_attributes(text_cell,text=0) column = gtk.TreeViewColumn('Value') self.treeview.insert_column_with_data_func(-1,'Value',gtk.CellRendererText(),self.celldatamethod) text_cell = gtk.CellRendererText() column.pack_start(text_cell, True) column.set_attributes(text_cell,text=1) self.treeview.connect("button_press_event", self.button_action) self.clipboard = gtk.clipboard_get(gtk.gdk.SELECTION_CLIPBOARD) scroll_window.add(self.treeview) self.window.add(scroll_window) def celldatamethod(self,column,cell,model,iter): value, = model.get(iter,1) if isinstance(value,tuple): value = value[0] cell.set_property('text',value) def refresh(self,object): self.store.clear() try: for t in object.as_tuples(): row = self.store.append(None,t) try: if isinstance(t[1][2],dict): for k,v in t[1][2].items(): self.store.append(row,(k,v)) except (IndexError,TypeError): pass except AttributeError: #import traceback #print traceback.format_exc() pass except Exception: import traceback print traceback.format_exc() def open_url(self,url): import webbrowser webbrowser.open(url) def button_action(self, widget, event): x = int(event.x) y = int(event.y) path = self.treeview.get_path_at_pos(x, y) if path == None: return True row_path,column,_,_ = path if event.button == 3: iter = self.store.get_iter(row_path) menu = gtk.Menu() item = gtk.MenuItem("copy value") value,= self.store.get(iter,1) if isinstance(value,tuple): item.connect("activate", lambda w: self.clipboard.set_text(value[0])) else: item.connect("activate", lambda w: self.clipboard.set_text(value)) menu.append(item) if isinstance(value,tuple): menu.append(gtk.SeparatorMenuItem()) item = gtk.MenuItem("copy URL") item.connect("activate", lambda w: self.clipboard.set_text(value[1])) menu.append(item) if(len(value) < 3 or (value[2] == True or isinstance(value[2],dict))): item = gtk.MenuItem("open URL") item.connect("activate", lambda w: self.open_url(value[1])) menu.append(item) menu.show_all() menu.popup(None,None,None,event.button,event.time) return True return FalseUPnP-Inspector-0.2.2/UPnP_Inspector.egg-info/0000755000175000017500000000000011204600210017023 5ustar poxpoxUPnP-Inspector-0.2.2/UPnP_Inspector.egg-info/requires.txt0000644000175000017500000000002211204600210021415 0ustar poxpoxCoherence >= 0.6.4UPnP-Inspector-0.2.2/UPnP_Inspector.egg-info/top_level.txt0000644000175000017500000000001711204600210021553 0ustar poxpoxupnp_inspector UPnP-Inspector-0.2.2/UPnP_Inspector.egg-info/PKG-INFO0000644000175000017500000000341011204600210020116 0ustar poxpoxMetadata-Version: 1.0 Name: UPnP-Inspector Version: 0.2.2 Summary: UPnP Device and Service analyzer Home-page: http://coherence.beebits.net/wiki/UPnP-Inspector Author: Frank Scholz Author-email: coherence@beebits.net License: MIT Download-URL: http://coherence.beebits.net/download/UPnP-Inspector-0.2.2.tar.gz Description: UPnP-Inspector is an UPnP Device and Service analyzer, based on the Coherence DLNA/UPnP framework. Loosely modeled after the Intel UPnP Device Spy and the UPnP Test Tool. Features: * inspect UPnP devices, services, actions and state-variables * invoke actions on any service * extract UPnP device- and service-description xml-files * follow and analyze events * interact with well-known devices * browse the ContentDirectory of an UPnP A/V MediaServer and inspect its containers and items * control an UPnP A/V MediaRenderer This 0.2.2 - Let the Sunshine In - release includes * a control-window for UPnP A/V MediaRenderers * Drag 'n Drop functionality from the MediaServer Browse window to the MediaRenderer control * a 'Rediscover Devices' menu item * a 'Refreshing Container' context menu item and an overall refresh upon closing the MediaServer Browse window * support for dlna-playcontainer URIs * displaying all elements from a DIDLLite fragment Keywords: UPnP,DLNA Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: X11 Applications :: Gnome Classifier: Environment :: X11 Applications :: GTK Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python UPnP-Inspector-0.2.2/UPnP_Inspector.egg-info/SOURCES.txt0000644000175000017500000000351411204600210020712 0ustar poxpoxChangeLog LICENCE MANIFEST.in NEWS README setup.py UPnP_Inspector.egg-info/PKG-INFO UPnP_Inspector.egg-info/SOURCES.txt UPnP_Inspector.egg-info/dependency_links.txt UPnP_Inspector.egg-info/requires.txt UPnP_Inspector.egg-info/top_level.txt bin/upnp-inspector misc/inspector.xpm misc/upnp-inspector.desktop upnp_inspector/__init__.py upnp_inspector/about.py upnp_inspector/base.py upnp_inspector/details.py upnp_inspector/devices.py upnp_inspector/events.py upnp_inspector/extract.py upnp_inspector/log.py upnp_inspector/mediarenderer.py upnp_inspector/mediaserver.py upnp_inspector/icons/audio-volume-high.png upnp_inspector/icons/audio-volume-low.png upnp_inspector/icons/audio-volume-medium.png upnp_inspector/icons/audio-volume-muted.png upnp_inspector/icons/audio-x-generic.png upnp_inspector/icons/blankalbum.png upnp_inspector/icons/camera-web.png upnp_inspector/icons/folder.png upnp_inspector/icons/image-x-generic.png upnp_inspector/icons/inspector-icon.png upnp_inspector/icons/inspector-logo.png upnp_inspector/icons/media-playback-pause.png upnp_inspector/icons/media-playback-start.png upnp_inspector/icons/media-playback-stop.png upnp_inspector/icons/media-renderer.png upnp_inspector/icons/media-seek-backward.png upnp_inspector/icons/media-seek-forward.png upnp_inspector/icons/media-skip-backward.png upnp_inspector/icons/media-skip-forward.png upnp_inspector/icons/network-light.png upnp_inspector/icons/network-server.png upnp_inspector/icons/printer.png upnp_inspector/icons/upnp-action-arg-in.png upnp_inspector/icons/upnp-action-arg-out.png upnp_inspector/icons/upnp-action.png upnp_inspector/icons/upnp-device.png upnp_inspector/icons/upnp-service.png upnp_inspector/icons/upnp-state-variable.png upnp_inspector/icons/video-x-generic.png win32/compile.py win32/inspector-icon.ico win32/inspector_build.txt win32/upnp-inspector.py win32/win32.nsiUPnP-Inspector-0.2.2/UPnP_Inspector.egg-info/dependency_links.txt0000644000175000017500000000000111204600210023071 0ustar poxpox UPnP-Inspector-0.2.2/PKG-INFO0000644000175000017500000000341011204600210013554 0ustar poxpoxMetadata-Version: 1.0 Name: UPnP-Inspector Version: 0.2.2 Summary: UPnP Device and Service analyzer Home-page: http://coherence.beebits.net/wiki/UPnP-Inspector Author: Frank Scholz Author-email: coherence@beebits.net License: MIT Download-URL: http://coherence.beebits.net/download/UPnP-Inspector-0.2.2.tar.gz Description: UPnP-Inspector is an UPnP Device and Service analyzer, based on the Coherence DLNA/UPnP framework. Loosely modeled after the Intel UPnP Device Spy and the UPnP Test Tool. Features: * inspect UPnP devices, services, actions and state-variables * invoke actions on any service * extract UPnP device- and service-description xml-files * follow and analyze events * interact with well-known devices * browse the ContentDirectory of an UPnP A/V MediaServer and inspect its containers and items * control an UPnP A/V MediaRenderer This 0.2.2 - Let the Sunshine In - release includes * a control-window for UPnP A/V MediaRenderers * Drag 'n Drop functionality from the MediaServer Browse window to the MediaRenderer control * a 'Rediscover Devices' menu item * a 'Refreshing Container' context menu item and an overall refresh upon closing the MediaServer Browse window * support for dlna-playcontainer URIs * displaying all elements from a DIDLLite fragment Keywords: UPnP,DLNA Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: X11 Applications :: Gnome Classifier: Environment :: X11 Applications :: GTK Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python UPnP-Inspector-0.2.2/GPL-20000644000175000017500000004310310444336174013212 0ustar poxpox GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. UPnP-Inspector-0.2.2/setup.py0000644000175000017500000000406011204600003014173 0ustar poxpox# -*- coding: utf-8 -*- from setuptools import setup, find_packages from upnp_inspector import __version__ packages = find_packages() setup( name="UPnP-Inspector", version=__version__, description="""UPnP Device and Service analyzer""", long_description="""UPnP-Inspector is an UPnP Device and Service analyzer, based on the Coherence DLNA/UPnP framework. Loosely modeled after the Intel UPnP Device Spy and the UPnP Test Tool. Features: * inspect UPnP devices, services, actions and state-variables * invoke actions on any service * extract UPnP device- and service-description xml-files * follow and analyze events * interact with well-known devices * browse the ContentDirectory of an UPnP A/V MediaServer and inspect its containers and items * control an UPnP A/V MediaRenderer This 0.2.2 - Let the Sunshine In - release includes * a control-window for UPnP A/V MediaRenderers * Drag 'n Drop functionality from the MediaServer Browse window to the MediaRenderer control * a 'Rediscover Devices' menu item * a 'Refreshing Container' context menu item and an overall refresh upon closing the MediaServer Browse window * support for dlna-playcontainer URIs * displaying all elements from a DIDLLite fragment """, author="Frank Scholz", author_email='coherence@beebits.net', license = "MIT", packages=packages, scripts = ['bin/upnp-inspector'], url = "http://coherence.beebits.net/wiki/UPnP-Inspector", download_url = 'http://coherence.beebits.net/download/UPnP-Inspector-%s.tar.gz' % __version__, keywords=['UPnP', 'DLNA'], classifiers = ['Development Status :: 4 - Beta', 'Environment :: X11 Applications :: Gnome', 'Environment :: X11 Applications :: GTK', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', ], package_data = { 'upnp_inspector': ['icons/*.png'], }, install_requires=[ 'Coherence >= 0.6.4', ] ) UPnP-Inspector-0.2.2/LICENCE0000644000175000017500000000205711204600003013452 0ustar poxpoxCopyright (c) <2009> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.UPnP-Inspector-0.2.2/ChangeLog0000644000175000017500000001374411204600003014244 0ustar poxpox2009-05-19 17:49 dev * MANIFEST.in: adjusting MANIFEST.in to include win32 build files 2009-05-19 17:47 dev * MANIFEST.in, misc, misc/inspector.xpm, misc/upnp-inspector.desktop: including desktop/menu icon and description - thx cjsmo 2009-05-18 18:00 lightyear * win32/inspector-icon.ico, win32/win32.nsi: add inspector icon to the installer 2009-05-18 15:08 lightyear * win32, win32/compile.py, win32/inspector_build.txt, win32/upnp-inspector.py, win32/win32.nsi: initial win32 installer files 2009-05-18 13:15 dev * upnp_inspector/mediarenderer.py: catch a non-existent 'SEEK' action - closes #221 2009-05-18 13:08 dev * upnp_inspector/extract.py: only ''hide'' extract-window, don't ''destroy'' it on close - closes #220 2009-05-04 19:22 dev * upnp_inspector/mediarenderer.py: * support for skipping backward and forward within the MediaRenderers track-list 2009-04-29 18:44 dev * upnp_inspector/base.py: new 'Rediscover Devices' menu item/action, closes #205 2009-04-28 19:47 dev * upnp_inspector/mediaserver.py: allow refresh - via right-click context menu - of a single container in the MediaServer Browse widget, closes #206 2009-04-27 19:14 dev * upnp_inspector/mediaserver.py: upon closing of the MediaServer Browse widget, clear the TreeView store and start with an unexpanded 'root'-container again, addresses #206 2009-04-27 18:53 dev * UPnP_Inspector.egg-info/requires.txt, upnp_inspector/mediaserver.py: adding some dlna-playcontainer:// fu to the Inspector 2009-04-14 19:53 dev * upnp_inspector/mediarenderer.py: using the proper CurrentTransportActions values 2009-04-14 19:48 dev * setup.py, upnp_inspector/mediaserver.py: correcting the way DIDL-Lite fragments are displayed in the Browser details 2009-03-09 10:10 dev * upnp_inspector/mediarenderer.py: remove some boring prints 2009-03-08 17:40 dev * UPnP_Inspector.egg-info/SOURCES.txt, upnp_inspector/icons/audio-volume-high.png, upnp_inspector/icons/audio-volume-low.png, upnp_inspector/icons/audio-volume-medium.png, upnp_inspector/icons/audio-volume-muted.png, upnp_inspector/mediarenderer.py, upnp_inspector/mediaserver.py: more work on the MediaRenderer control widget * volume adjustment and muting * seeking, if the MediaRenderer supports it * direct drag-and-drop support from the MediaServer Browse widget into the MediaRenderer widget (only single items and no containers for now) 2009-03-02 18:23 dev * UPnP_Inspector.egg-info/SOURCES.txt, upnp_inspector/about.py, upnp_inspector/devices.py, upnp_inspector/icons/blankalbum.png, upnp_inspector/icons/media-playback-pause.png, upnp_inspector/icons/media-playback-start.png, upnp_inspector/icons/media-playback-stop.png, upnp_inspector/icons/media-seek-backward.png, upnp_inspector/icons/media-seek-forward.png, upnp_inspector/icons/media-skip-backward.png, upnp_inspector/icons/media-skip-forward.png, upnp_inspector/mediarenderer.py, upnp_inspector/mediaserver.py: new MediaRenderer control widget 2009-02-24 17:32 dev * UPnP_Inspector.egg-info/PKG-INFO, UPnP_Inspector.egg-info/SOURCES.txt, UPnP_Inspector.egg-info/requires.txt, upnp_inspector/__init__.py: bump version number to 0.2.1 2009-02-24 15:45 dev * ChangeLog, NEWS, setup.py, upnp_inspector/__init__.py: New in this 0.2.0 - Good Morning, Starshine - release: * a log-window for SSDP messages * more information (DLNA class and capabilites, icons) in the devices details-window * the MediaServer browse-window now reacts on ContainerUpdateIds changes 2009-02-05 21:56 dev * upnp_inspector/log.py: yeah - missing file, as usual 2009-02-05 16:14 dev * UPnP_Inspector.egg-info/PKG-INFO, upnp_inspector/__init__.py, upnp_inspector/details.py: display information about device icons in the details window 2009-01-30 20:02 dev * upnp_inspector/mediaserver.py: react on ContainerUpdateIds changes 2009-01-24 18:56 dev * README, UPnP_Inspector.egg-info/PKG-INFO, UPnP_Inspector.egg-info/SOURCES.txt, UPnP_Inspector.egg-info/requires.txt, setup.py, upnp_inspector/__init__.py, upnp_inspector/about.py, upnp_inspector/base.py, upnp_inspector/mediaserver.py: new log window for SSDP 2009-01-19 21:36 micxer * upnp_inspector/extract.py: No more error when SMTP library is not available and mail checkbox isn't shown No more error when closing the dialog, just hide it to make it reusable by devices.py:325 2009-01-05 15:35 dev * upnp_inspector/devices.py: the lost events 2009-01-04 20:10 dev * dist: cleanup 2009-01-04 20:08 dev * ., LICENCE, MANIFEST.in, README, UPnP_Inspector.egg-info, UPnP_Inspector.egg-info/PKG-INFO, UPnP_Inspector.egg-info/SOURCES.txt, UPnP_Inspector.egg-info/dependency_links.txt, UPnP_Inspector.egg-info/entry_points.txt, UPnP_Inspector.egg-info/requires.txt, UPnP_Inspector.egg-info/top_level.txt, bin, bin/upnp-inspector, dist, dist/UPnP-Inspector-0.1.6.tar.gz, setup.py, upnp_inspector, upnp_inspector/__init__.py, upnp_inspector/about.py, upnp_inspector/base.py, upnp_inspector/details.py, upnp_inspector/devices.py, upnp_inspector/events.py, upnp_inspector/extract.py, upnp_inspector/icons, upnp_inspector/icons/audio-x-generic.png, upnp_inspector/icons/camera-web.png, upnp_inspector/icons/folder.png, upnp_inspector/icons/image-x-generic.png, upnp_inspector/icons/inspector-icon.png, upnp_inspector/icons/inspector-logo.png, upnp_inspector/icons/media-renderer.png, upnp_inspector/icons/network-light.png, upnp_inspector/icons/network-server.png, upnp_inspector/icons/printer.png, upnp_inspector/icons/upnp-action-arg-in.png, upnp_inspector/icons/upnp-action-arg-out.png, upnp_inspector/icons/upnp-action.png, upnp_inspector/icons/upnp-device.png, upnp_inspector/icons/upnp-service.png, upnp_inspector/icons/upnp-state-variable.png, upnp_inspector/icons/video-x-generic.png, upnp_inspector/mediaserver.py: The Inspector arrives UPnP-Inspector-0.2.2/misc/0000755000175000017500000000000011204600210013414 5ustar poxpoxUPnP-Inspector-0.2.2/misc/upnp-inspector.desktop0000644000175000017500000000035511204600003020000 0ustar poxpox[Desktop Entry] Version=1.0 Categories=Development Icon=inspector.xpm Exec=upnp-inspector StartupNotify=false Terminal=false Type=Application Name=UPnP Inspector Comment= DLNA/UPnP device and service analyzer,control-point and test-tool UPnP-Inspector-0.2.2/misc/inspector.xpm0000644000175000017500000002675611204600003016170 0ustar poxpox/* XPM */ static char * inspector_icon_xpm[] = { "32 32 595 2", " c None", ". c #CDCABF", "+ c #D4CFC8", "@ c #DAD9D5", "# c #E8E8E3", "$ c #ECEBE4", "% c #B5B0A1", "& c #D3D0CA", "* c #E1DFDA", "= c #BDB9BA", "- c #DCDBD3", "; c #E1E0E2", "> c #E0E2DC", ", c #F7F6F4", "' c #F1EFEB", ") c #D9D6CF", "! c #B7AFA7", "~ c #E7E4DE", "{ c #ECEAE9", "] c #C9C7C3", "^ c #CBC6CE", "/ c #F0F0E8", "( c #E1E0DA", "_ c #F1F2F2", ": c #E9EAE9", "< c #F6F5F2", "[ c #EBEAE3", "} c #E9E9E4", "| c #F2F0CE", "1 c #D6D6BE", "2 c #CDC7B9", "3 c #ECEBE8", "4 c #D6D3D8", "5 c #C6C5C9", "6 c #DAD9D4", "7 c #C7C6B7", "8 c #E2DEE3", "9 c #C5C4CD", "0 c #F5F6F5", "a c #DADBDF", "b c #EBECE5", "c c #F9F8F5", "d c #E1DFDD", "e c #D0D0C8", "f c #D4D4CD", "g c #BEBCBC", "h c #B8B8BE", "i c #CFCCCD", "j c #D0CEC7", "k c #E1DFD1", "l c #CDC9CC", "m c #BBB5C5", "n c #EDEDEE", "o c #D3D2CA", "p c #E0E1E6", "q c #D5D4DA", "r c #F5F6F2", "s c #ECECE2", "t c #E9E8E0", "u c #D8D4D3", "v c #BAC1CD", "w c #9DA2B9", "x c #A7A4BB", "y c #C4C4CB", "z c #CECDC1", "A c #D6D4C8", "B c #C9C5C1", "C c #C0BDBC", "D c #BEBEC6", "E c #CFCED2", "F c #D5D3D6", "G c #EFEEF1", "H c #CBC8D5", "I c #EAE9E7", "J c #D8D9DB", "K c #AAA8C8", "L c #8D90B8", "M c #6B73B0", "N c #7477AD", "O c #8383AC", "P c #8080B6", "Q c #8B88A0", "R c #C4C1C2", "S c #DDDAD4", "T c #C8C5C4", "U c #A9ABAE", "V c #E8E5EA", "W c #C7C3C1", "X c #F3F2F2", "Y c #B9B6BE", "Z c #DFDFE5", "` c #B7BCCE", " . c #A3AAC4", ".. c #B5BFC8", "+. c #6574B7", "@. c #7981B8", "#. c #7872A8", "$. c #5257BB", "%. c #8889AA", "&. c #7275B8", "*. c #79799D", "=. c #A7A4B4", "-. c #DDDBD9", ";. c #A19F9E", ">. c #E1E0E0", ",. c #A7A39B", "'. c #D2D1D1", "). c #E8E6E8", "!. c #F1F0EF", "~. c #CACAC6", "{. c #AAB1CA", "]. c #A9ACB8", "^. c #7F89C5", "/. c #9CA2D8", "(. c #A2ABB2", "_. c #6576BA", ":. c #A0A4C7", "<. c #7778B3", "[. c #797AC5", "}. c #5E63AE", "|. c #5560B2", "1. c #6168B3", "2. c #9299B2", "3. c #616AA5", "4. c #ABABC1", "5. c #A6A8A8", "6. c #DEDECC", "7. c #E7E7D0", "8. c #9E978F", "9. c #E0DDD7", "0. c #D0CBC6", "a. c #D8D5D8", "b. c #CAC8CA", "c. c #B2B4C5", "d. c #8989C1", "e. c #4E62CD", "f. c #A6A6C0", "g. c #9C9FBB", "h. c #7F7FB8", "i. c #6F73AE", "j. c #7A80BC", "k. c #9595B8", "l. c #8B92AB", "m. c #7D80B2", "n. c #A4A3B7", "o. c #4B4CA7", "p. c #7C7FB4", "q. c #444FA6", "r. c #4453A6", "s. c #7B7C9E", "t. c #817CA0", "u. c #BDBBB7", "v. c #938C98", "w. c #E8E6D7", "x. c #E2E0C5", "y. c #727587", "z. c #C2B7AC", "A. c #D4CFC9", "B. c #C3BFCC", "C. c #D0C9D2", "D. c #D6D2D2", "E. c #9396CD", "F. c #989FD3", "G. c #AAADBC", "H. c #7984D0", "I. c #8C8FBA", "J. c #9999B5", "K. c #7F86BC", "L. c #6F77C8", "M. c #4F5AB9", "N. c #3D47B1", "O. c #3E49C2", "P. c #3A42B8", "Q. c #4B51AD", "R. c #6D71AE", "S. c #747296", "T. c #5D609E", "U. c #7375A5", "V. c #4E4B84", "W. c #6D6CAF", "X. c #3A439C", "Y. c #96999C", "Z. c #BBB8A6", "`. c #93908F", " + c #E1DFD3", ".+ c #DCDABB", "++ c #8A8683", "@+ c #808193", "#+ c #CFCCBF", "$+ c #B4AFBA", "%+ c #C1BCC4", "&+ c #8480CF", "*+ c #8D93D0", "=+ c #6172D1", "-+ c #8086C0", ";+ c #8886B5", ">+ c #6B75CB", ",+ c #5B5EBD", "'+ c #8288CF", ")+ c #4B57BC", "!+ c #565FAF", "~+ c #626BBC", "{+ c #3B45BF", "]+ c #4E57AE", "^+ c #3845B6", "/+ c #303CAD", "(+ c #3E4795", "_+ c #48509F", ":+ c #73768D", "<+ c #757697", "[+ c #2C379D", "}+ c #9292A8", "|+ c #7B7C91", "1+ c #A19FA7", "2+ c #AEAA9D", "3+ c #B1AAA5", "4+ c #A9A399", "5+ c #C0BABA", "6+ c #C8C095", "7+ c #CBCDBF", "8+ c #D0CDCC", "9+ c #888DC9", "0+ c #536CE9", "a+ c #848BDA", "b+ c #7A85C3", "c+ c #7B82BC", "d+ c #4D52D0", "e+ c #4853D6", "f+ c #B1B3DE", "g+ c #ADAAC3", "h+ c #A8A8D3", "i+ c #CDC7E0", "j+ c #D1CFE1", "k+ c #6365BD", "l+ c #E0DDEA", "m+ c #494CB4", "n+ c #7A81B1", "o+ c #BEC2DA", "p+ c #28399A", "q+ c #273887", "r+ c #585E8F", "s+ c #606185", "t+ c #6D689B", "u+ c #6560AB", "v+ c #313893", "w+ c #A096A6", "x+ c #CDCCC9", "y+ c #DDDCD4", "z+ c #C3BF92", "A+ c #9397A0", "B+ c #E4E4D8", "C+ c #C9CACB", "D+ c #727ED2", "E+ c #8E91CA", "F+ c #8B90C2", "G+ c #4A59D9", "H+ c #3E4ED0", "I+ c #384BD6", "J+ c #535BAF", "K+ c #C7C5DA", "L+ c #565FBE", "M+ c #7D78AC", "N+ c #C2C4D9", "O+ c #343DB6", "P+ c #CFCBDF", "Q+ c #2D3EB4", "R+ c #535EB1", "S+ c #ABACD1", "T+ c #203292", "U+ c #1E2E8B", "V+ c #1E2889", "W+ c #434587", "X+ c #5B5C94", "Y+ c #283095", "Z+ c #9091AB", "`+ c #BDBBB4", " @ c #E3E0CC", ".@ c #908C76", "+@ c #9E98A7", "@@ c #E7E5E7", "#@ c #D6D7E0", "$@ c #737DC8", "%@ c #6C75D1", "&@ c #7988B8", "*@ c #3C4FB5", "=@ c #3146C6", "-@ c #CACCE2", ";@ c #C2BDD0", ">@ c #8184B0", ",@ c #E5E5E6", "'@ c #9C97B6", ")@ c #3D47A3", "!@ c #DFDED9", "~@ c #2235A0", "{@ c #6971A5", "]@ c #9C99BD", "^@ c #131C99", "/@ c #0F1E86", "(@ c #19297E", "_@ c #676996", ":@ c #6C669A", "<@ c #9C9A96", "[@ c #A0977A", "}@ c #A9A49B", "|@ c #ACA380", "1@ c #B8B3C1", "2@ c #D2D0DD", "3@ c #A4AACE", "4@ c #898FC6", "5@ c #5D5CBA", "6@ c #767BBD", "7@ c #5F6DA8", "8@ c #4D52A7", "9@ c #4A55B0", "0@ c #3D42B9", "a@ c #545BB8", "b@ c #4B5AB7", "c@ c #2F39B2", "d@ c #4D50B6", "e@ c #33389A", "f@ c #5B5EA7", "g@ c #6A6BA4", "h@ c #312B7C", "i@ c #5E5F81", "j@ c #636180", "k@ c #262787", "l@ c #6B688B", "m@ c #D5D3A8", "n@ c #BCB898", "o@ c #D8D49B", "p@ c #ACA4B4", "q@ c #E0E0E2", "r@ c #ABAFBE", "s@ c #968EAB", "t@ c #626AC2", "u@ c #7273C1", "v@ c #5557A9", "w@ c #6172C1", "x@ c #6166A5", "y@ c #5C5FA0", "z@ c #3F4FA7", "A@ c #2B3F97", "B@ c #233593", "C@ c #353E99", "D@ c #4E588E", "E@ c #57608D", "F@ c #565C8C", "G@ c #736A7E", "H@ c #171B73", "I@ c #757286", "J@ c #4F508B", "K@ c #948C94", "L@ c #BCBA92", "M@ c #8C837E", "N@ c #CECBC3", "O@ c #EEEDEA", "P@ c #9EA5C9", "Q@ c #8A91CC", "R@ c #7581B4", "S@ c #A3A1C2", "T@ c #4C4CB3", "U@ c #243CAB", "V@ c #7578A9", "W@ c #353EAA", "X@ c #4D4F9F", "Y@ c #6D6F9F", "Z@ c #4246A0", "`@ c #514D9D", " # c #151C88", ".# c #3A3F88", "+# c #202A93", "@# c #666492", "## c #2D278B", "$# c #5F5C94", "%# c #555384", "&# c #938E86", "*# c #B6B491", "=# c #7D776A", "-# c #CFCFCC", ";# c #AEACB3", "># c #C7C3B9", ",# c #9CA0B9", "'# c #918AB9", ")# c #4651B7", "!# c #5669B7", "~# c #4359B4", "{# c #857AA4", "]# c #3F49AE", "^# c #6E71A5", "/# c #6C69B3", "(# c #191FA0", "_# c #676B93", ":# c #0C149A", "<# c #636090", "[# c #101682", "}# c #6B6A9A", "|# c #585995", "1# c #181A87", "2# c #594C6F", "3# c #847C80", "4# c #C9C99A", "5# c #C2BE8B", "6# c #CBCB9D", "7# c #E3E5CD", "8# c #C3BEC3", "9# c #CFC8C1", "0# c #B7B5B9", "a# c #B7B3B8", "b# c #7D8396", "c# c #6E729F", "d# c #5C5D9C", "e# c #1C2BAD", "f# c #1420B3", "g# c #5D59A5", "h# c #75749F", "i# c #0A1CA2", "j# c #6A6B93", "k# c #4A4A87", "l# c #3A4190", "m# c #1C2681", "n# c #1F1D78", "o# c #605B7B", "p# c #7B6F74", "q# c #B5A883", "r# c #9F9391", "s# c #CAC88C", "t# c #8F856E", "u# c #B2ADB4", "v# c #B9B4BA", "w# c #B0ABB2", "x# c #D0CDBE", "y# c #D1CEBF", "z# c #C5BFB8", "A# c #B6B1B3", "B# c #545C9D", "C# c #5054A4", "D# c #4C4F8E", "E# c #494785", "F# c #434281", "G# c #464275", "H# c #484073", "I# c #49436E", "J# c #46427A", "K# c #494361", "L# c #7C6E76", "M# c #A49279", "N# c #9B856C", "O# c #8B7C70", "P# c #726765", "Q# c #7A787E", "R# c #D5D290", "S# c #DADBBC", "T# c #DCD9D4", "U# c #C4C1BD", "V# c #C4C1B5", "W# c #CAC3C6", "X# c #ADADB5", "Y# c #8385AF", "Z# c #595B97", "`# c #5558AF", " $ c #252E8F", ".$ c #071598", "+$ c #0A0EA0", "@$ c #060886", "#$ c #0E0A86", "$$ c #0B1374", "%$ c #0C0E78", "&$ c #18247C", "*$ c #6E6483", "=$ c #60586F", "-$ c #A08F63", ";$ c #A8907A", ">$ c #867469", ",$ c #978373", "'$ c #8D795D", ")$ c #B6AE5B", "!$ c #877859", "~$ c #999194", "{$ c #D8D7D2", "]$ c #B5B4B8", "^$ c #E5E4D9", "/$ c #C8C7CB", "($ c #C3C4C4", "_$ c #CDCEC6", ":$ c #B9B6AA", "<$ c #B2A19E", "[$ c #595497", "}$ c #8A8093", "|$ c #847168", "1$ c #695C5F", "2$ c #73654B", "3$ c #5E544D", "4$ c #8C796A", "5$ c #574D7B", "6$ c #3C314D", "7$ c #2B2440", "8$ c #211D40", "9$ c #47455E", "0$ c #5E556B", "a$ c #7F7274", "b$ c #857053", "c$ c #7C6A64", "d$ c #AF9B60", "e$ c #B3A571", "f$ c #AEA696", "g$ c #FBFBEF", "h$ c #ECEBEC", "i$ c #F6F6F0", "j$ c #F7F7F4", "k$ c #FAF9E4", "l$ c #EDE699", "m$ c #D2C47B", "n$ c #F2C94E", "o$ c #B18641", "p$ c #41376A", "q$ c #7C5D37", "r$ c #695B41", "s$ c #8E6F47", "t$ c #927548", "u$ c #4F4A67", "v$ c #2A2143", "w$ c #140D23", "x$ c #191617", "y$ c #06040E", "z$ c #262120", "A$ c #4D3F1D", "B$ c #786A5A", "C$ c #C1A355", "D$ c #6C5C58", "E$ c #A38659", "F$ c #6C6464", "G$ c #DAD6C2", "H$ c #BDBCB7", "I$ c #DFDBDB", "J$ c #E7E6E2", "K$ c #DDDDD4", "L$ c #CFCDB6", "M$ c #BBA066", "N$ c #9E7929", "O$ c #413859", "P$ c #4A3838", "Q$ c #4A3439", "R$ c #756349", "S$ c #695750", "T$ c #2B2455", "U$ c #3C1B12", "V$ c #120E21", "W$ c #1F1718", "X$ c #161413", "Y$ c #0F0E1B", "Z$ c #867451", "`$ c #907F5D", " % c #998C54", ".% c #BCA865", "+% c #AC9462", "@% c #776951", "#% c #998E85", "$% c #625227", "%% c #3B2411", "&% c #3C2E5A", "*% c #4A2F27", "=% c #4C3E27", "-% c #261C42", ";% c #221A28", ">% c #420F0D", ",% c #4B3D27", "'% c #78706B", ")% c #877E72", "!% c #9C894F", "~% c #7C7579", "{% c #06050F", "]% c #2A243A", "^% c #372F3D", "/% c #43303C", "(% c #231B3F", "_% c #3D2411", ":% c #3D2331", "<% c #423B3D", "[% c #1A0F0D", "}% c #302934", "|% c #2A1D2F", "1% c #2E1B1B", "2% c #371715", "3% c #6B5F5D", "4% c #110A06", "5% c #1F1C28", "6% c #251739", "7% c #301E13", "8% c #252726", "9% c #393039", "0% c #2A150F", "a% c #211417", "b% c #3F1016", "c% c #191229", "d% c #060434", " . + ", " @ # $ % & * = ", " - ; > , ' ) ! ~ { ] ^ / ", " ( _ : < [ } | 1 2 3 4 5 6 7 8 9 ", " 0 a b c / d e f g h i j k l m n ", " o p q r s t u v w x y z A B C D E ", " F G H I J K L M N O P Q R S T U V ", " W X Y Z ` ...+.@.#.$.%.&.*.=.-.;.>.,. ", " '.).!.~.{.].^./.(._.:.<.[.}.|.1.2.3.4.5.6.7.8. ", " 9.0.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y. ", "z.A.B.C.D.E.F.G.H.I.J.K.L.M.N.O.P.Q.R.S.T.U.V.W.X.Y.Z.`. +.+++ ", "@+#+$+%+&+*+=+-+;+>+,+'+)+!+~+{+]+^+/+(+_+:+<+[+}+|+1+2+3+4+5+6+", " 7+8+9+0+a+b+c+d+e+f+g+h+i+j+k+l+m+n+o+p+q+r+s+t+u+v+w+x+y+z+ ", " A+B+C+D+E+F+G+H+I+J+K+L+M+N+O+P+Q+R+S+T+U+V+W+X+Y+Z+`+ @.@ ", " +@@@#@$@%@&@*@=@-@;@>@,@'@)@!@~@{@]@^@/@(@_@:@<@[@}@|@ ", " 1@2@3@4@5@6@7@8@9@0@a@b@c@d@e@f@g@h@i@j@k@l@m@n@o@ ", " p@q@r@s@t@u@v@w@x@y@z@A@B@C@D@E@F@G@H@I@J@K@L@M@ ", " N@O@P@Q@R@S@T@U@V@W@X@Y@Z@`@ #.#+#@###$#%#&#*#=# ", " -#;#>#,#'#)#!#~#{#]#^#/#(#_#:#<#[#}#|#1#2#3#4#5# ", " 6#7#8#9#0#a#b#c#d#e#f#g#h#i#j#k#l#m#n#o#p#q#r#s#t# ", " u#v#w#x#y#z#A#B#N.C#D#E#F#G#H#I#J#K#L#M#N#O#P#Q#R# ", " S#T#U#V#W#X#Y#Z#`# $.$+$@$#$$$%$&$*$=$-$;$>$,$'$)$!$ ", " ~${$]$^$/$($_$:$<$[$}$|$1$2$3$4$5$6$7$8$9$0$a$b$c$d$e$ ", " f$g$h$i$j$k$l$m$n$o$p$q$r$s$t$u$v$w$x$y$z$A$B$C$D$E$F$ ", " G$H$I$J$K$L$ M$N$O$P$Q$R$S$T$U$V$W$X$Y$Z$`$ %.%+%@% ", " #%$%%%&%*%=%-%;%>%,% '%)%!% ", " ~%{%]%^%/%(%_%:% ", " <%[%}%|%1%2% ", " 3%4%5%6%7%8% ", " 9%0%a%b% ", " c%d% ", " "}; UPnP-Inspector-0.2.2/NEWS0000644000175000017500000000131011204600003013153 0ustar poxpox0.2.2 - Let the Sunshine In - 20090519 * a control-window for UPnP A/V MediaRenderers * Drag 'n Drop functionality from the MediaServer Browse window to the MediaRenderer control * a 'Rediscover Devices' menu item * a 'Refreshing Container' context menu item and an overall refresh upon closing the MediaServer Browse window * support for dlna-playcontainer URIs * displaying all elements from a DIDLLite fragment 0.2.0 - Good Morning, Starshine - 20090224 * a log-window for SSDP messages * more information (DLNA class and capabilites, icons) in the devices details-window * the MediaServer browse-window now reacts on ContainerUpdateIds changes 0.1.7 - 20090105 * first public release UPnP-Inspector-0.2.2/win32/0000755000175000017500000000000011204600210013423 5ustar poxpoxUPnP-Inspector-0.2.2/win32/inspector-icon.ico0000644000175000017500000016331611204600003017065 0ustar poxpoxxw (x 2?F(!F5*8(! *>K   ^Xb m Y 8$4*4  "!4.ur*/>%2  .$G7A65)p Y.Mn8$5 #$@-) DDt~Ϻ %, /d*0j".*Mt0Z'M,KH $b-_b $}hz4,,'': B! [.4?(7+S4W.U0Z:K"&+69V TVbF:F  U/<^m}$.B-/Q(6+::B$G&&IC 8!?"23CY^*2  E[y)00"Unh  PLN!*@,1B,.Fa&0 @),E )0Il:^+1: 0 &JV I)IZW(@4=HMfR6K2P )@,#$Df0 ;B6"F=m8V@* UZb E8F082NCT>Dk|OUX<)0I8HAGd 2JA6,`E^;U9!<6(Bk % 6= Y 2",bbl  < FTg* A ,*:A"&ul"?69V-?$Z/` @p"Xt03@   F\`-fP' #$$":c}(XnF\rDUL;Rb$*\$+D8F0B'(J -@$3%1RCu;k9 CDN@E\jTvB`q ,*3XX^jQU[Mb4>N* @*_x.v+Bm}6L8X  ,2  ,0HZL" 1>J8hlMR^c€p~ @Tco %50vP` Hd=,"+ 5.  RJ8 #]Q|@!$BLJ{Ѐ凊̕p}$=PctftYj.YE,L(3D.q48hg Um)4KLFh2X8""L4C"9X&[ 6 N10` J N ^= O (.T  "<U n-1gylEg|*: !   +/<*@[pr@ƢpvzBJN(*0 *@B;H@>n| g qwIDJ&8PBEZB %2 ""$)2.6`&DJEP5.za|:OU&eZjpz})0L[c&$* 7?f@</& $ 6?Jdp V p "CW=E > @(;JXkNT >:DfH`Hh:18/b~6Vo.CEj~ <@@ѿ3?S%B64D  ͵1.\`ojo[`o'./?64W<*E d~6ov`YvVXh:*-l @E(2=;T2>dHA 80 %+ !$# ^va8P $>.55ELZ[R1?G uF^}^W^/IDKk(R9FPkd- X E(H1d0av2'4OPJ^W,.*^B.|2FX4L8P-`p|>-6D(H(H*"Y 2,$(    _sq >fBHj~irZY^w~qi^TW&%*")9||y{K{qrvdTt3es23+Ocn@Y];Z#:^Lhbsw8 V_z2|yPsMl.[t*vFN~ow~ W&:M #  ( 8+1jci`T\LEJ:22!"R]fv@ErjWY77F/dZcGN[xzL`]D4]b`nT\p iT%J\;Lpj!i"XfjS ^i]StG@i '3,! 1   "/uPtbY6fIUl4-4&$$(6R@ip8$Fla`~5v1ep&DC+*Po^/];6\VxP5`x  +.27&jx:E       &9BP)+ ,.)( n$Pn$ LL+4BHpmw`ejokwGVZDL_G>Z:QeXt2.[+;I|hx )bpaLZrfnyq!?Yk,2.4S\r# %*OT|Z+W =/AOEtTbo€:Q_i0/^HLʼ`cPd8Tv]MO*/; 3l \  :6=BGR]Z0oj=>v R2. ?>TL6-n AGvqcl{BUSiW'Ⱥazghvuo|$-F,>Z!9n}="xUknr|xfk|srhd^_?:TNvjZJ8Q=1A!=&#=$$.<(ȣ5'.i)+W Jcn#e r g j{ ~ j B0+ $ R $+QJ#K`a b`o78| aj4j~iS*@P^kD]k8y42Q&;eaYc^4TlrvplqkTT25>A"n%#!$""̙ڪK     xoz | +w?5ʴȦwx1:> uz8~&Bd^|ng{cTkfpxDs+W^jJ]rTQG#HMdv;>@MhhľMVW|DF泻kb2%93MP~           |(0 x ]=;7D4D:]dGI^He&-ȶ[fjw+"1`plq/F׻zp% .3+!#   tXf f^H D$F"Q| *xr p zr m i7.w ssNTzVYk"0".":Uib0?cqHDAJ=LXB0A : C!B]r~T[}|xfSPapvDE\zNN&,6$&  | bm$0FUnnϰŤĦͮywu]Xj2/\Z xze kG]~JRzM]NWKf 3BF\>Z`N,CC H f0 `irjnY\tzĔztvfp2*0#* ,$4.u%h*;rt~ʴz|~vhoZ<;`E4A?$@%-2(+0.8,&>&=86;:D8LQRboy~r4J' }t1-1 % "$j}ZF>G5 &+IX`n{flot{]pIP@FпrbctxENUet|lj`4*8,4(}w2+Ԭ|ydCD5 F q.&h'Rk( *  k ^ [X ~h dj!.V(J"_"h#D!D06A$(ilz~HDSqfuS0;Q\WsG\|qjrrwTr}jnzLJ[@8KS38)=BRCBuQZ˻|wqppw|:15f\[zP<;h@;d#wqعcdf"Zm"%$u' }%4m r    n^ `l"Zt&e d<2D.%F.(?23svnnz}Cdz{c&zh{nalopopuwtzvr{UKDZPK^@:T*.].@..*,"$r|6: p {|d   5:xZbQ T]R9 H"8ompEL^);?BPN_qG  !2;vdbcdgWRf67,+82:3y,.u**.'$   ءvs4F?i,' ]FVQVYP~{tjTKKv(   ~ ZXdLPV^nWrz8R` &@/'4#4!-$fi4(<'5#, 5l3/_@@q pm7>M@& Lutw "gk   ` d r %6A-0pOfZdq+&'($%mH.B& &@hPNsrRLJ=PDi$"w5#36:ͷt}h {(~h_h$$" &"ztڹ'   ~Jam} eip||{M{ lEahMUrv|nt|? @wcUcsЪbavxzzy~|`ahn_ddbZLL@]X< 47B~!(yv &$j}{y$h &rR_Q x \˸{ l J 64nZl`j}CDMx&)Darzps|{z{ȖFOTd\fRZ`DK6.~hvX6565"v}`vexp=<,0R?i*8%;"4r;CKB Wz  ~e  s ĮPvP\Xnu%4[KXA'2@wzFb_pfg_gntx\]W?D4($&"6-2240"NRT#:7LB#.VP):\0`,1tNN !J Wff|&  jl % yz9@]MPj x  c^E&;n݀ƴ}xrntpjiݳzl}mJ=%'2!442 0$gr^b|z2,155 j@:W,)21$ joOS&! dgh`yphn7 x Fih Ľ0PFξ}>9)6%?Fyÿ 2EH@{r{lj\؂wtldev^cC*1A(.0$444g0ּVO. 0'0%l.0X#!$( %z# $,6w  '  jc' x8 ~_ j# |EL7jHFB"&jvU[;i_fpl~rkXǬNNjb`HN03J+G-E:D+#8"< 12e8$c*(Q0(z&pkmv#&~79|FI}JZ6o^Xxrg`V g K>"$J2Et[jP2.]"xj l} - :>^''\ S,g^TYx¸zxt̝;Tջs^QԪBHD?H;^hDH+2AD9-=*|!xd޾hho2:P$K(.10,(,#.-,)'%*/4 KO E2*,)XZUļ=2.:42 $l*+b r h_27!&z trpqTNXkcl6HI@lPO}jr:0F0B,F;9<28GM8=}58|^CQ$ ~C,0*"+%0$2".*(* * **,.0)  %i,){ y mL%nh~N)1P||nr03 j J'>!7<@鿽jPXֲZ_δtdl><6-H?KEUZ03x40̳ĿrrX9&?*D.B-. )"*#.$$(.3 ((( ( ((%  "( ,8)(# '$X T TSWθpl=Fh14N V>Kt$ bRNV]hR!R$&.wy;$#V I(`),^h l&83OR~Q4A@nn&@}@|oLTѺjpdZZXfawpJ6WP@:<34+6.4.3-3.}$!u mgijlr1 )&#(z$(f f \ ZU'q zMG[br{|.5p"&q*,Z 9#'c`^4&&c"#$h qqp|~mo`dŜn`d\i_bZueUCRPz9=o}pVJ/A)@,:+dZɲưLFkhJ851df,&ϼ#$ lKRD HD=@$E+H2<-Ԓ{rt,$gcʺyx *!_9+w}xs`^vw("z j h83s56 !*)2<78VY-5BS_sҼrT]{{|rgj`pa\IfT^X]Xwqǯs\D0N?I4?'K1J27&94˭o+-|ĨJAn1#j""& %%BKT*2 .w'2 DB>ʹzDHp$~ ;LM  RQ]>>`VTv~X2?t?Nz9H?@/)/"*-4%. .f go$*&%"!~*~4w(0--6s(.Ct-: Z-5*߿t~汻ϲhmoe{chlndadNnO`@Z:T8M5G6D8E7G1K/F%>'e[jtCO>?C8:&>){]NТ094>5+,2206pw:*2- .({׹'*($'"% 0!|-+.,/ 15!q&$dLVRC/!-(xXZiUk*.&5DeJ@ wvjfxohgӫZVdVO9[@XG>F>G6?24.wPRТ>>MA28G_m2<0." !ɬ&$/(&$&{ ,/$|| "1!^u\`HJP"*+x21t\]v~ȾӾi]^Xjbvnvra`[\RNg^UCU?R\l7-2624<JY4$D&89&,%309&0.,.+4"0# z"Z.*e^d95't'$7"1,y0*tPLĴ4,on{4d]k@VPrTI\GhZoff]`Zvuhgƻ^Tb^`aMKQHQBO?K|51>K:Di]PFEBӒq}t0Ajr}u24@@5$7I,/60B)@A*6(=8@-<':*9+:+<*.(,/-%)4l'>ltREy*&7BW^'$,.(( 3'c7/yI,7j\m-"Pnp:eabfbwKCT_mphWhV^MjZoajbptntlp迿cVa^lkTN\RUKRJUJTLSLVHV@Q=D:|SUʹD?pl8Iմк\ZIG("@R7>BBD1<D/u6','2=(:+;,:,A0+=.<+6&0)_$-xZ?/T2+X0q.@oys| (*{{@9D>hmde01)'4,ց~|) 2=(9+<,:,B0~8#}:%W c?A( t5:rLJk"%@JYAIloz{v)+0%<.~'(wXb߾ɽW Pkg@cvpTQvjcH}_nRiOo\ΧrpcHma[^84YaTMRNPOTL`P>6PR̴ѻAHP\ΰNF@6О|{10)*<:DDr$:(<(:*<,;,l2!q<'R!ms9lG*)'װ\'X-<(/.'50BDNvy.8@TAr '@r:.ipnugbWVdc~zpqfc|NdQTSiX~_vPYDKTUMEUGWIVNSR¢[VDHCR=GI>F:C-A+E0r>:\DGdC:,(A+D"PN5'2NRXi%FA@P*(0f=JvO^,$*yhx V0(k_pg{_hhswhrcdx~Z^b^_Ypeh>lYФʦs_aW{ܹki?7cVj\]SRMPPVZUXvu{ttnB3J=VO@BED}IChE<6;5r>6kB88*<23+=59.=.@.A.j=,x0$>6E?4,PCd*b3"'$^6/*(. :2(0,hHHphkȫi-"vD>v|OhjKZ`]oO0?*NT-!04P`ôfUXqMNʼi|vgKCХlms{tyƍPU笯jfqhbPlTu]yWNԖYVbdcmMUŶ`RB6GBTTXYQN_\WSd]M>^LQ>P>REPCL>M>H9G;H?>6E:F:F:F:THC8A4O@F1A0'C880:2<2=4JD412300JFL~C@E-6<mmyzadt-*0%,&84q52R85Y!#jcNQbt5s~|bzwD")˦}W[|aa[O~cnKTOõYafk\`~|UNXL֔MLedbVZNTJOEVJN>I4J3N8J6K8K7YCZEH6L>D:F:F8F7D3H6H6B0B,<&8(2'<4=6>9>;==>>BDy55iPPV;5a]b԰$,00;6:3C<|JF?,$UXj~mzmpt@LPI@J $-:ejonĚȺ`qstpdemafH{Xpuy~ЯγRKJDXR_\teP=TBSDVJVJUEO;L4S>VBRH>J.@0B2A2A/@0JSLKD;7;6x41QL{vjKI_1.p-*KHB>r71ZGRJ>J?IB4/>CRRQLG><1=0}=5t\gGH92a_K7:"%1&:$4"p.+03.cqpkW.4L7=3E?|(#,^np-AF@uwˮiqvylhlj~~_Ythh`lUZEeTʟCP_mZh~`kssyDzT^CNTJTHTITLkgpoͰ{a^N>8VHBv7GdjBDB=H`HYN\[V\|S\{jn_lkw^JSO~Q`Ⱦ}~TVXRgWzj¾κͼƴrXe\*:HR?DKK<7EA5C9396@4:%S:G8tIB̲y[^:Lz-8WYhozrtFFv1+L$$%#@ýȿb\gjіpodUZ>A=N:qD:x?3L6I4G;F14#GAQRhED^GFT16t|L+3ǀ cennjlĿ͜E@p_rah^wpvri^lRmeϷ|nQN_Zf]_XWSdfɶ“NH=8QUZ`G6]Ns?2`Q:2>4MITOXRȝvRUqDE|FGsACll`li23fE:B@;IHvLDfOykܶq\\>\:bLѶ~^f}a`}PJMFWPJLY`twmcOJ?9JBQITPZ_nHRQb9JQPQEMALCdchd~]Zh@=n>IFNVLhX|m~ŹɿuvZIKsbervÿkS\Z3A{zzz}]`|ȟ˾ѷĦČdgbOPs\`^CHVNP_\a`<5388,.=>QQLMqJH|LRZiHVaaGG:=^`X=AnnfWNG"&@@Ȗ`gMLVS~y,'FDJ[TbzMPfbedD@vPN}~qzmv~hYim UPNP-Inspector - fix up the documentation - remove the py-debug-binary before releasing - uninstaller does not remove start-menu-shortcuts - start-menu-shortcut should not contain version in name plans for after the release - talk with the pidgin guys about outsourcing the gtk-installer-part as a NSIS-Plugin because I guess other will need it later as well - clean up installer-compile-warnings - optional gtk-less-installer - outsource coherence to its own installer UPnP-Inspector-0.2.2/win32/compile.py0000644000175000017500000000063511204600003015431 0ustar poxpox# use the files in this folder first! #import os, sys #sys.path.insert(0, os.path.abspath(".")) from bbfreeze import Freezer includes = ['coherence', 'setuptools', 'cairo', 'pango', 'gtk', 'pangocairo', 'atk', 'xml', 'coherence.base', #'netifaces' ] f = Freezer("build", includes=includes) f.addScript("upnp-inspector.py", gui_only=True) f.include_py = True f.use_compression = True f()UPnP-Inspector-0.2.2/win32/win32.nsi0000644000175000017500000002721511204600003015107 0ustar poxpox ; global defines !define INSPECTOR_VERSION "0.2.0" !define INSTALLER_VERSION "beta5" !define GTK_RUNTIME_INSTALLER "gtk2-runtime*.exe" !define GTK_MIN_VERSION "2.6.10" !define GTK_REG_KEY "SOFTWARE\GTK\2.0" !define GTK_DEFAULT_INSTALL_PATH "$COMMONFILES\GTK\2.0" ; global vars Var GTK_FOLDER Var StartMenuFolder Var name Var ISSILENT ; global configuration SetCompress force SetCompressor /SOLID lzma Name "UPnP Inspector ${INSPECTOR_VERSION}" OutFile "UPnPInspector-${INSPECTOR_VERSION}-${INSTALLER_VERSION}-setup.exe" InstallDir "$PROGRAMFILES\UPnPInspector" XPStyle on ; helpers and macros !include "MUI.nsh" !include "Sections.nsh" !include "WinVer.nsh" !include "LogicLib.nsh" !include "FileFunc.nsh" !insertmacro GetParameters !insertmacro GetOptions !insertmacro GetParent !include "WordFunc.nsh" !insertmacro VersionCompare !insertmacro WordFind !insertmacro un.WordFind !define MUI_Icon "inspector-icon.ico" ; ----------------------------------- ; THE MENU !define MUI_ABORTWARNING !define MUI_FINISHPAGE_NOAUTOCLOSE !define MUI_FINISHPAGE_RUN "$INSTDIR\upnp-inspector.exe" !define MUI_PAGE_CUSTOMFUNCTION_PRE preWelcomePage !insertmacro MUI_PAGE_WELCOME ; GTK+ install page !define MUI_PAGE_CUSTOMFUNCTION_PRE preGtkDirPage !define MUI_PAGE_CUSTOMFUNCTION_LEAVE postGtkDirPage ;!define MUI_DIRECTORYPAGE_VARIABLE $GTK_FOLDER !insertmacro MUI_PAGE_COMPONENTS !insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_STARTMENU Application $StartMenuFolder !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_PAGE_FINISH !insertmacro MUI_LANGUAGE "English" ;Language strings LangString DESC_SecDummy ${LANG_ENGLISH} "UPnP Inspector ${INSPECTOR_VERSION}" LangString GTK_SECTION_DESCRIPTION ${LANG_ENGLISH} "GTK+ Package. To be installed if you dont have pre installed GTK+" ;Assign language strings to sections !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN !insertmacro MUI_DESCRIPTION_TEXT ${SecDummy} $(DESC_SecDummy) !insertmacro MUI_DESCRIPTION_TEXT ${SecGtk} $(GTK_SECTION_DESCRIPTION) !insertmacro MUI_FUNCTION_DESCRIPTION_END Section "Inspector" SecDummy SetOutPath "$INSTDIR" File /r "build\*.*" WriteUninstaller "$INSTDIR\uninstall.exe" !insertmacro MUI_STARTMENU_WRITE_BEGIN Application ;Create shortcuts CreateDirectory "$SMPROGRAMS\$StartMenuFolder" CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Uninstall.lnk" "$INSTDIR\Uninstall.exe" CreateShortCut "$SMPROGRAMS\$StartMenuFolder\UPnP Inspector.lnk" "$INSTDIR\upnp-inspector.exe" !insertmacro MUI_STARTMENU_WRITE_END SectionEnd ;-------------------------------- ;GTK+ Runtime Install Section Section "Gtk Installer" SecGtk Call CheckUserInstallRights Pop $R1 SetOutPath $TEMP SetOverwrite on File /oname=gtk-runtime.exe ${GTK_RUNTIME_INSTALLER} SetOverwrite off Call DoWeNeedGtk Pop $R0 Pop $R6 StrCmp $R0 "0" have_gtk StrCmp $R0 "1" upgrade_gtk StrCmp $R0 "2" upgrade_gtk ;StrCmp $R0 "3" no_gtk no_gtk ;no_gtk: StrCmp $R1 "NONE" gtk_no_install_rights ClearErrors ExecWait '"$TEMP\gtk-runtime.exe" /L=$LANGUAGE $ISSILENT /D=$GTK_FOLDER' IfErrors gtk_install_error done upgrade_gtk: StrCpy $GTK_FOLDER $R6 StrCmp $R0 "2" +2 ; Upgrade isn't optional MessageBox MB_YESNO $(GTK_UPGRADE_PROMPT) /SD IDYES IDNO done ClearErrors ExecWait '"$TEMP\gtk-runtime.exe" /L=$LANGUAGE $ISSILENT /D=$GTK_FOLDER' IfErrors gtk_install_error done gtk_install_error: Delete "$TEMP\gtk-runtime.exe" MessageBox MB_OK $(GTK_INSTALL_ERROR) /SD IDOK Quit have_gtk: StrCpy $GTK_FOLDER $R6 StrCmp $R1 "NONE" done ; If we have no rights, we can't re-install ; Even if we have a sufficient version of GTK+, we give user choice to re-install. ClearErrors ExecWait '"$TEMP\gtk-runtime.exe" /L=$LANGUAGE $ISSILENT' IfErrors gtk_install_error Goto done ;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; end got_install rights gtk_no_install_rights: ; Install GTK+ to the inspector directory StrCpy $GTK_FOLDER $INSTDIR ClearErrors ExecWait '"$TEMP\gtk-runtime.exe" /L=$LANGUAGE $ISSILENT /D=$GTK_FOLDER' IfErrors gtk_install_error SetOverwrite on ClearErrors CopyFiles /FILESONLY "$GTK_FOLDER\bin\*.dll" $GTK_FOLDER SetOverwrite off IfErrors gtk_install_error Delete "$GTK_FOLDER\bin\*.dll" Goto done ;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; end gtk_no_install_rights done: Delete "$TEMP\gtk-runtime.exe" SectionEnd ; end of GTK+ section Section "Uninstall" Delete "$INSTDIR\*.*" RMDir /r "$INSTDIR" !insertmacro MUI_STARTMENU_GETFOLDER Application $StartMenuFolder Delete "$SMPROGRAMS\$StartMenuFolder\Uninstall.lnk" Delete "$SMPROGRAMS\$StartMenuFolder\UPnP Inspector.lnk" RMDir "$SMPROGRAMS\$StartMenuFolder" SectionEnd ; ---------------- HELPERS ------------ ; Borrowed from the great pidgin project ; ; Usage: ; Call DoWeNeedGtk ; First Pop: ; 0 - We have the correct version ; Second Pop: Key where Version was found ; 1 - We have an old version that should work, prompt user for optional upgrade ; Second Pop: HKLM or HKCU depending on where GTK was found. ; 2 - We have an old version that needs to be upgraded ; Second Pop: HKLM or HKCU depending on where GTK was found. ; 3 - We don't have Gtk+ at all ; Second Pop: "NONE, HKLM or HKCU" depending on our rights.. ; Function DoWeNeedGtk ; Logic should be: ; - Check what user rights we have (HKLM or HKCU) ; - If HKLM rights.. ; - Only check HKLM key for GTK+ ; - If installed to HKLM, check it and return. ; - If HKCU rights.. ; - First check HKCU key for GTK+ ; - if good or bad exists stop and ret. ; - If no hkcu gtk+ install, check HKLM ; - If HKLM ver exists but old, return as if no ver exits. ; - If no rights ; - Check HKLM Push $0 Push $1 Push $2 Push $3 Call CheckUserInstallRights Pop $1 StrCmp $1 "HKLM" check_hklm StrCmp $1 "HKCU" check_hkcu check_hklm check_hkcu: ReadRegStr $0 HKCU ${GTK_REG_KEY} "Version" StrCpy $2 "HKCU" StrCmp $0 "" check_hklm have_gtk check_hklm: ReadRegStr $0 HKLM ${GTK_REG_KEY} "Version" StrCpy $2 "HKLM" StrCmp $0 "" no_gtk have_gtk have_gtk: ; GTK+ is already installed; check version. ; Change this to not even run the GTK installer if this version is already installed. ${VersionCompare} ${GTK_INSTALL_VERSION} $0 $3 IntCmp $3 1 +1 good_version good_version ${VersionCompare} ${GTK_MIN_VERSION} $0 $3 ; Bad version. If hklm ver and we have hkcu or no rights.. return no gtk StrCmp $1 "NONE" no_gtk ; if no rights.. can't upgrade StrCmp $1 "HKCU" 0 +2 ; if HKLM can upgrade.. StrCmp $2 "HKLM" no_gtk ; have hkcu rights.. if found hklm ver can't upgrade.. Push $2 IntCmp $3 1 +3 Push "1" ; Optional Upgrade Goto done Push "2" ; Mandatory Upgrade Goto done good_version: StrCmp $2 "HKLM" have_hklm_gtk have_hkcu_gtk have_hkcu_gtk: ; Have HKCU version ReadRegStr $0 HKCU ${GTK_REG_KEY} "Path" Goto good_version_cont have_hklm_gtk: ReadRegStr $0 HKLM ${GTK_REG_KEY} "Path" Goto good_version_cont good_version_cont: Push $0 ; The path to existing GTK+ Push "0" Goto done no_gtk: Push $1 ; our rights Push "3" Goto done done: ; The top two items on the stack are what we want to return Exch 4 Pop $1 Exch 4 Pop $0 Pop $3 Pop $2 FunctionEnd Function preWelcomePage Push $R0 Push $R1 Push $R2 Call DoWeNeedGtk Pop $R0 Pop $R2 IntCmp $R0 1 gtk_selection_done gtk_not_mandatory ; Make the GTK+ Section RO if it is required. !insertmacro SetSectionFlag ${SecGtk} ${SF_RO} Goto gtk_selection_done gtk_not_mandatory: ; Don't select the GTK+ section if we already have this version or newer installed !insertmacro UnselectSection ${SecGtk} gtk_selection_done: ; If on Win95/98/ME warn them that the GTK+ version wont work ${Unless} ${IsNT} !insertmacro UnselectSection ${SecGtk} !insertmacro SetSectionFlag ${SecGtk} ${SF_RO} MessageBox MB_OK $(GTK_WINDOWS_INCOMPATIBLE) /SD IDOK IntCmp $R0 1 done done ; Upgrade isn't optional - abort if we don't have a suitable version Quit ${EndIf} done: Pop $R2 Pop $R1 Pop $R0 FunctionEnd Function preGtkDirPage Push $R0 Push $R1 Call DoWeNeedGtk Pop $R0 Pop $R1 IntCmp $R0 2 +2 +2 no_gtk StrCmp $R0 "3" no_gtk no_gtk ; Don't show dir selector.. Upgrades are done to existing path.. Pop $R1 Pop $R0 Abort no_gtk: StrCmp $R1 "NONE" 0 no_gtk_cont ; Got no install rights.. Pop $R1 Pop $R0 Abort no_gtk_cont: ; Suggest path.. StrCmp $R1 "HKCU" 0 hklm1 ${GetParent} $SMPROGRAMS $R0 ${GetParent} $R0 $R0 StrCpy $R0 "$R0\GTK\2.0" Goto got_path hklm1: StrCpy $R0 "${GTK_DEFAULT_INSTALL_PATH}" got_path: StrCpy $name "GTK+ ${GTK_INSTALL_VERSION}" StrCpy $GTK_FOLDER $R0 Pop $R1 Pop $R0 FunctionEnd Function postGtkDirPage Push $R0 Push $GTK_FOLDER Call VerifyDir Pop $R0 StrCmp $R0 "0" 0 done MessageBox MB_OK $(GTK_BAD_INSTALL_PATH) /SD IDOK Pop $R0 Abort done: Pop $R0 FunctionEnd !macro CheckUserInstallRightsMacro UN Function ${UN}CheckUserInstallRights Push $0 Push $1 ClearErrors UserInfo::GetName IfErrors Win9x Pop $0 UserInfo::GetAccountType Pop $1 StrCmp $1 "Admin" 0 +3 StrCpy $1 "HKLM" Goto done StrCmp $1 "Power" 0 +3 StrCpy $1 "HKLM" Goto done StrCmp $1 "User" 0 +3 StrCpy $1 "HKCU" Goto done StrCmp $1 "Guest" 0 +3 StrCpy $1 "NONE" Goto done ; Unknown error StrCpy $1 "NONE" Goto done Win9x: StrCpy $1 "HKLM" done: Exch $1 Exch Pop $0 FunctionEnd !macroend !insertmacro CheckUserInstallRightsMacro "" !insertmacro CheckUserInstallRightsMacro "un." ; ; Usage: ; Push $0 ; Path string ; Call VerifyDir ; Pop $0 ; 0 - Bad path 1 - Good path ; Function VerifyDir Exch $0 Push $1 Push $2 Loop: IfFileExists $0 dir_exists StrCpy $1 $0 ; save last ${GetParent} $0 $0 StrLen $2 $0 ; IfFileExists "C:" on xp returns true and on win2k returns false ; So we're done in such a case.. IntCmp $2 2 loop_done ; GetParent of "C:" returns "" IntCmp $2 0 loop_done Goto Loop loop_done: StrCpy $1 "$0\GaImFooB" ; Check if we can create dir on this drive.. ClearErrors CreateDirectory $1 IfErrors DirBad DirGood dir_exists: ClearErrors FileOpen $1 "$0\pidginfoo.bar" w IfErrors PathBad PathGood DirGood: RMDir $1 Goto PathGood1 DirBad: RMDir $1 Goto PathBad1 PathBad: FileClose $1 Delete "$0\pidginfoo.bar" PathBad1: StrCpy $0 "0" Push $0 Goto done PathGood: FileClose $1 Delete "$0\pidginfoo.bar" PathGood1: StrCpy $0 "1" Push $0 done: Exch 3 ; The top of the stack contains the output variable Pop $0 Pop $2 Pop $1 FunctionEnd Function .onVerifyInstDir Push $0 Push $INSTDIR Call VerifyDir Pop $0 StrCmp $0 "0" 0 dir_good Pop $0 Abort dir_good: Pop $0 FunctionEnd UPnP-Inspector-0.2.2/win32/upnp-inspector.py0000644000175000017500000000272011204600003016764 0ustar poxpox#! /usr/bin/env python # -*- coding: utf-8 -*- # # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2009 - Frank Scholz """ Inspector is a UPnP device inspector, or device spy Based on the Coherence UPnP/DLNA framework http://coherence.beebits.net """ import os, sys from twisted.internet import gtk2reactor gtk2reactor.install() from twisted.internet import reactor from twisted.python import usage from upnp_inspector import __version__ from coherence import __version__ as coherence_version from upnp_inspector.base import Inspector class Options(usage.Options): optFlags = [ ['version','v', 'print out version'] ] optParameters = [['logfile', 'l', None, 'logfile'], ] def __init__(self): usage.Options.__init__(self) self['options'] = {} def opt_version(self): print "UPnP-Inspector version:", __version__ print "using Coherence:", coherence_version sys.exit(0) def opt_help(self): sys.argv.remove('--help') print self.__str__() sys.exit(0) if __name__ == '__main__': options = Options() try: options.parseOptions() except usage.UsageError, errortext: print '%s: %s' % (sys.argv[0], errortext) print '%s: Try --help for usage details.' % (sys.argv[0]) sys.exit(0) i = Inspector(logfile=options['logfile']) reactor.run() UPnP-Inspector-0.2.2/MANIFEST.in0000644000175000017500000000011611204600003014215 0ustar poxpoxrecursive-exclude debian * recursive-include misc * recursive-include win32 *