summaryrefslogtreecommitdiff
path: root/src/code.py
blob: ad634adef9239e3edac22274c20bf9ca46d82271 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# SPDX-FileCopyrightText: 2021 Phillip Burgess for Adafruit Industries
#
# SPDX-License-Identifier: MIT

"""
A macro/hotkey program for Adafruit MACROPAD. Macro setups are stored in the
/macros folder (configurable below), load up just the ones you're likely to
use. Plug into computer's USB port, use dial to select an application macro
set, press MACROPAD keys to send key sequences and other USB protocols.
"""

# pylint: disable=import-error, unused-import, too-few-public-methods

import os
import time
import displayio
import terminalio

import usb_cdc
import json

#from adafruit_display_shapes.rect import Rect
#from adafruit_display_text import label
from adafruit_macropad import MacroPad

from supervisor import runtime

from actions import Action
import skeleton
import menu
from sleep_mode import Power

# CONFIGURABLES ------------------------

MACRO_FOLDER = '/macros'


# INITIALIZATION -----------------------

# Do not reload the application when the files are changed
runtime.autoreload = False

macropad = MacroPad()
macropad.display.auto_refresh = False
macropad.pixels.auto_write = False

power = Power(macropad)

Action().set_macropad(macropad)

# Load all the macro key setups from .py files in MACRO_FOLDER
apps = []
files = os.listdir(MACRO_FOLDER)
files.sort()
for filename in files:
    if filename.startswith('._'):
        continue
    if filename.endswith('.py'):
        module_name = filename[:-3]
    elif  filename.endswith('.mpy'):
        module_name = filename[:-4]
    else:
        continue

    try:
        module = __import__(MACRO_FOLDER + '/' + module_name)
        apps.append(module.configuration)
    except (SyntaxError, ImportError, AttributeError, KeyError, NameError,
            IndexError, TypeError) as err:
        print("ERROR in", filename)
        import traceback
        traceback.print_exception(err, err, err.__traceback__)

if not apps:
    group[13].text = 'NO MACRO FILES FOUND'
    macropad.display.refresh()
    while True:
        pass

app_index = 0


def run_application(app):
    # Read encoder position. If it's changed, declare it as a button.
    position = app.macropad.encoder
    if position > app.position:
        app.position = position
        key_number = 12
        pressed = True
    elif position < app.position:
        app.position = position
        key_number = 13
        pressed = True
    else:
        event = app.macropad.keys.events.get()
        if not event:
            app.tick()
            power.tick()
            return # No key events
        key_number = event.key_number
        pressed = event.pressed

    power.execute_action(key_number, pressed or False)
    return app.execute_action(key_number, pressed or False)

def switch_layout(configuration):
    global app
    app = skeleton.Handler(macropad, configuration)
    power.set_configuration(configuration)
    app.start()


def search_and_switch(layout):
    for local_app in apps:
        if local_app.title == layout and app is not local_app:
            switch_layout(local_app)
            break

def stack(layout):
    for configuration in apps:
        if configuration.title == layout and app is not configuration:
            app.stack(configuration)
            app.start()
            break

def send_layout(_any):
    if usb_cdc.data.connected:
        usb_cdc.data.write(bytes("%s\n" % app.name, "utf-8"))

actions = {
    "layout" : search_and_switch,
    "stack" :  stack,
    "get_layout" : send_layout,
  }

switch_layout(apps[app_index])

# MAIN LOOP ----------------------------

while True:

    # First read from the serial
    # If we have an application name from the command line, switch to this app.
    if usb_cdc.data.connected:
        while usb_cdc.data.in_waiting != 0:
            line = usb_cdc.data.readline(usb_cdc.data.in_waiting).strip()
            try:
                commands = json.loads(line)
                for command in commands:
                    for key, value in command.items():
                        actions[key](value)
                continue
            except ValueError:
                print(line)
                pass

    # Handle encoder button. If state has changed, and if there's a
    # corresponding macro, set up variables to act on this just like
    # the keypad keys, as if it were a 13th key/macro.
    app.macropad.encoder_switch_debounced.update()
    encoder_switch = app.macropad.encoder_switch_debounced.pressed
    # On the encoder button, switch to the next mode
    if encoder_switch:

        config = menu.build_application(apps)
        menu_app = skeleton.Handler(macropad, config)
        menu_app.start()
        while True:
            configuration = run_application(menu_app)
            if configuration is None: continue
            switch_layout(configuration)
            break
        if configuration.is_layout:
            send_layout(None)

    run_application(app)
    continue