├── README ├── curses_interface.py ├── dbus_connection.py └── mountie.py /README: -------------------------------------------------------------------------------- 1 | # Copyright 2012 Daniel Anthofer 2 | # Modification and distribution under terms of 3 | # GNU GPL v. 3 is permitted 4 | # http://www.gnu.org/licenses/gpl-3.0.txt 5 | 6 | quickly mount and unmount usbsticks, external harddrives etc. 7 | 8 | dependencies: python>=3, dbus-python>=1.0, udisks 9 | 10 | mountie shall be a small tool to extend the usability of 11 | ranger file manager which lacks the possibility to mount 12 | filesystems 13 | -------------------------------------------------------------------------------- /curses_interface.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 Daniel Anthofer 2 | # Modification and distribution under terms of 3 | # GNU GPL v. 3 is permitted 4 | # http://www.gnu.org/licenses/gpl-3.0.txt 5 | 6 | '''create a simple line-based curses interface''' 7 | 8 | import curses 9 | 10 | class CursesInterface: 11 | '''methods: 12 | start_interface() 13 | update_strlist(strlist) change displayed content 14 | args: 15 | comdict: dictionary of key to press : function 16 | called with index of highlighted list element 17 | function must return string to display at bottom 18 | strlist: list of string (per line) 19 | helpstr: string at top display keys/functions (default move/quit) 20 | errortext: string at bottom (default empty) 21 | ''' 22 | 23 | #def __init__(self, comdict, strlist[, helpstr[, errortext]]) {{{ 24 | def __init__(self, comdict, strlist, \ 25 | helpstr = 'up/down: k/j\tquit: q/ESC', \ 26 | errortext = ''): 27 | '''args: 28 | comdict: dictionary of key to press : function(index of cursorline) 29 | strlist: list of string (per line) 30 | helpstr: string at the top to display keys/functions (default move/quit) 31 | errortext: string at the bottom (default empty) 32 | ''' 33 | self.comdict = comdict 34 | self.strlist = strlist 35 | self.helpstr = helpstr 36 | self.errortext = errortext 37 | self.cursorline = 1 38 | # }}} 39 | 40 | def end_interface(self): # {{{ 41 | self.stdscr.erase() 42 | self.stdscr.refresh() 43 | curses.curs_set(1) 44 | curses.nocbreak() 45 | curses.echo() 46 | curses.endwin() 47 | # }}} 48 | 49 | def update_strlist(self, strlist): # {{{ 50 | self.strlist = strlist 51 | # }}} 52 | 53 | def mainloop(self): # {{{ 54 | while True: 55 | self.stdscr.clear() 56 | self.maxy, self.maxx = self.stdscr.getmaxyx() 57 | self.stdscr.addstr(0, 0, self.helpstr, curses.A_BOLD) 58 | 59 | for i in range(1,len(self.strlist)+1): 60 | self.linestr = self.strlist[i-1] 61 | if self.cursorline == i: 62 | while len(self.linestr) < self.maxx: #just for the eyes 63 | self.linestr += (' ') 64 | self.stdscr.addstr(i, 0, self.linestr, curses.A_REVERSE) 65 | else: 66 | self.stdscr.addstr(i, 0, self.linestr) 67 | 68 | self.stdscr.addstr(self.maxy-1, 0, self.errortext, curses.A_BOLD) 69 | self.stdscr.refresh() 70 | 71 | self.errortext = '' 72 | c = self.stdscr.getch() 73 | if chr(c) in self.comdict.keys(): 74 | self.errortext = self.comdict[chr(c)](self.cursorline - 1) 75 | elif c == curses.KEY_UP or chr(c) == 'k': 76 | if self.cursorline > 1: 77 | self.cursorline -=1 78 | elif c == curses.KEY_DOWN or chr(c) == 'j': 79 | if self.cursorline < len(self.strlist): 80 | self.cursorline +=1 81 | elif c == 27 or chr(c) == 'q': #27=ESC 82 | self.end_interface() 83 | break 84 | # }}} 85 | 86 | def start_interface(self): # {{{ 87 | self.stdscr = curses.initscr() 88 | curses.noecho() 89 | curses.cbreak() 90 | self.stdscr.keypad(1) 91 | curses.curs_set(0) 92 | self.mainloop() 93 | # }}} 94 | -------------------------------------------------------------------------------- /dbus_connection.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 Daniel Anthofer 2 | # Modification and distribution under terms of 3 | # GNU GPL v. 3 is permitted 4 | # http://www.gnu.org/licenses/gpl-3.0.txt 5 | 6 | '''createDevList() returns a list of FileSystemDevice instances 7 | of currently avaliable file systems''' 8 | 9 | import dbus 10 | 11 | # FileSystemDevice(devicefilepath, system_bus) {{{ 12 | class FileSystemDevice: 13 | '''attributes: 14 | devicefile 15 | fstype 16 | label 17 | size 18 | mountpoint 19 | methods: 20 | mounted 21 | mount 22 | unmount 23 | ''' 24 | 25 | def __init__(self, devicefilepath, system_bus): 26 | self.filesystemproxy = system_bus.get_object('org.freedesktop.UDisks', devicefilepath) 27 | self.filesystempropertiesinterface = dbus.Interface(self.filesystemproxy, 'org.freedesktop.DBus.Properties') 28 | self.get_filesystem_property = self.filesystempropertiesinterface.get_dbus_method('Get') 29 | if not self.get_filesystem_property('org.freedesktop.UDisks.Device', 'IdUsage') == 'filesystem': 30 | raise TypeError('not a filesystem') 31 | self.filesystemmethodsinterface = dbus.Interface(self.filesystemproxy, 'org.freedesktop.UDisks.Device') 32 | 33 | self.devicefile = self.get_filesystem_property('org.freedesktop.UDisks.Device', 'DeviceFile') 34 | self.fstype = self.get_filesystem_property('org.freedesktop.UDisks.Device', 'IdType') 35 | self.label = self.get_filesystem_property('org.freedesktop.UDisks.Device', 'IdLabel') 36 | self.size = self.get_filesystem_property('org.freedesktop.UDisks.Device', 'DeviceSize') 37 | if self.get_filesystem_property('org.freedesktop.UDisks.Device', 'DeviceIsMounted'): 38 | self.mountpoint = str( self.get_filesystem_property('org.freedesktop.UDisks.Device', 'DeviceMountPaths')[0] ) 39 | else: 40 | self.mountpoint = None 41 | 42 | self.mount_fs = self.filesystemmethodsinterface.get_dbus_method('FilesystemMount') 43 | self.unmount_fs = self.filesystemmethodsinterface.get_dbus_method('FilesystemUnmount') 44 | 45 | def mounted(self): 46 | return bool(self.get_filesystem_property('org.freedesktop.UDisks.Device', 'DeviceIsMounted')) 47 | 48 | def mount(self): 49 | self.mountpoint = str( self.mount_fs(self.fstype, []) ) 50 | 51 | def unmount(self): 52 | self.unmount_fs([]) 53 | # }}} 54 | 55 | system_bus = dbus.SystemBus() 56 | proxy = system_bus.get_object('org.freedesktop.UDisks', '/org/freedesktop/UDisks') 57 | interface = dbus.Interface(proxy, 'org.freedesktop.UDisks') 58 | list_devices = interface.get_dbus_method('EnumerateDevices') 59 | 60 | def createDevList(): 61 | devList = [] 62 | for devfile in list_devices(): 63 | try: 64 | devList.append(FileSystemDevice(devfile, system_bus)) 65 | except TypeError: 66 | pass # skip devicefiles with i.e. partition tables 67 | return devList 68 | -------------------------------------------------------------------------------- /mountie.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2012 Daniel Anthofer 3 | # Modification and distribution under terms of 4 | # GNU GPL v. 3 is permitted 5 | # http://www.gnu.org/licenses/gpl-3.0.txt 6 | 7 | import curses 8 | import dbus 9 | import os 10 | 11 | 12 | class CursesInterface: 13 | '''methods: 14 | start_interface() 15 | update_strlist(strlist) change displayed content 16 | args: 17 | comdict: dictionary of key to press : function 18 | called with index of highlighted list element 19 | function must return string to display at bottom 20 | strlist: list of string (per line) 21 | helpstr: string at top display keys/functions (default move/quit) 22 | errortext: string at bottom (default empty) 23 | ''' 24 | 25 | #def __init__(self, comdict, strlist[, helpstr[, errortext]]) {{{ 26 | def __init__(self, comdict, strlist, \ 27 | helpstr = 'up/down: k/j\tquit: q/ESC', \ 28 | errortext = ''): 29 | '''args: 30 | comdict: dictionary of key to press : function(index of cursorline) 31 | strlist: list of string (per line) 32 | helpstr: string at the top to display keys/functions (default move/quit) 33 | errortext: string at the bottom (default empty) 34 | ''' 35 | self.comdict = comdict 36 | self.strlist = strlist 37 | self.helpstr = helpstr 38 | self.errortext = errortext 39 | self.cursorline = 1 40 | # }}} 41 | 42 | def end_interface(self): # {{{ 43 | self.stdscr.erase() 44 | self.stdscr.refresh() 45 | curses.curs_set(1) 46 | curses.nocbreak() 47 | curses.echo() 48 | curses.endwin() 49 | # }}} 50 | 51 | def update_strlist(self, strlist): # {{{ 52 | self.strlist = strlist 53 | # }}} 54 | 55 | def mainloop(self): # {{{ 56 | while True: 57 | self.stdscr.clear() 58 | self.maxy, self.maxx = self.stdscr.getmaxyx() 59 | self.stdscr.addstr(0, 0, self.helpstr, curses.A_BOLD) 60 | 61 | for i in range(1,len(self.strlist)+1): 62 | self.linestr = self.strlist[i-1] 63 | if self.cursorline == i: 64 | while len(self.linestr) < self.maxx: #just for the eyes 65 | self.linestr += (' ') 66 | self.stdscr.addstr(i, 0, self.linestr, curses.A_REVERSE) 67 | else: 68 | self.stdscr.addstr(i, 0, self.linestr) 69 | 70 | self.stdscr.addstr(self.maxy-1, 0, self.errortext, curses.A_BOLD) 71 | self.stdscr.refresh() 72 | 73 | self.errortext = '' 74 | c = self.stdscr.getch() 75 | if chr(c) in self.comdict.keys(): 76 | self.errortext = self.comdict[chr(c)](self.cursorline - 1) 77 | elif c == curses.KEY_UP or chr(c) == 'k': 78 | if self.cursorline > 1: 79 | self.cursorline -=1 80 | elif c == curses.KEY_DOWN or chr(c) == 'j': 81 | if self.cursorline < len(self.strlist): 82 | self.cursorline +=1 83 | elif c == 27 or chr(c) == 'q': #27=ESC 84 | self.end_interface() 85 | break 86 | # }}} 87 | 88 | def start_interface(self): # {{{ 89 | self.stdscr = curses.initscr() 90 | curses.noecho() 91 | curses.cbreak() 92 | self.stdscr.keypad(1) 93 | curses.curs_set(0) 94 | self.mainloop() 95 | # }}} 96 | 97 | 98 | 99 | 100 | 101 | # FileSystemDevice(devicefilepath, system_bus) {{{ 102 | class FileSystemDevice: 103 | '''attributes: 104 | devicefile 105 | fstype 106 | label 107 | size 108 | mountpoint 109 | methods: 110 | mounted 111 | mount 112 | unmount 113 | ''' 114 | 115 | def __init__(self, devicefilepath, system_bus): 116 | self.filesystemproxy = system_bus.get_object('org.freedesktop.UDisks', devicefilepath) 117 | self.filesystempropertiesinterface = dbus.Interface(self.filesystemproxy, 'org.freedesktop.DBus.Properties') 118 | self.get_filesystem_property = self.filesystempropertiesinterface.get_dbus_method('Get') 119 | if not self.get_filesystem_property('org.freedesktop.UDisks.Device', 'IdUsage') == 'filesystem': 120 | raise TypeError('not a filesystem') 121 | self.filesystemmethodsinterface = dbus.Interface(self.filesystemproxy, 'org.freedesktop.UDisks.Device') 122 | 123 | self.devicefile = self.get_filesystem_property('org.freedesktop.UDisks.Device', 'DeviceFile') 124 | self.fstype = self.get_filesystem_property('org.freedesktop.UDisks.Device', 'IdType') 125 | self.label = self.get_filesystem_property('org.freedesktop.UDisks.Device', 'IdLabel') 126 | self.size = self.get_filesystem_property('org.freedesktop.UDisks.Device', 'DeviceSize') 127 | if self.get_filesystem_property('org.freedesktop.UDisks.Device', 'DeviceIsMounted'): 128 | self.mountpoint = str( self.get_filesystem_property('org.freedesktop.UDisks.Device', 'DeviceMountPaths')[0] ) 129 | else: 130 | self.mountpoint = None 131 | 132 | self.mount_fs = self.filesystemmethodsinterface.get_dbus_method('FilesystemMount') 133 | self.unmount_fs = self.filesystemmethodsinterface.get_dbus_method('FilesystemUnmount') 134 | 135 | def mounted(self): 136 | return bool(self.get_filesystem_property('org.freedesktop.UDisks.Device', 'DeviceIsMounted')) 137 | 138 | def mount(self): 139 | self.mountpoint = str( self.mount_fs(self.fstype, []) ) 140 | 141 | def unmount(self): 142 | self.unmount_fs([]) 143 | # }}} 144 | 145 | system_bus = dbus.SystemBus() 146 | proxy = system_bus.get_object('org.freedesktop.UDisks', '/org/freedesktop/UDisks') 147 | interface = dbus.Interface(proxy, 'org.freedesktop.UDisks') 148 | list_devices = interface.get_dbus_method('EnumerateDevices') 149 | 150 | def createDevList(): 151 | devList = [] 152 | for devfile in list_devices(): 153 | try: 154 | devList.append(FileSystemDevice(devfile, system_bus)) 155 | except TypeError: 156 | pass # skip devicefiles with i.e. partition tables 157 | return devList 158 | 159 | 160 | 161 | 162 | 163 | class Mountie: 164 | 165 | def __init__(self): # {{{ 166 | self.devList = createDevList() 167 | self.comdict = { \ 168 | 'm' : self.toggleMounted, \ 169 | 'o' : self.openFilesystem, \ 170 | 'l' : self.openFilesystem, \ 171 | # 'ą' is equal to right arrow key 172 | 'ą' : self.openFilesystem, \ 173 | '\n' : self.openFilesystem \ 174 | } 175 | self.helpstr = "up/down: k/j\tmount/unmount: m\topen: o/l\tquit: q/Esc" 176 | self.cursesInterface = CursesInterface( \ 177 | self.comdict, self.getStringList(), self.helpstr) 178 | # }}} 179 | 180 | def toggleMounted(self, index): # {{{ 181 | if self.devList[index].mounted(): 182 | try: 183 | self.devList[index].unmount() 184 | except: 185 | return "error: unable to unmount: device may be busy" 186 | 187 | else: 188 | try: 189 | self.devList[index].mount() 190 | except: 191 | return "error: unable to mount" 192 | 193 | self.cursesInterface.update_strlist(self.getStringList()) 194 | return "" 195 | # }}} 196 | 197 | def openFilesystem(self, index): # {{{ 198 | if not self.devList[index].mounted(): 199 | self.toggleMounted(index) 200 | if self.devList[index].mounted(): 201 | os.system("ranger "+self.devList[index].mountpoint) 202 | else: 203 | return "error: unable to mount" 204 | return "" 205 | # }}} 206 | 207 | def getStringList(self): # {{{ 208 | strlist = [] 209 | for device in self.devList: 210 | txt = device.label + "\t" + device.devicefile 211 | if device.mounted(): 212 | txt += "\t" + device.mountpoint 213 | strlist.append(txt) 214 | return strlist 215 | # }}} 216 | 217 | if __name__ == '__main__': 218 | mountie = Mountie() 219 | mountie.cursesInterface.start_interface() 220 | --------------------------------------------------------------------------------