diff options
-rwxr-xr-x | gmm | 116 |
1 files changed, 109 insertions, 7 deletions
@@ -9,9 +9,20 @@ # https://stackoverflow.com/questions/23662280/how-to-log-the-contents-of-a-configparser/50362738#50362738 # srb_lib/srb_tk.py # https://coderslegacy.com/python/list-of-tkinter-widgets/ +# https://stackoverflow.com/questions/46331408/limiting-python-filedialog-to-a-specific-filetype/46339932#46339932 +# https://stackoverflow.com/questions/30614279/tkinter-treeview-get-selected-item-values +# https://www.reddit.com/r/learnpython/comments/hcn8cc/cant_bind_double_click_to_treeview_with_grid/ +# for drag and drop, I tried a few things that failed: +# https://sourceforge.net/projects/tkdnd/ failed because it does not support python or in between applications +# https://github.com/python/cpython/blob/main/Lib/tkinter/dnd.py says it is within the same application +# https://stackoverflow.com/questions/44887576/how-can-i-create-a-drag-and-drop-interface within but it does not work with a file dragged in. +# Improve: +# get tkstackrpms in a venv, so I can try pip install tkinterdnd2., https://www.delftstack.com/howto/python-tkinter/tkinter-drag-and-drop/#download-and-setup-the-essential-packages-for-drag-and-drop-in-tkinter +# need .desktop file that takes application/x-iso9660-image, that calls this with --gui -import argparse, sys, os, psutil, subprocess, json, configparser, tkinter as tk, tkinter.simpledialog +import argparse, sys, os, psutil, subprocess, json, configparser, tkinter as tk, tkinter.simpledialog, tkinter.filedialog import tkstackrpms as stk +import tkinter.ttk as ttk # LOW-LEVEL values appname = "gmm" @@ -160,6 +171,15 @@ def unmount_iso_to_path(isofile, mount_point): if debuglev(5): if mount_e: ferror(mount_e) + # and now delete the empty directory + if not mount_point: + mount_point = isofile + if os.path.isdir(mount_point) and (not os.listdir(mount_point)): + try: + os.rmdir(mount_point) + except Exception as e: + if debuglev(5): + ferror(f"INFO: while trying to remove now-empty dir {mount_point}, got error {e}, continuing...") def get_iso_mounted_to_path(mount_point, mounts = None): """ @@ -244,9 +264,7 @@ def save_config(configfile): config.write(cf) # GRAPHICAL APP -# want mount iso... file dialog, that has two mimetype choices: *.iso, or * -# a combo box/list box thing of mounted isos, mount point -# a config file/dialog for choosing default mounts dir, which is managed by this app. E.g., /mnt/iso so the first disc is /mnt/iso/1 or something. Or ~/mnt/iso. Something like that. +# Settings window def NewWindow(textvar, func1, ok_button_func, mounts=None): window = tk.Toplevel() #window.geometry("250x250") @@ -263,6 +281,10 @@ def NewWindow(textvar, func1, ok_button_func, mounts=None): ent_mounts_dir.configure(state="disabled") return window +# want mount iso... file dialog, that has two mimetype choices: *.iso, or * +# a combo box/list box thing of mounted isos, mount point +# a config file/dialog for choosing default mounts dir, which is managed by this app. E.g., /mnt/iso so the first disc is /mnt/iso/1 or something. Or ~/mnt/iso. Something like that. +# MAIN WINDOW class App(tk.Frame): def __init__(self, master): super().__init__(master) @@ -270,11 +292,12 @@ class App(tk.Frame): self.mounts = list_mounts() self.mounts_dir = tk.StringVar() self.master.title("Graphical Mount Manager") - self.master.minsize(250,250) + self.master.minsize(550,200) imgicon = stk.get_scaled_icon("dvd_unmount",24,"default","","apps") self.master.tk.call("wm","iconphoto",self.master._w,imgicon) menu = tk.Menu(self.master) menu_file = tk.Menu(menu,tearoff=0) + menu_file.add_command(label="Mount iso...", command=self.func_mount_iso_dialog, underline=0) menu_file.add_command(label="Settings...", command=self.func_open_settings, underline=0) menu_file.add_separator() menu_file.add_command(label="Exit", command=self.func_exit, underline=1) @@ -285,6 +308,19 @@ class App(tk.Frame): self.master.config(menu=menu) self.grid() # use this instead of pack() self.background_color = self.master.cget("bg") + self.mlframe = tk.Frame(self.master) + self.mlframe.grid(row=0,column=0,columnspan=3,sticky="ew") + # treeview, which requires ttk + self.ml = ttk.Treeview(self.mlframe, columns=("source","mountpoint"),show="headings") + #help(self.ml) + self.ml.grid(column=0,row=0,columnspan=3,sticky="ew") + self.ml.heading("source",text="Source") + self.ml.heading("mountpoint",text="Mount point") + self.ml.column("source",width=400) + self.ml.bind("<Double-1>", self.func_double_click_entry) + # unmount button + self.unmount_btn = tk.Button(self.master,text="Unmount",command=self.func_unmount_current_selection,underline=0).grid(column=0,row=1) + self.master.bind("<Alt-u>",self.func_unmount_current_selection) # initial load self.refresh_form("initial") @@ -310,12 +346,63 @@ class App(tk.Frame): ferror(f"DEBUG: when trying to hide settings window, got {e}") self.refresh_form() + def get_current_selection(self, attribute="mountpoint"): + """ + Get the current path, or other attribute, from the treeview + """ + value = None + # There might be nothing selected, so return nothing + values = self.ml.item(self.ml.focus())["values"] + if debuglev(9): + ferror(f"DEBUG: got values {values}") + try: + if attribute == "mountpoint": + value = values[1] + elif attribute == "source": + value = values[0] + except IndexError: + return None + return value + + def func_unmount_current_selection(self, o1 = None): + if debuglev(9): + ferror(f"func_unmount_current_selection: {o1}") + # get current selection + path = self.get_current_selection() + if path: + unmount_iso_to_path(path,"") + self.refresh_form() + elif debuglev(4): + ferror(f"INFO: Nothing selected to unmount, continuing...") + def refresh_form(self,secondObj = None): if debuglev(9): ferror(f"DEBUG: refresh_form got secondObj {secondObj}, class {type(secondObj)}") # compare all config settings to see if they are different load_config(conffile) # this populates object "config" self.mounts_dir.set(config[appname]["mounts_dir"]) + # reload mounts + self.mounts = list_mounts() + self.ml.delete(*self.ml.get_children()) + for i in self.mounts: + self.ml.insert("",tk.END, values=(i["source"],i["mountpoint"])) + + def func_double_click_entry(self, o1 = None, o2 = None, o3 = None): + """ + Open the mounted directory when double-clicked. + """ + if debuglev(9): + ferror(f"DEBUG: double-click {o1},{o2},{o3}") + # It is possible to have nothing selected, so just throw a warning + path = self.get_current_selection() + if path: + if debuglev(1): + ferror(f"Running xdg-open {path}") + subprocess.Popen(["xdg-open",path]) + else: + if debuglev(4): + ferror(f"INFO: No item selected to open, continuing...") + pass def save_settings(self,secondObj = None): if debuglev(1): @@ -324,7 +411,22 @@ class App(tk.Frame): save_config(conffile) self.refresh_form() + def func_mount_iso_dialog(self): + """ + Display a file chooser dialog to mount. + """ + filename = tk.filedialog.askopenfilename( + filetypes = [ + ("Disc image","*.iso"), + ("All files","*") + ] + ) + if filename: + ferror(f"Got {filename}") + mount_iso_to_default(filename,config) + self.refresh_form() +# WORKHERE: support drag-and-drop onto the treeview? # MAIN CLI if "__main__" == __name__: mounts = list_mounts() @@ -349,7 +451,7 @@ if "__main__" == __name__: left = paths[0] right = paths[1] if os.path.isfile(left): - if os.path.isdir(right) or (not os.paths.exists(right)): + if os.path.isdir(right) or (not os.path.exists(right)): if os.path.ismount(right): if debuglev(8): ferror(f"DEBUG: Investigating current mount {right} to see if it is same as {left}") @@ -371,7 +473,7 @@ if "__main__" == __name__: print(f"INFO: will mount {left} to {right}.") mount_iso_to_path(left, right) elif os.path.isfile(right): - for i in path: + for i in paths: mount_iso_to_default(i,config) else: ferror(f"ERROR: second path {right} is not a dir or file. Choose a different spot") |