Skip to content

Commit 97ffe5d

Browse files
authored
Add files via upload
1 parent 1251469 commit 97ffe5d

File tree

3 files changed

+269
-0
lines changed

3 files changed

+269
-0
lines changed

Sparky/Sparky.py

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
import tkinter as tk
2+
from tkinter import ttk, filedialog, messagebox
3+
4+
# Modifier masks
5+
MOD_NONE = 0x00
6+
MOD_CTRL = 0x01
7+
MOD_SHIFT = 0x02
8+
MOD_ALT = 0x04
9+
MOD_GUI = 0x08 # Windows (GUI) key
10+
11+
# Keycodes set (partial, extend as needed)
12+
KEY_CODES = {
13+
'a': 0x04, 'b': 0x05, 'c': 0x06, 'd': 0x07, 'e': 0x08, 'f': 0x09, 'g': 0x0A,
14+
'h': 0x0B, 'i': 0x0C, 'j': 0x0D, 'k': 0x0E, 'l': 0x0F, 'm': 0x10, 'n': 0x11,
15+
'o': 0x12, 'p': 0x13, 'q': 0x14, 'r': 0x15, 's': 0x16, 't': 0x17, 'u': 0x18,
16+
'v': 0x19, 'w': 0x1A, 'x': 0x1B, 'y': 0x1C, 'z': 0x1D,
17+
18+
'1': 0x1E, '2': 0x1F, '3': 0x20, '4': 0x21, '5': 0x22,
19+
'6': 0x23, '7': 0x24, '8': 0x25, '9': 0x26, '0': 0x27,
20+
21+
'ENTER': 0x28, 'ESC': 0x29, 'BACKSPACE': 0x2A, 'TAB': 0x2B, 'SPACE': 0x2C,
22+
'MINUS': 0x2D, 'EQUAL': 0x2E, 'LEFTBRACE': 0x2F, 'RIGHTBRACE': 0x30, 'BACKSLASH': 0x31,
23+
'SEMICOLON': 0x33, 'APOSTROPHE': 0x34, 'GRAVE': 0x35, 'COMMA': 0x36, 'DOT': 0x37,
24+
'SLASH': 0x38,
25+
}
26+
27+
SHIFT_CHARACTERS = {
28+
'!': '1', '@': '2', '#': '3', '$': '4', '%': '5',
29+
'^': '6', '&': '7', '*': '8', '(': '9', ')': '0',
30+
'_': 'MINUS', '+': 'EQUAL', '{': 'LEFTBRACE', '}': 'RIGHTBRACE',
31+
'|': 'BACKSLASH', ':': 'SEMICOLON', '"': 'APOSTROPHE',
32+
'~': 'GRAVE', '<': 'COMMA', '>': 'DOT', '?': 'SLASH'
33+
}
34+
35+
def char_to_keycode(c):
36+
if c.isalpha():
37+
mod = MOD_SHIFT if c.isupper() else MOD_NONE
38+
key = KEY_CODES[c.lower()]
39+
return (mod, key)
40+
elif c.isdigit():
41+
return (MOD_NONE, KEY_CODES[c])
42+
elif c == ' ':
43+
return (MOD_NONE, KEY_CODES['SPACE'])
44+
elif c in SHIFT_CHARACTERS:
45+
mod = MOD_SHIFT
46+
base_char = SHIFT_CHARACTERS[c]
47+
key = KEY_CODES[base_char]
48+
return (mod, key)
49+
else:
50+
symbol_map = {
51+
'-': 'MINUS', '=': 'EQUAL', '[': 'LEFTBRACE', ']': 'RIGHTBRACE',
52+
'\\': 'BACKSLASH', ';': 'SEMICOLON', '\'': 'APOSTROPHE', '`': 'GRAVE',
53+
',': 'COMMA', '.': 'DOT', '/': 'SLASH'
54+
}
55+
if c in symbol_map:
56+
return (MOD_NONE, KEY_CODES[symbol_map[c]])
57+
return (MOD_NONE, KEY_CODES['SPACE']) # fallback
58+
59+
def convert_duckyscript_to_arduino(code: str, layout='US', sketch_name='duckify_sketch'):
60+
lines = code.strip().splitlines()
61+
output = []
62+
output_message = ("""
63+
//+-----------------------------------+
64+
//|This sketch is converted by Sparky.|
65+
//| .-------. |
66+
//| By | PXZYA | |
67+
//| '-------' |
68+
//+-----------------------------------+
69+
""")
70+
output.append(output_message)
71+
output.append(f'//-> Platform: Digispark')
72+
output.append(f'//-> Keyboard Layout: {layout}\n')
73+
output.append('#include "DigiKeyboard.h"\n')
74+
75+
string_arrays = []
76+
string_index = 0
77+
setup_lines = []
78+
79+
default_delay = 0
80+
has_default_delay = False
81+
82+
def progmem_array(string):
83+
arr = []
84+
for c in string:
85+
mod, key = char_to_keycode(c)
86+
arr.append(mod)
87+
arr.append(key)
88+
return arr
89+
90+
for line in lines:
91+
stripped = line.strip()
92+
if not stripped or stripped.startswith('//'):
93+
# Preserve comments
94+
if stripped.startswith('//'):
95+
output.append(stripped)
96+
continue
97+
98+
parts = stripped.split()
99+
cmd = parts[0].upper()
100+
101+
if cmd == 'DELAY':
102+
ms = int(parts[1]) if len(parts) > 1 and parts[1].isdigit() else 0
103+
setup_lines.append(f' DigiKeyboard.delay({ms}); // DELAY {ms}')
104+
continue
105+
106+
if cmd == 'DEFAULTDELAY' or cmd == 'DEFAULT_DELAY':
107+
ms = int(parts[1]) if len(parts) > 1 and parts[1].isdigit() else 0
108+
default_delay = ms
109+
has_default_delay = True
110+
setup_lines.append(f' DigiKeyboard.delay({ms}); // DEFAULTDELAY {ms}')
111+
continue
112+
113+
if cmd == 'STRING':
114+
string_content = stripped[7:]
115+
array_name = f'key_arr_{string_index}'
116+
string_index += 1
117+
bytes_arr = progmem_array(string_content)
118+
bytes_str = ', '.join(str(b) for b in bytes_arr)
119+
output.append(f'// {string_content}')
120+
output.append(f'const uint8_t {array_name}[] PROGMEM = {{{bytes_str}}};')
121+
setup_lines.append(f' duckyString({array_name}, sizeof({array_name})); // STRING {string_content}')
122+
if has_default_delay:
123+
setup_lines.append(f' DigiKeyboard.delay({default_delay});')
124+
continue
125+
126+
if cmd == 'ENTER':
127+
setup_lines.append(f' DigiKeyboard.sendKeyStroke({KEY_CODES["ENTER"]}, 0); // ENTER')
128+
if has_default_delay:
129+
setup_lines.append(f' DigiKeyboard.delay({default_delay});')
130+
continue
131+
132+
# Handle modifiers + key combos like GUI r
133+
modifiers = 0
134+
keycode = None
135+
mod_map = {'CTRL': MOD_CTRL, 'CONTROL': MOD_CTRL, 'ALT': MOD_ALT, 'SHIFT': MOD_SHIFT, 'GUI': MOD_GUI, 'WINDOWS': MOD_GUI}
136+
137+
# Count modifiers in all but last token, last token is key
138+
for p in parts[:-1]:
139+
if p.upper() in mod_map:
140+
modifiers |= mod_map[p.upper()]
141+
last_token = parts[-1].lower()
142+
143+
if last_token in KEY_CODES:
144+
keycode = KEY_CODES[last_token]
145+
elif len(last_token) == 1:
146+
# single char
147+
mod, kc = char_to_keycode(last_token)
148+
if mod != MOD_NONE:
149+
modifiers |= mod
150+
keycode = kc
151+
else:
152+
# unknown fallback to 'r'
153+
keycode = KEY_CODES['r']
154+
155+
if cmd.upper() in mod_map and len(parts) == 1:
156+
# Single modifier press? Ignore for now or could handle if needed
157+
continue
158+
159+
# Compose sendKeyStroke for modifier+key
160+
setup_lines.append(f' DigiKeyboard.sendKeyStroke({keycode}, {modifiers}); // {" ".join(parts)}')
161+
if has_default_delay:
162+
setup_lines.append(f' DigiKeyboard.delay({default_delay});')
163+
164+
output.append('')
165+
output.append('void duckyString(const uint8_t* keys, size_t len) { ')
166+
output.append(' for(size_t i=0; i<len; i+=2) {')
167+
output.append(' DigiKeyboard.sendKeyStroke(pgm_read_byte_near(keys + i+1), pgm_read_byte_near(keys + i));')
168+
output.append(' }')
169+
output.append('}\n')
170+
171+
output.append('void setup() {')
172+
output.append(' pinMode(1, OUTPUT); // Enable LED')
173+
output.append(' digitalWrite(1, LOW); // Turn LED off')
174+
output.append(' DigiKeyboard.sendKeyStroke(0); // Tell computer no key is pressed\n')
175+
176+
output.extend(setup_lines)
177+
178+
output.append('}\n')
179+
output.append('void loop() {}')
180+
output.append('')
181+
output.append('// Created by Sparky')
182+
183+
return '\n'.join(output)
184+
185+
186+
class sparklingGUI:
187+
def __init__(self, root):
188+
self.root = root
189+
self.root.title("Sparky Script Converter")
190+
self.root.geometry("470x700")
191+
self.root.configure(bg="#f4f6f8")
192+
self.root.iconbitmap("src/icon.ico")
193+
194+
195+
# Title
196+
tk.Label(root, text="Sparky", font=("Segoe UI", 18, "bold"), bg="#f4f6f8", fg="#000000").pack(pady=10)
197+
tk.Label(root, text="Convert Ducky Script → Arduino (.ino) for Digispark", bg="#f4f6f8").pack(pady=5)
198+
199+
# Layout / name inputs
200+
frame_top = tk.Frame(root, bg="#f4f6f8")
201+
frame_top.pack(fill=tk.X, padx=20, pady=10)
202+
203+
tk.Label(frame_top, text="Keyboard Layout:", bg="#f4f6f8").grid(row=0, column=0, sticky="w")
204+
self.layout_var = tk.StringVar(value="US")
205+
ttk.Combobox(frame_top, textvariable=self.layout_var, values=["US", "DE", "FR", "GB"], width=10, state="readonly").grid(row=0, column=1, padx=5)
206+
207+
tk.Label(frame_top, text="Sketch Name:", bg="#f4f6f8").grid(row=0, column=2, sticky="w", padx=(20, 0))
208+
self.name_var = tk.StringVar(value="sparky_sketch")
209+
tk.Entry(frame_top, textvariable=self.name_var, width=20).grid(row=0, column=3, padx=5)
210+
211+
# Script input
212+
tk.Label(root, text="Ducky Script Input:", bg="#f4f6f8").pack(anchor="w", padx=20)
213+
self.script_text = tk.Text(root, height=12, font=("Consolas", 11))
214+
self.script_text.pack(fill=tk.BOTH, expand=False, padx=20, pady=(0, 10))
215+
216+
# Buttons
217+
frame_buttons = tk.Frame(root, bg="#f4f6f8")
218+
frame_buttons.pack(pady=5)
219+
ttk.Button(frame_buttons, text="Convert", command=self.convert_script).grid(row=0, column=0, padx=10)
220+
ttk.Button(frame_buttons, text="Save as .ino", command=self.save_file).grid(row=0, column=1, padx=10)
221+
ttk.Button(frame_buttons, text="Clear", command=self.clear_text).grid(row=0, column=2, padx=10)
222+
ttk.Button(frame_buttons, text="Exit", command=root.quit).grid(row=0, column=3, padx=10)
223+
224+
# Output
225+
tk.Label(root, text="Arduino IDE Sketch Output (.ino):", bg="#f4f6f8").pack(anchor="w", padx=20)
226+
self.output_text = tk.Text(root, height=18, font=("Consolas", 11), bg="#f0f0f0")
227+
self.output_text.pack(fill=tk.BOTH, expand=True, padx=20, pady=(0, 20))
228+
self.output_text.configure(state="disabled")
229+
230+
def convert_script(self):
231+
script = self.script_text.get("1.0", tk.END)
232+
layout = self.layout_var.get()
233+
name = self.name_var.get().strip() or "sparkling_sketch"
234+
try:
235+
result = convert_duckyscript_to_arduino(script, layout, name)
236+
self.output_text.configure(state="normal")
237+
self.output_text.delete("1.0", tk.END)
238+
self.output_text.insert(tk.END, result)
239+
self.output_text.configure(state="disabled")
240+
# messagebox.showinfo("Success", "Conversion complete!")
241+
except Exception as e:
242+
messagebox.showerror("Error", str(e))
243+
244+
def save_file(self):
245+
output = self.output_text.get("1.0", tk.END).strip()
246+
if not output:
247+
messagebox.showwarning("No Output", "Please convert a script first.")
248+
return
249+
file = filedialog.asksaveasfilename(
250+
defaultextension=".ino",
251+
filetypes=[("Arduino Sketch", "*.ino"), ("Text files", "*.txt")],
252+
title="Save Arduino Sketch"
253+
)
254+
if file:
255+
with open(file, "w", encoding="utf-8") as f:
256+
f.write(output)
257+
messagebox.showinfo("Saved", f"File saved as:\n{file}")
258+
259+
def clear_text(self):
260+
self.script_text.delete("1.0", tk.END)
261+
self.output_text.configure(state="normal")
262+
self.output_text.delete("1.0", tk.END)
263+
self.output_text.configure(state="disabled")
264+
265+
266+
if __name__ == "__main__":
267+
root = tk.Tk()
268+
app = sparklingGUI(root)
269+
root.mainloop()

Sparky/src/icon.ico

12.5 KB
Binary file not shown.

Sparky/src/icon.png

55.5 KB
Loading

0 commit comments

Comments
 (0)