diff options
author | B Stack <bgstack15@gmail.com> | 2020-09-25 07:48:00 -0400 |
---|---|---|
committer | B Stack <bgstack15@gmail.com> | 2020-09-25 07:48:00 -0400 |
commit | e2207076cc3b312a8c5f75655f8b46025de17732 (patch) | |
tree | 19976489c9336fb9717383357c3dd1af078fe5a3 | |
parent | initial commit (diff) | |
download | myautomount-e2207076cc3b312a8c5f75655f8b46025de17732.tar.gz myautomount-e2207076cc3b312a8c5f75655f8b46025de17732.tar.bz2 myautomount-e2207076cc3b312a8c5f75655f8b46025de17732.zip |
WIP: add many features to trayicon
-rw-r--r-- | .gitignore | 1 | ||||
-rwxr-xr-x | automount-trayicon.py | 255 |
2 files changed, 256 insertions, 0 deletions
@@ -1 +1,2 @@ .*.swp +/old diff --git a/automount-trayicon.py b/automount-trayicon.py new file mode 100755 index 0000000..7e11879 --- /dev/null +++ b/automount-trayicon.py @@ -0,0 +1,255 @@ +#!/usr/bin/python3 +# startdate: 2020-09-24 13:17 +# References: +# vm2:~/dev/logout-manager/src/usr/bin/logout-manager-trayicon +# gapan/xdgmenumaker +# vim: ts=3 sw=3 sts=3 +# IMPROVE: +# use inotify for when to call reestablish_menu? +# move out config to separate file (read main .conf, but in shell format) +# add separator and "hide this icon" menu item like logout-manager-trayicon +# fix tabbing +# remove /sdb? (as option) +# hook up the click action to actually executing "xdg-open /browse/sdb" + +import gi, os, fnmatch, sys +gi.require_version("Gtk","3.0") +from gi.repository import Gtk +from gi.repository import Gdk +import xdg.DesktopEntry as dentry +import xdg.Exceptions as exc + +# FUNCTIONS + +class App: + ''' + A class to keep individual app details in. + ''' + + def __init__(self, name, icon, command, path): + self.name = name + self.icon = icon + self.command = command + self.path = path + + def __repr__(self): + return repr((self.name, self.icon, self.command, + self.path)) + +class MenuEntry: + ''' + A class for each menu entry. Includes the class category and app details + from the App class. + ''' + + def __init__(self, category, app): + self.category = category + self.app = app + + def __repr__(self): + return repr((self.category, self.app.name, self.app.icon, + self.app.command, self.app.path)) + +def remove_command_keys(command, desktopfile, icon): + # replace the %i (icon key) if it's there. This is what freedesktop has to + # say about it: "The Icon key of the desktop entry expanded as two + # arguments, first --icon and then the value of the Icon key. Should not + # expand to any arguments if the Icon key is empty or missing." + if icon: + command = command.replace('"%i"', '--icon {}'.format(icon)) + command = command.replace("'%i'", '--icon {}'.format(icon)) + command = command.replace('%i', '--icon {}'.format(icon)) + # some KDE apps have this "-caption %c" in a few variations. %c is "The + # translated name of the application as listed in the appropriate Name key + # in the desktop entry" according to freedesktop. All apps launch without a + # problem without it as far as I can tell, so it's better to remove it than + # have to deal with extra sets of nested quotes which behave differently in + # each WM. This is not 100% failure-proof. There might be other variations + # of this out there, but we can't account for every single one. If someone + # finds one another one, I can always add it later. + command = command.replace('-caption "%c"', '') + command = command.replace("-caption '%c'", '') + command = command.replace('-caption %c', '') + # replace the %k key. This is what freedesktop says about it: "The + # location of the desktop file as either a URI (if for example gotten from + # the vfolder system) or a local filename or empty if no location is + # known." + command = command.replace('"%k"', desktopfile) + command = command.replace("'%k'", desktopfile) + command = command.replace('%k', desktopfile) + # removing any remaining keys from the command. That can potentially remove + # any other trailing options after the keys, + command = command.partition('%')[0] + return command + +def icon_strip(icon): + # strip the directory and extension from the icon name + icon = os.path.basename(icon) + main, ext = os.path.splitext(icon) + ext = ext.lower() + if ext == '.png' or ext == '.svg' or ext == '.svgz' or ext == '.xpm': + return main + return icon + +def get_entry_info(desktopfile, ico_paths=True): + # customized from gapan/xdgmenumaker + de = dentry.DesktopEntry(filename=desktopfile) + + name = de.getName().encode('utf-8') + + if True: + icon = de.getIcon() + # full resolution of path is not required in this GTK program. + #if ico_paths: + # icon = icon_full_path(icon) + #else: + # icon = icon_strip(icon) + else: + icon = None + + command = de.getExec() + command = remove_command_keys(command, desktopfile, icon) + + terminal = de.getTerminal() + if terminal: + command = '{term} -e {cmd}'.format(term=terminal_app, cmd=command) + + path = de.getPath() + if not path: + path = None + + categories = de.getCategories() + category = "Removable" # hardcoded xdg "category" for automount-trayicon + + app = App(name, icon, command, path) + mentry = MenuEntry(category, app) + return mentry + +def desktopfilelist(params): + # Ripped entirely from gapan/xdgmenumaker + dirs = [] + for i in params: + print("Checking dir",i) + i = i.rstrip('/') + if i not in dirs: + dirs.append(i) + filelist = [] + df_temp = [] + for d in dirs: + xdgdir = '{}/'.format(d) # xdg spec is to use "{}/applications" as this string, so use an applications subdir. + if os.path.isdir(xdgdir): + for root, dirnames, filenames in os.walk(xdgdir): + for i in fnmatch.filter(filenames, '*.desktop'): + # for duplicate .desktop files that exist in more + # than one locations, only keep the first occurence. + # That one should have precedence anyway (e.g. + # ~/.local/share/applications has precedence over + # /usr/share/applications + if i not in df_temp: + df_temp.append(i) + filelist.append(os.path.join(root, i)) + return filelist + +class MainIcon(Gtk.StatusIcon): + def __init__(self): + Gtk.StatusIcon.__init__(self) + self.set_from_icon_name("media-removable") + self.traymenu = Gtk.Menu() + self.connect("button-press-event", self.on_button_press_event) + self.connect("popup-menu", self.context_menu) + self.reestablish_menu() + + def reestablish_menu(self,widget = None): + try: + if self.menuitems: + del self.menuitems + except: + pass + try: + for i in self.traymenu.get_children(): + self.traymenu.remove(i) + except: + pass + self.menuitems = [] + for entry in get_desktop_entries(["/media"]): + print('Label {} icon {}'.format(entry.app.name,entry.app.icon)) + self.add_action_to_menu(str(entry.app.name.decode("utf-8")),entry.app.path,entry.app.icon,self.dummy_action,entry.app.command) + #self.add_action_to_menu("foobar"+str(myrand),"media-floppy",self.dummy_action,"foo bar baz action "+str(myrand)) + #self.add_action_to_menu("mmc","media-flash-sd-mmc",self.dummy_action,"flubber second action") + #self.add_action_to_menu("what is this?","media-tape",self.dummy_action,"call ALF!") + self.add_separator_to_menu() + self.add_action_to_menu("Update menu","",None,self.reestablish_menu,"re establish") + self.add_action_to_menu("Hide this icon","","system-logout",self.exit,"exit") + self.menuitem_count = len(self.menuitems) - 1 + self.set_tooltip_text(str(self.menuitem_count)+" mount point"+("s" if self.menuitem_count > 1 else "")) + + def UNUSED_transform_command(self, entry_command): + return entry_command.replace("xdg-open ","umount ") + + def dummy_action(self, widget): + print("hello world") + x=0 + y=-1 + for n in widget.get_parent().get_children(): + x+=1 + if widget == n: + y=x-1 + break + print(y) + print(self.menuitems[y]) + def add_separator_to_menu(self): + i=Gtk.SeparatorMenuItem.new() + i.set_visible(True) + self.traymenu.append(i) + def add_action_to_menu(self,label_str,label_paren_str,icon_str,function_func,action_str): + self.menuitems.append(action_str) + full_label_str=label_str + if label_paren_str is not None and label_paren_str != "": + full_label_str += " (" + label_paren_str + ")" + i = Gtk.ImageMenuItem.new_with_mnemonic(full_label_str) + j = Gtk.Image.new_from_icon_name(icon_str,32) + j.show() + i.set_image(j) + i.set_always_show_image(True) + i.show() + i.connect("activate", function_func) + self.traymenu.append(i) + def on_button_press_event(self, b_unknown, event: Gdk.EventButton): + # for some reason the single click functionality prevents the double click functionality + if Gdk.EventType._2BUTTON_PRESS == event.type: + print("Double click!") + else: + print("Single click") + self.traymenu.popup(None, None, + self.position_menu, + self, + event.button, + Gtk.get_current_event_time()) + def context_menu(self, widget, event_button, event_time): + self.traymenu.popup(None, None, + self.position_menu, + self, + event_button, + Gtk.get_current_event_time()) + def exit(self, widget): + quit() + +#print(desktopfilelist(["/media"])) +def get_desktop_entries(dir_array): + entries = [] + for desktopfile in desktopfilelist(dir_array): + try: + entry = get_entry_info(desktopfile, True) + if entry is not None: + #print(entry) + entries.append(entry) + except exc.ParsingError as Ex: + print(Ex) + pass + return entries + +#for entry in get_desktop_entries(["/media"]): +# print('label {} icon {}'.format(entry.app.name,entry.app.icon)) +# MAIN +icon = MainIcon() +Gtk.main() |