├── .github └── workflows │ └── update-github-pages.yml ├── LICENSE ├── README.md ├── circuitpython ├── boot.py ├── code.py ├── fonts │ └── 6x12.pcf ├── lib │ ├── circuitpython-8.x │ │ ├── adafruit_bitmap_font │ │ │ ├── __init__.py │ │ │ ├── bdf.mpy │ │ │ ├── bitmap_font.mpy │ │ │ ├── glyph_cache.mpy │ │ │ ├── pcf.mpy │ │ │ └── ttf.mpy │ │ ├── adafruit_debouncer.mpy │ │ ├── adafruit_display_shapes │ │ │ ├── __init__.py │ │ │ ├── circle.mpy │ │ │ ├── line.mpy │ │ │ ├── multisparkline.mpy │ │ │ ├── polygon.mpy │ │ │ ├── rect.mpy │ │ │ ├── roundrect.mpy │ │ │ ├── sparkline.mpy │ │ │ └── triangle.mpy │ │ ├── adafruit_display_text │ │ │ ├── __init__.mpy │ │ │ ├── bitmap_label.mpy │ │ │ ├── label.mpy │ │ │ ├── scrolling_label.mpy │ │ │ ├── scrolling_label.mpy.ori │ │ │ └── scrolling_label.py.bak │ │ ├── adafruit_hid │ │ │ ├── __init__.mpy │ │ │ ├── consumer_control.mpy │ │ │ ├── consumer_control_code.mpy │ │ │ ├── keyboard.mpy │ │ │ ├── keyboard_layout_base.mpy │ │ │ ├── keyboard_layout_us.mpy │ │ │ ├── keyboard_layout_win_br.mpy │ │ │ ├── keyboard_layout_win_cz.mpy │ │ │ ├── keyboard_layout_win_da.mpy │ │ │ ├── keyboard_layout_win_de.mpy │ │ │ ├── keyboard_layout_win_es.mpy │ │ │ ├── keyboard_layout_win_fr.mpy │ │ │ ├── keyboard_layout_win_hu.mpy │ │ │ ├── keyboard_layout_win_it.mpy │ │ │ ├── keyboard_layout_win_po.mpy │ │ │ ├── keyboard_layout_win_sw.mpy │ │ │ ├── keyboard_layout_win_tr.mpy │ │ │ ├── keyboard_layout_win_uk.mpy │ │ │ ├── keycode.mpy │ │ │ ├── keycode_win_br.mpy │ │ │ ├── keycode_win_cz.mpy │ │ │ ├── keycode_win_da.mpy │ │ │ ├── keycode_win_de.mpy │ │ │ ├── keycode_win_es.mpy │ │ │ ├── keycode_win_fr.mpy │ │ │ ├── keycode_win_hu.mpy │ │ │ ├── keycode_win_it.mpy │ │ │ ├── keycode_win_po.mpy │ │ │ ├── keycode_win_sw.mpy │ │ │ ├── keycode_win_tr.mpy │ │ │ ├── keycode_win_uk.mpy │ │ │ └── mouse.mpy │ │ ├── adafruit_macropad.mpy │ │ ├── adafruit_midi │ │ │ ├── __init__.mpy │ │ │ ├── channel_pressure.mpy │ │ │ ├── control_change.mpy │ │ │ ├── control_change_values.mpy │ │ │ ├── midi_continue.mpy │ │ │ ├── midi_message.mpy │ │ │ ├── mtc_quarter_frame.mpy │ │ │ ├── note_off.mpy │ │ │ ├── note_on.mpy │ │ │ ├── pitch_bend.mpy │ │ │ ├── polyphonic_key_pressure.mpy │ │ │ ├── program_change.mpy │ │ │ ├── start.mpy │ │ │ ├── stop.mpy │ │ │ ├── system_exclusive.mpy │ │ │ └── timing_clock.mpy │ │ ├── adafruit_pixelbuf.mpy │ │ ├── adafruit_simple_text_display.mpy │ │ ├── adafruit_ticks.mpy │ │ └── neopixel.mpy │ └── circuitpython-9.x │ │ ├── adafruit_bitmap_font │ │ ├── __init__.py │ │ ├── bdf.mpy │ │ ├── bitmap_font.mpy │ │ ├── glyph_cache.mpy │ │ ├── pcf.mpy │ │ └── ttf.mpy │ │ ├── adafruit_debouncer.mpy │ │ ├── adafruit_display_shapes │ │ ├── __init__.py │ │ ├── arc.mpy │ │ ├── circle.mpy │ │ ├── line.mpy │ │ ├── multisparkline.mpy │ │ ├── polygon.mpy │ │ ├── rect.mpy │ │ ├── roundrect.mpy │ │ ├── sparkline.mpy │ │ └── triangle.mpy │ │ ├── adafruit_display_text │ │ ├── __init__.mpy │ │ ├── bitmap_label.mpy │ │ ├── label.mpy │ │ ├── outlined_label.mpy │ │ └── scrolling_label.mpy │ │ ├── adafruit_hid │ │ ├── __init__.mpy │ │ ├── consumer_control.mpy │ │ ├── consumer_control_code.mpy │ │ ├── keyboard.mpy │ │ ├── keyboard_layout_base.mpy │ │ ├── keyboard_layout_us.mpy │ │ ├── keyboard_layout_win_br.mpy │ │ ├── keyboard_layout_win_cz.mpy │ │ ├── keyboard_layout_win_da.mpy │ │ ├── keyboard_layout_win_de.mpy │ │ ├── keyboard_layout_win_es.mpy │ │ ├── keyboard_layout_win_fr.mpy │ │ ├── keyboard_layout_win_hu.mpy │ │ ├── keyboard_layout_win_it.mpy │ │ ├── keyboard_layout_win_po.mpy │ │ ├── keyboard_layout_win_sw.mpy │ │ ├── keyboard_layout_win_tr.mpy │ │ ├── keyboard_layout_win_uk.mpy │ │ ├── keycode.mpy │ │ ├── keycode_win_br.mpy │ │ ├── keycode_win_cz.mpy │ │ ├── keycode_win_da.mpy │ │ ├── keycode_win_de.mpy │ │ ├── keycode_win_es.mpy │ │ ├── keycode_win_fr.mpy │ │ ├── keycode_win_hu.mpy │ │ ├── keycode_win_it.mpy │ │ ├── keycode_win_po.mpy │ │ ├── keycode_win_sw.mpy │ │ ├── keycode_win_tr.mpy │ │ ├── keycode_win_uk.mpy │ │ └── mouse.mpy │ │ ├── adafruit_macropad.mpy │ │ ├── adafruit_midi │ │ ├── __init__.mpy │ │ ├── channel_pressure.mpy │ │ ├── control_change.mpy │ │ ├── control_change_values.mpy │ │ ├── midi_continue.mpy │ │ ├── midi_message.mpy │ │ ├── mtc_quarter_frame.mpy │ │ ├── note_off.mpy │ │ ├── note_on.mpy │ │ ├── pitch_bend.mpy │ │ ├── polyphonic_key_pressure.mpy │ │ ├── program_change.mpy │ │ ├── start.mpy │ │ ├── stop.mpy │ │ ├── system_exclusive.mpy │ │ └── timing_clock.mpy │ │ ├── adafruit_pixelbuf.mpy │ │ ├── adafruit_simple_text_display.mpy │ │ ├── adafruit_ticks.mpy │ │ └── neopixel.mpy └── utils │ ├── __init__.py │ ├── devices.py │ ├── system.py │ └── utils.py ├── dev-notes.txt └── webui ├── css ├── style.css └── style.min.css ├── dev ├── Connection.js ├── index.html └── main.js ├── img ├── macropad-128.png ├── macropad-256.png ├── macropad-512.png ├── macropad-64.png └── macropad-910.png ├── index.html ├── js ├── main.js └── modules │ ├── classes │ ├── Dialogs.js │ ├── KeyContainer.js │ ├── MacroDict.js │ ├── SerialConnectionHandler.js │ ├── Sortable.js │ └── Translation.js │ └── utils.js ├── lang ├── de.json └── template.json ├── scss ├── _animated.scss ├── _bordered-pulled.scss ├── _core.scss ├── _fixed-width.scss ├── _functions.scss ├── _icons.scss ├── _list.scss ├── _mixins.scss ├── _rotated-flipped.scss ├── _screen-reader.scss ├── _shims.scss ├── _sizing.scss ├── _stacked.scss ├── _variables.scss ├── brands.scss ├── fontawesome.scss ├── regular.scss ├── solid.scss ├── style.scss └── v4-shims.scss └── webfonts ├── fa-brands-400.ttf ├── fa-brands-400.woff2 ├── fa-regular-400.ttf ├── fa-regular-400.woff2 ├── fa-solid-900.ttf ├── fa-solid-900.woff2 ├── fa-v4compatibility.ttf └── fa-v4compatibility.woff2 /.github/workflows/update-github-pages.yml: -------------------------------------------------------------------------------- 1 | name: Update GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - 'webui/**' 9 | 10 | workflow_dispatch: 11 | 12 | permissions: 13 | contents: read 14 | pages: write 15 | id-token: write 16 | 17 | concurrency: 18 | group: 'pages' 19 | cancel-in-progress: false 20 | 21 | jobs: 22 | deploy: 23 | environment: 24 | name: github-pages 25 | url: ${{ steps.deployment.outputs.page_url }} 26 | runs-on: ubuntu-latest 27 | 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v3 31 | - name: Setup Pages 32 | uses: actions/configure-pages@v3 33 | - name: Upload artifact 34 | uses: actions/upload-pages-artifact@v2 35 | with: 36 | path: './webui' 37 | - name: Deploy to GitHub Pages 38 | id: deployment 39 | uses: actions/deploy-pages@v2 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MacroPad by MCHilli 2 | 3 | 4 | 5 | This is an [Adafruit MacroPad](https://www.adafruit.com/product/5128) script that allows you to manage your macros via a [**WebUI**](https://mchilli.github.io/macropad/). 6 | 7 | --- 8 | 9 | #### Features: 10 | 11 | - Make groups to organize your macros 12 | - Groups can store more macros or groups 13 | - Define encoder macros for different groups 14 | - Choose colors for every single macro or group 15 | - Up to about **400** key configs, depending on the size of the macros 16 | - Save your configurations locally by downloading it as a JSON file 17 | 18 | - Device settings: 19 | - Choose a keyboard layout suitable for your language 20 | - Set a display timeout to prevent burn-in 21 | - Use a Unicode Font **(increases the font loading time)** 22 | - Flip the rotation of the device by 180 degrees 23 | - Adjust the LCD and LED brightness 24 | 25 | #### Installation: 26 | 27 | Flash circuitpython on to your macropad, following this [guide](https://learn.adafruit.com/adafruit-macropad-rp2040/circuitpython). 28 | 29 | Then just extract the content of "[macropad-circuitpython-x.x.zip](https://github.com/mchilli/macropad/releases/latest/)" to your device. 30 | 31 | Now just configure your macropad over the [WebUI](https://mchilli.github.io/macropad/): 32 | 33 | `Connect` → select `MacroPad by MCHilli` → Create Macros → `Upload` 34 | 35 | If you are happy with your configuration don't forget to `Store`! Otherwise your macros will not be saved on your MacroPad and will also not appear after rebooting your device! 36 | 37 | #### Other Informations: 38 | 39 | Your can enable the USB storage either by pressing the yellow blinking key (top, left) when plugging the device, enable it through the WebUI under "reboot" or you can set a macro with the device function "enable_usb". 40 | 41 | To play **mono** audio files such as .mp3 or .wav, place them on the macropad and define the path to the file in the WebUI. The available files are listed in the macro after the connection has been established. Keep in mind that the macropad is blocked during playback. So make sure that the sound files are not too long! 42 | 43 | #### Ideas: 44 | 45 | - Further translations for the WebUI. If you want to help me there is a template.json in the "lang" folder 46 | - You tell me, feel free to contribute 47 | 48 | --- 49 | 50 | #### Browser limitation: 51 | 52 | The script use the [Web Serial API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API) for communication. The only browsers that currently support this API are _Chrome_, _Edge_ and _Opera_, so unfortunately you'll have to use one of them. 53 | 54 | #### Used libraries and icons: 55 | 56 | - [SortableJS](https://github.com/SortableJS/Sortable) 57 | - [Font Awesome](https://fontawesome.com/) 58 | -------------------------------------------------------------------------------- /circuitpython/boot.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import board 4 | import digitalio 5 | import storage 6 | import supervisor 7 | import usb_cdc 8 | 9 | from utils.utils import path_exist 10 | 11 | USBENABLEDFILE = "usbenabled" 12 | 13 | supervisor.set_usb_identification('MCHilli', 'MacroPad by MCHilli') 14 | 15 | usb_enabled = False 16 | if path_exist(USBENABLEDFILE): 17 | usb_enabled = True 18 | storage.remount("/", readonly=False) 19 | os.remove(USBENABLEDFILE) 20 | storage.remount("/", readonly=True) 21 | 22 | # the yellow blinking button when plug in the macropad 23 | key1 = digitalio.DigitalInOut(board.KEY1) 24 | key1.switch_to_input(pull=digitalio.Pull.UP) 25 | 26 | if not key1.value or usb_enabled: 27 | print("USB enabled") 28 | usb_cdc.enable(console=True, data=True) 29 | else: 30 | print("USB disabled") 31 | usb_cdc.enable(console=False, data=True) 32 | storage.disable_usb_drive() 33 | storage.remount("/", readonly=False) 34 | -------------------------------------------------------------------------------- /circuitpython/code.py: -------------------------------------------------------------------------------- 1 | """ 2 | @Author: MCHilli 3 | """ 4 | 5 | import gc 6 | import json 7 | import time 8 | 9 | import displayio 10 | import terminalio 11 | import storage 12 | import supervisor 13 | import usb_cdc 14 | 15 | from adafruit_bitmap_font.bitmap_font import load_font 16 | from adafruit_display_text.bitmap_label import Label 17 | from adafruit_macropad import MacroPad 18 | from adafruit_hid.consumer_control_code import ConsumerControlCode 19 | from adafruit_hid.mouse import Mouse 20 | 21 | from utils.devices import Encoder, Key 22 | from utils.system import System 23 | from utils.utils import get_audio_files 24 | 25 | gc.enable() 26 | supervisor.runtime.autoreload = False 27 | 28 | VERSION = "1.4.2" 29 | # The file in which the settings are saved 30 | SETTINGSFILE = "settings.json" 31 | # The file in which the macros are saved 32 | MACROFILE = "macros.json" 33 | # The default root configuration 34 | MACRODEFAULT = "{\"type\":\"group\",\"label\":\"Macros\",\"content\":[false,false,false,false,false,false,false,false,false,false,false,false],\"encoder\":{\"switch\":[],\"increased\":[],\"decreased\":[]}}" 35 | # The memory limit on which the garbage collector fired 36 | MEMORYLIMIT = 18000 37 | # The byte size to read out the serial data after a transfer error 38 | READOUTSIZE = 64 39 | 40 | SETTINGS = { 41 | # Time in seconds until the display turns off 42 | "sleeptime": 2, 43 | # Supported keyboard layouts: br, cz, da, de, es, fr, hu, it, po, sw, tr, uk, us 44 | "keyboardlayout": "us", 45 | # Use a unicode bitmap font, which will increas the initial load time! 46 | "useunicodefont": False, 47 | # Flips the rotation of the device by 180 degrees 48 | "fliprotation": False, 49 | # Set the LCD and LED Brightness 50 | "brightness": 0.1 51 | } 52 | 53 | try: 54 | with open(SETTINGSFILE, "rb") as f: 55 | SETTINGS.update(json.load(f)) 56 | except OSError: 57 | pass 58 | 59 | if SETTINGS["keyboardlayout"] == "br": 60 | from adafruit_hid.keyboard_layout_win_br import KeyboardLayout 61 | from adafruit_hid.keycode_win_br import Keycode 62 | elif SETTINGS["keyboardlayout"] == "cz": 63 | from adafruit_hid.keyboard_layout_win_cz import KeyboardLayout 64 | from adafruit_hid.keycode_win_cz import Keycode 65 | elif SETTINGS["keyboardlayout"] == "da": 66 | from adafruit_hid.keyboard_layout_win_da import KeyboardLayout 67 | from adafruit_hid.keycode_win_da import Keycode 68 | elif SETTINGS["keyboardlayout"] == "de": 69 | from adafruit_hid.keyboard_layout_win_de import KeyboardLayout 70 | from adafruit_hid.keycode_win_de import Keycode 71 | elif SETTINGS["keyboardlayout"] == "es": 72 | from adafruit_hid.keyboard_layout_win_es import KeyboardLayout 73 | from adafruit_hid.keycode_win_es import Keycode 74 | elif SETTINGS["keyboardlayout"] == "fr": 75 | from adafruit_hid.keyboard_layout_win_fr import KeyboardLayout 76 | from adafruit_hid.keycode_win_fr import Keycode 77 | elif SETTINGS["keyboardlayout"] == "hu": 78 | from adafruit_hid.keyboard_layout_win_hu import KeyboardLayout 79 | from adafruit_hid.keycode_win_hu import Keycode 80 | elif SETTINGS["keyboardlayout"] == "it": 81 | from adafruit_hid.keyboard_layout_win_it import KeyboardLayout 82 | from adafruit_hid.keycode_win_it import Keycode 83 | elif SETTINGS["keyboardlayout"] == "po": 84 | from adafruit_hid.keyboard_layout_win_po import KeyboardLayout 85 | from adafruit_hid.keycode_win_po import Keycode 86 | elif SETTINGS["keyboardlayout"] == "sw": 87 | from adafruit_hid.keyboard_layout_win_sw import KeyboardLayout 88 | from adafruit_hid.keycode_win_sw import Keycode 89 | elif SETTINGS["keyboardlayout"] == "tr": 90 | from adafruit_hid.keyboard_layout_win_tr import KeyboardLayout 91 | from adafruit_hid.keycode_win_tr import Keycode 92 | elif SETTINGS["keyboardlayout"] == "uk": 93 | from adafruit_hid.keyboard_layout_win_uk import KeyboardLayout 94 | from adafruit_hid.keycode_win_uk import Keycode 95 | else: 96 | from adafruit_hid.keyboard_layout_us import KeyboardLayout 97 | from adafruit_hid.keycode import Keycode 98 | 99 | class MacroApp(): 100 | """ Main Class """ 101 | 102 | def __init__(self) -> None: 103 | self.macropad = MacroPad( 104 | layout_class=KeyboardLayout, 105 | rotation=180 if SETTINGS["fliprotation"] else 0 106 | ) 107 | 108 | self.macropad.display.auto_refresh = False 109 | self.macropad.display.brightness = SETTINGS["brightness"] 110 | self.macropad.display.root_group = displayio.Group() 111 | 112 | self.macropad.pixels.auto_write = False 113 | self.macropad.pixels.brightness = SETTINGS["brightness"] 114 | 115 | self.readonly = storage.getmount('/').readonly 116 | self.serial_data = usb_cdc.data 117 | self.serial_last_state = False 118 | 119 | self.encoder = Encoder(self.macropad) 120 | 121 | self._init_keys() 122 | self._init_group_label() 123 | 124 | self._init_macros() 125 | 126 | def _save_settings(self, new_settings) -> None: 127 | """ store the new settings in the settingsfile 128 | """ 129 | if self.readonly: 130 | return False 131 | with open(SETTINGSFILE, "w") as f: 132 | json.dump(new_settings, f, separators=(",", ":")) 133 | return True 134 | 135 | def _init_macros(self) -> None: 136 | """ initiate the macros 137 | """ 138 | self.macro_store = {} 139 | self.group_stack = ["0"] 140 | 141 | try: 142 | with open(MACROFILE, "r") as f: 143 | self.macro_store = json.load(f) 144 | except Exception: 145 | self.macro_store = {"0": MACRODEFAULT} 146 | 147 | self._init_group() 148 | 149 | def _save_macros(self) -> bool: 150 | """ store the macros in the macro file 151 | """ 152 | if self.readonly: 153 | return False 154 | 155 | with open(MACROFILE, "w") as f: 156 | f.write("{\n") 157 | for i, (key_id, macro) in enumerate(self.macro_store.items()): 158 | macro_str = str(macro).replace("\"", "\\\"") 159 | f.write("\"%s\":\"%s\"" % (key_id, macro_str)) 160 | if i < len(self.macro_store) - 1: 161 | f.write(",\n") 162 | f.write("\n}") 163 | return True 164 | 165 | def _init_group_label(self) -> None: 166 | """ Initialize and add a centered label to the display 167 | """ 168 | self.group_label = Label( 169 | font=load_font( 170 | "/fonts/6x12.pcf") if SETTINGS["useunicodefont"] else terminalio.FONT, 171 | text="", 172 | padding_top=0, 173 | padding_bottom=0, 174 | padding_left=0, 175 | padding_right=0, 176 | color=0xffffff, 177 | anchored_position=( 178 | self.macropad.display.width // 2, 179 | self.macropad.display.height - 10), 180 | anchor_point=(0.5, 0.0) 181 | ) 182 | 183 | self.macropad.display.root_group.append(self.group_label) 184 | 185 | def _init_keys(self) -> None: 186 | """ Initiate the keys and a display group for each key 187 | """ 188 | self.keys = [] 189 | 190 | for i in range(self.macropad.keys.key_count): 191 | label = Label( 192 | font=load_font( 193 | "/fonts/6x12.pcf") if SETTINGS["useunicodefont"] else terminalio.FONT, 194 | text="", 195 | padding_top=0, 196 | padding_bottom=1, 197 | padding_left=4, 198 | padding_right=4, 199 | color=0xffffff, 200 | anchored_position=( 201 | (self.macropad.display.width - 2) / 2 * (i % 3) + 1, 202 | self.macropad.display.height / 5 * (i // 3) + 2), 203 | anchor_point=((i % 3) / 2, 0.0) 204 | ) 205 | 206 | self.keys.append(Key(self.macropad, i, label)) 207 | self.macropad.display.root_group.append(label) 208 | 209 | def _init_group(self) -> None: 210 | """ initiate the group content 211 | """ 212 | self._update_encoder_macros() 213 | self._update_tab() 214 | 215 | gc.collect() 216 | 217 | def run_macro(self, item: tuple[str, list], *args) -> None: 218 | """ run the macro, can be: 219 | Int | Float (e.g. 0.25): delay in seconds 220 | String (e.g. "Foo"): corresponding keys pressed & released 221 | Dict {}: 222 | 'kc': Keycodes (e.g. "SHIFT"): key pressed | (e.g. "-SHIFT"): key released 223 | 'ccc': Consumer Control codes (e.g. "MUTE") 224 | 'mse': Dict {}: 225 | 'x': horizontally Mouse movement (e.g. 10 | -10) 226 | 'y': vertically Mouse movement (e.g. 10 | -10) 227 | 'w': Mousewheel movement (e.g. 1 | -1) 228 | 'b': Buttoncodes (e.g. "LEFT") 229 | 'sys': System Class Methodname 230 | 231 | Args: 232 | item (key_id:str, content:list): the key id and content data 233 | """ 234 | for key in item[1]: 235 | if isinstance(key, (int, float)): 236 | time.sleep(key) 237 | elif isinstance(key, str): 238 | try: 239 | self.macropad.keyboard_layout.write(key) 240 | except ValueError: 241 | # if any of the characters has no keycode 242 | pass 243 | elif isinstance(key, dict): 244 | if 'kc' in key: 245 | key_codes = [ 246 | getattr(Keycode, key_name, None) 247 | for key_name in key['kc'].lstrip('-+').upper().split(',') 248 | if getattr(Keycode, key_name, None) is not None 249 | ] 250 | if key_codes: 251 | if key['kc'] == 'RELALL': 252 | # release all keys 253 | self.macropad.keyboard.release_all() 254 | elif key['kc'][0] == '+': 255 | # tap keys 256 | self.macropad.keyboard.press(*key_codes) 257 | self.macropad.keyboard.release(*key_codes) 258 | elif key['kc'][0] == '-': 259 | # release keys 260 | self.macropad.keyboard.release(*key_codes) 261 | else: 262 | # press keys 263 | self.macropad.keyboard.press(*key_codes) 264 | if 'ccc' in key: 265 | control_code = getattr( 266 | ConsumerControlCode, key['ccc'].upper(), None) 267 | if control_code: 268 | self.macropad.consumer_control.press(control_code) 269 | self.macropad.consumer_control.release() 270 | if 'tone' in key: 271 | self.macropad.play_tone( 272 | key['tone']['frequency'], key['tone']['duration']) 273 | if 'file' in key: 274 | try: 275 | self.macropad.play_file(key['file']) 276 | except Exception: 277 | pass 278 | if 'mse' in key: 279 | if "b" in key["mse"]: 280 | btn = getattr( 281 | Mouse, f"{key['mse']['b'].upper()}_BUTTON", None) 282 | if btn: 283 | self.macropad.mouse.click(btn) 284 | self.macropad.mouse.move( 285 | key["mse"].get('x', 0), 286 | key["mse"].get('y', 0), 287 | key["mse"].get('w', 0)) 288 | if 'sys' in key: 289 | method = getattr(System, key['sys'], None) 290 | if method: 291 | method(self) 292 | 293 | self.macropad.keyboard.release_all() 294 | self.macropad.mouse.release_all() 295 | 296 | def open_group(self, item: tuple[str, list], *args) -> None: 297 | """ open a group 298 | 299 | Args: 300 | item (key_id:str, content:list): the key id and content data 301 | """ 302 | self.group_stack.append(item[0]) 303 | self._init_group() 304 | 305 | def close_group(self, *args) -> None: 306 | """ close a group and go a level up 307 | """ 308 | if len(self.group_stack) > 1: 309 | self.group_stack.pop() 310 | 311 | self._init_group() 312 | 313 | def go_to_root(self, *args) -> None: 314 | """ close a group and go to root 315 | """ 316 | del self.group_stack[1:] 317 | 318 | self._init_group() 319 | 320 | def _update_tab(self) -> None: 321 | """ update the current displayed group tab 322 | """ 323 | key_funcs = { 324 | "macro": self.run_macro, 325 | "group": self.open_group 326 | } 327 | 328 | for key in self.keys: 329 | key.clear_props() 330 | 331 | group = json.loads(self.macro_store[self.group_stack[-1]]) 332 | 333 | for i, key_id in enumerate(group["content"][:self.macropad.keys.key_count]): 334 | if key_id: 335 | macro_data = json.loads(self.macro_store[str(key_id)]) 336 | key_type = macro_data["type"] 337 | 338 | key = self.keys[i] 339 | key.type = key_type 340 | key.label = macro_data["label"] 341 | key.color = macro_data["color"] 342 | key.set_func(key_funcs.get(key_type), (str(key_id), macro_data["content"])) 343 | 344 | self.group_label.text = group["label"] 345 | 346 | for key in self.keys: 347 | key.update_colors() 348 | 349 | def _update_encoder_macros(self) -> None: 350 | """ update the rotary encoder macros defined for opened group 351 | """ 352 | group = json.loads(self.macro_store[self.group_stack[-1]]) 353 | 354 | self.encoder.update_encoder_macros( 355 | on_switch = group.get("encoder", {}).get("switch"), 356 | on_increased = group.get("encoder", {}).get("increased"), 357 | on_decreased = group.get("encoder", {}).get("decreased") 358 | ) 359 | 360 | def _handle_serial_data(self, payload: object) -> dict: 361 | """ handle the data comming over the serial connection 362 | 363 | Args: 364 | payload (object): the data 365 | 366 | Returns: 367 | dict: response, sended over the serial connection 368 | """ 369 | response = {} 370 | 371 | if 'command' not in payload.keys(): 372 | response['ERR'] = 'Wrong payload: %s' % payload 373 | return response 374 | 375 | command = payload['command'] 376 | 377 | if command == 'get_settings': 378 | response['ACK'] = 'settings' 379 | response['CONTENT'] = SETTINGS 380 | return response 381 | 382 | elif command == 'set_settings': 383 | if 'content' not in payload.keys(): 384 | response['ERR'] = 'No content: %s' % payload 385 | return response 386 | 387 | content = payload['content'] 388 | 389 | if self._save_settings(content): 390 | response['ACK'] = 'Settings are set' 391 | else: 392 | response['ERR'] = 'Cannot set settings because USB storage is enabled' 393 | 394 | return response 395 | 396 | elif command == 'get_macros': 397 | self._send_serial_data({ 398 | "ACK":"macros", 399 | "CONTENT": "start" 400 | }) 401 | 402 | for key_id in self.macro_store.keys(): 403 | self._send_serial_data({ 404 | "ACK":"macros", 405 | "ID": key_id, 406 | "CONTENT": self.macro_store[key_id] 407 | }) 408 | 409 | # transfer complete 410 | response['ACK'] = 'macros' 411 | response['CONTENT'] = 'end' 412 | return response 413 | 414 | elif command == 'set_macros': 415 | if 'content' not in payload.keys(): 416 | response['ERR'] = 'No content: %s' % payload 417 | return response 418 | 419 | content = payload['content'] 420 | 421 | if content == "start": 422 | # prepare transfer 423 | self.macro_store = {} 424 | 425 | gc.collect() 426 | return 427 | 428 | elif content == "end": 429 | # transfer complete 430 | self._init_group() 431 | self._display_on() 432 | 433 | else: 434 | self.macro_store[payload['id']] = content 435 | return 436 | 437 | response['ACK'] = 'Macros received' 438 | response['CONTENT'] = len(self.macro_store) - 1 439 | return response 440 | 441 | elif command == 'save_macros': 442 | if self._save_macros(): 443 | response['ACK'] = 'Macros stored' 444 | else: 445 | response['ERR'] = 'Cannot store macros because USB storage is enabled' 446 | 447 | return response 448 | 449 | elif command == 'enable_usb': 450 | System.enable_usb() 451 | 452 | response['ACK'] = 'Enable USB' 453 | return response 454 | 455 | elif command == 'soft_reset': 456 | System.soft_reset() 457 | 458 | response['ACK'] = 'Softreset' 459 | return response 460 | 461 | elif command == 'hard_reset': 462 | System.hard_reset() 463 | 464 | response['ACK'] = 'Hardreset' 465 | return response 466 | 467 | else: 468 | response['ERR'] = 'Unkown command: %s' % command 469 | return response 470 | 471 | def _send_serial_data(self, payload: dict) -> None: 472 | """ prepare and send data over serial connection 473 | 474 | Args: 475 | payload (dict): the data 476 | """ 477 | json.dump(payload, self.serial_data, separators=(',', ':')) 478 | self.serial_data.write(b'\n') 479 | 480 | def _display_on(self) -> None: 481 | """ Turn on the display if it's in sleep mode and reset the sleep timer. 482 | """ 483 | if self.macropad.display_sleep: 484 | self.macropad.display_sleep = False 485 | self.sleep_timer = time.monotonic() 486 | 487 | def start(self) -> None: 488 | """ Start the Mainloop 489 | """ 490 | self.sleep_timer = time.monotonic() 491 | active_key = None 492 | data_error = False 493 | 494 | while True: 495 | # display timeout 496 | if not self.macropad.display_sleep and time.monotonic() - self.sleep_timer > SETTINGS["sleeptime"]: 497 | self.macropad.display_sleep = True 498 | 499 | self.macropad.display.refresh() 500 | 501 | # garbage collection 502 | if gc.mem_free() < MEMORYLIMIT: 503 | gc.collect() 504 | 505 | # send after the connection is established 506 | if self.serial_last_state != self.serial_data.connected: 507 | self.serial_last_state = self.serial_data.connected 508 | if self.serial_data.connected: 509 | self._send_serial_data( 510 | {'ACK': 'version', 'CONTENT': VERSION}) 511 | self._send_serial_data( 512 | {'ACK': 'audiofiles', 'CONTENT': get_audio_files()}) 513 | self._send_serial_data( 514 | {'ACK': 'usbenabled', 'CONTENT': self.readonly}) 515 | 516 | # serial connection 517 | if self.serial_data.connected: 518 | # if an error occured, reload saved macrofile 519 | if data_error and self.serial_data.in_waiting == 0: 520 | data_error = False 521 | self._init_macros() 522 | 523 | gc.collect() 524 | 525 | self._send_serial_data( 526 | {"WARN": 'Reloaded: %s' % MACROFILE}) 527 | 528 | # if an error occured, read all data to out to free the connection 529 | elif data_error: 530 | self.serial_data.read(min(READOUTSIZE, self.serial_data.in_waiting)) 531 | 532 | # try to handle all incoming data 533 | elif self.serial_data.in_waiting > 0: 534 | try: 535 | response = self._handle_serial_data(json.load(self.serial_data)) 536 | if response: 537 | self._send_serial_data(response) 538 | except Exception as e: 539 | # prepare error handling 540 | self._send_serial_data({"ERR": e}) 541 | data_error = True 542 | 543 | # get key events, so no inputs will be stored during connection 544 | # self.macropad.keys.events.get() 545 | # continue 546 | 547 | # key event handling 548 | key_event = self.macropad.keys.events.get() 549 | if key_event: 550 | self._display_on() 551 | if key_event.pressed and not any([key.pressed for key in self.keys]): 552 | self.keys[key_event.key_number].pressed = True 553 | active_key = key_event.key_number 554 | active_key_delay = time.monotonic() 555 | 556 | elif key_event.released and key_event.key_number == active_key: 557 | self.keys[key_event.key_number].pressed = False 558 | active_key = None 559 | 560 | # if a key is pressed continuously, the function triggers again after a short delay 561 | if active_key is not None and time.monotonic() - active_key_delay > 0.75: 562 | self._display_on() 563 | self.keys[active_key].call_func() 564 | 565 | # encoder event handling 566 | if self.encoder.switch and self.encoder.on_switch: 567 | self._display_on() 568 | self.run_macro((self.group_stack[-1], self.encoder.on_switch)) 569 | 570 | if self.encoder.increased and self.encoder.on_increased: 571 | self._display_on() 572 | self.run_macro((self.group_stack[-1], self.encoder.on_increased)) 573 | 574 | if self.encoder.decreased and self.encoder.on_decreased: 575 | self._display_on() 576 | self.run_macro((self.group_stack[-1], self.encoder.on_decreased)) 577 | 578 | MacroApp().start() -------------------------------------------------------------------------------- /circuitpython/fonts/6x12.pcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/fonts/6x12.pcf -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_bitmap_font/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_bitmap_font/__init__.py -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_bitmap_font/bdf.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_bitmap_font/bdf.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_bitmap_font/bitmap_font.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_bitmap_font/bitmap_font.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_bitmap_font/glyph_cache.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_bitmap_font/glyph_cache.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_bitmap_font/pcf.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_bitmap_font/pcf.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_bitmap_font/ttf.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_bitmap_font/ttf.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_debouncer.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_debouncer.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_display_shapes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_display_shapes/__init__.py -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_display_shapes/circle.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_display_shapes/circle.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_display_shapes/line.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_display_shapes/line.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_display_shapes/multisparkline.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_display_shapes/multisparkline.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_display_shapes/polygon.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_display_shapes/polygon.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_display_shapes/rect.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_display_shapes/rect.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_display_shapes/roundrect.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_display_shapes/roundrect.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_display_shapes/sparkline.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_display_shapes/sparkline.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_display_shapes/triangle.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_display_shapes/triangle.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_display_text/__init__.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_display_text/__init__.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_display_text/bitmap_label.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_display_text/bitmap_label.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_display_text/label.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_display_text/label.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_display_text/scrolling_label.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_display_text/scrolling_label.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_display_text/scrolling_label.mpy.ori: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_display_text/scrolling_label.mpy.ori -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_display_text/scrolling_label.py.bak: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2019 Scott Shawcroft for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | """ 6 | `adafruit_display_text.scrolling_label` 7 | ==================================================== 8 | 9 | Displays text into a fixed-width label that scrolls leftward 10 | if the full_text is large enough to need it. 11 | 12 | * Author(s): Tim Cocks 13 | 14 | Implementation Notes 15 | -------------------- 16 | 17 | **Hardware:** 18 | 19 | **Software and Dependencies:** 20 | 21 | * Adafruit CircuitPython firmware for the supported boards: 22 | https://circuitpython.org/downloads 23 | 24 | """ 25 | 26 | __version__ = "0.0.0+auto.0" 27 | __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Text.git" 28 | 29 | import time 30 | from adafruit_display_text import bitmap_label 31 | 32 | try: 33 | from typing import Optional 34 | from fontio import FontProtocol 35 | except ImportError: 36 | pass 37 | 38 | 39 | class ScrollingLabel(bitmap_label.Label): 40 | """ScrollingLabel - A fixed-width label that will scroll to the left 41 | in order to show the full text if it's larger than the fixed-width. 42 | 43 | :param font: The font to use for the label. 44 | :type: ~fontio.FontProtocol 45 | :param int max_characters: The number of characters that sets the fixed-width. Default is 10. 46 | :param str text: The full text to show in the label. If this is longer than 47 | ``max_characters`` then the label will scroll to show everything. 48 | :param float animate_time: The number of seconds in between scrolling animation 49 | frames. Default is 0.3 seconds. 50 | :param int current_index: The index of the first visible character in the label. 51 | Default is 0, the first character. Will increase while scrolling.""" 52 | 53 | # pylint: disable=too-many-arguments 54 | def __init__( 55 | self, 56 | font: FontProtocol, 57 | max_characters: int = 10, 58 | text: Optional[str] = "", 59 | animate_time: Optional[float] = 0.3, 60 | current_index: Optional[int] = 0, 61 | **kwargs 62 | ) -> None: 63 | 64 | super().__init__(font, **kwargs) 65 | self.animate_time = animate_time 66 | self._current_index = current_index 67 | self._last_animate_time = -1 68 | self.max_characters = max_characters 69 | 70 | if text[-1] != " ": 71 | text = "{} ".format(text) 72 | self._full_text = text 73 | 74 | self.update() 75 | 76 | def update(self, force: bool = False) -> None: 77 | """Attempt to update the display. If ``animate_time`` has elapsed since 78 | previews animation frame then move the characters over by 1 index. 79 | Must be called in the main loop of user code. 80 | 81 | :param bool force: whether to ignore ``animation_time`` and force the update. 82 | Default is False. 83 | :return: None 84 | """ 85 | _now = time.monotonic() 86 | if force or self._last_animate_time + self.animate_time <= _now: 87 | 88 | if len(self.full_text) <= self.max_characters: 89 | super()._set_text(self.full_text, self.scale) 90 | self._last_animate_time = _now 91 | return 92 | 93 | if self.current_index + self.max_characters <= len(self.full_text): 94 | _showing_string = self.full_text[ 95 | self.current_index : self.current_index + self.max_characters 96 | ] 97 | else: 98 | _showing_string_start = self.full_text[self.current_index :] 99 | _showing_string_end = "{}".format( 100 | self.full_text[ 101 | : (self.current_index + self.max_characters) 102 | % len(self.full_text) 103 | ] 104 | ) 105 | 106 | _showing_string = "{}{}".format( 107 | _showing_string_start, _showing_string_end 108 | ) 109 | super()._set_text(_showing_string, self.scale) 110 | self.current_index += 1 111 | self._last_animate_time = _now 112 | 113 | return 114 | 115 | @property 116 | def current_index(self) -> int: 117 | """Index of the first visible character. 118 | 119 | :return int: The current index 120 | """ 121 | return self._current_index 122 | 123 | @current_index.setter 124 | def current_index(self, new_index: int) -> None: 125 | if new_index < len(self.full_text): 126 | self._current_index = new_index 127 | else: 128 | self._current_index = new_index % len(self.full_text) 129 | 130 | @property 131 | def full_text(self) -> str: 132 | """The full text to be shown. If it's longer than ``max_characters`` then 133 | scrolling will occur as needed. 134 | 135 | :return str: The full text of this label. 136 | """ 137 | return self._full_text 138 | 139 | @full_text.setter 140 | def full_text(self, new_text: str) -> None: 141 | # if new_text[-1] != " ": 142 | # new_text = "{} ".format(new_text) 143 | self._full_text = new_text 144 | self.current_index = 0 145 | self.update() 146 | 147 | @property 148 | def text(self): 149 | """The full text to be shown. If it's longer than ``max_characters`` then 150 | scrolling will occur as needed. 151 | 152 | :return str: The full text of this label. 153 | """ 154 | return self.full_text 155 | 156 | @text.setter 157 | def text(self, new_text): 158 | self.full_text = new_text 159 | -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/__init__.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/__init__.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/consumer_control.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/consumer_control.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/consumer_control_code.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/consumer_control_code.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/keyboard.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/keyboard.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/keyboard_layout_base.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/keyboard_layout_base.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/keyboard_layout_us.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/keyboard_layout_us.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/keyboard_layout_win_br.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/keyboard_layout_win_br.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/keyboard_layout_win_cz.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/keyboard_layout_win_cz.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/keyboard_layout_win_da.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/keyboard_layout_win_da.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/keyboard_layout_win_de.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/keyboard_layout_win_de.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/keyboard_layout_win_es.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/keyboard_layout_win_es.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/keyboard_layout_win_fr.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/keyboard_layout_win_fr.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/keyboard_layout_win_hu.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/keyboard_layout_win_hu.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/keyboard_layout_win_it.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/keyboard_layout_win_it.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/keyboard_layout_win_po.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/keyboard_layout_win_po.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/keyboard_layout_win_sw.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/keyboard_layout_win_sw.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/keyboard_layout_win_tr.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/keyboard_layout_win_tr.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/keyboard_layout_win_uk.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/keyboard_layout_win_uk.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/keycode.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/keycode.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/keycode_win_br.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/keycode_win_br.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/keycode_win_cz.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/keycode_win_cz.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/keycode_win_da.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/keycode_win_da.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/keycode_win_de.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/keycode_win_de.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/keycode_win_es.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/keycode_win_es.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/keycode_win_fr.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/keycode_win_fr.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/keycode_win_hu.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/keycode_win_hu.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/keycode_win_it.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/keycode_win_it.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/keycode_win_po.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/keycode_win_po.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/keycode_win_sw.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/keycode_win_sw.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/keycode_win_tr.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/keycode_win_tr.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/keycode_win_uk.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/keycode_win_uk.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_hid/mouse.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_hid/mouse.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_macropad.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_macropad.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_midi/__init__.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_midi/__init__.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_midi/channel_pressure.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_midi/channel_pressure.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_midi/control_change.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_midi/control_change.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_midi/control_change_values.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_midi/control_change_values.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_midi/midi_continue.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_midi/midi_continue.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_midi/midi_message.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_midi/midi_message.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_midi/mtc_quarter_frame.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_midi/mtc_quarter_frame.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_midi/note_off.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_midi/note_off.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_midi/note_on.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_midi/note_on.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_midi/pitch_bend.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_midi/pitch_bend.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_midi/polyphonic_key_pressure.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_midi/polyphonic_key_pressure.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_midi/program_change.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_midi/program_change.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_midi/start.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_midi/start.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_midi/stop.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_midi/stop.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_midi/system_exclusive.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_midi/system_exclusive.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_midi/timing_clock.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_midi/timing_clock.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_pixelbuf.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_pixelbuf.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_simple_text_display.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_simple_text_display.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/adafruit_ticks.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/adafruit_ticks.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-8.x/neopixel.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-8.x/neopixel.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_bitmap_font/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_bitmap_font/__init__.py -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_bitmap_font/bdf.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_bitmap_font/bdf.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_bitmap_font/bitmap_font.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_bitmap_font/bitmap_font.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_bitmap_font/glyph_cache.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_bitmap_font/glyph_cache.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_bitmap_font/pcf.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_bitmap_font/pcf.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_bitmap_font/ttf.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_bitmap_font/ttf.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_debouncer.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_debouncer.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_display_shapes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_display_shapes/__init__.py -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_display_shapes/arc.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_display_shapes/arc.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_display_shapes/circle.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_display_shapes/circle.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_display_shapes/line.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_display_shapes/line.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_display_shapes/multisparkline.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_display_shapes/multisparkline.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_display_shapes/polygon.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_display_shapes/polygon.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_display_shapes/rect.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_display_shapes/rect.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_display_shapes/roundrect.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_display_shapes/roundrect.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_display_shapes/sparkline.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_display_shapes/sparkline.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_display_shapes/triangle.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_display_shapes/triangle.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_display_text/__init__.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_display_text/__init__.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_display_text/bitmap_label.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_display_text/bitmap_label.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_display_text/label.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_display_text/label.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_display_text/outlined_label.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_display_text/outlined_label.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_display_text/scrolling_label.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_display_text/scrolling_label.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/__init__.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/__init__.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/consumer_control.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/consumer_control.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/consumer_control_code.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/consumer_control_code.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/keyboard.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/keyboard.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/keyboard_layout_base.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/keyboard_layout_base.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/keyboard_layout_us.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/keyboard_layout_us.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/keyboard_layout_win_br.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/keyboard_layout_win_br.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/keyboard_layout_win_cz.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/keyboard_layout_win_cz.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/keyboard_layout_win_da.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/keyboard_layout_win_da.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/keyboard_layout_win_de.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/keyboard_layout_win_de.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/keyboard_layout_win_es.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/keyboard_layout_win_es.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/keyboard_layout_win_fr.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/keyboard_layout_win_fr.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/keyboard_layout_win_hu.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/keyboard_layout_win_hu.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/keyboard_layout_win_it.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/keyboard_layout_win_it.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/keyboard_layout_win_po.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/keyboard_layout_win_po.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/keyboard_layout_win_sw.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/keyboard_layout_win_sw.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/keyboard_layout_win_tr.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/keyboard_layout_win_tr.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/keyboard_layout_win_uk.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/keyboard_layout_win_uk.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/keycode.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/keycode.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/keycode_win_br.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/keycode_win_br.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/keycode_win_cz.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/keycode_win_cz.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/keycode_win_da.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/keycode_win_da.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/keycode_win_de.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/keycode_win_de.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/keycode_win_es.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/keycode_win_es.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/keycode_win_fr.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/keycode_win_fr.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/keycode_win_hu.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/keycode_win_hu.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/keycode_win_it.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/keycode_win_it.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/keycode_win_po.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/keycode_win_po.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/keycode_win_sw.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/keycode_win_sw.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/keycode_win_tr.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/keycode_win_tr.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/keycode_win_uk.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/keycode_win_uk.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_hid/mouse.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_hid/mouse.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_macropad.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_macropad.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_midi/__init__.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_midi/__init__.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_midi/channel_pressure.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_midi/channel_pressure.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_midi/control_change.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_midi/control_change.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_midi/control_change_values.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_midi/control_change_values.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_midi/midi_continue.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_midi/midi_continue.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_midi/midi_message.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_midi/midi_message.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_midi/mtc_quarter_frame.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_midi/mtc_quarter_frame.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_midi/note_off.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_midi/note_off.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_midi/note_on.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_midi/note_on.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_midi/pitch_bend.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_midi/pitch_bend.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_midi/polyphonic_key_pressure.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_midi/polyphonic_key_pressure.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_midi/program_change.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_midi/program_change.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_midi/start.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_midi/start.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_midi/stop.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_midi/stop.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_midi/system_exclusive.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_midi/system_exclusive.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_midi/timing_clock.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_midi/timing_clock.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_pixelbuf.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_pixelbuf.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_simple_text_display.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_simple_text_display.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/adafruit_ticks.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/adafruit_ticks.mpy -------------------------------------------------------------------------------- /circuitpython/lib/circuitpython-9.x/neopixel.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/lib/circuitpython-9.x/neopixel.mpy -------------------------------------------------------------------------------- /circuitpython/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/circuitpython/utils/__init__.py -------------------------------------------------------------------------------- /circuitpython/utils/devices.py: -------------------------------------------------------------------------------- 1 | from adafruit_macropad import MacroPad 2 | from adafruit_display_text.label import Label 3 | 4 | from utils.utils import center 5 | 6 | class Encoder(): 7 | """ Handles the rotary encoder """ 8 | def __init__(self, macropad:MacroPad) -> None: 9 | self._macropad = macropad 10 | self._last_position = 0 11 | 12 | self.update_encoder_macros() 13 | 14 | @property 15 | def switch(self) -> bool: 16 | return self.switch_debounced 17 | 18 | @property 19 | def switch_debounced(self) -> bool: 20 | self._macropad.encoder_switch_debounced.update() 21 | return self._macropad.encoder_switch_debounced.pressed 22 | 23 | @property 24 | def increased(self) -> bool: 25 | postion = self._macropad.encoder 26 | if postion > self._last_position: 27 | self._last_position = postion 28 | return True 29 | return False 30 | 31 | @property 32 | def decreased(self) -> bool: 33 | postion = self._macropad.encoder 34 | if postion < self._last_position: 35 | self._last_position = postion 36 | return True 37 | return False 38 | 39 | @property 40 | def on_switch(self) -> None: 41 | return self._on_switch 42 | 43 | @property 44 | def on_increased(self) -> None: 45 | return self._on_increased 46 | 47 | @property 48 | def on_decreased(self) -> None: 49 | return self._on_decreased 50 | 51 | def update_encoder_macros(self, on_switch:function=None, on_increased:function=None, on_decreased:function=None) -> None: 52 | self._on_switch = on_switch 53 | self._on_increased = on_increased 54 | self._on_decreased = on_decreased 55 | 56 | 57 | class Key(): 58 | """ Handles a single key """ 59 | def __init__(self, macropad:MacroPad, index:int, label:Label) -> None: 60 | self._macropad = macropad 61 | self._index = index 62 | self._pressed = False 63 | self._label = label 64 | 65 | self.clear_props() 66 | 67 | @property 68 | def pressed(self) -> bool: 69 | """ Status Property 70 | """ 71 | return self._pressed 72 | 73 | @pressed.setter 74 | def pressed(self, pressed:bool) -> None: 75 | self._pressed = pressed 76 | self._on_pressed() if self.pressed else self._on_released() 77 | 78 | @property 79 | def label(self) -> str: 80 | """ Label Property 81 | """ 82 | return self._label.text 83 | 84 | @label.setter 85 | def label(self, label:str) -> None: 86 | self._label.text = center(label, 6, ' ') if len(label) <= 6 else label[:6] 87 | 88 | @property 89 | def type(self) -> str: 90 | """ Type Property 91 | """ 92 | return self._type 93 | 94 | @type.setter 95 | def type(self, type:str) -> None: 96 | self._type = type 97 | 98 | @property 99 | def color(self) -> tuple: 100 | """ Color Property 101 | """ 102 | return self._color 103 | 104 | @color.setter 105 | def color(self, color:tuple) -> None: 106 | self._color = color 107 | 108 | def update_colors(self) -> None: 109 | """ update the backgroundcolor and color based on type 110 | """ 111 | if self.type in [None, "group"]: 112 | self._label.background_color = 0x000000 113 | self._label.color = 0xffffff 114 | else: 115 | self._label.background_color = 0xffffff 116 | self._label.color = 0x000000 117 | 118 | self._set_led(self.color) 119 | 120 | def _set_led(self, color:list[int, int, int] | str) -> None: 121 | """ set and update the led color 122 | 123 | Args: 124 | color (list | string): the led color (R, G, B) | rrggbb 125 | """ 126 | if isinstance(color, str): 127 | color = (int(color[0:2], 16), int(color[2:4], 16), int(color[4:6], 16)) 128 | self._macropad.pixels[self._index] = color 129 | self._macropad.pixels.show() 130 | 131 | def clear_props(self) -> None: 132 | """ clear all properties so the key is off 133 | """ 134 | self._label.text = "" 135 | self._type = None 136 | self._color = '000000' 137 | self._func = None 138 | self._func_args = None 139 | 140 | def set_func(self, func:function, args:dict = None) -> None: 141 | """ set the function which called when the key is pressed 142 | 143 | Args: 144 | func (function): the function which called on key press 145 | args (dict, optional): optionally arguments passed to func. Defaults to None. 146 | """ 147 | self._func = func 148 | self._func_args = args 149 | 150 | def call_func(self) -> None: 151 | """ calls the function if setted with set_func 152 | """ 153 | if not self._func: 154 | return 155 | if self._func_args: 156 | return self._func(self._func_args) 157 | return self._func() 158 | 159 | def _on_pressed(self) -> None: 160 | """ Action that triggered when Key is pressed 161 | """ 162 | if self._func: 163 | self._set_led('ffffff') 164 | self.call_func() 165 | 166 | def _on_released(self) -> None: 167 | """ Action that triggered when Key is released 168 | """ 169 | self._set_led(self.color) 170 | 171 | -------------------------------------------------------------------------------- /circuitpython/utils/system.py: -------------------------------------------------------------------------------- 1 | import microcontroller 2 | import supervisor 3 | 4 | USBENABLEDFILE = "usbenabled" 5 | 6 | class System(): 7 | def enable_usb(app=None) -> None: 8 | try: 9 | with open(USBENABLEDFILE, "a") as f: pass 10 | System.hard_reset() 11 | except Exception: 12 | pass 13 | 14 | def soft_reset(app=None) -> None: 15 | supervisor.reload() 16 | 17 | def hard_reset(app=None) -> None: 18 | microcontroller.reset() 19 | 20 | def close_group(app=None) -> None: 21 | app.close_group() 22 | 23 | def go_to_root(app=None) -> None: 24 | app.go_to_root() 25 | 26 | def decrease_brightness(app=None) -> None: 27 | if app.macropad.display.brightness > 0: 28 | brightness = (round(app.macropad.display.brightness * 10) - 1) / 10 29 | app.macropad.display.brightness = brightness 30 | app.macropad.pixels.brightness = brightness 31 | app.macropad.pixels.show() 32 | 33 | def increase_brightness(app=None) -> None: 34 | if app.macropad.display.brightness < 1: 35 | brightness = (round(app.macropad.display.brightness * 10) + 1) / 10 36 | app.macropad.display.brightness = brightness 37 | app.macropad.pixels.brightness = brightness 38 | app.macropad.pixels.show() -------------------------------------------------------------------------------- /circuitpython/utils/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | def to_chunks(lst, n): 4 | for i in range(0, len(lst), n): 5 | yield lst[i:i + n] 6 | 7 | def center(string:str, width:int = 0, sep:str = ' '): 8 | left = (width - len(string)) // 2 9 | right = width - len(string) - left 10 | return f"{sep * left}{string}{sep * right}" 11 | 12 | def path_exist(path): 13 | try: 14 | os.stat(path) 15 | return True 16 | except OSError: 17 | return False 18 | 19 | def list_files(directory, current_path=""): 20 | ignore_dirs = {"/fonts", "/utils", "/lib"} 21 | 22 | for item in os.listdir(directory + current_path): 23 | full_path = directory + current_path + "/" + item 24 | relative_path = current_path + "/" + item 25 | 26 | if full_path.startswith("/"): 27 | full_path = full_path[1:] 28 | 29 | if relative_path in ignore_dirs: 30 | continue 31 | 32 | try: 33 | os.listdir(directory + current_path + "/" + item) 34 | yield from list_files(directory, relative_path) 35 | except OSError: 36 | yield full_path 37 | 38 | def get_audio_files() -> list: 39 | audio_files = [] 40 | for file_path in list_files("/"): 41 | if file_path.endswith("mp3") or file_path.endswith("wav"): 42 | audio_files.append(file_path[1:]) 43 | return audio_files -------------------------------------------------------------------------------- /dev-notes.txt: -------------------------------------------------------------------------------- 1 | Key Types: 2 | "group": a group containing more groups or macros 3 | "macro": a macro that will be executed 4 | "blank": a blank spacer for formating 5 | 6 | Macro Types: 7 | 8 | Wait (e.g. 0.1) 9 | 10 | String Input (e.g. 'Hello World') 11 | 12 | Keycodes (e.g. {'kc': 'CONTROL'}): 13 | A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 14 | ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, 15 | ENTER, RETURN, ESCAPE, BACKSPACE, TAB, SPACEBAR, SPACE, MINUS, EQUALS, LEFT_BRACKET, RIGHT_BRACKET, 16 | BACKSLASH, POUND, SEMICOLON, QUOTE, GRAVE_ACCENT, COMMA, PERIOD, FORWARD_SLASH, CAPS_LOCK, 17 | F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18, F19, F20, F21, F22, 18 | F23, F24, PRINT_SCREEN, SCROLL_LOCK, PAUSE, INSERT, HOME, PAGE_UP, DELETE, END, PAGE_DOWN, 19 | RIGHT_ARROW, LEFT_ARROW, DOWN_ARROW, UP_ARROW, KEYPAD_NUMLOCK, KEYPAD_FORWARD_SLASH, KEYPAD_ASTERISK, 20 | KEYPAD_MINUS, KEYPAD_PLUS, KEYPAD_ENTER, KEYPAD_ONE, KEYPAD_TWO, KEYPAD_THREE, KEYPAD_FOUR, KEYPAD_FIVE, 21 | KEYPAD_SIX, KEYPAD_SEVEN, KEYPAD_EIGHT, KEYPAD_NINE, KEYPAD_ZERO, KEYPAD_PERIOD, KEYPAD_BACKSLASH, 22 | APPLICATION, POWER, KEYPAD_EQUALS, LEFT_CONTROL, CONTROL, LEFT_SHIFT, SHIFT, LEFT_ALT, ALT, OPTION, 23 | LEFT_GUI, GUI, WINDOWS, COMMAND, RIGHT_CONTROL, RIGHT_SHIFT, RIGHT_ALT, RIGHT_GUI 24 | 25 | Consumer Control Codes (e.g. {'ccc': 'PLAY_PAUSE'}): 26 | MUTE, VOLUME_INCREMENT, VOLUME_DECREMENT, RECORD, FAST_FORWARD, REWIND, SCAN_NEXT_TRACK, 27 | SCAN_PREVIOUS_TRACK, STOP, EJECT, PLAY_PAUSE, BRIGHTNESS_DECREMENT, BRIGHTNESS_INCREMENT 28 | 29 | Mouse Events (e.g. {'mse': {'x': 10, 'y': -10, 'w': 1, 'b': 'LEFT'}}): 30 | 'x': horizontally Mouse movement (e.g. 10 | -10) 31 | 'y': vertically Mouse movement (e.g. 10 | -10) 32 | 'w': Mousewheel movement (e.g. 1 | -1) 33 | 'b': LEFT, MIDDLE, RIGHT 34 | 35 | System Functions (e.g. {'sys': 'soft_reset'}): 36 | enable_USB, soft_reset, hard_reset, decrease_brightness, increase_brightness 37 | 38 | 39 | 40 | Serial Connection Commands: 41 | get_macros, set_macros, save_macros, enable_usb, soft_reset, hard_reset 42 | 43 | Keyboardlayouts: 44 | br, cz, da, de, es, fr, hu, it, po, sw, tr, uk, us 45 | 46 | 47 | Browser limitation: 48 | only Chromebased Browsers 49 | 50 | Used libraries: 51 | SortableJS - https://github.com/SortableJS/Sortable 52 | Font Awesome - https://fontawesome.com/ 53 | -------------------------------------------------------------------------------- /webui/dev/Connection.js: -------------------------------------------------------------------------------- 1 | class LineBreakTransformer { 2 | constructor() { 3 | // A container for holding stream data until a new line. 4 | this.chunks = ''; 5 | } 6 | 7 | transform(chunk, controller) { 8 | // Append new chunks to existing chunks. 9 | this.chunks += chunk; 10 | // For each line breaks in chunks, send the parsed lines out. 11 | const lines = this.chunks.split('\n'); 12 | this.chunks = lines.pop(); 13 | lines.forEach((line) => controller.enqueue(line)); 14 | } 15 | 16 | flush(controller) { 17 | // When the stream is closed, flush any remaining chunks out. 18 | controller.enqueue(this.chunks); 19 | } 20 | } 21 | 22 | class SerialConnectionHandler { 23 | constructor({ 24 | port = port, 25 | onReceived = this.onReceived, 26 | onConnectionChanged = this.onConnectionChanged, 27 | } = {}) { 28 | this.port = port; 29 | this.onReceived = onReceived; 30 | this.onConnectionChanged = onConnectionChanged; 31 | 32 | this.connected = false; 33 | 34 | this._open(); 35 | } 36 | 37 | async _init() { 38 | const writerTextEncoder = new TextEncoderStream(); 39 | this.writableStreamClosed = writerTextEncoder.readable.pipeTo(this.port.writable); 40 | this.writer = writerTextEncoder.writable.getWriter(); 41 | 42 | const readerTextDecoder = new TextDecoderStream(); 43 | this.readableStreamClosed = this.port.readable.pipeTo(readerTextDecoder.writable); 44 | this.reader = readerTextDecoder.readable 45 | .pipeThrough(new TransformStream(new LineBreakTransformer())) 46 | .getReader(); 47 | 48 | await this._listen(); 49 | } 50 | 51 | async _open() { 52 | await this.port 53 | .open({ baudRate: 9600 }) 54 | .then(async () => { 55 | console.log('Success: Port open'); 56 | 57 | this.connected = true; 58 | this.onConnectionChanged(this.connected); 59 | 60 | await this._init(); 61 | }) 62 | .catch((e) => { 63 | console.error('Error: Can`t open Port'); 64 | }); 65 | } 66 | 67 | async close() { 68 | if (this.port.readable) { 69 | this.writer.close(); 70 | await this.writableStreamClosed; 71 | 72 | this.reader.cancel(); 73 | await this.readableStreamClosed.catch(() => {}); 74 | 75 | await this.port.close(); 76 | console.log('Success: Port closed'); 77 | 78 | this.connected = false; 79 | this.onConnectionChanged(this.connected); 80 | } 81 | } 82 | 83 | async _listen() { 84 | try { 85 | // Listen to data coming from the serial device. 86 | while (true) { 87 | const { value, done } = await this.reader.read(); 88 | if (done) { 89 | // Allow the serial port to be closed later. 90 | this.reader.releaseLock(); 91 | break; 92 | } 93 | 94 | let payload = JSON.parse(value); 95 | this.onReceived(payload); 96 | } 97 | } catch (e) {} 98 | } 99 | 100 | async send(payload) { 101 | if (this.port.writable) { 102 | await this.writer.write(JSON.stringify(payload) + '\n'); 103 | } 104 | } 105 | 106 | onReceived() {} 107 | onConnectionChanged() {} 108 | } 109 | -------------------------------------------------------------------------------- /webui/dev/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MCHPad Web UI 7 | 8 | 9 | 10 |
11 |
12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 |
23 |
24 |         Macro Types:
25 |             "group": a group containing more groups or macros
26 |             "macro": a macro that will be executed
27 |             "blank": a blank spacer for formating
28 |     
29 |         Keycodes (kc):
30 |             A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z,
31 |             ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE,
32 |             ENTER, RETURN, ESCAPE, BACKSPACE, TAB, SPACEBAR, SPACE, MINUS, EQUALS, LEFT_BRACKET, RIGHT_BRACKET,
33 |             BACKSLASH, POUND, SEMICOLON, QUOTE, GRAVE_ACCENT, COMMA, PERIOD, FORWARD_SLASH, CAPS_LOCK,
34 |             F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18, F19, F20, F21, F22,
35 |             F23, F24, PRINT_SCREEN, SCROLL_LOCK, PAUSE, INSERT, HOME, PAGE_UP, DELETE, END, PAGE_DOWN,
36 |             RIGHT_ARROW, LEFT_ARROW, DOWN_ARROW, UP_ARROW, KEYPAD_NUMLOCK, KEYPAD_FORWARD_SLASH, KEYPAD_ASTERISK,
37 |             KEYPAD_MINUS, KEYPAD_PLUS, KEYPAD_ENTER, KEYPAD_ONE, KEYPAD_TWO, KEYPAD_THREE, KEYPAD_FOUR, KEYPAD_FIVE,
38 |             KEYPAD_SIX, KEYPAD_SEVEN, KEYPAD_EIGHT, KEYPAD_NINE, KEYPAD_ZERO, KEYPAD_PERIOD, KEYPAD_BACKSLASH,
39 |             APPLICATION, POWER, KEYPAD_EQUALS, LEFT_CONTROL, CONTROL, LEFT_SHIFT, SHIFT, LEFT_ALT, ALT, OPTION,
40 |             LEFT_GUI, GUI, WINDOWS, COMMAND, RIGHT_CONTROL, RIGHT_SHIFT, RIGHT_ALT, RIGHT_GUI
41 |     
42 |         Consumer Control Codes (ccc):
43 |             MUTE, VOLUME_INCREMENT, VOLUME_DECREMENT, RECORD, FAST_FORWARD, REWIND, SCAN_NEXT_TRACK,
44 |             SCAN_PREVIOUS_TRACK, STOP, EJECT, PLAY_PAUSE, BRIGHTNESS_DECREMENT, BRIGHTNESS_INCREMENT
45 |     
46 |         Play Tone (tone):
47 |             'frequency': the frequency of the tone (e.g. 392 | 951 | 1253)
48 |             'duration': the duration in seconds (e.g. 0.1 | 1.2)
49 |     
50 |         Mouse Events (mse):
51 |             'x': horizontally Mouse movement (e.g. 10 | -10)
52 |             'y': vertically Mouse movement (e.g. 10 | -10)
53 |             'w': Mousewheel movement (e.g. 1 | -1)
54 |             'b': LEFT, MIDDLE, RIGHT
55 |     
56 |         System Functions (sys):
57 |             enable_USB, soft_reset, hard_reset, close_group, go_to_root, decrease_brightness, increase_brightness
58 |     
59 |         Serial Connection Commands:
60 |             get_macros, set_macros, save_macros, enable_usb, soft_reset, hard_reset
61 |             
62 |
63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /webui/dev/main.js: -------------------------------------------------------------------------------- 1 | let connection = {}; 2 | const connectionBtn = document.querySelector('#connection'); 3 | const sendBtn = document.querySelector('#send'); 4 | const cmdBtns = document.querySelectorAll('.simple-cmd'); 5 | const data = document.querySelector('#data'); 6 | 7 | function payloadHandler(payload) { 8 | if ('ERR' in payload) { 9 | console.warn('Error: ' + payload.ERR); 10 | } else if ('ACK' in payload) { 11 | switch (payload.ACK) { 12 | case 'macros': 13 | data.value = JSON.stringify(payload.CONTENT, null, 2); 14 | break; 15 | } 16 | } 17 | } 18 | 19 | function disableBtns(disable) { 20 | sendBtn.disabled = disable; 21 | cmdBtns.forEach((element) => { 22 | element.disabled = disable; 23 | }); 24 | } 25 | 26 | connectionBtn.addEventListener('click', async (event) => { 27 | if (connection.connected) { 28 | connection.close(); 29 | data.value = ''; 30 | connectionBtn.innerText = 'Connect'; 31 | disableBtns(true); 32 | } else { 33 | if ('serial' in navigator) { 34 | await navigator.serial 35 | .requestPort() 36 | .then((port) => { 37 | connection = new SerialConnectionHandler({ 38 | port: port, 39 | onReceived: payloadHandler, 40 | }); 41 | connectionBtn.innerText = 'Disonnected'; 42 | disableBtns(false); 43 | }) 44 | .catch((e) => { 45 | console.warn('Error: No port selected'); 46 | }); 47 | } 48 | } 49 | }); 50 | 51 | sendBtn.addEventListener('click', async () => { 52 | if (data.value != '') { 53 | try { 54 | await connection.send({ 55 | command: 'set_macros', 56 | content: JSON.parse(data.value), 57 | }); 58 | } catch (e) { 59 | console.error('can`t parse json string'); 60 | } 61 | } 62 | }); 63 | 64 | cmdBtns.forEach((element) => { 65 | element.addEventListener('click', async () => { 66 | await connection.send({ 67 | command: element.dataset.cmd, 68 | }); 69 | }); 70 | }); 71 | 72 | // triggers when the device is unplugged 73 | navigator.serial.addEventListener('disconnect', (event) => { 74 | connection = {}; 75 | data.value = ''; 76 | connectionBtn.innerText = 'Connect'; 77 | }); 78 | -------------------------------------------------------------------------------- /webui/img/macropad-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/webui/img/macropad-128.png -------------------------------------------------------------------------------- /webui/img/macropad-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/webui/img/macropad-256.png -------------------------------------------------------------------------------- /webui/img/macropad-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/webui/img/macropad-512.png -------------------------------------------------------------------------------- /webui/img/macropad-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/webui/img/macropad-64.png -------------------------------------------------------------------------------- /webui/img/macropad-910.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/webui/img/macropad-910.png -------------------------------------------------------------------------------- /webui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MacroPad WebUI 7 | 8 | 9 | 10 | 11 | 12 |
13 |

MacroPad WebUI

14 |
15 | 16 |
17 |
18 |
19 |
20 |
21 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /webui/js/modules/classes/KeyContainer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as utils from '../utils.js'; 4 | 5 | /** 6 | * Represents a KeyContainer class for managing keyboard keys. 7 | * @class KeyContainer 8 | */ 9 | export default class KeyContainer { 10 | /** 11 | * Creates an instance of KeyContainer. 12 | * @constructor 13 | * @param {Object} options - The options for initializing the KeyContainer. 14 | * @param {string} [options.type='blank'] - The type of the key container. 15 | * @param {string} [options.label=''] - The label for the key container. 16 | * @param {number[]} [options.color=[255, 255, 255]] - The color of the key container. 17 | * @param {Array} [options.content=[]] - The content of the key container. 18 | * @param {Object} [options.encoder={}] - The encoder configuration. 19 | * @param {Function} [options.onButtonPressed=()=>{}] - The callback function for button presses. 20 | */ 21 | constructor({ 22 | type = 'blank', 23 | label = '', 24 | color = [255, 255, 255], 25 | content = [], 26 | encoder = {}, 27 | onButtonPressed = () => {}, 28 | } = {}) { 29 | this.type = type; 30 | this.label = label; 31 | this.color = color; 32 | this.content = content; 33 | 34 | const defaultEncoder = { 35 | switch: [], 36 | increased: [], 37 | decreased: [], 38 | }; 39 | this.encoder = { ...defaultEncoder, ...encoder }; 40 | 41 | this.onButtonPressed = onButtonPressed; 42 | 43 | this.DOM = this._initDOM(); 44 | this.setType(this.type); 45 | 46 | return this.DOM.container; 47 | } 48 | 49 | /** 50 | * Initializes the DOM elements for the KeyContainer. 51 | * @returns {Object} - An object containing DOM elements. 52 | */ 53 | _initDOM() { 54 | let DOM = {}; 55 | 56 | DOM.container = utils.create({ 57 | attributes: { 58 | class: 'key-container', 59 | style: `background-color: #${utils.validateHex(this.color)}`, 60 | }, 61 | children: [ 62 | (DOM.type = utils.create({ 63 | attributes: { 64 | title: utils.capitalize(this.type), 65 | class: 'key-type-icon', 66 | }, 67 | children: [ 68 | utils.create({ 69 | type: 'i', 70 | attributes: { 71 | class: this._getTypeIcon(), 72 | }, 73 | }), 74 | ], 75 | events: { 76 | click: (event) => event.stopPropagation(), 77 | }, 78 | })), 79 | utils.create({ 80 | attributes: { 81 | title: _('Move'), 82 | class: 'key-handle button', 83 | }, 84 | children: [ 85 | utils.create({ 86 | type: 'i', 87 | attributes: { 88 | class: 'fa-solid fa-up-down-left-right', 89 | }, 90 | }), 91 | ], 92 | events: { 93 | click: (event) => event.stopPropagation(), 94 | }, 95 | }), 96 | (DOM.label = utils.create({ 97 | text: this.label, 98 | attributes: { 99 | class: 'key-label', 100 | }, 101 | })), 102 | utils.create({ 103 | attributes: { 104 | class: 'key-controls', 105 | }, 106 | children: [ 107 | utils.create({ 108 | attributes: { 109 | title: _('Delete'), 110 | class: 'button delete', 111 | }, 112 | children: [ 113 | utils.create({ 114 | type: 'i', 115 | attributes: { 116 | class: 'fa-solid fa-trash', 117 | }, 118 | }), 119 | ], 120 | events: { 121 | click: (event) => this.onButtonPressed(event, this, 'delete'), 122 | }, 123 | }), 124 | utils.create({ 125 | attributes: { 126 | title: _('Edit'), 127 | class: 'button edit', 128 | }, 129 | children: [ 130 | utils.create({ 131 | type: 'i', 132 | attributes: { 133 | class: 'fa-solid fa-pen', 134 | }, 135 | }), 136 | ], 137 | events: { 138 | click: (event) => this.onButtonPressed(event, this, 'edit'), 139 | }, 140 | }), 141 | ], 142 | }), 143 | ], 144 | events: { 145 | click: (event) => this.onButtonPressed(event, this, this.clickEventCommand), 146 | }, 147 | }); 148 | 149 | DOM.container.instance = this; 150 | 151 | return DOM; 152 | } 153 | 154 | /** 155 | * Set the click event command based on the element's type and content. 156 | * If a matching action is found, it sets the corresponding command; otherwise, it sets to false. 157 | * If the element's type is neither 'group' nor 'macro', it sets the command to false. 158 | */ 159 | _setClickEventCommand() { 160 | const actions = { 161 | close_group: 'close', 162 | go_to_root: 'root', 163 | }; 164 | 165 | switch (this.type) { 166 | case 'group': 167 | this.clickEventCommand = 'open'; 168 | break; 169 | case 'macro': 170 | const action = this.content.find((macro) => actions[macro.sys]); 171 | 172 | this.clickEventCommand = action ? actions[action.sys] : false; 173 | break; 174 | default: 175 | this.clickEventCommand = false; 176 | break; 177 | } 178 | 179 | this.DOM.container.classList.toggle('clickable', this.clickEventCommand); 180 | } 181 | 182 | /** 183 | * Clears the data of the KeyContainer instance. 184 | */ 185 | clearData() { 186 | this.setType(); 187 | this.setLabel(); 188 | this.setColor(); 189 | this.setContent(); 190 | this.setEncoder(); 191 | } 192 | 193 | /** 194 | * Retrieves the data associated with the KeyContainer. 195 | * @returns {Object} - The data associated with the KeyContainer. 196 | */ 197 | getData() { 198 | switch (this.type) { 199 | case 'blank': 200 | return { 201 | type: this.type, 202 | }; 203 | case 'macro': 204 | return { 205 | type: this.type, 206 | color: this.color, 207 | label: this.label, 208 | content: this.content, 209 | }; 210 | case 'group': 211 | return { 212 | type: this.type, 213 | color: this.color, 214 | label: this.label, 215 | content: this.content, 216 | encoder: this.encoder, 217 | }; 218 | } 219 | } 220 | 221 | /** 222 | * Retrieves all data associated with the KeyContainer. 223 | * @returns {Object} - The data associated with the KeyContainer. 224 | */ 225 | getAllData() { 226 | return { 227 | type: this.type, 228 | color: this.color, 229 | label: this.label, 230 | content: this.content, 231 | encoder: this.encoder, 232 | }; 233 | } 234 | 235 | /** 236 | * Sets the type of the KeyContainer. 237 | * @param {string} [type='blank'] - The type to set for the KeyContainer. 238 | */ 239 | setType(type = 'blank') { 240 | this.type = type; 241 | 242 | this.DOM.container.classList.remove('blank', 'macro', 'group'); 243 | this.DOM.container.classList.add(this.type); 244 | 245 | this._setClickEventCommand(); 246 | 247 | this.DOM.type.classList.toggle('invisible', this.type === 'blank'); 248 | this.DOM.type.title = _(utils.capitalize(this.type)); 249 | this.DOM.type.children[0].className = this._getTypeIcon(); 250 | } 251 | 252 | /** 253 | * Returns the CSS class for the type icon based on the KeyContainer's type. 254 | * @returns {string} - The CSS class for the type icon. 255 | */ 256 | _getTypeIcon() { 257 | switch (this.type) { 258 | case 'blank': 259 | return ''; 260 | case 'macro': 261 | return 'fa-solid fa-cubes'; 262 | case 'group': 263 | return 'fa-solid fa-folder-open'; 264 | 265 | default: 266 | break; 267 | } 268 | } 269 | 270 | /** 271 | * Sets the label for the KeyContainer. 272 | * @param {string} [label=''] - The label to set for the KeyContainer. 273 | */ 274 | setLabel(label = '') { 275 | this.label = label; 276 | this.DOM.label.innerText = this.label; 277 | } 278 | 279 | /** 280 | * Sets the color for the KeyContainer. 281 | * @param {string} [color='ffffff'] - The color to set for the KeyContainer. 282 | */ 283 | setColor(color = 'ffffff') { 284 | this.color = utils.validateHex(color); 285 | utils.style(this.DOM.container, { 286 | 'background-color': `#${this.color}`, 287 | }); 288 | } 289 | 290 | /** 291 | * Sets the content for the KeyContainer. 292 | * @param {Array} [content=[]] - The content to set for the KeyContainer. 293 | */ 294 | setContent(content = []) { 295 | this.content = content; 296 | this._setClickEventCommand(); 297 | } 298 | 299 | /** 300 | * Sets the encoder configuration for the KeyContainer. 301 | * @param {Object} [encoder={switch: [], increased: [], decreased: []}] - The encoder configuration to set. 302 | */ 303 | setEncoder( 304 | encoder = { 305 | switch: [], 306 | increased: [], 307 | decreased: [], 308 | } 309 | ) { 310 | this.encoder = encoder; 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /webui/js/modules/classes/SerialConnectionHandler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class LineBreakTransformer { 4 | constructor() { 5 | // A container for holding stream data until a new line. 6 | this.chunks = ''; 7 | } 8 | 9 | transform(chunk, controller) { 10 | // Append new chunks to existing chunks. 11 | this.chunks += chunk; 12 | // For each line breaks in chunks, send the parsed lines out. 13 | const lines = this.chunks.split('\n'); 14 | this.chunks = lines.pop(); 15 | lines.forEach((line) => controller.enqueue(line)); 16 | } 17 | 18 | flush(controller) { 19 | // When the stream is closed, flush any remaining chunks out. 20 | controller.enqueue(this.chunks); 21 | } 22 | } 23 | 24 | export default class SerialConnectionHandler { 25 | constructor({ port, onReceived = () => {}, onConnectionChanged = () => {} } = {}) { 26 | this.port = port; 27 | this.onReceived = onReceived; 28 | this.onConnectionChanged = onConnectionChanged; 29 | 30 | this.connected = false; 31 | 32 | this.writer = null; 33 | this.writableStreamClosed = null; 34 | this.reader = null; 35 | this.readableStreamClosed = null; 36 | 37 | this._init(); 38 | } 39 | 40 | async _init() { 41 | try { 42 | await this.port.open({ baudRate: 9600 }); 43 | console.log('Success: Port open'); 44 | 45 | this.connected = true; 46 | this.onConnectionChanged(this.connected); 47 | 48 | const writerTextEncoder = new TextEncoderStream(); 49 | this.writableStreamClosed = writerTextEncoder.readable.pipeTo(this.port.writable); 50 | this.writer = writerTextEncoder.writable.getWriter(); 51 | 52 | const readerTextDecoder = new TextDecoderStream(); 53 | this.readableStreamClosed = this.port.readable.pipeTo(readerTextDecoder.writable); 54 | this.reader = readerTextDecoder.readable 55 | .pipeThrough(new TransformStream(new LineBreakTransformer())) 56 | .getReader(); 57 | 58 | await this._listen(); 59 | } catch (error) { 60 | console.error('Error during initialization:', error); 61 | } 62 | } 63 | 64 | async close() { 65 | try { 66 | if (this.port.readable) { 67 | this.writer.close(); 68 | await this.writableStreamClosed; 69 | 70 | this.reader.cancel(); 71 | await this.readableStreamClosed.catch(() => {}); 72 | 73 | await this.port.close(); 74 | console.log('Success: Port closed'); 75 | this.connected = false; 76 | this.onConnectionChanged(this.connected); 77 | } 78 | } catch (error) { 79 | console.error('Error during close:', error); 80 | } 81 | } 82 | 83 | async _listen() { 84 | try { 85 | while (true) { 86 | const { value, done } = await this.reader.read(); 87 | if (done) { 88 | this.reader.releaseLock(); 89 | break; 90 | } 91 | 92 | let payload; 93 | try { 94 | payload = JSON.parse(value); 95 | this.onReceived(payload); 96 | } catch (parseError) { 97 | console.error('Error parsing JSON:', parseError); 98 | } 99 | } 100 | } catch (error) { 101 | console.error('Error while listening:', error); 102 | this.connected = false; 103 | this.onConnectionChanged(this.connected); 104 | } 105 | } 106 | 107 | async send(payload) { 108 | try { 109 | if (this.port.writable) { 110 | await this.writer.write(JSON.stringify(payload) + '\n'); 111 | } 112 | } catch (error) { 113 | console.error('Error while sending:', error); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /webui/js/modules/classes/Translation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default class Translation { 4 | constructor() { 5 | this.translations = {}; 6 | this.currentLanguage = null; 7 | } 8 | 9 | async loadLanguage(language) { 10 | try { 11 | if (language !== 'en') { 12 | const response = await fetch(`lang/${language}.json`); 13 | this.translations[language] = await response.json(); 14 | } 15 | this.currentLanguage = language; 16 | } catch (error) { 17 | console.error(`Failed to load translations for '${language}', loaded 'en' instead`); 18 | } 19 | } 20 | 21 | translate(key) { 22 | const language = this.currentLanguage; 23 | 24 | if (!language || language === 'en') { 25 | return key; 26 | } 27 | 28 | const translationsForLanguage = this.translations[language]; 29 | if (!translationsForLanguage) { 30 | return key; 31 | } 32 | 33 | const translation = translationsForLanguage[key]; 34 | return translation || key; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /webui/js/modules/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Creates an HTML element with specified attributes, text, children, and event listeners. 5 | * @param {Object} options - An object containing options for creating the element. 6 | * @param {string} options.type - The type of HTML element to create (default: 'div'). 7 | * @param {string} options.text - The text content for the created element (default: undefined). 8 | * @param {Object} options.attributes - An object containing attributes to set on the element (default: {}). 9 | * @param {Array} options.children - An array of child elements to append (default: []). 10 | * @param {Object} options.events - An object containing event listeners to attach (default: {}). 11 | * @returns {HTMLElement} - The created HTML element. 12 | */ 13 | export function create({ 14 | type = 'div', 15 | text = undefined, 16 | attributes = {}, 17 | children = [], 18 | events = {}, 19 | } = {}) { 20 | const element = document.createElement(type); 21 | for (const [attribute, value] of Object.entries(attributes)) { 22 | element.setAttribute(attribute, value); 23 | } 24 | if (text !== undefined) { 25 | element.innerHTML = text; 26 | } 27 | for (const child of children) { 28 | element.append(child); 29 | } 30 | for (const [event, handler] of Object.entries(events)) { 31 | element.addEventListener(event, handler); 32 | } 33 | return element; 34 | } 35 | 36 | /** 37 | * Sets CSS styles on a specified HTML element. 38 | * @param {HTMLElement} target - The target element to apply the styles to. 39 | * @param {Object} attributes - An object containing CSS style properties and their values. 40 | * @throws {Error} - Throws an error if the target is not a valid HTMLElement. 41 | */ 42 | export function style(target = document.documentElement, attributes = {}) { 43 | if (!(target instanceof HTMLElement)) { 44 | throw new Error('The target must be a valid HTMLElement.'); 45 | } 46 | 47 | for (const [attribute, value] of Object.entries(attributes)) { 48 | if (typeof value === 'string' || typeof value === 'number') { 49 | target.style.setProperty(attribute, String(value)); 50 | } else { 51 | console.warn(`Invalid value '${value}' for attribute '${attribute}'. Ignored.`); 52 | } 53 | } 54 | } 55 | 56 | /** 57 | * Appends a list of elements to a specified container element. 58 | * @param {Element} container - The container element to which the elements will be appended. 59 | * @param {Array} elements - An array of elements to append. 60 | */ 61 | export function appendElements(container, elements) { 62 | for (const element of elements) { 63 | container.appendChild(element); 64 | } 65 | } 66 | 67 | /** 68 | * Generates a random unique identifier (ID) consisting of alphanumeric characters. 69 | * @returns {string} - A random unique identifier. 70 | */ 71 | export function uniqueId() { 72 | let ID = ''; 73 | const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; 74 | for (let i = 0; i < 12; i++) { 75 | ID += characters.charAt(Math.floor(Math.random() * 36)); 76 | } 77 | return ID; 78 | } 79 | 80 | /** 81 | * Capitalizes the first letter of a given string. 82 | * @param {string} s - The input string to capitalize. 83 | * @returns {string} - The input string with the first letter capitalized. 84 | */ 85 | export function capitalize(s) { 86 | return s[0].toUpperCase() + s.slice(1); 87 | } 88 | 89 | /** 90 | * Converts a hexadecimal color string to an RGB color array. 91 | * @param {string} hexcolor - The hexadecimal color string (e.g., '#RRGGBB'). 92 | * @returns {Array} - An array representing the RGB color values [R, G, B]. 93 | */ 94 | export function hexToRGB(hexcolor) { 95 | const r = parseInt(hexcolor.substring(1, 3), 16); 96 | const g = parseInt(hexcolor.substring(3, 5), 16); 97 | const b = parseInt(hexcolor.substring(5, 7), 16); 98 | return [r, g, b]; 99 | } 100 | 101 | /** 102 | * Converts an RGB color array to a hexadecimal color string. 103 | * @param {Array} rgb - An array representing the RGB color values [R, G, B]. 104 | * @returns {string} - The hexadecimal color string (e.g. 'RRGGBB'). 105 | */ 106 | export function rgbToHex([r, g, b]) { 107 | const intToHex = (c) => { 108 | const hex = c.toString(16); 109 | return hex.length == 1 ? '0' + hex : hex; 110 | }; 111 | return intToHex(r) + intToHex(g) + intToHex(b); 112 | } 113 | 114 | /** 115 | * Validates and converts RGB color objects to hex format if needed. 116 | * @param {string|object} color - The color in hex format or RGB object. 117 | * @returns {string} - The color in hex format (e.g. 'RRGGBB'). 118 | */ 119 | export function validateHex(color) { 120 | return typeof color === 'object' ? rgbToHex(color) : color; 121 | } 122 | 123 | /** 124 | * Converts a JavaScript object to a JSON file and offers it for download. 125 | * @param {Object} obj - The JavaScript object to be converted to JSON. 126 | * @param {string} [filename="data.json"] - The name of the downloaded JSON file (optional). 127 | */ 128 | export function downloadObjectAsJson(obj, filename = 'data.json') { 129 | const json = JSON.stringify(obj); 130 | const blob = new Blob([json], { type: 'application/json' }); 131 | const url = URL.createObjectURL(blob); 132 | 133 | const a = document.createElement('a'); 134 | a.href = url; 135 | a.download = filename; 136 | 137 | a.addEventListener('click', () => { 138 | setTimeout(() => { 139 | URL.revokeObjectURL(url); 140 | }, 40000); 141 | }); 142 | 143 | a.dispatchEvent( 144 | new MouseEvent('click', { 145 | view: window, 146 | bubbles: true, 147 | cancelable: false, 148 | }) 149 | ); 150 | } 151 | 152 | /** 153 | * Opens a file selection dialog to allow the user to choose a file with the specified file type(s). 154 | * Reads the selected file's content as text and returns it. 155 | * @param {string} [accept='*'] - The file type(s) accepted by the file selection dialog 156 | * (e.g., '.txt', 'image/*'). Defaults to accepting all file types. 157 | * @returns {Promise} A promise that resolves with the text content of the selected file 158 | * or rejects with an error message if no file is selected or an error occurs. 159 | */ 160 | export function openFile(accept = '*') { 161 | return new Promise((resolve, reject) => { 162 | const input = document.createElement('input'); 163 | input.type = 'file'; 164 | input.accept = accept; 165 | input.onchange = (e) => { 166 | const file = e.target.files[0]; 167 | input.remove(); 168 | if (!file) { 169 | reject('No file selected'); 170 | return; 171 | } 172 | const reader = new FileReader(); 173 | reader.onload = () => { 174 | const content = reader.result; 175 | resolve(content); 176 | }; 177 | reader.readAsText(file); 178 | }; 179 | input.click(); 180 | }); 181 | } 182 | 183 | /** 184 | * Converts nested JSON data into a key ID-based structure for data storage. 185 | * @param {Object} baseData - The base JSON data to convert. 186 | * @returns {Object} A data store with each item referenced by a unique key ID. 187 | */ 188 | export function convertJsonToFileIds(baseData) { 189 | let idCounter = 0 190 | let dataStore = {} 191 | 192 | const processContent = (content) => { 193 | let processedContent = []; 194 | 195 | content.forEach(item => { 196 | if (item.type === "group") { 197 | idCounter += 1; 198 | const keyId = idCounter; 199 | dataStore[keyId] = { 200 | type: "group", 201 | label: item.label, 202 | color: validateHex(item.color), 203 | content: processContent(item.content), 204 | encoder: { 205 | switch: item.encoder.switch, 206 | increased: item.encoder.increased, 207 | decreased: item.encoder.decreased 208 | } 209 | }; 210 | processedContent.push(keyId); 211 | } else if (item.type === "macro") { 212 | idCounter += 1; 213 | const fileId = idCounter; 214 | dataStore[fileId] = { 215 | type: "macro", 216 | label: item.label, 217 | color: validateHex(item.color), 218 | content: item.content 219 | }; 220 | processedContent.push(fileId); 221 | } else { 222 | processedContent.push(false); 223 | } 224 | }); 225 | 226 | return processedContent; 227 | } 228 | 229 | dataStore[0] = { 230 | type: "group", 231 | label: baseData.label, 232 | content: processContent(baseData.content), 233 | encoder: { 234 | switch: baseData.encoder.switch, 235 | increased: baseData.encoder.increased, 236 | decreased: baseData.encoder.decreased 237 | } 238 | }; 239 | 240 | return dataStore; 241 | } 242 | 243 | /** 244 | * Restores structured JSON data from file IDs in the dataStore. 245 | * @param {Object} dataStore - The source data store containing file data by IDs. 246 | * @returns {Object} The reconstructed root data with nested group and macro content. 247 | */ 248 | export function restoreJsonFromFileIds(dataStore) { 249 | const loadContent = (fileIds) => { 250 | let content = []; 251 | fileIds.forEach(fileId => { 252 | if (fileId === false) { 253 | content.push({ type: "blank" }); 254 | } else { 255 | let data = dataStore[fileId]; 256 | if (data.type === "group") { 257 | let groupData = { 258 | type: "group", 259 | label: data.label, 260 | color: validateHex(data.color), 261 | content: loadContent(data.content), 262 | encoder: { 263 | switch: data.encoder.switch, 264 | increased: data.encoder.increased, 265 | decreased: data.encoder.decreased 266 | } 267 | }; 268 | content.push(groupData); 269 | } else if (data.type === "macro") { 270 | let macroData = { 271 | type: "macro", 272 | label: data.label, 273 | color: validateHex(data.color), 274 | content: data.content 275 | }; 276 | content.push(macroData); 277 | } 278 | } 279 | }); 280 | return content; 281 | }; 282 | 283 | let rootData = dataStore[0]; 284 | return { 285 | label: rootData.label, 286 | content: loadContent(rootData.content), 287 | encoder: { 288 | switch: rootData.encoder.switch, 289 | increased: rootData.encoder.increased, 290 | decreased: rootData.encoder.decreased 291 | } 292 | }; 293 | } 294 | 295 | /** 296 | * Default key definitions for a component. 297 | * @property {Object} empty - Represents an empty key. 298 | * @property {Object} close - Represents a close macro key. 299 | */ 300 | export const defaultKeys = { 301 | empty: { 302 | type: 'blank' 303 | }, 304 | macro: { 305 | type: 'macro', 306 | label: 'unkown', 307 | color: 'ffffff', 308 | content: [] 309 | }, 310 | group: { 311 | type: 'group', 312 | label: 'unkown', 313 | color: 'ffffff', 314 | content: [], 315 | encoder: { 316 | switch: [], 317 | increased: [], 318 | decreased: [] 319 | } 320 | }, 321 | close: { 322 | type: 'macro', 323 | label: '<-', 324 | color: '121212', 325 | content: [{ sys: 'close_group' }], 326 | } 327 | }; -------------------------------------------------------------------------------- /webui/lang/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "New": "Neu", 3 | "Open": "Öffnen", 4 | "Save": "Speichern", 5 | "Connect": "Verbinden", 6 | "Disconnect": "Trennen", 7 | "Download": "Runterladen", 8 | "Upload": "Hochladen", 9 | "Store": "Speichern", 10 | "Settings": "Einstellungen", 11 | "Reboot": "Neustart", 12 | "Move": "Verschieben", 13 | "Delete": "Löschen", 14 | "Edit": "Bearbeiten", 15 | "Soft Reboot": "Soft Neustart", 16 | "Hard Reboot": "Hard Neustart", 17 | "Enable USB": "USB aktivieren", 18 | "Duplicate": "Duplizieren", 19 | 20 | "Warning": "Warnung", 21 | "Copied": "Kopiert", 22 | "Pasted": "Eingefügt", 23 | "Type": "Typ", 24 | "Blank": "Leer", 25 | "Back": "Zurück", 26 | "Macro": "Makro", 27 | "Group": "Gruppe", 28 | "Label": "Beschriftung", 29 | "The label cannot be longer than 6 characters": "Die Beschriftung darf nicht länger als 6 Zeichen sein", 30 | "You have to enter a label!": "Sie müssen eine Beschriftung eingeben!", 31 | "LED Color": "LED-Farbe", 32 | "Content": "Inhalt", 33 | "Encoder switch": "Drehreglertaste", 34 | "Encoder increase": "Drehregler erhöhen", 35 | "Encoder decrease": "Drehregler verringern", 36 | "The settings can only be changed if the USB storage is disabled": "Die Einstellungen können nur geändert werden, wenn der USB-Speicher deaktiviert ist", 37 | "The macros can only be stored if the USB storage is disabled": "Die Makros können nur gespeichert werden, wenn der USB-Speicher deaktiviert ist", 38 | "Keyboard Layout": "Tastaturlayout", 39 | "Portuguese (Brazil)": "Portugiesisch (Brasilien)", 40 | "Czech": "Tschechisch", 41 | "Danish": "Dänisch", 42 | "German": "Deutsch", 43 | "Spanish": "Spanisch", 44 | "French": "Französisch", 45 | "Hungarian": "Ungarisch", 46 | "Italian": "Italienisch", 47 | "Polish": "Polnisch", 48 | "Swedish": "Schwedisch", 49 | "Turkish": "Türkisch", 50 | "English UK": "Englisch (UK)", 51 | "English US": "Englisch (US)", 52 | "Display Timeout": "Display-Timeout", 53 | "The display timeout in seconds": "Das Display-Timeout in Sekunden", 54 | "Unicode Font": "Unicode-Schrift", 55 | "This slightly increases the initial loading time!": "Dies erhöht leicht die anfängliche Ladezeit!", 56 | "Flip Rotation": "Geräteausrichtung drehen", 57 | "LCD/LED Brightness": "LCD/LED-Helligkeit", 58 | "Select a macro type": "Wähle einen Makrotyp", 59 | "Wait": "Warte", 60 | "String": "Zeichenkette", 61 | "Keycode": "Tastencode", 62 | "Consumer Control Code": "Medien-Kontrollcode", 63 | "Mouse Event": "Mausevent", 64 | "Tone": "Ton", 65 | "Audio file": "Audiodatei", 66 | "Device Function": "Gerätefunktion", 67 | "seconds": "sekunden", 68 | "String Input": "Zeicheneingabe", 69 | "Tap": "Tippen", 70 | "Press": "Drücken", 71 | "Release": "Loslassen", 72 | "Release all": "Alle loslassen", 73 | "Play": "Spiel", 74 | "Hz for": "Hz für", 75 | "X": "X", 76 | "Y": "Y", 77 | "Whl": "Whl", 78 | "Btn": "Btn", 79 | 80 | "Download from Macropad": "Vom Macropad herunterladen", 81 | "Upload to Macropad": "Auf das Macropad hochladen", 82 | "Store on Macropad (only stored macros are available after reset)": "Auf dem Macropad speichern (nur gespeicherte Makros sind nach dem Neustarten verfügbar)", 83 | "Open the dialog with device settings": "Den Dialog mit den Geräteeinstellungen öffnen", 84 | "Open the dialog with reboot options": "Den Dialog mit den Neustartoptionen öffnen", 85 | "Export configuration": "Konfiguration exportieren", 86 | "Import configuration": "Konfiguration importieren", 87 | "Copy configuration": "Konfiguration kopieren", 88 | "Paste configuration": "Konfiguration einfügen", 89 | "Restart the script": "Skript neu starten", 90 | "Reboot the device (Disable USB storage)": "Das Gerät neu starten (USB-Speicher deaktivieren)", 91 | "Enable USB storage (you cannot store on the device when USB is enabled)": "USB-Speicher aktivieren (Sie können nichts auf dem Gerät speichern, wenn USB aktiviert ist)", 92 | "Attention, it blocks the device for given seconds!": "Achtung, es blockiert das Gerät für die angegebene Zeit!", 93 | "Chord of the tone": "Akkord des Tons", 94 | "Frequency of the tone in Hz": "Frequenz des Tons in Hz", 95 | "Duration of the tone in seconds": "Dauer des Tons in Sekunden", 96 | "Path to the .mp3 or .wav file": "Pfad zur .mp3- oder .wav-Datei", 97 | "Horizontally Mouse movement (e.g. 10 | -10)": "Horizontale Mausbewegung (z.B. 10 | -10)", 98 | "Vertically Mouse movement (e.g. 10 | -10)": "Vertikale Mausbewegung (z.B. 10 | -10)", 99 | "Mousewheel movement (e.g. 1 | -1)": "Mausradbewegung (z.B. 1 | -1)", 100 | "Mouse Button": "Maustaste", 101 | 102 | "Your browser does not support serial connections": "Ihr Browser unterstützt keine seriellen Verbindungen", 103 | "Changed settings only take effect after a soft reset": "Geänderte Einstellungen werden erst nach einem Soft-Reset wirksam", 104 | "Connected to macropad": "Mit dem Macropad verbunden", 105 | "Disconnected from macropad": "Vom Macropad getrennt", 106 | "Received from macropad": "Vom Macropad empfangen", 107 | "%s macros sended to macropad": "%s Makros an das Macropad gesendet", 108 | "Stored on macropad": "Auf dem Macropad gespeichert", 109 | "Settings set on macropad": "Einstellungen auf dem Macropad festgelegt", 110 | "Macros loaded from file": "Makros aus Datei geladen", 111 | "%s has no configured navigation key!": "%s hat keine konfigurierte Navigationstaste!", 112 | "Circuitpython files are up to date": "Circuitpython Dateien sind auf dem neuesten Stand", 113 | "New circuitpython files available": "Neue Circuitpython Dateien verfügbar", 114 | 115 | "Do you really want to disconnect?": "Möchten Sie wirklich trennen?", 116 | "No macros configured": "Keine Makros konfiguriert", 117 | "Do you really want to replace the current macros?": "Möchten Sie wirklich die aktuellen Makros ersetzen?", 118 | "Do you really want to replace the current macros and load it from the macropad?": "Möchten Sie wirklich die aktuellen Makros ersetzen und sie vom Macropad laden?", 119 | "Do you really want to send the current macros to the macropad?": "Möchten Sie wirklich die aktuellen Makros an das Macropad senden?", 120 | "Do you really want to delete the current macros?": "Möchten Sie wirklich die aktuellen Makros löschen?", 121 | "Do you really want to delete this key configuration?": "Möchten Sie wirklich diese Tastenkonfiguration löschen?", 122 | "Do you really want to change the type and lose your configuration?": "Möchten Sie wirklich den Typ ändern und Ihre Konfiguration verlieren?", 123 | "Do you really want to replace this configuration?": "Möchten Sie wirklich diese Konfiguration ersetzen?", 124 | "Do you really want to reset the script?": "Möchten Sie wirklich das Skript neu starten?", 125 | "Do you really want to reboot the macropad?": "Möchten Sie wirklich das Macropad neu starten?", 126 | "Do you really want to reboot the macropad to enable USB storage?": "Möchten Sie wirklich das Macropad neu starten, um den USB-Speicher zu aktivieren?" 127 | } 128 | -------------------------------------------------------------------------------- /webui/lang/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "New": "", 3 | "Open": "", 4 | "Save": "", 5 | "Connect": "", 6 | "Disconnect": "", 7 | "Download": "", 8 | "Upload": "", 9 | "Store": "", 10 | "Settings": "", 11 | "Reboot": "", 12 | "Move": "", 13 | "Delete": "", 14 | "Edit": "", 15 | "Soft Reboot": "", 16 | "Hard Reboot": "", 17 | "Enable USB": "", 18 | "Duplicate": "", 19 | 20 | "Warning": "", 21 | "Copied": "", 22 | "Pasted": "", 23 | "Type": "", 24 | "Blank": "", 25 | "Back": "", 26 | "Macro": "", 27 | "Group": "", 28 | "Label": "", 29 | "The label cannot be longer than 6 characters": "", 30 | "You have to enter a label!": "", 31 | "LED Color": "", 32 | "Content": "", 33 | "Encoder switch": "", 34 | "Encoder increase": "", 35 | "Encoder decrease": "", 36 | "The settings can only be changed if the USB storage is disabled": "", 37 | "The macros can only be stored if the USB storage is disabled": "", 38 | "Keyboard Layout": "", 39 | "Portuguese (Brazil)": "", 40 | "Czech": "", 41 | "Danish": "", 42 | "German": "", 43 | "Spanish": "", 44 | "French": "", 45 | "Hungarian": "", 46 | "Italian": "", 47 | "Polish": "", 48 | "Swedish": "", 49 | "Turkish": "", 50 | "English UK": "", 51 | "English US": "", 52 | "Display Timeout": "", 53 | "The display timeout in seconds": "", 54 | "Unicode Font": "", 55 | "This slightly increases the initial loading time!": "", 56 | "Flip Rotation": "", 57 | "LCD/LED Brightness": "", 58 | "Select a macro type": "", 59 | "Wait": "", 60 | "String": "", 61 | "Keycode": "", 62 | "Consumer Control Code": "", 63 | "Mouse Event": "", 64 | "Tone": "", 65 | "Audio file": "", 66 | "Device Function": "", 67 | "seconds": "", 68 | "String Input": "", 69 | "Tap": "", 70 | "Press": "", 71 | "Release": "", 72 | "Release all": "", 73 | "Play": "", 74 | "Hz for": "", 75 | "X": "", 76 | "Y": "", 77 | "Whl": "", 78 | "Btn": "", 79 | 80 | "Download from Macropad": "", 81 | "Upload to Macropad": "", 82 | "Store on Macropad (only stored macros are available after reset)": "", 83 | "Open the dialog with device settings": "", 84 | "Open the dialog with reboot options": "", 85 | "Export configuration": "", 86 | "Import configuration": "", 87 | "Copy configuration": "", 88 | "Paste configuration": "", 89 | "Restart the script": "", 90 | "Reboot the device (Disable USB storage)": "", 91 | "Enable USB storage (you cannot store on the device when USB is enabled)": "", 92 | "Attention, it blocks the device for given seconds!": "", 93 | "Chord of the tone": "", 94 | "Frequency of the tone in Hz": "", 95 | "Duration of the tone in seconds": "", 96 | "Path to the .mp3 or .wav file": "", 97 | "Horizontally Mouse movement (e.g. 10 | -10)": "", 98 | "Vertically Mouse movement (e.g. 10 | -10)": "", 99 | "Mousewheel movement (e.g. 1 | -1)": "", 100 | "Mouse Button": "", 101 | 102 | "Your browser does not support serial connections": "", 103 | "Changed settings only take effect after a soft reset": "", 104 | "Connected to macropad": "", 105 | "Disconnected from macropad": "", 106 | "Received from macropad": "", 107 | "%s macros sended to macropad": "", 108 | "Stored on macropad": "", 109 | "Settings set on macropad": "", 110 | "Macros loaded from file": "", 111 | "%s has no configured navigation key!": "", 112 | "Circuitpython files are up to date": "", 113 | "New circuitpython files available": "", 114 | 115 | "Do you really want to disconnect?": "", 116 | "No macros configured": "", 117 | "Do you really want to replace the current macros?": "", 118 | "Do you really want to replace the current macros and load it from the macropad?": "", 119 | "Do you really want to send the current macros to the macropad?": "", 120 | "Do you really want to delete the current macros?": "", 121 | "Do you really want to delete this key configuration?": "", 122 | "Do you really want to change the type and lost your configuration?": "", 123 | "Do you really want to replace this configuration?": "", 124 | "Do you really want to reset the script?": "", 125 | "Do you really want to reboot the macropad?": "", 126 | "Do you really want to reboot the macropad to enable USB storage?": "" 127 | } 128 | -------------------------------------------------------------------------------- /webui/scss/_animated.scss: -------------------------------------------------------------------------------- 1 | // animating icons 2 | // -------------------------- 3 | 4 | .#{$fa-css-prefix}-beat { 5 | animation-name: #{$fa-css-prefix}-beat; 6 | animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0s); 7 | animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal); 8 | animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s); 9 | animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite); 10 | animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, ease-in-out); 11 | } 12 | 13 | .#{$fa-css-prefix}-bounce { 14 | animation-name: #{$fa-css-prefix}-bounce; 15 | animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0s); 16 | animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal); 17 | animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s); 18 | animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite); 19 | animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, cubic-bezier(0.280, 0.840, 0.420, 1)); 20 | } 21 | 22 | .#{$fa-css-prefix}-fade { 23 | animation-name: #{$fa-css-prefix}-fade; 24 | animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0s); 25 | animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal); 26 | animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s); 27 | animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite); 28 | animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, cubic-bezier(.4,0,.6,1)); 29 | } 30 | 31 | .#{$fa-css-prefix}-beat-fade { 32 | animation-name: #{$fa-css-prefix}-beat-fade; 33 | animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0s); 34 | animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal); 35 | animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s); 36 | animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite); 37 | animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, cubic-bezier(.4,0,.6,1)); 38 | } 39 | 40 | .#{$fa-css-prefix}-flip { 41 | animation-name: #{$fa-css-prefix}-flip; 42 | animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0s); 43 | animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal); 44 | animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s); 45 | animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite); 46 | animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, ease-in-out); 47 | } 48 | 49 | .#{$fa-css-prefix}-shake { 50 | animation-name: #{$fa-css-prefix}-shake; 51 | animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0s); 52 | animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal); 53 | animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s); 54 | animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite); 55 | animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, linear); 56 | } 57 | 58 | .#{$fa-css-prefix}-spin { 59 | animation-name: #{$fa-css-prefix}-spin; 60 | animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0s); 61 | animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal); 62 | animation-duration: var(--#{$fa-css-prefix}-animation-duration, 2s); 63 | animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite); 64 | animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, linear); 65 | } 66 | 67 | .#{$fa-css-prefix}-spin-reverse { 68 | --#{$fa-css-prefix}-animation-direction: reverse; 69 | } 70 | 71 | .#{$fa-css-prefix}-pulse, 72 | .#{$fa-css-prefix}-spin-pulse { 73 | animation-name: #{$fa-css-prefix}-spin; 74 | animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal); 75 | animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s); 76 | animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite); 77 | animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, steps(8)); 78 | } 79 | 80 | // if agent or operating system prefers reduced motion, disable animations 81 | // see: https://www.smashingmagazine.com/2020/09/design-reduced-motion-sensitivities/ 82 | // see: https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion 83 | @media (prefers-reduced-motion: reduce) { 84 | .#{$fa-css-prefix}-beat, 85 | .#{$fa-css-prefix}-bounce, 86 | .#{$fa-css-prefix}-fade, 87 | .#{$fa-css-prefix}-beat-fade, 88 | .#{$fa-css-prefix}-flip, 89 | .#{$fa-css-prefix}-pulse, 90 | .#{$fa-css-prefix}-shake, 91 | .#{$fa-css-prefix}-spin, 92 | .#{$fa-css-prefix}-spin-pulse { 93 | animation-delay: -1ms; 94 | animation-duration: 1ms; 95 | animation-iteration-count: 1; 96 | transition-delay: 0s; 97 | transition-duration: 0s; 98 | } 99 | } 100 | 101 | @keyframes #{$fa-css-prefix}-beat { 102 | 0%, 90% { transform: scale(1); } 103 | 45% { transform: scale(var(--#{$fa-css-prefix}-beat-scale, 1.25)); } 104 | } 105 | 106 | @keyframes #{$fa-css-prefix}-bounce { 107 | 0% { transform: scale(1,1) translateY(0); } 108 | 10% { transform: scale(var(--#{$fa-css-prefix}-bounce-start-scale-x, 1.1),var(--#{$fa-css-prefix}-bounce-start-scale-y, 0.9)) translateY(0); } 109 | 30% { transform: scale(var(--#{$fa-css-prefix}-bounce-jump-scale-x, 0.9),var(--#{$fa-css-prefix}-bounce-jump-scale-y, 1.1)) translateY(var(--#{$fa-css-prefix}-bounce-height, -0.5em)); } 110 | 50% { transform: scale(var(--#{$fa-css-prefix}-bounce-land-scale-x, 1.05),var(--#{$fa-css-prefix}-bounce-land-scale-y, 0.95)) translateY(0); } 111 | 57% { transform: scale(1,1) translateY(var(--#{$fa-css-prefix}-bounce-rebound, -0.125em)); } 112 | 64% { transform: scale(1,1) translateY(0); } 113 | 100% { transform: scale(1,1) translateY(0); } 114 | } 115 | 116 | @keyframes #{$fa-css-prefix}-fade { 117 | 50% { opacity: var(--#{$fa-css-prefix}-fade-opacity, 0.4); } 118 | } 119 | 120 | @keyframes #{$fa-css-prefix}-beat-fade { 121 | 0%, 100% { 122 | opacity: var(--#{$fa-css-prefix}-beat-fade-opacity, 0.4); 123 | transform: scale(1); 124 | } 125 | 50% { 126 | opacity: 1; 127 | transform: scale(var(--#{$fa-css-prefix}-beat-fade-scale, 1.125)); 128 | } 129 | } 130 | 131 | @keyframes #{$fa-css-prefix}-flip { 132 | 50% { 133 | transform: rotate3d(var(--#{$fa-css-prefix}-flip-x, 0), var(--#{$fa-css-prefix}-flip-y, 1), var(--#{$fa-css-prefix}-flip-z, 0), var(--#{$fa-css-prefix}-flip-angle, -180deg)); 134 | } 135 | } 136 | 137 | @keyframes #{$fa-css-prefix}-shake { 138 | 0% { transform: rotate(-15deg); } 139 | 4% { transform: rotate(15deg); } 140 | 8%, 24% { transform: rotate(-18deg); } 141 | 12%, 28% { transform: rotate(18deg); } 142 | 16% { transform: rotate(-22deg); } 143 | 20% { transform: rotate(22deg); } 144 | 32% { transform: rotate(-12deg); } 145 | 36% { transform: rotate(12deg); } 146 | 40%, 100% { transform: rotate(0deg); } 147 | } 148 | 149 | @keyframes #{$fa-css-prefix}-spin { 150 | 0% { transform: rotate(0deg); } 151 | 100% { transform: rotate(360deg); } 152 | } 153 | 154 | -------------------------------------------------------------------------------- /webui/scss/_bordered-pulled.scss: -------------------------------------------------------------------------------- 1 | // bordered + pulled icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-border { 5 | border-color: var(--#{$fa-css-prefix}-border-color, #{$fa-border-color}); 6 | border-radius: var(--#{$fa-css-prefix}-border-radius, #{$fa-border-radius}); 7 | border-style: var(--#{$fa-css-prefix}-border-style, #{$fa-border-style}); 8 | border-width: var(--#{$fa-css-prefix}-border-width, #{$fa-border-width}); 9 | padding: var(--#{$fa-css-prefix}-border-padding, #{$fa-border-padding}); 10 | } 11 | 12 | .#{$fa-css-prefix}-pull-left { 13 | float: left; 14 | margin-right: var(--#{$fa-css-prefix}-pull-margin, #{$fa-pull-margin}); 15 | } 16 | 17 | .#{$fa-css-prefix}-pull-right { 18 | float: right; 19 | margin-left: var(--#{$fa-css-prefix}-pull-margin, #{$fa-pull-margin}); 20 | } 21 | -------------------------------------------------------------------------------- /webui/scss/_core.scss: -------------------------------------------------------------------------------- 1 | // base icon class definition 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix} { 5 | font-family: var(--#{$fa-css-prefix}-style-family, '#{$fa-style-family}'); 6 | font-weight: var(--#{$fa-css-prefix}-style, #{$fa-style}); 7 | } 8 | 9 | .#{$fa-css-prefix}, 10 | .#{$fa-css-prefix}-classic, 11 | .#{$fa-css-prefix}-sharp, 12 | .fas, 13 | .#{$fa-css-prefix}-solid, 14 | .far, 15 | .#{$fa-css-prefix}-regular, 16 | .fab, 17 | .#{$fa-css-prefix}-brands { 18 | -moz-osx-font-smoothing: grayscale; 19 | -webkit-font-smoothing: antialiased; 20 | display: var(--#{$fa-css-prefix}-display, #{$fa-display}); 21 | font-style: normal; 22 | font-variant: normal; 23 | line-height: 1; 24 | text-rendering: auto; 25 | } 26 | 27 | .fas, 28 | .#{$fa-css-prefix}-classic, 29 | .#{$fa-css-prefix}-solid, 30 | .far, 31 | .#{$fa-css-prefix}-regular { 32 | font-family: 'Font Awesome 6 Free'; 33 | } 34 | 35 | .fab, 36 | .#{$fa-css-prefix}-brands { 37 | font-family: 'Font Awesome 6 Brands'; 38 | } 39 | 40 | 41 | %fa-icon { 42 | @include fa-icon; 43 | } 44 | -------------------------------------------------------------------------------- /webui/scss/_fixed-width.scss: -------------------------------------------------------------------------------- 1 | // fixed-width icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-fw { 5 | text-align: center; 6 | width: $fa-fw-width; 7 | } 8 | -------------------------------------------------------------------------------- /webui/scss/_functions.scss: -------------------------------------------------------------------------------- 1 | // functions 2 | // -------------------------- 3 | 4 | // fa-content: convenience function used to set content property 5 | @function fa-content($fa-var) { 6 | @return unquote("\"#{ $fa-var }\""); 7 | } 8 | 9 | // fa-divide: Originally obtained from the Bootstrap https://github.com/twbs/bootstrap 10 | // 11 | // Licensed under: The MIT License (MIT) 12 | // 13 | // Copyright (c) 2011-2021 Twitter, Inc. 14 | // Copyright (c) 2011-2021 The Bootstrap Authors 15 | // 16 | // Permission is hereby granted, free of charge, to any person obtaining a copy 17 | // of this software and associated documentation files (the "Software"), to deal 18 | // in the Software without restriction, including without limitation the rights 19 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 20 | // copies of the Software, and to permit persons to whom the Software is 21 | // furnished to do so, subject to the following conditions: 22 | // 23 | // The above copyright notice and this permission notice shall be included in 24 | // all copies or substantial portions of the Software. 25 | // 26 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 32 | // THE SOFTWARE. 33 | 34 | @function fa-divide($dividend, $divisor, $precision: 10) { 35 | $sign: if($dividend > 0 and $divisor > 0, 1, -1); 36 | $dividend: abs($dividend); 37 | $divisor: abs($divisor); 38 | $quotient: 0; 39 | $remainder: $dividend; 40 | @if $dividend == 0 { 41 | @return 0; 42 | } 43 | @if $divisor == 0 { 44 | @error "Cannot divide by 0"; 45 | } 46 | @if $divisor == 1 { 47 | @return $dividend; 48 | } 49 | @while $remainder >= $divisor { 50 | $quotient: $quotient + 1; 51 | $remainder: $remainder - $divisor; 52 | } 53 | @if $remainder > 0 and $precision > 0 { 54 | $remainder: fa-divide($remainder * 10, $divisor, $precision - 1) * .1; 55 | } 56 | @return ($quotient + $remainder) * $sign; 57 | } 58 | -------------------------------------------------------------------------------- /webui/scss/_icons.scss: -------------------------------------------------------------------------------- 1 | // specific icon class definition 2 | // ------------------------- 3 | 4 | /* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen 5 | readers do not read off random characters that represent icons */ 6 | 7 | @each $name, $icon in $fa-icons { 8 | .#{$fa-css-prefix}-#{$name}::before { content: unquote("\"#{ $icon }\""); } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /webui/scss/_list.scss: -------------------------------------------------------------------------------- 1 | // icons in a list 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-ul { 5 | list-style-type: none; 6 | margin-left: var(--#{$fa-css-prefix}-li-margin, #{$fa-li-margin}); 7 | padding-left: 0; 8 | 9 | > li { position: relative; } 10 | } 11 | 12 | .#{$fa-css-prefix}-li { 13 | left: calc(var(--#{$fa-css-prefix}-li-width, #{$fa-li-width}) * -1); 14 | position: absolute; 15 | text-align: center; 16 | width: var(--#{$fa-css-prefix}-li-width, #{$fa-li-width}); 17 | line-height: inherit; 18 | } 19 | -------------------------------------------------------------------------------- /webui/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | // mixins 2 | // -------------------------- 3 | 4 | // base rendering for an icon 5 | @mixin fa-icon { 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | display: inline-block; 9 | font-style: normal; 10 | font-variant: normal; 11 | font-weight: normal; 12 | line-height: 1; 13 | } 14 | 15 | // sets relative font-sizing and alignment (in _sizing) 16 | @mixin fa-size ($font-size) { 17 | font-size: fa-divide($font-size, $fa-size-scale-base) * 1em; // converts step in sizing scale into an em-based value that's relative to the scale's base 18 | line-height: fa-divide(1, $font-size) * 1em; // sets the line-height of the icon back to that of it's parent 19 | vertical-align: (fa-divide(6, $font-size) - fa-divide(3, 8)) * 1em; // vertically centers the icon taking into account the surrounding text's descender 20 | } 21 | 22 | // only display content to screen readers 23 | // see: https://www.a11yproject.com/posts/2013-01-11-how-to-hide-content/ 24 | // see: https://hugogiraudel.com/2016/10/13/css-hide-and-seek/ 25 | @mixin fa-sr-only() { 26 | position: absolute; 27 | width: 1px; 28 | height: 1px; 29 | padding: 0; 30 | margin: -1px; 31 | overflow: hidden; 32 | clip: rect(0, 0, 0, 0); 33 | white-space: nowrap; 34 | border-width: 0; 35 | } 36 | 37 | // use in conjunction with .sr-only to only display content when it's focused 38 | @mixin fa-sr-only-focusable() { 39 | &:not(:focus) { 40 | @include fa-sr-only(); 41 | } 42 | } 43 | 44 | // sets a specific icon family to use alongside style + icon mixins 45 | 46 | // convenience mixins for declaring pseudo-elements by CSS variable, 47 | // including all style-specific font properties, and both the ::before 48 | // and ::after elements in the duotone case. 49 | @mixin fa-icon-solid($fa-var) { 50 | @extend %fa-icon; 51 | @extend .fa-solid; 52 | 53 | &::before { 54 | content: unquote("\"#{ $fa-var }\""); 55 | } 56 | } 57 | 58 | @mixin fa-icon-regular($fa-var) { 59 | @extend %fa-icon; 60 | @extend .fa-regular; 61 | 62 | &::before { 63 | content: unquote("\"#{ $fa-var }\""); 64 | } 65 | } 66 | 67 | @mixin fa-icon-brands($fa-var) { 68 | @extend %fa-icon; 69 | @extend .fa-brands; 70 | 71 | &::before { 72 | content: unquote("\"#{ $fa-var }\""); 73 | } 74 | } 75 | 76 | -------------------------------------------------------------------------------- /webui/scss/_rotated-flipped.scss: -------------------------------------------------------------------------------- 1 | // rotating + flipping icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-rotate-90 { 5 | transform: rotate(90deg); 6 | } 7 | 8 | .#{$fa-css-prefix}-rotate-180 { 9 | transform: rotate(180deg); 10 | } 11 | 12 | .#{$fa-css-prefix}-rotate-270 { 13 | transform: rotate(270deg); 14 | } 15 | 16 | .#{$fa-css-prefix}-flip-horizontal { 17 | transform: scale(-1, 1); 18 | } 19 | 20 | .#{$fa-css-prefix}-flip-vertical { 21 | transform: scale(1, -1); 22 | } 23 | 24 | .#{$fa-css-prefix}-flip-both, 25 | .#{$fa-css-prefix}-flip-horizontal.#{$fa-css-prefix}-flip-vertical { 26 | transform: scale(-1, -1); 27 | } 28 | 29 | .#{$fa-css-prefix}-rotate-by { 30 | transform: rotate(var(--#{$fa-css-prefix}-rotate-angle, none)); 31 | } 32 | -------------------------------------------------------------------------------- /webui/scss/_screen-reader.scss: -------------------------------------------------------------------------------- 1 | // screen-reader utilities 2 | // ------------------------- 3 | 4 | // only display content to screen readers 5 | .sr-only, 6 | .#{$fa-css-prefix}-sr-only { 7 | @include fa-sr-only; 8 | } 9 | 10 | // use in conjunction with .sr-only to only display content when it's focused 11 | .sr-only-focusable, 12 | .#{$fa-css-prefix}-sr-only-focusable { 13 | @include fa-sr-only-focusable; 14 | } 15 | -------------------------------------------------------------------------------- /webui/scss/_sizing.scss: -------------------------------------------------------------------------------- 1 | // sizing icons 2 | // ------------------------- 3 | 4 | // literal magnification scale 5 | @for $i from 1 through 10 { 6 | .#{$fa-css-prefix}-#{$i}x { 7 | font-size: $i * 1em; 8 | } 9 | } 10 | 11 | // step-based scale (with alignment) 12 | @each $size, $value in $fa-sizes { 13 | .#{$fa-css-prefix}-#{$size} { 14 | @include fa-size($value); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /webui/scss/_stacked.scss: -------------------------------------------------------------------------------- 1 | // stacking icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-stack { 5 | display: inline-block; 6 | height: 2em; 7 | line-height: 2em; 8 | position: relative; 9 | vertical-align: $fa-stack-vertical-align; 10 | width: $fa-stack-width; 11 | } 12 | 13 | .#{$fa-css-prefix}-stack-1x, 14 | .#{$fa-css-prefix}-stack-2x { 15 | left: 0; 16 | position: absolute; 17 | text-align: center; 18 | width: 100%; 19 | z-index: var(--#{$fa-css-prefix}-stack-z-index, #{$fa-stack-z-index}); 20 | } 21 | 22 | .#{$fa-css-prefix}-stack-1x { 23 | line-height: inherit; 24 | } 25 | 26 | .#{$fa-css-prefix}-stack-2x { 27 | font-size: 2em; 28 | } 29 | 30 | .#{$fa-css-prefix}-inverse { 31 | color: var(--#{$fa-css-prefix}-inverse, #{$fa-inverse}); 32 | } 33 | -------------------------------------------------------------------------------- /webui/scss/brands.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | * Copyright 2023 Fonticons, Inc. 5 | */ 6 | @import 'functions'; 7 | @import 'variables'; 8 | 9 | :root, :host { 10 | --#{$fa-css-prefix}-style-family-brands: 'Font Awesome 6 Brands'; 11 | --#{$fa-css-prefix}-font-brands: normal 400 1em/1 'Font Awesome 6 Brands'; 12 | } 13 | 14 | @font-face { 15 | font-family: 'Font Awesome 6 Brands'; 16 | font-style: normal; 17 | font-weight: 400; 18 | font-display: $fa-font-display; 19 | src: url('#{$fa-font-path}/fa-brands-400.woff2') format('woff2'), 20 | url('#{$fa-font-path}/fa-brands-400.ttf') format('truetype'); 21 | } 22 | 23 | .fab, 24 | .#{$fa-css-prefix}-brands { 25 | font-weight: 400; 26 | } 27 | 28 | @each $name, $icon in $fa-brand-icons { 29 | .#{$fa-css-prefix}-#{$name}:before { content: unquote("\"#{ $icon }\""); } 30 | } 31 | -------------------------------------------------------------------------------- /webui/scss/fontawesome.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | * Copyright 2023 Fonticons, Inc. 5 | */ 6 | // Font Awesome core compile (Web Fonts-based) 7 | // ------------------------- 8 | 9 | @import 'functions'; 10 | @import 'variables'; 11 | @import 'mixins'; 12 | @import 'core'; 13 | @import 'sizing'; 14 | @import 'fixed-width'; 15 | @import 'list'; 16 | @import 'bordered-pulled'; 17 | @import 'animated'; 18 | @import 'rotated-flipped'; 19 | @import 'stacked'; 20 | @import 'icons'; 21 | @import 'screen-reader'; 22 | -------------------------------------------------------------------------------- /webui/scss/regular.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | * Copyright 2023 Fonticons, Inc. 5 | */ 6 | @import 'functions'; 7 | @import 'variables'; 8 | 9 | :root, :host { 10 | --#{$fa-css-prefix}-style-family-classic: '#{ $fa-style-family }'; 11 | --#{$fa-css-prefix}-font-regular: normal 400 1em/1 '#{ $fa-style-family }'; 12 | } 13 | 14 | @font-face { 15 | font-family: 'Font Awesome 6 Free'; 16 | font-style: normal; 17 | font-weight: 400; 18 | font-display: $fa-font-display; 19 | src: url('#{$fa-font-path}/fa-regular-400.woff2') format('woff2'), 20 | url('#{$fa-font-path}/fa-regular-400.ttf') format('truetype'); 21 | } 22 | 23 | .far, 24 | .#{$fa-css-prefix}-regular { 25 | font-weight: 400; 26 | } 27 | -------------------------------------------------------------------------------- /webui/scss/solid.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | * Copyright 2023 Fonticons, Inc. 5 | */ 6 | @import 'functions'; 7 | @import 'variables'; 8 | 9 | :root, :host { 10 | --#{$fa-css-prefix}-style-family-classic: '#{ $fa-style-family }'; 11 | --#{$fa-css-prefix}-font-solid: normal 900 1em/1 '#{ $fa-style-family }'; 12 | } 13 | 14 | @font-face { 15 | font-family: 'Font Awesome 6 Free'; 16 | font-style: normal; 17 | font-weight: 900; 18 | font-display: $fa-font-display; 19 | src: url('#{$fa-font-path}/fa-solid-900.woff2') format('woff2'), 20 | url('#{$fa-font-path}/fa-solid-900.ttf') format('truetype'); 21 | } 22 | 23 | .fas, 24 | .#{$fa-css-prefix}-solid { 25 | font-weight: 900; 26 | } 27 | -------------------------------------------------------------------------------- /webui/scss/style.scss: -------------------------------------------------------------------------------- 1 | @import 'fontawesome.scss'; 2 | @import 'solid.scss'; 3 | 4 | // document classes 5 | :root { 6 | font-family: monospace; 7 | user-select: none; 8 | 9 | --color-blue: #4fc1e9; 10 | --color-red: #fc6e51; 11 | --color-green: #add468; 12 | --color-yellow: #ffce54; 13 | --color-grey: #808080; 14 | --color-lightgrey: #cccccc; 15 | --color-black: #404040; 16 | --color-white: #ffffff; 17 | 18 | --shadow-color: #808080bf; 19 | --shadow-outset: 3px 3px 2px 1px var(--shadow-color); 20 | --shadow-outset-active: 1px 1px 2px 1px var(--shadow-color); 21 | --shadow-inset: inset 2px 2px 2px 1px var(--shadow-color); 22 | --shadow-inset-active: inset 4px 4px 2px 1px var(--shadow-color); 23 | 24 | --key-entry-size: 100px; 25 | --key-entries-gap: calc(var(--key-entry-size) / 10); 26 | 27 | --key-control-button-size: 20px; 28 | --key-control-padding: 2px; 29 | --key-control-border-radius: 10px; 30 | 31 | --dialog-button-size: 40px; 32 | --dialog-gap: calc(var(--dialog-button-size) / 10); 33 | 34 | --macro-visible-count: 10; 35 | --macro-entry-min-width: 400px; 36 | --macro-entry-height: 30px; 37 | } 38 | 39 | body { 40 | margin: var(--key-entries-gap); 41 | background-color: var(--color-white); 42 | color: var(--color-black); 43 | } 44 | 45 | // global classes 46 | .hidden { 47 | display: none !important; 48 | } 49 | 50 | .invisible { 51 | visibility: hidden !important; 52 | } 53 | 54 | .button { 55 | cursor: pointer; 56 | } 57 | 58 | // project classes 59 | 60 | .github-corner:active, 61 | .github-corner:visited, 62 | .github-corner svg { 63 | fill: var(--color-black); 64 | color: var(--color-white); 65 | } 66 | 67 | // Main container 68 | .main-container { 69 | display: flex; 70 | flex-direction: column; 71 | align-items: center; 72 | 73 | .key-entries { 74 | display: grid; 75 | grid-template-columns: repeat(3, 1fr); 76 | gap: var(--key-entries-gap); 77 | margin-bottom: calc(var(--key-entries-gap) / 2); 78 | padding: calc(var(--key-entries-gap) / 2); 79 | overflow: hidden; 80 | 81 | .key-container { 82 | display: grid; 83 | grid-template-rows: max-content 1fr max-content; 84 | width: var(--key-entry-size); 85 | height: var(--key-entry-size); 86 | border: 1px solid var(--shadow-color); 87 | border-radius: var(--key-control-border-radius); 88 | box-sizing: border-box; 89 | overflow: hidden; 90 | 91 | &.blank { 92 | background-color: transparent !important; 93 | } 94 | 95 | &.clickable { 96 | cursor: pointer; 97 | } 98 | 99 | &.clickable:hover { 100 | box-shadow: var(--shadow-outset); 101 | } 102 | 103 | &.clickable:active { 104 | box-shadow: var(--shadow-outset-active); 105 | } 106 | 107 | .key-type-icon { 108 | grid-row: 1; 109 | cursor: help; 110 | background-color: var(--color-white); 111 | border-right: 1px solid var(--shadow-color); 112 | border-bottom: 1px solid var(--shadow-color); 113 | border-bottom-right-radius: var(--key-control-border-radius); 114 | padding: 0 var(--key-control-padding) 0 0; 115 | } 116 | 117 | .key-handle { 118 | justify-self: end; 119 | grid-row: 1; 120 | cursor: grab; 121 | background-color: var(--color-white); 122 | border-left: 1px solid var(--shadow-color); 123 | border-bottom: 1px solid var(--shadow-color); 124 | border-bottom-left-radius: var(--key-control-border-radius); 125 | padding: 0 0 0 var(--key-control-padding); 126 | } 127 | 128 | &.blank .key-label { 129 | display: none; 130 | } 131 | 132 | .key-label { 133 | grid-row: 2; 134 | grid-column: 1/3; 135 | justify-self: center; 136 | align-self: center; 137 | font-weight: bold; 138 | word-break: break-all; 139 | background-color: var(--color-white); 140 | border: 1px solid var(--shadow-color); 141 | border-radius: var(--key-control-border-radius); 142 | padding: var(--key-control-padding) calc(var(--key-control-padding) * 3); 143 | } 144 | 145 | .key-controls { 146 | display: flex; 147 | grid-row: 3; 148 | grid-column: 1/3; 149 | justify-self: end; 150 | background-color: var(--color-white); 151 | border-left: 1px solid var(--shadow-color); 152 | border-top: 1px solid var(--shadow-color); 153 | border-top-left-radius: var(--key-control-border-radius); 154 | padding: var(--key-control-padding) 0 0 var(--key-control-padding); 155 | 156 | .button { 157 | &.edit:hover { 158 | color: var(--color-blue); 159 | } 160 | &.delete:hover { 161 | color: var(--color-red); 162 | } 163 | } 164 | } 165 | 166 | .key-type-icon, 167 | .button { 168 | width: var(--key-control-button-size); 169 | height: var(--key-control-button-size); 170 | display: flex; 171 | justify-content: center; 172 | align-items: center; 173 | } 174 | 175 | &.blank .button.delete { 176 | display: none; 177 | } 178 | } 179 | } 180 | } 181 | 182 | // Big buttons 183 | .main-container .app-controls, 184 | .main-container .device-controls, 185 | .main-container .key-entries-controls, 186 | .dialog-container .dialog-reboot-buttons { 187 | display: flex; 188 | flex-wrap: wrap; 189 | justify-content: center; 190 | gap: var(--key-entries-gap); 191 | margin-bottom: var(--key-entries-gap); 192 | 193 | .button { 194 | display: flex; 195 | flex-direction: column; 196 | justify-content: space-evenly; 197 | align-items: center; 198 | width: var(--key-entry-size); 199 | height: calc(var(--key-entry-size) / 2); 200 | box-sizing: border-box; 201 | align-self: flex-end; 202 | user-select: none; 203 | overflow: hidden; 204 | position: relative; 205 | background-color: var(--color-blue); 206 | border: 1px solid var(--color-grey); 207 | 208 | div { 209 | text-align: center; 210 | } 211 | 212 | * { 213 | pointer-events: none; 214 | } 215 | 216 | &.deactivated { 217 | background-color: var(--color-lightgrey) !important; 218 | color: var(--color-grey) !important; 219 | 220 | &:hover { 221 | cursor: initial !important; 222 | box-shadow: none !important; 223 | } 224 | 225 | &:active { 226 | transform: none !important; 227 | box-shadow: none !important; 228 | } 229 | } 230 | 231 | &.transfer { 232 | background-color: var(--color-lightgrey) !important; 233 | color: var(--color-grey) !important; 234 | 235 | &:hover { 236 | cursor: initial !important; 237 | box-shadow: none !important; 238 | } 239 | 240 | &:active { 241 | transform: none !important; 242 | box-shadow: none !important; 243 | } 244 | 245 | i { 246 | &::before { 247 | content: "\f2f1"; 248 | } 249 | 250 | animation-name: fa-spin; 251 | animation-delay: 0s; 252 | animation-direction: normal; 253 | animation-duration: 2s; 254 | animation-iteration-count: infinite; 255 | animation-timing-function: linear; 256 | } 257 | } 258 | } 259 | } 260 | 261 | .main-container .key-entries-controls { 262 | .container { 263 | display: flex; 264 | gap: var(--key-entries-gap); 265 | width: calc(var(--key-entry-size) * 3 + var(--key-entries-gap) * 2); 266 | } 267 | 268 | .button:last-child { 269 | flex-grow: 1; 270 | } 271 | } 272 | 273 | // Dialog 274 | .dialog-container { 275 | position: absolute; 276 | top: 0; 277 | left: 0; 278 | height: 100%; 279 | width: 100%; 280 | background-color: rgba(255, 255, 255, 0.5); 281 | 282 | .dialog { 283 | display: grid; 284 | grid-auto-columns: auto var(--dialog-button-size); 285 | column-gap: var(--dialog-gap); 286 | position: absolute; 287 | width: fit-content; 288 | min-width: 320px; 289 | background-color: var(--color-white); 290 | border: 1px solid var(--shadow-color); 291 | border-radius: var(--dialog-gap); 292 | box-sizing: border-box; 293 | box-shadow: var(--shadow-outset); 294 | overflow: auto; 295 | 296 | .dialog-header { 297 | grid-row: 1; 298 | grid-column: 1 / 3; 299 | display: flex; 300 | gap: var(--dialog-gap); 301 | margin-right: var(--dialog-button-size); 302 | padding-right: var(--dialog-gap); 303 | height: var(--dialog-button-size); 304 | background-color: var(--color-black); 305 | color: var(--color-white); 306 | 307 | .dialog-header-label { 308 | display: flex; 309 | align-items: center; 310 | justify-content: center; 311 | width: 100%; 312 | font-weight: bold; 313 | font-size: large; 314 | cursor: move; 315 | } 316 | 317 | .dialog-button { 318 | border-top: none; 319 | color: var(--color-black); 320 | 321 | .dialog-button-label { 322 | position: absolute; 323 | transform: rotate(-45deg); 324 | opacity: 0; 325 | } 326 | } 327 | } 328 | 329 | .dialog-content { 330 | grid-row: 2; 331 | grid-column: 1 / 3; 332 | margin-left: var(--dialog-gap); 333 | margin-top: var(--dialog-gap); 334 | 335 | .dialog-prompt { 336 | margin-right: calc(var(--dialog-button-size) + var(--dialog-gap)); 337 | max-width: 300px; 338 | } 339 | 340 | .dialog-inputs { 341 | grid-row: 2; 342 | grid-column: 1 / 3; 343 | display: grid; 344 | align-items: start; 345 | row-gap: var(--dialog-gap); 346 | 347 | .dialog-input { 348 | display: grid; 349 | gap: var(--dialog-gap); 350 | } 351 | 352 | .dialog-input-shorten { 353 | margin-right: calc(var(--dialog-button-size) + var(--dialog-gap)); 354 | } 355 | 356 | .input-color div { 357 | display: flex; 358 | 359 | input { 360 | width: 100%; 361 | } 362 | } 363 | 364 | summary { 365 | cursor: pointer; 366 | } 367 | 368 | .input-macros { 369 | display: grid; 370 | grid-template-columns: 1fr var(--dialog-button-size); 371 | gap: var(--dialog-gap); 372 | 373 | .input-sortable { 374 | display: grid; 375 | gap: var(--dialog-gap); 376 | border: 1px solid var(--color-grey); 377 | box-sizing: border-box; 378 | padding: var(--dialog-gap); 379 | max-height: calc(var(--macro-entry-height) * var(--macro-visible-count) + var(--dialog-gap) * (var(--macro-visible-count) + 1)); 380 | overflow: auto; 381 | 382 | .macro-entry-container { 383 | display: flex; 384 | gap: var(--dialog-gap); 385 | align-items: center; 386 | min-width: var(--macro-entry-min-width); 387 | height: var(--macro-entry-height); 388 | padding: var(--dialog-gap); 389 | background-color: var(--color-green); 390 | border: 1px solid var(--color-grey); 391 | border-radius: calc(var(--key-control-border-radius) / 2); 392 | box-sizing: border-box; 393 | 394 | .macro-entry-handle { 395 | cursor: grab; 396 | } 397 | 398 | .macro-entry-content { 399 | display: flex; 400 | flex-grow: 1; 401 | align-items: center; 402 | text-wrap: nowrap; 403 | } 404 | 405 | .macro-entry-controls { 406 | display: flex; 407 | gap: var(--dialog-gap); 408 | margin: 0 0 0 auto; 409 | 410 | i { 411 | cursor: pointer; 412 | 413 | &.fa-clone:hover { 414 | color: var(--color-yellow); 415 | } 416 | &.fa-trash:hover { 417 | color: var(--color-red); 418 | } 419 | } 420 | } 421 | } 422 | } 423 | } 424 | 425 | &.blank .input-label, 426 | &.blank .input-color, 427 | &.blank .input-content, 428 | &.blank .input-encoder, 429 | &.group .input-content, 430 | &.macro .input-encoder { 431 | display: none; 432 | } 433 | } 434 | 435 | .dialog-reboot-buttons { 436 | margin-right: var(--dialog-gap); 437 | margin-bottom: var(--dialog-gap); 438 | } 439 | } 440 | 441 | .dialog-button { 442 | border: 1px solid var(--color-grey); 443 | border-right: none; 444 | 445 | &.close { 446 | grid-row: 1; 447 | grid-column: 2; 448 | border: none; 449 | border-bottom: 1px solid var(--color-grey); 450 | border-left: 1px solid var(--color-grey); 451 | } 452 | 453 | &.ok { 454 | grid-row: 3; 455 | grid-column: 2; 456 | margin-top: 4px; 457 | border: none; 458 | border-top: 1px solid var(--color-grey); 459 | border-left: 1px solid var(--color-grey); 460 | } 461 | } 462 | } 463 | } 464 | 465 | // Notification 466 | .notification-container { 467 | display: flex; 468 | flex-direction: column-reverse; 469 | position: absolute; 470 | right: 0; 471 | bottom: 0; 472 | 473 | .notification { 474 | display: grid; 475 | grid-auto-columns: auto var(--dialog-button-size); 476 | align-items: center; 477 | position: relative; 478 | min-height: var(--dialog-button-size); 479 | background-color: var(--color-black); 480 | border: 1px solid var(--color-grey); 481 | border-radius: 4px; 482 | box-sizing: border-box; 483 | overflow: hidden; 484 | 485 | &.info { 486 | background-color: var(--color-blue); 487 | } 488 | &.success { 489 | background-color: var(--color-green); 490 | } 491 | &.warning { 492 | background-color: var(--color-yellow); 493 | } 494 | &.error { 495 | background-color: var(--color-red); 496 | } 497 | 498 | .notification-message { 499 | padding: 0 calc(var(--dialog-button-size) / 4); 500 | } 501 | 502 | .dialog-button.close { 503 | border-left: 1px solid var(--color-grey); 504 | } 505 | } 506 | } 507 | 508 | // Dialog and notification buttons 509 | .dialog-container .dialog-button, 510 | .notification-container .dialog-button { 511 | display: flex; 512 | justify-content: center; 513 | align-items: center; 514 | flex-shrink: 0; 515 | flex-grow: 0; 516 | position: relative; 517 | width: var(--dialog-button-size); 518 | height: var(--dialog-button-size); 519 | background-color: var(--color-white); 520 | box-sizing: border-box; 521 | overflow: hidden; 522 | cursor: pointer; 523 | 524 | &.close { 525 | grid-row: 1; 526 | grid-column: 2; 527 | background-color: var(--color-red); 528 | } 529 | 530 | &.ok { 531 | grid-row: 3; 532 | grid-column: 2; 533 | background-color: var(--color-blue); 534 | margin-top: 4px; 535 | } 536 | } 537 | 538 | // Button hover effect 539 | .main-container .app-controls .button:hover, 540 | .main-container .device-controls .button:hover, 541 | .main-container .key-entries-controls .button:hover { 542 | box-shadow: var(--shadow-outset); 543 | } 544 | .main-container .app-controls .button:active, 545 | .main-container .device-controls .button:active, 546 | .main-container .key-entries-controls .button:active { 547 | box-shadow: var(--shadow-outset-active); 548 | transform: translate(1px, 1px); 549 | } 550 | .dialog-container .dialog-button:hover, 551 | .dialog-container .dialog-reboot-buttons .button:hover, 552 | .notification-container .dialog-button:hover { 553 | box-shadow: var(--shadow-inset); 554 | } 555 | .dialog-container .dialog-button:active, 556 | .dialog-container .dialog-reboot-buttons .button:active, 557 | .notification-container .dialog-button:active { 558 | box-shadow: var(--shadow-inset-active); 559 | } 560 | -------------------------------------------------------------------------------- /webui/scss/v4-shims.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | * Copyright 2023 Fonticons, Inc. 5 | */ 6 | // V4 shims compile (Web Fonts-based) 7 | // ------------------------- 8 | 9 | @import 'functions'; 10 | @import 'variables'; 11 | @import 'shims'; 12 | -------------------------------------------------------------------------------- /webui/webfonts/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/webui/webfonts/fa-brands-400.ttf -------------------------------------------------------------------------------- /webui/webfonts/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/webui/webfonts/fa-brands-400.woff2 -------------------------------------------------------------------------------- /webui/webfonts/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/webui/webfonts/fa-regular-400.ttf -------------------------------------------------------------------------------- /webui/webfonts/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/webui/webfonts/fa-regular-400.woff2 -------------------------------------------------------------------------------- /webui/webfonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/webui/webfonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /webui/webfonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/webui/webfonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /webui/webfonts/fa-v4compatibility.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/webui/webfonts/fa-v4compatibility.ttf -------------------------------------------------------------------------------- /webui/webfonts/fa-v4compatibility.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchilli/macropad/abba97d9af8981ea8324a1682682fb0dc713d6d2/webui/webfonts/fa-v4compatibility.woff2 --------------------------------------------------------------------------------