Initial public release

This commit is contained in:
2026-07-17 15:29:53 -04:00
commit 2d71ce77a1
81 changed files with 32056 additions and 0 deletions
+388
View File
@@ -0,0 +1,388 @@
"""Pop-out editor for the Get Variables Semi-Auto sequence.
A sequence is an ordered list of steps that the host renders to PowerShell at
upload time. Each step is one of:
wait {"kind": "wait", "ms": int}
send_keys {"kind": "send_keys", "keys": str}
run {"kind": "run", "command": str}
check {"kind": "check", "command": str,
"operator": "equals"|"contains"|"regex"|"numeric_gt"|"numeric_lt"|"exists",
"expected": str, # for `exists`, this is the path
"value": str} # value the variable is set to on match
The 1-based index of each ``check`` step becomes its toggle_count outcome,
so check steps are read top-to-bottom by the device's Scroll Lock decoder.
"""
import tkinter as tk
from tkinter import ttk, messagebox
_BG = "#2D2D3D"
_CARD_BG = "#252535"
_ENTRY_BG = "#1E1E2E"
_FG = "#FFFFFF"
_DIM_FG = "#AAAAAA"
_BTN_BG = "#3D3D5C"
_SAVE_BG = "#0077CC"
_DEL_FG = "#FF7777"
STEP_KINDS = [
("Wait", "wait"),
("Send Keys", "send_keys"),
("Run Command", "run"),
("Check + Signal", "check"),
]
CHECK_OPERATORS = [
("Output equals", "equals"),
("Output contains", "contains"),
("Output matches regex", "regex"),
("Output > number", "numeric_gt"),
("Output < number", "numeric_lt"),
("Path exists", "exists"),
]
def _kind_label(kind: str) -> str:
for label, k in STEP_KINDS:
if k == kind:
return label
return kind
def _op_label(op: str) -> str:
for label, k in CHECK_OPERATORS:
if k == op:
return label
return op
class SequenceEditorDialog(tk.Toplevel):
"""Modal dialog that edits a sequence in-place via on_save callback."""
def __init__(self, parent, sequence: list, on_save):
super().__init__(parent)
self.title("Get Variables — Sequence Editor")
self.geometry("720x600")
self.minsize(560, 420)
self.configure(bg=_BG)
self.transient(parent.winfo_toplevel())
self._on_save = on_save
# Per-step copy so Cancel discards edits (steps are flat dicts, no nested mutables)
self._steps = [dict(s) for s in (sequence or [])]
try:
import ctypes
self.update_idletasks()
hwnd = ctypes.windll.user32.GetParent(self.winfo_id())
ctypes.windll.dwmapi.DwmSetWindowAttribute(
hwnd, 20, ctypes.byref(ctypes.c_int(1)), ctypes.sizeof(ctypes.c_int))
except Exception:
pass
self._build_ui()
self._refresh()
self.protocol("WM_DELETE_WINDOW", self.destroy)
def _build_ui(self):
hdr = tk.Frame(self, bg=_BG)
hdr.pack(fill="x", padx=14, pady=(12, 6))
tk.Label(hdr, text="Sequence", bg=_BG, fg="#0AACFF",
font=("Segoe UI", 12, "bold")).pack(side="left")
tk.Label(hdr,
text="Each Check step's index (1, 2, 3 …) becomes its toggle "
"count. First match wins — no match = Fail output.",
bg=_BG, fg=_DIM_FG, font=("Segoe UI", 8),
justify="left", wraplength=460).pack(side="left", padx=(12, 0))
bar = tk.Frame(self, bg=_BG)
bar.pack(fill="x", padx=14, pady=(0, 6))
tk.Label(bar, text="Add step:", bg=_BG, fg=_FG,
font=("Segoe UI", 9, "bold")).pack(side="left")
for label, kind in STEP_KINDS:
tk.Button(bar, text=f"+ {label}", bg=_BTN_BG, fg=_FG,
relief="flat", font=("Segoe UI", 9), padx=8,
command=lambda k=kind: self._add_step(k)).pack(
side="left", padx=(6, 0))
outer = tk.Frame(self, bg=_BG)
outer.pack(fill="both", expand=True, padx=14, pady=4)
canvas = tk.Canvas(outer, bg=_BG, highlightthickness=0)
scrollbar = tk.Scrollbar(outer, orient="vertical", command=canvas.yview)
canvas.configure(yscrollcommand=scrollbar.set)
scrollbar.pack(side="right", fill="y")
canvas.pack(side="left", fill="both", expand=True)
self._canvas = canvas
self._cards_frame = tk.Frame(canvas, bg=_BG)
self._cards_window = canvas.create_window((0, 0), window=self._cards_frame,
anchor="nw")
self._cards_frame.bind(
"<Configure>",
lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
canvas.bind(
"<Configure>",
lambda e: canvas.itemconfig(self._cards_window, width=e.width))
canvas.bind_all("<MouseWheel>", lambda e: canvas.yview_scroll(
int(-1 * (e.delta / 120)), "units"))
btns = tk.Frame(self, bg=_BG)
btns.pack(fill="x", padx=14, pady=(4, 12))
tk.Button(btns, text="Cancel", bg=_BTN_BG, fg=_FG, relief="flat",
padx=12, font=("Segoe UI", 9),
command=self.destroy).pack(side="right", padx=(6, 0))
tk.Button(btns, text="Save", bg=_SAVE_BG, fg=_FG, relief="flat",
padx=14, font=("Segoe UI", 9, "bold"),
command=self._save).pack(side="right")
def _refresh(self):
for w in self._cards_frame.winfo_children():
w.destroy()
check_idx = 0
for i, step in enumerate(self._steps):
if step.get("kind") == "check":
check_idx += 1
self._draw_card(i, step, check_idx if step.get("kind") == "check" else None)
if not self._steps:
tk.Label(self._cards_frame,
text="No steps yet — click an + Add button above to begin.",
bg=_BG, fg=_DIM_FG, font=("Segoe UI", 9, "italic")).pack(
pady=30)
def _draw_card(self, idx: int, step: dict, check_n):
card = tk.Frame(self._cards_frame, bg=_CARD_BG, bd=0)
card.pack(fill="x", pady=4, padx=2)
head = tk.Frame(card, bg=_CARD_BG)
head.pack(fill="x", padx=8, pady=(6, 2))
kind = step.get("kind", "")
title = f"{idx + 1}. {_kind_label(kind)}"
if check_n is not None:
title += f" [toggle = {check_n}]"
tk.Label(head, text=title, bg=_CARD_BG, fg="#0AACFF",
font=("Segoe UI", 10, "bold")).pack(side="left")
tk.Button(head, text="×", bg=_CARD_BG, fg=_DEL_FG, relief="flat",
font=("Segoe UI", 10, "bold"), width=2,
command=lambda i=idx: self._delete(i)).pack(side="right")
tk.Button(head, text="", bg=_CARD_BG, fg=_FG, relief="flat",
font=("Segoe UI", 10), width=2,
command=lambda i=idx: self._move(i, +1)).pack(side="right")
tk.Button(head, text="", bg=_CARD_BG, fg=_FG, relief="flat",
font=("Segoe UI", 10), width=2,
command=lambda i=idx: self._move(i, -1)).pack(side="right")
body = tk.Frame(card, bg=_CARD_BG)
body.pack(fill="x", padx=10, pady=(0, 8))
if kind == "wait":
self._draw_wait_fields(body, step)
elif kind == "send_keys":
self._draw_send_keys_fields(body, step)
elif kind == "run":
self._draw_run_fields(body, step)
elif kind == "check":
self._draw_check_fields(body, step)
else:
tk.Label(body, text=f"Unknown step kind: {kind}",
bg=_CARD_BG, fg=_DEL_FG,
font=("Segoe UI", 9, "italic")).pack(anchor="w")
def _draw_wait_fields(self, parent, step):
row = tk.Frame(parent, bg=_CARD_BG)
row.pack(fill="x")
tk.Label(row, text="Wait (ms):", bg=_CARD_BG, fg=_FG,
font=("Segoe UI", 9), width=12, anchor="w").pack(side="left")
v = tk.StringVar(value=str(step.get("ms", 500)))
e = tk.Entry(row, textvariable=v, bg=_ENTRY_BG, fg=_FG,
insertbackground=_FG, font=("Consolas", 10),
relief="flat", width=10)
e.pack(side="left", padx=4, ipady=2)
def save(*_):
try:
step["ms"] = max(0, int(v.get()))
except ValueError:
pass
v.trace_add("write", save)
def _draw_send_keys_fields(self, parent, step):
row = tk.Frame(parent, bg=_CARD_BG)
row.pack(fill="x")
tk.Label(row, text="Keys:", bg=_CARD_BG, fg=_FG,
font=("Segoe UI", 9), width=12, anchor="w").pack(side="left")
v = tk.StringVar(value=str(step.get("keys", "")))
tk.Entry(row, textvariable=v, bg=_ENTRY_BG, fg=_FG,
insertbackground=_FG, font=("Consolas", 10),
relief="flat").pack(side="left", fill="x", expand=True,
padx=4, ipady=2)
tk.Label(parent,
text="WScript.Shell SendKeys syntax — e.g. {ENTER}, ^c, +{TAB 3}",
bg=_CARD_BG, fg=_DIM_FG, font=("Segoe UI", 8)).pack(
anchor="w", pady=(2, 0))
def save(*_):
step["keys"] = v.get()
v.trace_add("write", save)
def _draw_run_fields(self, parent, step):
tk.Label(parent, text="PowerShell:", bg=_CARD_BG, fg=_FG,
font=("Segoe UI", 9)).pack(anchor="w")
text = tk.Text(parent, height=3, bg=_ENTRY_BG, fg=_FG,
insertbackground=_FG, font=("Consolas", 10),
relief="flat", wrap="none")
text.insert("1.0", step.get("command", ""))
text.pack(fill="x", pady=2)
def save(*_):
step["command"] = text.get("1.0", "end-1c")
text.bind("<KeyRelease>", save)
def _draw_check_fields(self, parent, step):
op_row = tk.Frame(parent, bg=_CARD_BG)
op_row.pack(fill="x")
tk.Label(op_row, text="Operator:", bg=_CARD_BG, fg=_FG,
font=("Segoe UI", 9), width=12, anchor="w").pack(side="left")
op_display = [d for d, _ in CHECK_OPERATORS]
op_value = [v for _, v in CHECK_OPERATORS]
cur = step.get("operator", "equals")
idx = op_value.index(cur) if cur in op_value else 0
op_var = tk.StringVar(value=op_display[idx])
op_box = ttk.Combobox(op_row, textvariable=op_var, values=op_display,
state="readonly")
op_box.pack(side="left", fill="x", expand=True, padx=4)
# Operator-specific fields; rebuilt on operator change
spec = tk.Frame(parent, bg=_CARD_BG)
spec.pack(fill="x", pady=(4, 0))
val_row = tk.Frame(parent, bg=_CARD_BG)
val_row.pack(fill="x", pady=(6, 0))
tk.Label(val_row, text="On match, set var to:",
bg=_CARD_BG, fg=_FG,
font=("Segoe UI", 9), width=20, anchor="w").pack(side="left")
val_var = tk.StringVar(value=str(step.get("value", "")))
tk.Entry(val_row, textvariable=val_var, bg=_ENTRY_BG, fg=_FG,
insertbackground=_FG, font=("Consolas", 10),
relief="flat").pack(side="left", fill="x", expand=True,
padx=4, ipady=2)
def save_value(*_):
step["value"] = val_var.get()
val_var.trace_add("write", save_value)
def render_spec():
for w in spec.winfo_children():
w.destroy()
current_op = step.get("operator", "equals")
if current_op == "exists":
tk.Label(spec, text="Path:", bg=_CARD_BG, fg=_FG,
font=("Segoe UI", 9), width=12,
anchor="w").pack(side="left")
pv = tk.StringVar(value=step.get("expected", ""))
tk.Entry(spec, textvariable=pv, bg=_ENTRY_BG, fg=_FG,
insertbackground=_FG, font=("Consolas", 10),
relief="flat").pack(side="left", fill="x",
expand=True, padx=4, ipady=2)
def save_path(*_):
step["expected"] = pv.get()
pv.trace_add("write", save_path)
else:
tk.Label(spec, text="Command:", bg=_CARD_BG, fg=_FG,
font=("Segoe UI", 9)).pack(anchor="w")
cmd_text = tk.Text(spec, height=2, bg=_ENTRY_BG, fg=_FG,
insertbackground=_FG, font=("Consolas", 10),
relief="flat", wrap="none")
cmd_text.insert("1.0", step.get("command", ""))
cmd_text.pack(fill="x", pady=2)
def save_cmd(*_):
step["command"] = cmd_text.get("1.0", "end-1c")
cmd_text.bind("<KeyRelease>", save_cmd)
exp_row = tk.Frame(spec, bg=_CARD_BG)
exp_row.pack(fill="x", pady=(2, 0))
exp_label = "Number:" if current_op in (
"numeric_gt", "numeric_lt") else (
"Pattern:" if current_op == "regex" else "Expected:")
tk.Label(exp_row, text=exp_label, bg=_CARD_BG, fg=_FG,
font=("Segoe UI", 9), width=12,
anchor="w").pack(side="left")
ev = tk.StringVar(value=step.get("expected", ""))
tk.Entry(exp_row, textvariable=ev, bg=_ENTRY_BG, fg=_FG,
insertbackground=_FG, font=("Consolas", 10),
relief="flat").pack(side="left", fill="x",
expand=True, padx=4, ipady=2)
def save_exp(*_):
step["expected"] = ev.get()
ev.trace_add("write", save_exp)
def on_op_change(*_):
disp = op_var.get()
i = op_display.index(disp) if disp in op_display else 0
new_op = op_value[i]
if new_op != step.get("operator"):
step["operator"] = new_op
render_spec()
op_var.trace_add("write", on_op_change)
render_spec()
def _add_step(self, kind: str):
step = {"kind": kind}
if kind == "wait":
step["ms"] = 500
elif kind == "send_keys":
step["keys"] = ""
elif kind == "run":
step["command"] = ""
elif kind == "check":
step.update({
"operator": "equals",
"command": "",
"expected": "",
"value": "",
})
self._steps.append(step)
self._refresh()
def _delete(self, i: int):
if 0 <= i < len(self._steps):
self._steps.pop(i)
self._refresh()
def _move(self, i: int, delta: int):
j = i + delta
if 0 <= j < len(self._steps):
self._steps[i], self._steps[j] = self._steps[j], self._steps[i]
self._refresh()
def _save(self):
# An empty value would silently set the variable to "", which is rarely intended
check_count = 0
for i, step in enumerate(self._steps):
if step.get("kind") == "check":
check_count += 1
if not (step.get("value") or "").strip():
if not messagebox.askyesno(
"Empty value",
f"Check step {check_count} has no value to set the "
f"variable to. Save anyway? (Variable will be set "
f"to an empty string on match.)",
parent=self,
):
return
break
try:
self._on_save(self._steps)
except Exception as e:
messagebox.showerror("Sequence Editor", str(e), parent=self)
return
self.destroy()