"""Modal popup invoked when a device sends a Request BLE Variable(s) op. The dialog lists the variable names the device asked for, pre-fills with the current device-profile values (if any), and returns the user's edits via the on_submit callback. Clicking Cancel returns None — the BLE client falls back to the existing values in that case. """ import tkinter as tk from tkinter import messagebox class RequestVariableDialog(tk.Toplevel): _BG = "#2D2D3D" _ROW_BG = "#252535" _ENTRY_BG = "#1E1E2E" _FG = "#FFFFFF" _DIM_FG = "#AAAAAA" _BTN_BG = "#3D3D5C" _SAVE_BG = "#0077CC" def __init__(self, parent, mac: str, names: list, current: dict, on_submit, play_sound: bool = True): super().__init__(parent) self.title(f"Variable Request — {mac}") self.geometry("520x420") self.minsize(380, 240) self.configure(bg=self._BG) self.transient(parent) self.grab_set() self._on_submit = on_submit self._mac = mac self._names = list(names) self._current = dict(current or {}) self._rows = [] 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() # Intentionally NOT binding globally on the Toplevel: a stray # Enter keystroke pending in the OS keyboard queue when this dialog # focus_force()s itself in would auto-submit before the user types, # send empty values back to the device, and "end the routine." # Submission goes through the Send to Device button only. self.protocol("WM_DELETE_WINDOW", self._cancel) if play_sound: try: import winsound winsound.MessageBeep(winsound.MB_ICONEXCLAMATION) except Exception: pass self.lift() self.focus_force() def _build_ui(self): hdr = tk.Frame(self, bg=self._BG) hdr.pack(fill="x", padx=14, pady=(12, 4)) tk.Label(hdr, text="Variable Request", bg=self._BG, fg="#0AACFF", font=("Segoe UI", 11, "bold")).pack(side="left") tk.Label(hdr, text=f"Device {self._mac}", bg=self._BG, fg=self._DIM_FG, font=("Segoe UI", 8)).pack(side="left", padx=(10, 0)) tk.Label(self, bg=self._BG, fg=self._DIM_FG, font=("Segoe UI", 8), text="Fill in values below; the device will receive them and " "the device profile will be updated.").pack( anchor="w", padx=14) outer = tk.Frame(self, bg=self._BG) outer.pack(fill="both", expand=True, padx=14, pady=8) canvas = tk.Canvas(outer, bg=self._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) rows_frame = tk.Frame(canvas, bg=self._BG) rows_window = canvas.create_window((0, 0), window=rows_frame, anchor="nw") rows_frame.bind("", lambda e: canvas.configure(scrollregion=canvas.bbox("all"))) canvas.bind("", lambda e: canvas.itemconfig(rows_window, width=e.width)) first_entry = None for name in self._names: row = tk.Frame(rows_frame, bg=self._ROW_BG, pady=3) row.pack(fill="x", pady=2) tk.Label(row, text=name, bg=self._ROW_BG, fg=self._FG, font=("Segoe UI", 9, "bold"), width=18, anchor="w").pack( side="left", padx=(8, 4)) var = tk.StringVar(value=str(self._current.get(name, ""))) entry = tk.Entry(row, textvariable=var, bg=self._ENTRY_BG, fg=self._FG, insertbackground=self._FG, font=("Segoe UI", 9), relief="flat") entry.pack(side="left", fill="x", expand=True, padx=(0, 8), ipady=3) self._rows.append((name, var)) if first_entry is None: first_entry = entry if first_entry is not None: first_entry.focus_set() btns = tk.Frame(self, bg=self._BG) btns.pack(fill="x", padx=14, pady=(4, 12)) tk.Button(btns, text="Cancel", bg=self._BTN_BG, fg=self._FG, relief="flat", padx=12, font=("Segoe UI", 9), command=self._cancel).pack(side="right", padx=(6, 0)) tk.Button(btns, text="Send to Device", bg=self._SAVE_BG, fg=self._FG, relief="flat", padx=14, font=("Segoe UI", 9, "bold"), command=self._submit).pack(side="right") def _submit(self): result = {name: var.get() for name, var in self._rows} try: self._on_submit(result) except Exception as e: messagebox.showerror("Variable Request", str(e), parent=self) return self.destroy() def _cancel(self): try: self._on_submit(None) except Exception: pass self.destroy()