diff --git a/README.md b/README.md index 5dde3f2..223f2e9 100644 --- a/README.md +++ b/README.md @@ -42,21 +42,24 @@ That is it! Modify `pico_usb.txt` to change the functionality. See below to know - pico_usb.txt - here is where your executable pseudo-code is located. - layout.txt - here is where you select your keyboard layout. -- code.py - interpreter that executes your pesudo code. Free to modify. (1) -- boot.py - this code executes before the USB is recognised. Free to modify. (1) +- code.py - interpreter that executes your pesudo code. Free to modify. +- boot.py - this code executes before the USB is recognised. Free to modify. **pico_usb.txt API:** -- delay() - waits for the specified amount of time before resuming execution. Example: delay(0.8) -- press() - presses one or more buttons once. For example to press enter, use `press(enter)`. To "select all", use `press(control + a)`. -- write() - sequentially presses many buttons in a row. example: `write(Hello world!)` -- hold() - presses and holds down one or more buttons until `release()` is called -- release() - releases **all** held keys -- move(x, y) - moves the mouse on the main display to the given location, from the current location as a reference. negative x = left, possitive x = right, negative y = down, possitive y = up. -- click(btn)- clicks the mouse. `btn` is the mouse button, options are left, right, middle -- scroll(x) - scrolls the mouse. Negative number scrolls down, possitive scroll up -- volume(x) - Modifies the system volume. Negative numbers move the volume slider down by x, possitive move it up by x. min volume = 0. max = 100. `volume(mute)` mutes the speakers. -- loop() - loops everything before this command +- `#` - must be the first character in the line. That line will be ignored. +- `delay` - waits for the specified amount of time before resuming execution. Example: `delay 0.8` +- `press` - presses one or more buttons once. For example to press enter, use `press enter`. To "select all", use `press control + a`. Press also causes the Pico to release all keys after being executed. +- `write` - sequentially presses many buttons in a row. example: `write Hello world!` +- `writefile` - reads a file and uses the contents as keypresses. Make sure the file isn't too large (>100kB). example: `writefile commands.txt` +- `hold` - presses and holds down one or more buttons until `release` is called +- `release` - releases **all** held keys +- `move x, y` - moves the mouse on the main display to the given location, from the current location as a reference. negative x = left, possitive x = right, negative y = down, possitive y = up. +- `click btn` - clicks the mouse. `btn` is the mouse button, options are left, right, middle +- `scroll` - scrolls the mouse. Negative number scrolls down, possitive scroll up +- `volume x` - Modifies the system volume. Negative numbers move the volume slider down by x, possitive move it up by x. min volume = 0. max = 100. `volume mute` mutes the speakers. +- `loop` - loops everything after this command up until the end of the file +- `loop x` - same as loop but stops after x times ## Development diff --git a/src/boot.py b/src/boot.py index bbcdf1b..5fd9fb8 100644 --- a/src/boot.py +++ b/src/boot.py @@ -26,22 +26,16 @@ storage.remount("/", readonly=False) m = storage.getmount("/") m.label = "PicoUSB" -storage.remount("/", readonly=True) -storage.enable_usb_drive() -time.sleep(0.1) #wait a bit so the button gets pulled up +time.sleep(0.05) # Wait a bit so the button gets pulled up if mode.value: storage.disable_usb_drive() else: - time.sleep(0.1) #check again after 100ms to see if the button is still pressed - if mode.value: - storage.disable_usb_drive() - else: - storage.enable_usb_drive() - microcontroller.on_next_reset(microcontroller.RunMode.SAFE_MODE) - microcontroller.reset() - + storage.remount("/", readonly=True) + storage.enable_usb_drive() + microcontroller.on_next_reset(microcontroller.RunMode.SAFE_MODE) + microcontroller.reset() # in case you screw up and disable usb drive without the ability to enable it, to enter safe mode write in shell: # import microcontroller diff --git a/src/code.py b/src/code.py index 1250753..b6010ff 100644 --- a/src/code.py +++ b/src/code.py @@ -1,6 +1,4 @@ import io -import os -import re import time import board import storage @@ -13,10 +11,15 @@ from adafruit_hid.keycode import Keycode from adafruit_hid.mouse import Mouse +from exceptions import PicoCommandException, PicoLayoutException, PicoKeyboardException + cc = ConsumerControl(usb_hid.devices) kb = Keyboard(usb_hid.devices) ms = Mouse(usb_hid.devices) +from adafruit_hid.keyboard_layout_us import KeyboardLayout +layout = KeyboardLayout(kb) + bt = digitalio.DigitalInOut(board.GP25) bt.direction = digitalio.Direction.INPUT bt.pull = digitalio.Pull.UP @@ -25,44 +28,76 @@ led.direction = digitalio.Direction.OUTPUT led.value = True -looping = False -loop_pos = 0 -def execute_command(function, command): - if function == "DELAY": - if command.isdigit(): - time.sleep(float(command)) +def change_layout(layout_id: str): + global layout + global KeyboardLayout + del KeyboardLayout + if layout_id == "US": + from adafruit_hid.keyboard_layout_us import KeyboardLayout + elif layout_id in ("SI", "HR", "BA"): + from keyboard_layouts.keyboard_layout_win_cr import KeyboardLayout + elif layout_id == "GB": + from keyboard_layouts.keyboard_layout_win_uk import KeyboardLayout + elif layout_id == "FR": + from keyboard_layouts.keyboard_layout_win_fr import KeyboardLayout + elif layout_id == "CZ": + from keyboard_layouts.keyboard_layout_win_cz import KeyboardLayout + elif layout_id == "BR": + from keyboard_layouts.keyboard_layout_win_br import KeyboardLayout + elif layout_id == "DE": + from keyboard_layouts.keyboard_layout_win_de import KeyboardLayout + elif layout_id == "ES": + from keyboard_layouts.keyboard_layout_win_es import KeyboardLayout + elif layout_id == "HU": + from keyboard_layouts.keyboard_layout_win_hu import KeyboardLayout + elif layout_id == "IT": + from keyboard_layouts.keyboard_layout_win_it import KeyboardLayout + elif layout_id == "PO": + from keyboard_layouts.keyboard_layout_win_po import KeyboardLayout + elif layout_id == "SE": + from keyboard_layouts.keyboard_layout_win_sw import KeyboardLayout + elif layout_id == "TR": + from keyboard_layouts.keyboard_layout_win_tr import KeyboardLayout + elif layout_id == "BE": + from keyboard_layouts.keyboard_layout_win_bene import KeyboardLayout + else: + raise PicoLayoutException("Unknown keyboard layout") + layout = KeyboardLayout(kb) + + +def execute_command(function: str, command: str): + if function[0] == '#': + return + if function in ("DELAY", "SLEEP"): + time.sleep(float(command)) + elif function == "LAYOUT": + change_layout(command) elif function == "PRESS": - command = command.split(" + ") - for c in range(0, len(command), 1): - command[c] = command[c].upper() - if len(command) <= 6: - keys = [0] * len(command) - for idx in range(0, len(command), 1): - keys[idx] = getattr(Keycode, command[idx]) - kb.send(*keys) - elif function == "WRITE": - layout.write(command) + command: list[str] = [x.strip().upper() for x in command.split("+")] + if len(command) > 6: + raise PicoKeyboardException("Too many keys pressed at once!") + kb.send(*(Keycode.__dict__[k] for k in command)) + elif function in ("WRITE", "WRITELN"): + layout.write(command.replace("\\n", "\n")) + if function == "WRITELN": + kb.send(layout._char_to_keycode('\n')) + elif function == "WRITEFILE": + layout.write(open(command, "r").read().replace("\r", "")) elif function == "HOLD": - command = command.split(" + ") - for c in range(0, len(command), 1): - command[c] = command[c].upper() - if len(command) <= 6: - keys = [0] * len(command) - for idx in range(0, len(command), 1): - keys[idx] = getattr(Keycode, command[idx]) - kb.press(*keys) + command = [x.strip().upper() for x in command.split("+")] + if len(command) > 6: + raise PicoKeyboardException("Too many keys held at once!") + kb.press(*(Keycode.__dict__[k] for k in command)) elif function == "RELEASE": kb.release_all() elif function == "MOVE": - command = command.split(", ") - pos = [0] * 2 - for i in range(0, len(command), 1): - pos[i] = int(command[i]) - ms.move(x=pos[0], y=-1*pos[1], wheel=0) + x, y = [int(a) for a in command.split(',')] + ms.move(x=x, y=-1*y, wheel=0) elif function == "SCROLL": ms.move(x=0, y=0, wheel=int(command)) elif function == "CLICK": + command = command.lower() # We love consistency!! if command == "left": ms.click(Mouse.LEFT_BUTTON) elif command == "middle": @@ -70,92 +105,56 @@ def execute_command(function, command): elif command == "right": ms.click(Mouse.RIGHT_BUTTON) elif function == "VOLUME": - if command.isdigit(): - for vc in range(0, abs(int(command)), 1): - if int(command) > 0: - cc.send(ConsumerControlCode.VOLUME_INCREMENT) - elif int(command) < 0: - cc.send(ConsumerControlCode.VOLUME_DECREMENT) - elif command == "mute": + if command.lower() == "mute": cc.send(ConsumerControlCode.MUTE) - + else: + amount = int(command) + to_send = ConsumerControlCode.VOLUME_INCREMENT + if amount < 0: + amount = -amount + to_send = ConsumerControlCode.VOLUME_DECREMENT + for _ in range(amount): + cc.send(to_send) + else: + raise PicoCommandException("Unknown command") -def get_substr(string, start, end): - command = "" - for idx in range(start+1, end): - command += string[idx] - return command try: - file = io.open("/layout.txt", "r") - line = file.readline() - command = get_substr(line, line.find("("), line.rfind(")")) - if command == "US": - from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS - layout = KeyboardLayoutUS(kb) - elif command == "CRO": - from keyboard_layouts.keyboard_layout_win_cr import KeyboardLayout - layout = KeyboardLayout(kb) - elif command == "UK": - from keyboard_layouts.keyboard_layout_win_uk import KeyboardLayout - layout = KeyboardLayout(kb) - elif command == "FR": - from keyboard_layouts.keyboard_layout_win_fr import KeyboardLayout - layout = KeyboardLayout(kb) - elif command == "CZ": - from keyboard_layouts.keyboard_layout_win_cz import KeyboardLayout - layout = KeyboardLayout(kb) - elif command == "BR": - from keyboard_layouts.keyboard_layout_win_br import KeyboardLayout - layout = KeyboardLayout(kb) - elif command == "DE": - from keyboard_layouts.keyboard_layout_win_de import KeyboardLayout - layout = KeyboardLayout(kb) - elif command == "ES": - from keyboard_layouts.keyboard_layout_win_es import KeyboardLayout - layout = KeyboardLayout(kb) - elif command == "HU": - from keyboard_layouts.keyboard_layout_win_hu import KeyboardLayout - layout = KeyboardLayout(kb) - elif command == "IT": - from keyboard_layouts.keyboard_layout_win_it import KeyboardLayout - layout = KeyboardLayout(kb) - elif command == "PO": - from keyboard_layouts.keyboard_layout_win_po import KeyboardLayout - layout = KeyboardLayout(kb) - elif command == "SW": - from keyboard_layouts.keyboard_layout_win_sw import KeyboardLayout - layout = KeyboardLayout(kb) - elif command == "TR": - from keyboard_layouts.keyboard_layout_win_tr import KeyboardLayout - layout = KeyboardLayout(kb) - elif command == "BE": - from keyboard_layouts.keyboard_layout_win_bene import KeyboardLayout - layout = KeyboardLayout(kb) - file = io.open("/pico_usb.txt", "r") - line = file.readline() - while line != "": - function = line.split("(",1)[0].upper() - command = get_substr(line, line.find("("), line.rfind(")")) - if looping == False: - loop_pos += len(line) - if function == "LOOP": - looping = True + loop_pos = 0 + loop_times = -1 + # It is a good idea to seek and constantly read instead + # of storing the whole file in RAM, as the file could + # potentially be bigger than our tiny RAM + file: io.TextIOWrapper = io.open("/pico_usb.txt", "r") + file.seek(0, 2) # Move to the end of the file + file_end = file.tell() + file.seek(0) + while line := file.readline(): + if not line.strip(): + continue + line = line.rstrip('\r\n').split(" ", 1) + if len(line) == 2: + function, command = line + else: + function = line[0] + command = None + function = function.strip().upper() + if function.upper() == "LOOP": + loop_pos = file.tell() + if command: + loop_times = int(command) + continue execute_command(function, command) - line = file.readline() - file.close() - file = io.open("/pico_usb.txt", "r") - while looping == True: - file.seek(loop_pos) - line = file.readline() - while line != "": - function = line.split("(",1)[0].upper() - command = get_substr(line, line.find("("), line.rfind(")")) - execute_command(function, command) - line = file.readline() - - file.close() - -except OSError as e: - print(e) -kb.release_all() + if loop_pos and file.tell() == file_end: + if not loop_times: + break + file.seek(loop_pos) + # Explicitly check if we set loop_times so we + # don't end up decrementing it and creating + # a huge number (could result in a crash) + if loop_times != -1: + loop_times -= 1 +finally: + kb.release_all() + if file: + file.close() diff --git a/src/example.txt b/src/example.txt index baedcde..45b522e 100644 --- a/src/example.txt +++ b/src/example.txt @@ -1,15 +1,21 @@ # dont forget to change your keyboard layout in layout.txt -delay(3) #on slower computers it is better to have bigger delays. I usually use delay(0.8) on my faster PC. -press(windows + d) # minimizes all windows -delay(2) -press(windows + e) # opens windows explorer on windows -delay(2) -press(control + l) # edit path/link. -delay(2) -write(https://youtu.be/dQw4w9WgXcQ) -delay(2) -press(enter) # Will open the default browser. -delay(2) -press(windows + d) # minimizes all windows -volume(100) +# on slower computers it is better to have bigger delays. I usually use delay(0.8) on my faster PC. +delay 3 +# minimizes all windows +press windows + d +delay 2 +# opens windows explorer on windows +press windows + e +delay 2 +# edit path/link. +press control + l +delay 2 +write https://youtu.be/dQw4w9WgXcQ +delay 2 +# Will open the default browser. +press enter +delay 2 +# minimizes all windows +press windows + d +volume 100 diff --git a/src/exceptions.py b/src/exceptions.py new file mode 100644 index 0000000..3c236f0 --- /dev/null +++ b/src/exceptions.py @@ -0,0 +1,3 @@ +class PicoLayoutException(Exception): pass +class PicoCommandException(Exception): pass +class PicoKeyboardException(Exception): pass diff --git a/src/layout.txt b/src/layout.txt deleted file mode 100644 index aea7cf7..0000000 --- a/src/layout.txt +++ /dev/null @@ -1,21 +0,0 @@ -layout(CRO) - -# to change layout, edit the country layout in between the brackets. Example: layout(US) -# layouts generated using Circuitpython_Keyboard_Layouts -# check it out: https://github.com/Neradoc/Circuitpython_Keyboard_Layouts.git - -#current supported layouts: -# US - United States -# CRO - Croatia/Slovenia/Bosnia -# UK - United Kingdom -# FR - French -# CZ - Czechia -# BR - Brazil -# DE - Germany -# ES - Spain -# HU - Hungary -# IT - Italy -# PO - Poland -# SW - Sweden -# TR - Turkey -# BE - Belgium \ No newline at end of file diff --git a/src/pico_usb.txt b/src/pico_usb.txt index 1c42da4..fcdb83b 100644 --- a/src/pico_usb.txt +++ b/src/pico_usb.txt @@ -1,37 +1,56 @@ # PicoUSB Payloads go in this file -# DO NOT FORGET to change your country keyboard layout in layout.txt! ###################################################################################### ### Commands: ### -### delay() - delays the execution for the number of seconds that is in ### +### delay - delays the execution for the number of seconds that is in ### ### between brackets, example: delay(0.8) ### -### press() - presses once, all together, one or more buttons. for example, ### +### press - presses once, all together, one or more buttons. for example, ### ### to press enter, use press(enter), to "select all", ### ### use press(control + a). ### -### write() - writes down anything that is written between the brackets. ### +### write - writes down anything that is written between the brackets. ### ### example: write(https://www.youtube.com/) ### -### hold() - holds down one or more buttons ### -### release() - releases all held keys (all) ### -### move(x, y) - moves the mouse on the main display to the given location, ### +### hold - holds down one or more buttons ### +### release - releases all held keys (all) ### +### move x, y - moves the mouse on the main display to the given location, ### ### from the current location as a reference. negative x = left, ### ### positive x = right, negative y = down, positive y = up. ### -### click(btn)- btn is the mouse button, options are left, right, middle ### -### scroll(x) - negative number scrolls down, possitive scrolls up ### -### volume(x) - negative number is volume down by x, positive volume up by x. ### +### click btn - btn is the mouse button, options are left, right, middle ### +### scroll x - negative number scrolls down, possitive scrolls up ### +### volume x - negative number is volume down by x, positive volume up by x. ### ### min volume = 0. max = 100. volume(mute) mutes the speakers. ### -### loop() - forever loops everything after the loop command. Use loop only once. ### +### loop - forever loops everything after the loop command. Use loop only once. ### ###################################################################################### -delay(1) -press(windows + d) # minimizes all windows -delay(1) -press(windows + r) # Run window -delay(2) -write(notepad) -press(enter) -delay(2) -write(Hello from PicoUSB!) -delay(1) -loop() -write(!) -move(5, 5) -delay(0.5) +# Current supported layouts: +# SI - Slovenia +# US - United States (default) +# GB - United Kingdom +# FR - French +# CZ - Czechia +# BR - Brazil +# DE - Germany +# ES - Spain +# HU - Hungary +# IT - Italy +# PO - Poland +# SE - Sweden +# TR - Turkey +# BE - Belgium +# CR - Croatia +# BA - Bosnia + +layout US + +delay 1 +press(gui + d) # minimizes all windows +delay 1 +press gui + r # Run window +delay 2 +write notepad +press enter +delay 2 +write Hello from PicoUSB! +delay 1 +loop +write ! +move 5, 5 +delay 0.5