Minor update of the --gui functionality

This commit is contained in:
Miroslav Stampar 2025-09-15 00:03:04 +02:00
parent f7aa757a9f
commit 71a12bff64
3 changed files with 196 additions and 54 deletions

View File

@ -177,7 +177,7 @@ ce6e1c1766acd95168f7708ddcacaa4a586c21ffc9e92024c4715611c802b60c lib/core/dicts
c9d1f64648062d7962caf02c4e2e7d84e8feb2a14451146f627112aae889afcd lib/core/dump.py c9d1f64648062d7962caf02c4e2e7d84e8feb2a14451146f627112aae889afcd lib/core/dump.py
c1f211843ccc93a50639ae6f4a50eb434f334e095d9fea440cebe589004374f3 lib/core/enums.py c1f211843ccc93a50639ae6f4a50eb434f334e095d9fea440cebe589004374f3 lib/core/enums.py
00a9b29caa81fe4a5ef145202f9c92e6081f90b2a85cd76c878d520d900ad856 lib/core/exception.py 00a9b29caa81fe4a5ef145202f9c92e6081f90b2a85cd76c878d520d900ad856 lib/core/exception.py
629c0d06d4f4d093badfc8d1de49432d058f66f3223b08dded012eaf05719de2 lib/core/gui.py 1c48804c10b94da696d3470efbd25d2fff0f0bbf2af0101aaac8f8c097fce02b lib/core/gui.py
4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/core/__init__.py 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/core/__init__.py
3d308440fb01d04b5d363bfbe0f337756b098532e5bb7a1c91d5213157ec2c35 lib/core/log.py 3d308440fb01d04b5d363bfbe0f337756b098532e5bb7a1c91d5213157ec2c35 lib/core/log.py
2a06dc9b5c17a1efdcdb903545729809399f1ee96f7352cc19b9aaa227394ff3 lib/core/optiondict.py 2a06dc9b5c17a1efdcdb903545729809399f1ee96f7352cc19b9aaa227394ff3 lib/core/optiondict.py
@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl
d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py
1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py
d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py
9321b30f898d67fd5505426274f562af6f95e426c57f95c54eb69174beb48278 lib/core/settings.py 84eb7f5d155afce459120ab42efb8f5baa104f8ed2f8e64d8d8cbdea62a79dc7 lib/core/settings.py
1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py
4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py
cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py

View File

@ -61,18 +61,6 @@ def runGui(parser):
else: else:
self.set(self.old_value) 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("<<NotebookTabChanged>>", 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: try:
window = _tkinter.Tk() window = _tkinter.Tk()
except Exception as ex: except Exception as ex:
@ -81,11 +69,41 @@ def runGui(parser):
window.title(VERSION_STRING) 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() 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) # Try to use a more modern theme if available
style.theme_use("custom") 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 # Reference: https://stackoverflow.com/a/10018670
def center(window): def center(window):
@ -138,16 +156,16 @@ def runGui(parser):
config = {} config = {}
for key in window._widgets: for key in window._widgets:
dest, type = key dest, widget_type = key
widget = window._widgets[key] widget = window._widgets[key]
if hasattr(widget, "get") and not widget.get(): if hasattr(widget, "get") and not widget.get():
value = None value = None
elif type == "string": elif widget_type == "string":
value = widget.get() value = widget.get()
elif type == "float": elif widget_type == "float":
value = float(widget.get()) value = float(widget.get())
elif type == "int": elif widget_type == "int":
value = int(widget.get()) value = int(widget.get())
else: else:
value = bool(widget.var.get()) value = bool(widget.var.get())
@ -155,7 +173,9 @@ def runGui(parser):
config[dest] = value config[dest] = value
for option in parser.option_list: 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) handle, configFile = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.CONFIG, text=True)
os.close(handle) os.close(handle)
@ -183,12 +203,20 @@ def runGui(parser):
top = _tkinter.Toplevel() top = _tkinter.Toplevel()
top.title("Console") 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 # 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("<Key>", onKeyPress) text.bind("<Key>", onKeyPress)
text.bind("<Return>", onReturnPress) text.bind("<Return>", onReturnPress)
text.pack() text.pack(fill=_tkinter.BOTH, expand=True)
text.focus() text.focus()
center(top) center(top)
@ -196,7 +224,6 @@ def runGui(parser):
while True: while True:
line = "" line = ""
try: try:
# line = queue.get_nowait()
line = queue.get(timeout=.1) line = queue.get(timeout=.1)
text.insert(_tkinter.END, line) text.insert(_tkinter.END, line)
except _queue.Empty: except _queue.Empty:
@ -206,9 +233,10 @@ def runGui(parser):
if not alive: if not alive:
break 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="Open", state=_tkinter.DISABLED)
filemenu.add_command(label="Save", state=_tkinter.DISABLED) filemenu.add_command(label="Save", state=_tkinter.DISABLED)
filemenu.add_separator() filemenu.add_separator()
@ -217,7 +245,7 @@ def runGui(parser):
menubar.add_command(label="Run", command=run) 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="Official site", command=lambda: webbrowser.open(SITE))
helpmenu.add_command(label="Github pages", command=lambda: webbrowser.open(GIT_PAGE)) helpmenu.add_command(label="Github pages", command=lambda: webbrowser.open(GIT_PAGE))
helpmenu.add_command(label="Wiki pages", command=lambda: webbrowser.open(WIKI_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)) 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) menubar.add_cascade(label="Help", menu=helpmenu)
window.config(menu=menubar) window.config(menu=menubar, bg=bg_color)
window._widgets = {} 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 # Add header label
frames = {} 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: for group in parser.option_groups:
frame = frames[group.title] = _tkinter.Frame(notebook, width=200, height=200) # Create a frame with scrollbar for the tab
notebook.add(frames[group.title], text=group.title) 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(
"<Configure>",
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(): if group.get_description():
_tkinter.Label(frame, text="%s:" % group.get_description()).grid(column=0, row=1, columnspan=3, sticky=_tkinter.W) desc_label = _tkinter.Label(scrollable_frame, text=group.get_description(),
_tkinter.Label(frame).grid(column=0, row=2, sticky=_tkinter.W) wraplength=600, justify="left",
row += 2 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: 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": 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": 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": 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: else:
var = _tkinter.IntVar() 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.var = var
widget.grid(row=row, column=1, sticky="w", padx=5, pady=2)
first = first or widget # Help text (truncated to improve performance)
widget.grid(column=1, row=row, sticky=_tkinter.W) 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 window._widgets[(option.dest, option.type)] = widget
# Set default value
default = defaults.get(option.dest) default = defaults.get(option.dest)
if default: if default:
if hasattr(widget, "insert"): if hasattr(widget, "insert"):
widget.insert(0, default) widget.insert(0, default)
elif hasattr(widget, "var"):
_tkinter.Label(frame, text=" %s" % option.help).grid(column=2, row=row, sticky=_tkinter.W) widget.var.set(1 if default else 0)
row += 1 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") # Update the scroll region after adding all widgets
notebook.enable_traversal() 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() window.mainloop()

View File

@ -19,7 +19,7 @@ from lib.core.enums import OS
from thirdparty import six from thirdparty import six
# sqlmap version (<major>.<minor>.<month>.<monthly commit>) # sqlmap version (<major>.<minor>.<month>.<monthly commit>)
VERSION = "1.9.9.3" VERSION = "1.9.9.4"
TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable"
TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} 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) VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE)