#!/usr/bin/env python3 import gi gi.require_version("Gtk", "3.0") from gi.repository import Gtk, Gio from gi.repository import GdkPixbuf import os import stat import sys import time builder = Gtk.Builder() builder.add_from_file(os.path.realpath(os.path.dirname(__file__))+"/ui.glade") HOME=os.environ.get('HOME') DEFAULT_SAVE_PATH=HOME+"/.local/share/applications/" listbox_recents = builder.get_object("listboxRecents") #recents manager recent_manager = Gtk.RecentManager() recents = recent_manager.get_items() recents_count = len(recents) act_recents_count = 0 desktop_recents_path = [] desktop_recents_name = [] #get_row_at_index def refreshRecents(): global act_recents_count global desktop_recents_name global desktop_recents_name global recents recents = recent_manager.get_items() j=0 while j<act_recents_count: listbox_recents.remove(listbox_recents.get_row_at_index(0)) j+=1 i=0 desktop_recents_path = [] desktop_recents_name = [] while i<recents_count and len(desktop_recents_path) < 5: if recents[i].get_uri()[-8:] == ".desktop" and recents[i].get_uri()[:7]=="file://" and os.path.isfile(recents[i].get_uri()[7:]): desktop_recents_path.append(recents[i].get_uri()[7:]) desktop_recents_name.append(recents[i].get_display_name()) i+=1 act_recents_count=len(desktop_recents_name) i=0 while i<act_recents_count: box= Gtk.Box(orientation=Gtk.Orientation.VERTICAL) labelName= Gtk.Label() labelPath= Gtk.Label() labelName.set_xalign(0) labelPath.set_xalign(0) labelPath.set_opacity(0.5) box.set_spacing(6) labelName.set_text(desktop_recents_name[i]) labelPath.set_text(desktop_recents_path[i]) box.pack_end(labelPath, True, True, 0) box.pack_end(labelName, True, True, 0) row=Gtk.ListBoxRow() row.add(box) listbox_recents.add(row) i+=1 refreshRecents() class App(Gtk.Application): def __init__(self): Gtk.Application.__init__(self, application_id="com.gabmus.mlauncher", flags=Gio.ApplicationFlags.FLAGS_NONE) builder.get_object("aboutdialog").connect("delete-event", lambda *_: builder.get_object("aboutdialog").hide() or True) self.connect("activate", self.activateCb) def do_startup(self): # start the application Gtk.Application.do_startup(self) def activateCb(self, app): window = builder.get_object("window1") window.set_wmclass("mLauncher", "mLauncher") app.add_window(window) appMenu=Gio.Menu() appMenu.append("About", "app.about") appMenu.append("Quit", "app.quit") about_action = Gio.SimpleAction.new("about", None) about_action.connect("activate", self.on_about_activate) app.add_action(about_action) quit_action = Gio.SimpleAction.new("quit", None) quit_action.connect("activate", self.on_quit_activate) app.add_action(quit_action) app.set_app_menu(appMenu) window.show_all() def on_about_activate(self, *agrs): builder.get_object("aboutdialog").show() def on_quit_activate(self, *args): self.quit() def wait(time_lapse): time_start = time.time() time_end = (time_start + time_lapse) while time_end > time.time(): while Gtk.events_pending(): Gtk.main_iteration() class Handler: iconPath="icon" infobarButtonsActionCode=0 savePath=DEFAULT_SAVE_PATH+"default-mLauncher.desktop" untouchableLines= "\n" customSavePath= None def __init__(self): self.updateRadios() def showInfo(self, message, buttons, reHide=False, buttonsAction=None): buttonOk=builder.get_object("buttonInfobarOk") buttonCancel=builder.get_object("buttonInfobarCancel") infobar=builder.get_object("infobar") labelInfobar=builder.get_object("labelInfobar") if buttons: buttonOk.show() buttonCancel.show() self.infobarButtonsActionCode=buttonsAction else: buttonOk.hide() buttonCancel.hide() labelInfobar.set_text(message) infobar.show() if reHide: wait(5) infobar.hide() def updateRadios(self): radio_iconName = builder.get_object("radiobuttonIconName") entry_iconName = builder.get_object("entryIconName") filechooser_iconName = builder.get_object("filechooserbuttonIconPath") if radio_iconName.get_active(): entry_iconName.set_sensitive(True) filechooser_iconName.set_sensitive(False) builder.get_object("filechooserbuttonIconPath").unselect_all() else: entry_iconName.set_sensitive(False) filechooser_iconName.set_sensitive(True) builder.get_object("imageIcon").set_from_icon_name("gnome-panel-launcher", Gtk.IconSize.DIALOG) self.iconPath="icon" entry_iconName.set_text("") def on_entryIconName_insert_text(self, entry): iconName = builder.get_object("entryIconName").get_text().strip() builder.get_object("imageIcon").set_from_icon_name(iconName, Gtk.IconSize.DIALOG) self.iconPath=iconName def on_radiobuttonIcon_group_changed(self, button): self.updateRadios() def onDeleteWindow(self, *args): Gtk.main_quit(*args) def buttonSave_clicked_cb(self, button): name=builder.get_object("entryName").get_text().strip() executable=builder.get_object("entryExec").get_text().strip() path=builder.get_object("entryPath").get_text().strip() category=builder.get_object("comboboxtext-entry").get_text() terminal=builder.get_object("switchTerminal").get_state() keywords=builder.get_object("entryKeywords").get_text().strip() #check if the name contains nothing or only spaces if name and executable: if path and not os.path.isdir(path): self.showInfo("Your path is invalid! Clear or correct it!", False, True) else: #build string launcherString="[Desktop Entry]\n" launcherString+="Encoding=UTF-8\n" launcherString+="Type=Application\n" launcherString+="Categories="+category+"\n" launcherString+="Terminal=" if terminal: launcherString+="true\n" else: launcherString+="false\n" launcherString+="Exec="+executable+"\n" if path: launcherString+="Path="+path+"\n" launcherString+="Name="+name+"\n" launcherString+="Icon="+self.iconPath+"\n" launcherString+="Keywords="+keywords+"\n" launcherString+=self.untouchableLines if self.customSavePath: self.savePath=self.customSavePath else: self.savePath=DEFAULT_SAVE_PATH+name+".desktop" if not os.path.isfile(self.savePath): out_file = open(self.savePath,"w") out_file.write(launcherString) out_file.close() st= os.stat(self.savePath) os.chmod(self.savePath, st.st_mode | stat.S_IEXEC) recent_manager.add_item("file://"+self.savePath) print("\n\nDebug:\n"+launcherString) self.showInfo("File saved ("+self.savePath+")", False, True) else: #the infobar has to delete the file and recall this method self.showInfo("The file "+self.savePath+" already exists. Do you want to replace it?", True, False, 1) else: self.showInfo("Missing name and/or executable! Fix it!", False, True) def scale_image(self, filename): pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(filename, 48, 48) return pixbuf def on_filechooserbuttonIcon_file_set(self, button): imgObj=builder.get_object("imageIcon") self.iconPath=button.get_filename() pixbuf=self.scale_image(button.get_filename()) imgObj.set_from_pixbuf(pixbuf) def on_filechooserbuttonExecutable_file_set(self, button): entry_exec=builder.get_object("entryExec") execFileName=button.get_filename() processedFileName="" i=0 while i<len(execFileName): if execFileName[i] == " ": processedFileName+="\\ " elif execFileName[i] == "\\": processedFileName+="\\\\" else: processedFileName+=execFileName[i] i+=1 entry_exec.set_text(processedFileName) def on_filechooserbuttonPath_file_set(self, button): entry_path=builder.get_object("entryPath") pathFileName=button.get_filename() entry_path.set_text(pathFileName) def on_buttonInfobarClose_clicked(self, infobar, closebutton): infobar.hide() def on_buttonInfobarCancel_clicked(self, button): builder.get_object("infobar").hide() def on_buttonInfobarOk_clicked(self, button): if self.infobarButtonsActionCode == 1: os.remove(self.savePath) self.buttonSave_clicked_cb(button) else: print("Infobar action code was "+self.infobarButtonsActionCode+". Tell the programmer something's wrong!") def on_buttonOpen_clicked(self, button): if popover.get_visible(): popover.hide() else: refreshRecents() popover.show_all() def resetUI(self): builder.get_object("entryName").set_text("") builder.get_object("entryExec").set_text("") builder.get_object("entryPath").set_text("") builder.get_object("comboboxtext-entry").set_text("") builder.get_object("switchTerminal").set_state(False) def processFile(self, path): try: with open(path) as f: lines = f.readlines() self.customSavePath=path name=builder.get_object("entryName") executable=builder.get_object("entryExec") path=builder.get_object("entryPath") category=builder.get_object("comboboxtext-entry") terminal=builder.get_object("switchTerminal") keywords=builder.get_object("entryKeywords") self.resetUI() i=0 self.untouchableLines= "\n" skip = [False, False, False, False, False, False, False] while len(lines)>0: if lines[i].strip() == "[Desktop Entry]": lines.pop(i) elif lines[i].strip()[:9] == "Encoding=": lines.pop(i) elif lines[i].strip() == "Type=Application": lines.pop(i) elif lines[i].strip()[:9] == "Terminal=" and not skip[0]: if "true" in lines[i]: terminal.set_state(True) else: terminal.set_state(False) lines.pop(i) skip[0]=True elif lines[i].strip()[:5] == "Exec=" and not skip[1]: executable.set_text(lines[i].strip()[5:]) lines.pop(i) skip[1]=True elif lines[i].strip()[:5] == "Name=" and not skip[2]: name.set_text(lines[i].strip()[5:]) lines.pop(i) skip[2]=True elif lines[i].strip()[:5] == "Name[": lines.pop(i) elif lines[i].strip()[:5] == "Icon=" and not skip[3]: mIcon=lines[i].strip()[5:] print(os.path.isfile(mIcon)) if os.path.isfile(mIcon): builder.get_object("radiobuttonIconPath").set_active(True) builder.get_object("radiobuttonIconName").set_active(False) builder.get_object("filechooserbuttonIconPath").set_filename(mIcon) #self.updateRadios() else: builder.get_object("radiobuttonIconPath").set_active(False) builder.get_object("radiobuttonIconName").set_active(True) builder.get_object("entryIconName").set_text(mIcon) #self.updateRadios() self.iconPath=mIcon lines.pop(i) skip[3]=True elif lines[i].strip()[:5] == "Path=" and not skip[4]: path.set_text(lines[i].strip()[5:]) lines.pop(i) skip[4]=True elif lines[i].strip()[:11] == "Categories=" and not skip[5]: category.set_text(lines[i].strip()[11:]) lines.pop(i) skip[5]=True elif lines[i].strip()[:9] == "Keywords=" and not skip[6]: keywords.set_text(lines[i].strip()[9:]) lines.pop(i) skip[6]=True else: self.untouchableLines+=lines.pop(i) except: self.customSavePath= None print("ERR: exception thrown") self.showInfo("The file does not exist! Maybe you deleted it?", False, True) popover.hide() def on_listboxRecents_row_activated(self, listbox, row): selectedFilePath=row.get_children()[0].get_children()[1].get_text() self.processFile(selectedFilePath) def on_buttonOpenBrowse_clicked(self, button): builder.get_object("filechooserdialogOpenBrowse").set_current_folder_uri("file://"+HOME) builder.get_object("filechooserdialogOpenBrowse").show_all() popover.hide() def on_buttonDesktopChooserOk_clicked(self, button): self.on_filechooserdialogOpenBrowse_file_activated(builder.get_object("filechooserdialogOpenBrowse")) def on_filechooserdialogOpenBrowse_file_activated(self, dialog): if dialog.get_filename()[-8:] == ".desktop": self.processFile(dialog.get_filename()) else: self.showInfo("The file selected is not valid. Look for one with a .desktop extension.", False, True) dialog.hide() def on_buttonDesktopChooserCancel_clicked(self, button): builder.get_object("filechooserdialogOpenBrowse").hide() def on_buttonSaveAsChooserCancel_clicked(self, button): builder.get_object("filechooserdialogSaveAs").hide() def on_buttonDesktopChooserOpenDefaultFolder_clicked(self, button): builder.get_object("filechooserdialogOpenBrowse").set_current_folder_uri("file://"+HOME+"/.local/share/applications") def on_buttonSaveAsChooserOpenDefaultFolder_clicked(self, button): builder.get_object("filechooserdialogSaveAs").set_current_folder_uri("file://"+HOME+"/.local/share/applications") def on_buttonSaveAs_clicked(self, button): builder.get_object("filechooserdialogSaveAs").set_current_folder_uri("file://"+HOME) builder.get_object("filechooserdialogSaveAs").show_all() def on_buttonSaveAsChooserOk_clicked(self, button): path=builder.get_object("filechooserdialogSaveAs").get_filename() if path[-8:] != ".desktop": path+=".desktop" self.customSavePath=path builder.get_object("filechooserdialogSaveAs").hide() self.buttonSave_clicked_cb(button) popover = Gtk.Popover.new(builder.get_object("buttonOpen")) popover.add(builder.get_object("openerWidget")) builder.connect_signals(Handler()) if __name__ == "__main__": app= App() app.run(sys.argv) #Gtk.main()