"""Shared constants and key mappings for the ATOMS3 MacroPad app.""" import os APP_NAME = "ATOMS3 MacroPad" APP_VERSION = "1.0.0" DEVICE_ID = "ATOMS3-MACROPAD" ESPRESSIF_VID = 0x303A SCREEN_W = 128 SCREEN_H = 128 # Project-relative storage. utils/constants.py → repo root → config/ _PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) APPDATA_DIR = os.path.join(_PROJECT_ROOT, "config") PROJECT_FILE = os.path.join(APPDATA_DIR, "project.json") IMAGES_DIR = os.path.join(APPDATA_DIR, "images") PROFILES_DIR = os.path.join(APPDATA_DIR, "profiles") PROFILES_META_FILE = os.path.join(APPDATA_DIR, "profiles_meta.json") BACKUPS_DIR = os.path.join(APPDATA_DIR, "backups") DEFAULT_PROFILE_NAME = "Default" DEFAULT_HOLD_MS = 500 DEFAULT_TYPE_DELAY = 15 DEFAULT_ORIENTATION = 0 DEFAULT_RESUME_DELAY = 0 # seconds, 0 = disabled DEFAULT_COMBO_PRE_MS = 500 # ms delay before key combo is sent DEFAULT_COMBO_POST_MS = 500 # ms delay after key combo is sent DEFAULT_PROBE_TIMEOUT_MS = 300 # ms to wait for host LED response in PC Alive Check DEFAULT_MEDIA_HOLD_MS = 100 # ms to hold media key before release DEFAULT_TYPE_SHIFT_EXTRA_MS = 25 # extra ms per shifted char (uppercase, !@#$ etc.) DEFAULT_TYPE_SETTLE_MS = 150 # ms after last char to let HID reports drain # Pause-screen text-box margins (pixels at the device's 128x128 LCD). # These control where the pause-text wrapping/truncation box sits inside # the screen and are mirrored 1:1 into the firmware's drawWrapped() call. DEFAULT_PAUSE_MARGIN_LEFT = 4 DEFAULT_PAUSE_MARGIN_RIGHT = 4 DEFAULT_PAUSE_MARGIN_TOP = 16 DEFAULT_PAUSE_MARGIN_BOTTOM = 12 # Device LCD dimensions — used by the pause preview to render at the # same proportions as the device. DEVICE_SCREEN_W = 128 DEVICE_SCREEN_H = 128 NODE_TYPES = { "start": {"label": "Start", "color": "#2ECC71", "desc": "Routine starts here"}, "text": {"label": "Type Text", "color": "#4A90D9", "desc": "Type a string of text"}, "combo": {"label": "Key Combo", "color": "#D94A4A", "desc": "Press a key combination"}, "pause": {"label": "Pause", "color": "#D9A84A", "desc": "Wait for click or timer"}, "branch": {"label": "Branch", "color": "#9B59B6", "desc": "Choose between paths"}, "delay": {"label": "Delay", "color": "#7F8C8D", "desc": "Wait milliseconds"}, "repeat": {"label": "Loop", "color": "#27AE60", "desc": "Repeat nodes N times"}, "loop_selector": {"label": "Loop Selector", "color": "#F1C40F", "desc": "Prompt user for loop count"}, "iteration_branch": {"label": "Iteration Branch", "color": "#8E44AD", "desc": "Branch based on loop iteration number"}, "note": {"label": "Note", "color": "#555555", "desc": "On-canvas annotation (not sent to device)"}, "aggregator": {"label": "Aggregator", "color": "#5D6D7E", "desc": "Merge multiple paths into one output"}, "mouse": {"label": "Mouse Click", "color": "#E67E22", "desc": "Send mouse button"}, "media": {"label": "Media Key", "color": "#1ABC9C", "desc": "Media control key"}, "bluetooth": {"label": "Variables", "color": "#0077CC", "desc": "Read/write variables (BLE or local)"}, "rs232": {"label": "RS232 Send", "color": "#8B5CF6", "desc": "Send data via RS232 serial"}, "subroutine": {"label": "Sub-Routine", "color": "#FF6B9D", "desc": "Call a reusable sub-routine"}, "pc_alive_check": {"label": "PC Alive Check", "color": "#F59E0B", "desc": "Check if host PC is responding"}, "macro": {"label": "Macro", "color": "#14B8A6", "desc": "Replay an exact recorded key-press sequence"}, } SUBROUTINES_PROFILE_NAME = "Sub-Routines" RS232_BAUD_RATES = [300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200] RS232_DATA_BITS = [5, 6, 7, 8] RS232_STOP_BITS = ["1", "1.5", "2"] RS232_PARITY = ["none", "even", "odd"] RS232_LINE_ENDINGS = [("None", "none"), ("CR (\\r)", "cr"), ("LF (\\n)", "lf"), ("CRLF (\\r\\n)", "crlf")] MODIFIER_KEYS = ["ctrl", "shift", "alt", "gui", "rctrl", "rshift", "ralt", "rgui"] MODIFIER_LABELS = { "ctrl": "Ctrl", "shift": "Shift", "alt": "Alt", "gui": "Win", "rctrl": "Right Ctrl", "rshift": "Right Shift", "ralt": "AltGr", "rgui": "Right Win", } SPECIAL_KEYS = [ "enter", "esc", "backspace", "tab", "space", "delete", "insert", "home", "end", "pageup", "pagedown", "up", "down", "left", "right", "capslock", "numlock", "scrolllock", "printscreen", "pause", "menu", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10", "f11", "f12", "f13", "f14", "f15", "f16", "f17", "f18", "f19", "f20", "f21", "f22", "f23", "f24", ] MOUSE_BUTTONS = ["left", "right", "middle"] MOUSE_ACTIONS = ["click", "double", "press", "release"] MEDIA_ACTIONS = [ ("vol_up", "Volume Up"), ("vol_down", "Volume Down"), ("mute", "Mute"), ("play_pause", "Play/Pause"), ("next", "Next Track"), ("prev", "Previous Track"), ("stop", "Stop"), ("brightness_up", "Brightness Up"), ("brightness_down", "Brightness Down"), ] ORIENTATION_OPTIONS = [ (0, "Normal (0\u00b0)"), (1, "90\u00b0"), (2, "180\u00b0"), (3, "270\u00b0"), ] # (name, GUI hex preview) DISPLAY_COLORS = [ ("white", "#FFFFFF"), ("red", "#FF4444"), ("green", "#44FF88"), ("blue", "#4488FF"), ("yellow", "#FFFF44"), ("cyan", "#44FFFF"), ("magenta", "#FF44FF"), ("orange", "#FFA040"), ] # HID USB usage codes for the "macro" recording node. # Macro events are stored as [t_ms, action, hid_code] tuples. Using raw HID # usage codes (not ASCII) lets us press/release each physical key independently # — essential when modifiers and letters overlap or when multiple keys are # held at once. The firmware calls keyboard.pressRaw() / keyboard.releaseRaw() # with these codes directly, so modifiers never get retyped out from under a # chord. # # Map from Tkinter keysym → HID USB usage code (standard 104-key layout). # Keysym ordering reference: https://www.tcl.tk/man/tcl/TkCmd/keysyms.html TKKEYSYM_TO_HID = { # --- Letters (both cases map to the same physical key) --- **{chr(c): 0x04 + (c - ord('a')) for c in range(ord('a'), ord('z') + 1)}, **{chr(c): 0x04 + (c - ord('A')) for c in range(ord('A'), ord('Z') + 1)}, # --- Top-row digits (1..0) --- "1": 0x1E, "2": 0x1F, "3": 0x20, "4": 0x21, "5": 0x22, "6": 0x23, "7": 0x24, "8": 0x25, "9": 0x26, "0": 0x27, # --- Whitespace / edit --- "Return": 0x28, "KP_Enter": 0x58, "Escape": 0x29, "BackSpace": 0x2A, "Tab": 0x2B, "space": 0x2C, # --- Punctuation (US layout shift-pair maps to same key) --- "minus": 0x2D, "underscore": 0x2D, "equal": 0x2E, "plus": 0x2E, "bracketleft": 0x2F, "braceleft": 0x2F, "bracketright": 0x30, "braceright": 0x30, "backslash": 0x31, "bar": 0x31, "semicolon": 0x33, "colon": 0x33, "apostrophe": 0x34, "quotedbl": 0x34, "grave": 0x35, "asciitilde": 0x35, "comma": 0x36, "less": 0x36, "period": 0x37, "greater": 0x37, "slash": 0x38, "question": 0x38, # Shifted digits — map to the underlying digit key "exclam": 0x1E, "at": 0x1F, "numbersign": 0x20, "dollar": 0x21, "percent": 0x22, "asciicircum": 0x23, "ampersand": 0x24, "asterisk": 0x25, "parenleft": 0x26, "parenright": 0x27, # --- Locks / system --- "Caps_Lock": 0x39, "Num_Lock": 0x53, "Scroll_Lock": 0x47, "Print": 0x46, "Pause": 0x48, "Menu": 0x65, # --- Navigation --- "Insert": 0x49, "Home": 0x4A, "Prior": 0x4B, "Delete": 0x4C, "End": 0x4D, "Next": 0x4E, "Right": 0x4F, "Left": 0x50, "Down": 0x51, "Up": 0x52, # --- Function keys --- **{f"F{i}": 0x3A + (i - 1) for i in range(1, 13)}, # F1..F12 → 0x3A..0x45 **{f"F{i}": 0x68 + (i - 13) for i in range(13, 25)}, # F13..F24 → 0x68..0x73 # --- Modifiers (0xE0..0xE7) --- "Control_L": 0xE0, "Shift_L": 0xE1, "Alt_L": 0xE2, "Super_L": 0xE3, "Control_R": 0xE4, "Shift_R": 0xE5, "Alt_R": 0xE6, "Super_R": 0xE7, # Numpad "KP_Divide": 0x54, "KP_Multiply": 0x55, "KP_Subtract": 0x56, "KP_Add": 0x57, "KP_Decimal": 0x63, **{f"KP_{i}": 0x59 + (i - 1) for i in range(1, 10)}, # KP_1..KP_9 → 0x59..0x61 "KP_0": 0x62, } def hid_code_label(code: int) -> str: """Friendly short label for a HID usage code (for the macro event list).""" # Prefer a pretty name over the raw keysym PRETTY = { 0x28: "Enter", 0x29: "Esc", 0x2A: "Backspace", 0x2B: "Tab", 0x2C: "Space", 0x4C: "Delete", 0x49: "Insert", 0x4A: "Home", 0x4D: "End", 0x4B: "PageUp", 0x4E: "PageDown", 0x4F: "→", 0x50: "←", 0x51: "↓", 0x52: "↑", 0x39: "CapsLock", 0x53: "NumLock", 0x47: "ScrollLock", 0x46: "PrtSc", 0x48: "Pause", 0x65: "Menu", 0x58: "KP Enter", 0x54: "KP /", 0x55: "KP *", 0x56: "KP -", 0x57: "KP +", 0x63: "KP .", 0xE0: "LCtrl", 0xE1: "LShift", 0xE2: "LAlt", 0xE3: "LWin", 0xE4: "RCtrl", 0xE5: "RShift", 0xE6: "RAlt", 0xE7: "RWin", } if code in PRETTY: return PRETTY[code] if 0x04 <= code <= 0x1D: return chr(ord('A') + (code - 0x04)) if 0x1E <= code <= 0x26: return str(1 + (code - 0x1E)) if code == 0x27: return "0" if 0x3A <= code <= 0x45: return f"F{1 + (code - 0x3A)}" if 0x68 <= code <= 0x73: return f"F{13 + (code - 0x68)}" if 0x59 <= code <= 0x61: return f"KP{1 + (code - 0x59)}" if code == 0x62: return "KP0" PUNCT = { 0x2D: "-", 0x2E: "=", 0x2F: "[", 0x30: "]", 0x31: "\\", 0x33: ";", 0x34: "'", 0x35: "`", 0x36: ",", 0x37: ".", 0x38: "/", } return PUNCT.get(code, f"0x{code:02X}") CANVAS_BG = "#1E1E2E" NODE_HEADER_HEIGHT = 24 NODE_MIN_WIDTH = 160 NODE_PORT_RADIUS = 6 NODE_BODY_COLOR = "#2D2D3D" NODE_SELECTED_BORDER = "#FFFFFF" NODE_UPSTREAM_BORDER = "#22C55E" # green — matches input-port color (neighbor wires INTO our input) NODE_DOWNSTREAM_BORDER = "#EF4444" # red — matches output-port color (neighbor is wired FROM our output) # Backwards-compatible alias; older code may still import this name. NODE_CONNECTED_BORDER = NODE_UPSTREAM_BORDER NODE_TEXT_COLOR = "#FFFFFF" NODE_PORT_IN_COLOR = "#4ADE80" NODE_PORT_OUT_COLOR = "#F87171" WIRE_COLOR = "#6B7280" WIRE_SELECTED_COLOR = "#FBBF24" GRID_SIZE = 20 GRID_COLOR = "#2A2A3A"