aboutsummaryrefslogtreecommitdiff
path: root/automount-trayicon.py
blob: 7e1187935151a72b3b59b154f2fcb788b551c4ce (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
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()
bgstack15