#!/usr/bin/env python # -*- coding: utf-8 -*- ############################################################################### ## ## Copyright (C) 2007 manatlan manatlan[at]gmail(dot)com ## ## OFFICIAL WEBSITE/DOC : http://manatlan.infogami.com/popdown ## ## 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; version 2 only. ## ## 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. ## ############################################################################### import pygtk pygtk.require('2.0') import gtk import sys import os import re import stat import shlex import cPickle import locale from subprocess import Popen,call,PIPE ICONSIZE=22 def existingCommand(l): """ return the 1st existing command from the list """ for c in l: if call(["which",c],stdout=PIPE)==0: return c print "Miss ",l # should could edit multiple files at once EDITOR = existingCommand(["geany","gedit","scite","kate"]) # should understand switch "-e" for execute TERMINAL = existingCommand(["gnome-terminal","xfce4-terminal","konsole"]) OPENER = existingCommand(["gnome-open","xdg-open"]) def walktree (top = ".", depthfirst = True): names = os.listdir(top) if not depthfirst: yield top, names for name in names: try: st = os.lstat(os.path.join(top, name)) except os.error: continue if stat.S_ISDIR(st.st_mode): for (newtop, children) in walktree (os.path.join(top, name), depthfirst): yield newtop, children if depthfirst: yield top, names def listLaunchers(rep): l=[] for (basepath, children) in walktree(rep,True): for child in children: if child.lower().endswith(".desktop"): l.append( os.path.join(basepath, child) ) return l def pixbuf_from_file(file,size): pb = gtk.gdk.pixbuf_new_from_file(file) (wx,wy) = pb.get_width(),pb.get_height() rx=1.0*wx/size ry=1.0*wy/size if rx>ry: rr=rx else: rr=ry return pb.scale_simple(int(wx/rr), int(wy/rr) ,3) # 3= best quality (gtk.gdk.INTERP_HYPER) def imagefile_from_icon(icon): """ retourne la filename de l'icon si elle existe, sinon None""" file=icon if not os.path.isfile(file): file = os.path.join("/usr/share/icons/",icon) if not os.path.isfile(file): file = os.path.join("/usr/share/pixmaps/",icon) if not os.path.isfile(file): file = None return file def info_from_desktop(file): dict={} for ligne in open(file,"r"): ligne=ligne.strip() if "=" in ligne: p=ligne.index("=") dict[ligne[:p].strip().lower()] = ligne[p+1:].strip() return dict def image_from_buf(buf): if buf: if type(buf)==tuple: # C'est un pixbuf serialize, on le converti en image -> img data, has_alpha, bits_per_sample, width, height, rowstride = buf pixbuf=gtk.gdk.pixbuf_new_from_data(data,gtk.gdk.COLORSPACE_RGB,has_alpha, bits_per_sample, width, height, rowstride) img = gtk.Image() img.set_from_pixbuf(pixbuf) else: # certainement un icon system img = gtk.image_new_from_icon_name(buf,ICONSIZE) return img class gtkMenu(gtk.ImageMenuItem): def __init__(self,name,type='folder'): gtk.ImageMenuItem.__init__(self,name) self.set_image(gtk.image_new_from_icon_name(type,ICONSIZE)) self.__m = gtk.Menu() self.set_submenu(self.__m) def append(self,m): self.__m.append(m) def getMenu(self): return self.__m def createMenu(name): """ return a menu object should implement .append(x), where x can be a menu or an item ) """ name = re.subn("^\d+. *","",name)[0] # folder can be order like this "0010. folder", so "0010. " is removed before displaying return gtkMenu( name ) def createItem(tuple): """ return a item object """ desktop,order,name,exe,buf = tuple img = image_from_buf(buf) item = gtk.ImageMenuItem(name) item.set_image(img) #save data in item ;-) item.exe = exe item.desktop = desktop return item def createMenuList(liste,callbackCreateMenu,callbackCreateItem): """ returm a list of Item/SubMenu in function of list of tuple 'liste' (path should be in index 0 of a tuple) """ # create list of unique folders -> folders folders=[] for elt in liste: folder=os.path.dirname(elt[0]) if folder and not folder in folders: folders.append(folder) # build a dict of all menu created (from folders)-> menu # (Create SubMenus) menu={} for path in folders: spath,nom = os.path.split(path) menu[path]=callbackCreateMenu(nom) menu[""]=[] # init root menu as a simple list # SORT FOLDER/MENU paths = menu.keys() paths.sort(lambda a,b : cmp(a.lower(),b.lower())) # link menus together (from menu) for path in paths: if path: spath,nom = os.path.split(path) menu[spath].append(menu[path]) # link items on menu (from liste and menu) # (Create Items) for elt in liste: file=elt[0] spath,nom = os.path.split(file) menu[spath].append( callbackCreateItem(elt) ) return menu[""] # return the simple list def createItemList(liste,callbackCreateItem): """ returm a list of Item in function of list of tuple 'liste' """ return [callbackCreateItem(elt) for elt in liste] class MyMenu(gtk.Menu): def __init__(self,rep,l,args): gtk.Menu.__init__(self) self.__args = args self.connect("deactivate",self.seferme) menu = createMenuList(l,createMenu,createItem) #~ menu = createItemList(l,createItem) # populate menu ... for item in menu: self.append(item) def initItems(obj): # /!\ recursif /!\ dfs=[] for entry in obj: if type(entry)==gtkMenu: items=initItems( entry.getMenu() ) # append a separator to this menu entry.append( gtk.SeparatorMenuItem() ) # append a "edit this files' to this menu ed = gtk.ImageMenuItem("Edit desktop files") ed.connect("activate",self.click,EDITOR+" "+" ".join(['"%s"'% i for i in items])) entry.append( ed ) elif type(entry)==gtk.ImageMenuItem: # connect the click event on this entry entry.connect("activate",self.click,entry.exe) # connect click dfs.append( os.path.join(rep,entry.desktop)) return dfs dfs=initItems(self) item=gtk.SeparatorMenuItem() self.append( item ) mitem = gtkMenu("popdown","gnome-settings") item = gtk.ImageMenuItem("Edit desktop files") item.connect("activate",self.click,EDITOR+" "+" ".join(['"%s"'% i for i in dfs])) mitem.append( item ) item = gtk.ImageMenuItem("Open folder") item.connect("activate",self.click,"""%s "%s" """%(OPENER,rep)) mitem.append( item ) item = gtk.ImageMenuItem("Refresh") item.connect("activate",self.refresh,l) mitem.append( item ) self.append( mitem ) self.show_all() def click(self,widget,file): #TODO: better handling args here \ sarg ="" for i in self.__args: sarg+=" " + i.replace(" ","\\ ") file = re.subn("\%\w",sarg,file)[0].strip() #TODO: better handling args here / #~ open("/home/manatlan/Launchers/log.txt","a+").write(file+"\n") print "Launch :",file Popen(shlex.split(file)) gtk.main_quit() def refresh(self,widget,launchers): launchers.refresh() gtk.main_quit() def seferme(self,*a): gtk.main_quit() def getLocaleValue(dict,key): """ >>> getLocaleValue({"name":12},"name") 12 >>> getLocaleValue({"name[fr]":12,"name":13},"name") 12 >>> getLocaleValue({"name[fr_fr]":12},"name") 12 """ try: lang=locale.getdefaultlocale()[0].lower() return dict.get("%s[%s]"%(key,lang),dict.get("%s[%s]"%(key,lang[:2]),dict.get(key,None))) except: return dict.get(key,None) def getInfoFromDesktopFile(rep,i): desktop = i[len(rep)+1:] info = info_from_desktop(i) type = info["type"].lower() if type=="link": exe = """%s "%s" """%(OPENER,info["url"]) elif type=="application": exe = info.get("exec",None) if exe: if info.get("terminal","false").lower() in ("true","yes"): exe = """%s -e "%s" """%(TERMINAL,exe) order = int(info.get("order",5000)) name = getLocaleValue(info,"name") icon = getLocaleValue(info,"icon") if icon: file = imagefile_from_icon(icon) if file: # c'est un fichier on le charge, le resize et le serialize -> buf pb = pixbuf_from_file(file,ICONSIZE) buf = (pb.get_pixels(),pb.get_has_alpha(),pb.get_bits_per_sample(),pb.get_width(),pb.get_height(),pb.get_rowstride()) else: # ce n'est pas un fichier, c certainement un icon system buf = icon else: buf=None if exe: return (desktop,order,name,exe, buf ) else: return None class Launchers(object): def __init__(self,rep): assert os.path.isdir(rep) self.__file = os.path.join(rep,"popdown.cache") if os.path.isfile(self.__file): self.__list = cPickle.load(open(self.__file, 'rb')) else: self.refresh() def refresh(self): rep=os.path.dirname(self.__file) l=listLaunchers(rep) self.__list=[] for i in l: tuple=getInfoFromDesktopFile(rep,i) if tuple: self.__list.append( tuple ) else: print "WARNING :",i,"is not launchable !" self.__list.sort(lambda a,b:cmp( "%08d %s"%(a[1],a[2].lower()), "%08d %s"%(b[1],b[2].lower()), )) if len(self.__list)>0: try: cPickle.dump(self.__list, open(self.__file, 'wb')) except: print "WARNING : Can't save cache here",self.__file def __iter__(self): for i in self.__list: yield i def __len__(self): return len(self.__list) USAGE = """ USAGE: %s [file [file] ...] Popdown V0.90 by manatlan, copyright 2007 under GPL2 Licence. Display a speedy gtk popupmenu of all ".Desktop-files" in a folder, and let you choose what do you want to launch. : folder containing desktop-files [file..] : optionnal files passed as arguments to a launcher """ % os.path.basename(sys.argv[0]) def main(): if not( EDITOR and TERMINAL and OPENER ): print USAGE print "ERROR : don't find any commands, please edit",sys.argv[0] else: if len(sys.argv)<2: print USAGE print "ERROR : invalid arguments" return -1 else: rep=sys.argv[1] if os.path.basename(rep)=="popdown.cache": rep = os.path.dirname(rep) if not os.path.isdir(rep): print USAGE print "ERROR : The folder '%s' doesn't exists !!!" % sys.argv[1] return -1 else: l=Launchers(rep) if len(l)==0: print USAGE print "ERROR : No .Desktop-files in this folder : ",rep return -1 else: m=MyMenu(rep,l,sys.argv[2:]) m.popup(None,None,None,1,0) gtk.main() return 0 if __name__ == "__main__": #~ import doctest #~ doctest.testmod() #~ sys.argv=["x","/home/manatlan/Launchers"] sys.exit(main())