235 lines
10 KiB
Python
235 lines
10 KiB
Python
"""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"
|