From 71a12bff649fc2ada5db2c32374860b37735ec67 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 15 Sep 2025 00:03:04 +0200 Subject: [PATCH] Minor update of the --gui functionality --- data/txt/sha256sums.txt | 4 +- lib/core/gui.py | 244 +++++++++++++++++++++++++++++++--------- lib/core/settings.py | 2 +- 3 files changed, 196 insertions(+), 54 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index fd1ba641e..202d82374 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -177,7 +177,7 @@ ce6e1c1766acd95168f7708ddcacaa4a586c21ffc9e92024c4715611c802b60c lib/core/dicts c9d1f64648062d7962caf02c4e2e7d84e8feb2a14451146f627112aae889afcd lib/core/dump.py c1f211843ccc93a50639ae6f4a50eb434f334e095d9fea440cebe589004374f3 lib/core/enums.py 00a9b29caa81fe4a5ef145202f9c92e6081f90b2a85cd76c878d520d900ad856 lib/core/exception.py -629c0d06d4f4d093badfc8d1de49432d058f66f3223b08dded012eaf05719de2 lib/core/gui.py +1c48804c10b94da696d3470efbd25d2fff0f0bbf2af0101aaac8f8c097fce02b lib/core/gui.py 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/core/__init__.py 3d308440fb01d04b5d363bfbe0f337756b098532e5bb7a1c91d5213157ec2c35 lib/core/log.py 2a06dc9b5c17a1efdcdb903545729809399f1ee96f7352cc19b9aaa227394ff3 lib/core/optiondict.py @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -9321b30f898d67fd5505426274f562af6f95e426c57f95c54eb69174beb48278 lib/core/settings.py +84eb7f5d155afce459120ab42efb8f5baa104f8ed2f8e64d8d8cbdea62a79dc7 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/core/gui.py b/lib/core/gui.py index 024918a34..a324ba901 100644 --- a/lib/core/gui.py +++ b/lib/core/gui.py @@ -61,18 +61,6 @@ def runGui(parser): else: self.set(self.old_value) - # Reference: https://code.activestate.com/recipes/580726-tkinter-notebook-that-fits-to-the-height-of-every-/ - class AutoresizableNotebook(_tkinter_ttk.Notebook): - def __init__(self, master=None, **kw): - _tkinter_ttk.Notebook.__init__(self, master, **kw) - self.bind("<>", self._on_tab_changed) - - def _on_tab_changed(self, event): - event.widget.update_idletasks() - - tab = event.widget.nametowidget(event.widget.select()) - event.widget.configure(height=tab.winfo_reqheight()) - try: window = _tkinter.Tk() except Exception as ex: @@ -81,11 +69,41 @@ def runGui(parser): window.title(VERSION_STRING) - # Reference: https://www.holadevs.com/pregunta/64750/change-selected-tab-color-in-ttknotebook + # Set theme and colors + bg_color = "#f5f5f5" + fg_color = "#333333" + accent_color = "#2c7fb8" + window.configure(background=bg_color) + + # Configure styles style = _tkinter_ttk.Style() - settings = {"TNotebook.Tab": {"configure": {"padding": [5, 1], "background": "#fdd57e"}, "map": {"background": [("selected", "#C70039"), ("active", "#fc9292")], "foreground": [("selected", "#ffffff"), ("active", "#000000")]}}} - style.theme_create("custom", parent="alt", settings=settings) - style.theme_use("custom") + + # Try to use a more modern theme if available + available_themes = style.theme_names() + if 'clam' in available_themes: + style.theme_use('clam') + elif 'alt' in available_themes: + style.theme_use('alt') + + # Configure notebook style + style.configure("TNotebook", background=bg_color) + style.configure("TNotebook.Tab", + padding=[10, 4], + background="#e1e1e1", + font=('Helvetica', 9)) + style.map("TNotebook.Tab", + background=[("selected", accent_color), ("active", "#7fcdbb")], + foreground=[("selected", "white"), ("active", "white")]) + + # Configure button style + style.configure("TButton", + padding=4, + relief="flat", + background=accent_color, + foreground="white", + font=('Helvetica', 9)) + style.map("TButton", + background=[('active', '#41b6c4')]) # Reference: https://stackoverflow.com/a/10018670 def center(window): @@ -138,16 +156,16 @@ def runGui(parser): config = {} for key in window._widgets: - dest, type = key + dest, widget_type = key widget = window._widgets[key] if hasattr(widget, "get") and not widget.get(): value = None - elif type == "string": + elif widget_type == "string": value = widget.get() - elif type == "float": + elif widget_type == "float": value = float(widget.get()) - elif type == "int": + elif widget_type == "int": value = int(widget.get()) else: value = bool(widget.var.get()) @@ -155,7 +173,9 @@ def runGui(parser): config[dest] = value for option in parser.option_list: - config[option.dest] = defaults.get(option.dest, None) + # Only set default if not already set by the user + if option.dest not in config or config[option.dest] is None: + config[option.dest] = defaults.get(option.dest, None) handle, configFile = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.CONFIG, text=True) os.close(handle) @@ -183,12 +203,20 @@ def runGui(parser): top = _tkinter.Toplevel() top.title("Console") + top.configure(background=bg_color) + + # Create a frame for the console + console_frame = _tkinter.Frame(top, bg=bg_color) + console_frame.pack(fill=_tkinter.BOTH, expand=True, padx=10, pady=10) # Reference: https://stackoverflow.com/a/13833338 - text = _tkinter_scrolledtext.ScrolledText(top, undo=True) + text = _tkinter_scrolledtext.ScrolledText(console_frame, undo=True, wrap=_tkinter.WORD, + bg="#2c3e50", fg="#ecf0f1", + insertbackground="white", + font=('Consolas', 10)) text.bind("", onKeyPress) text.bind("", onReturnPress) - text.pack() + text.pack(fill=_tkinter.BOTH, expand=True) text.focus() center(top) @@ -196,7 +224,6 @@ def runGui(parser): while True: line = "" try: - # line = queue.get_nowait() line = queue.get(timeout=.1) text.insert(_tkinter.END, line) except _queue.Empty: @@ -206,9 +233,10 @@ def runGui(parser): if not alive: break - menubar = _tkinter.Menu(window) + # Create a menu bar + menubar = _tkinter.Menu(window, bg=bg_color, fg=fg_color) - filemenu = _tkinter.Menu(menubar, tearoff=0) + filemenu = _tkinter.Menu(menubar, tearoff=0, bg=bg_color, fg=fg_color) filemenu.add_command(label="Open", state=_tkinter.DISABLED) filemenu.add_command(label="Save", state=_tkinter.DISABLED) filemenu.add_separator() @@ -217,7 +245,7 @@ def runGui(parser): menubar.add_command(label="Run", command=run) - helpmenu = _tkinter.Menu(menubar, tearoff=0) + helpmenu = _tkinter.Menu(menubar, tearoff=0, bg=bg_color, fg=fg_color) helpmenu.add_command(label="Official site", command=lambda: webbrowser.open(SITE)) helpmenu.add_command(label="Github pages", command=lambda: webbrowser.open(GIT_PAGE)) helpmenu.add_command(label="Wiki pages", command=lambda: webbrowser.open(WIKI_PAGE)) @@ -226,59 +254,173 @@ def runGui(parser): helpmenu.add_command(label="About", command=lambda: _tkinter_messagebox.showinfo("About", "Copyright (c) 2006-2025\n\n (%s)" % DEV_EMAIL_ADDRESS)) menubar.add_cascade(label="Help", menu=helpmenu) - window.config(menu=menubar) + window.config(menu=menubar, bg=bg_color) window._widgets = {} - notebook = AutoresizableNotebook(window) + # Create header frame + header_frame = _tkinter.Frame(window, bg=bg_color, height=60) + header_frame.pack(fill=_tkinter.X, pady=(0, 5)) + header_frame.pack_propagate(0) - first = None - frames = {} + # Add header label + title_label = _tkinter.Label(header_frame, text="Configuration", + font=('Helvetica', 14), + fg=accent_color, bg=bg_color) + title_label.pack(side=_tkinter.LEFT, padx=15) + # Add run button in header + run_button = _tkinter_ttk.Button(header_frame, text="Run", command=run, width=12) + run_button.pack(side=_tkinter.RIGHT, padx=15) + + # Create notebook + notebook = _tkinter_ttk.Notebook(window) + notebook.pack(expand=1, fill="both", padx=5, pady=(0, 5)) + + # Store tab information for background loading + tab_frames = {} + tab_canvases = {} + tab_scrollable_frames = {} + tab_groups = {} + + # Create empty tabs with scrollable areas first (fast) for group in parser.option_groups: - frame = frames[group.title] = _tkinter.Frame(notebook, width=200, height=200) - notebook.add(frames[group.title], text=group.title) + # Create a frame with scrollbar for the tab + tab_frame = _tkinter.Frame(notebook, bg=bg_color) + tab_frames[group.title] = tab_frame - _tkinter.Label(frame).grid(column=0, row=0, sticky=_tkinter.W) + # Create a canvas with scrollbar + canvas = _tkinter.Canvas(tab_frame, bg=bg_color, highlightthickness=0) + scrollbar = _tkinter_ttk.Scrollbar(tab_frame, orient="vertical", command=canvas.yview) + scrollable_frame = _tkinter.Frame(canvas, bg=bg_color) + + # Store references + tab_canvases[group.title] = canvas + tab_scrollable_frames[group.title] = scrollable_frame + tab_groups[group.title] = group + + # Configure the canvas scrolling + scrollable_frame.bind( + "", + lambda e, canvas=canvas: canvas.configure(scrollregion=canvas.bbox("all")) + ) + + canvas.create_window((0, 0), window=scrollable_frame, anchor="nw") + canvas.configure(yscrollcommand=scrollbar.set) + + # Pack the canvas and scrollbar + canvas.pack(side="left", fill="both", expand=True) + scrollbar.pack(side="right", fill="y") + + # Add the tab to the notebook + notebook.add(tab_frame, text=group.title) + + # Add a loading indicator + loading_label = _tkinter.Label(scrollable_frame, text="Loading options...", + font=('Helvetica', 12), + fg=accent_color, bg=bg_color) + loading_label.pack(expand=True) + + # Function to populate a tab in the background + def populate_tab(tab_name): + group = tab_groups[tab_name] + scrollable_frame = tab_scrollable_frames[tab_name] + canvas = tab_canvases[tab_name] + + # Remove loading indicator + for child in scrollable_frame.winfo_children(): + child.destroy() + + # Add content to the scrollable frame + row = 0 - row = 1 if group.get_description(): - _tkinter.Label(frame, text="%s:" % group.get_description()).grid(column=0, row=1, columnspan=3, sticky=_tkinter.W) - _tkinter.Label(frame).grid(column=0, row=2, sticky=_tkinter.W) - row += 2 + desc_label = _tkinter.Label(scrollable_frame, text=group.get_description(), + wraplength=600, justify="left", + font=('Helvetica', 9), + fg="#555555", bg=bg_color) + desc_label.grid(row=row, column=0, columnspan=3, sticky="w", padx=10, pady=(10, 5)) + row += 1 for option in group.option_list: - _tkinter.Label(frame, text="%s " % parser.formatter._format_option_strings(option)).grid(column=0, row=row, sticky=_tkinter.W) + # Option label + option_label = _tkinter.Label(scrollable_frame, + text=parser.formatter._format_option_strings(option) + ":", + font=('Helvetica', 9), + fg=fg_color, bg=bg_color, + anchor="w") + option_label.grid(row=row, column=0, sticky="w", padx=10, pady=2) + # Input widget if option.type == "string": - widget = _tkinter.Entry(frame) + widget = _tkinter.Entry(scrollable_frame, font=('Helvetica', 9), + relief="sunken", bd=1, width=20) + widget.grid(row=row, column=1, sticky="w", padx=5, pady=2) elif option.type == "float": - widget = ConstrainedEntry(frame, regex=r"\A\d*\.?\d*\Z") + widget = ConstrainedEntry(scrollable_frame, regex=r"\A\d*\.?\d*\Z", + font=('Helvetica', 9), + relief="sunken", bd=1, width=10) + widget.grid(row=row, column=1, sticky="w", padx=5, pady=2) elif option.type == "int": - widget = ConstrainedEntry(frame, regex=r"\A\d*\Z") + widget = ConstrainedEntry(scrollable_frame, regex=r"\A\d*\Z", + font=('Helvetica', 9), + relief="sunken", bd=1, width=10) + widget.grid(row=row, column=1, sticky="w", padx=5, pady=2) else: var = _tkinter.IntVar() - widget = _tkinter.Checkbutton(frame, variable=var) + widget = _tkinter.Checkbutton(scrollable_frame, variable=var, + bg=bg_color, activebackground=bg_color) widget.var = var + widget.grid(row=row, column=1, sticky="w", padx=5, pady=2) - first = first or widget - widget.grid(column=1, row=row, sticky=_tkinter.W) + # Help text (truncated to improve performance) + help_text = option.help + if len(help_text) > 100: + help_text = help_text[:100] + "..." + help_label = _tkinter.Label(scrollable_frame, text=help_text, + font=('Helvetica', 8), + fg="#666666", bg=bg_color, + wraplength=400, justify="left") + help_label.grid(row=row, column=2, sticky="w", padx=5, pady=2) + + # Store widget reference window._widgets[(option.dest, option.type)] = widget + # Set default value default = defaults.get(option.dest) if default: if hasattr(widget, "insert"): widget.insert(0, default) - - _tkinter.Label(frame, text=" %s" % option.help).grid(column=2, row=row, sticky=_tkinter.W) + elif hasattr(widget, "var"): + widget.var.set(1 if default else 0) row += 1 - _tkinter.Label(frame).grid(column=0, row=row, sticky=_tkinter.W) + # Add some padding at the bottom + _tkinter.Label(scrollable_frame, bg=bg_color, height=1).grid(row=row, column=0) - notebook.pack(expand=1, fill="both") - notebook.enable_traversal() + # Update the scroll region after adding all widgets + canvas.update_idletasks() + canvas.configure(scrollregion=canvas.bbox("all")) - first.focus() + # Update the UI to show the tab is fully loaded + window.update_idletasks() + # Function to populate tabs in the background + def populate_tabs_background(): + for tab_name in tab_groups.keys(): + # Schedule each tab to be populated with a small delay between them + window.after(100, lambda name=tab_name: populate_tab(name)) + + # Start populating tabs in the background after a short delay + window.after(500, populate_tabs_background) + + # Set minimum window size + window.update() + window.minsize(800, 500) + + # Center the window on screen + center(window) + + # Start the GUI window.mainloop() diff --git a/lib/core/settings.py b/lib/core/settings.py index 78b9bd7a8..ecdd0e2ca 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from lib.core.enums import OS from thirdparty import six # sqlmap version (...) -VERSION = "1.9.9.3" +VERSION = "1.9.9.4" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE)