Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add rgb-effects qmk sub command #20160

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions builddefs/build_keyboard.mk
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,7 @@ ifneq ("$(KEYMAP_H)","")
endif

OPT_DEFS += -DKEYMAP_C=\"$(KEYMAP_C)\"
OPT_DEFS += -save-temps=obj

# If a keymap or userspace places their keymap array in another file instead, allow for it to be included
# !!NOTE!! -- For this to work, the source file cannot be part of $(SRC), so users should not add it via `SRC += <file>`
Expand Down
45 changes: 45 additions & 0 deletions lib/python/qmk/c_parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,51 @@
layout_macro_define_regex = re.compile(r'^#\s*define')


def extract_enum_from_c_file_as_dict(contents: str, enum_identifier: str):
"""
Extracts an enum from the contents of a C file and returns it as a python dictionary.

Args:
contents (str): The contents of the C file as a string.
enum_identifier (str): The identifier for the enum to extract.

Returns:
dict: A dictionary representation of the extracted enum.
"""
# define the regex pattern to match the entire enum block
enum_pattern = re.compile(f'enum\\s+{enum_identifier}\\s*{{([^}}]*)}}', re.DOTALL)

# use the regex pattern to find the enum block
enum_match = enum_pattern.search(contents)

# if the enum block was found, parse it to extract the desired enum values
if not enum_match:
return False

enum_block = enum_match.group(1)
enum_values = {}
last_value = -1

flat_list = []
for line in enum_block.splitlines():
line = line.strip() # remove any leading/trailing whitespaces
if line: # skip empty lines
flat_list.extend(line.split(','))
for line in flat_list:
if line.strip().startswith('typedef') or line.strip().startswith('#') or line.strip().startswith("//"):
continue
if "=" in line:
enum_name, enum_value = line.split('=')
last_value = int(enum_value.replace(",", "").strip())
else:
enum_name = line.replace(",", "").strip()
if len(enum_name) == 0:
continue
last_value += 1
enum_values[enum_name.strip()] = last_value
return enum_values


def _get_chunks(it, size):
"""Break down a collection into smaller parts
"""
Expand Down
1 change: 1 addition & 0 deletions lib/python/qmk/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
'qmk.cli.new.keymap',
'qmk.cli.painter',
'qmk.cli.pytest',
'qmk.cli.rgb_effects',
'qmk.cli.via2json',
]

Expand Down
57 changes: 57 additions & 0 deletions lib/python/qmk/cli/rgb_effects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import json
import os

from milc import cli
from qmk.decorators import automagic_keyboard, automagic_keymap
from qmk.keyboard import keyboard_completer, keyboard_folder
from qmk.path import is_keyboard
from pathlib import Path
from qmk.constants import KEYBOARD_OUTPUT_PREFIX
from qmk.c_parse import extract_enum_from_c_file_as_dict
from qmk.json_encoders import InfoJSONEncoder


@cli.argument("-kb", "--keyboard", type=keyboard_folder, completer=keyboard_completer)
@cli.argument('-km', '--keymap', help='keymap to evaluate')
@cli.subcommand("RGB Effect information")
@automagic_keyboard
@automagic_keymap
def rgb_effects(cli):
"""Output enabled RGB matrix effects as json list format for use with via"""

# Determine our keyboard(s)
if not cli.args.keyboard:
cli.log.error('Missing parameter: --keyboard')
cli.subcommands['rgb-effects'].print_help()
return False

if not is_keyboard(cli.args.keyboard):
cli.log.error('Invalid keyboard: "%s"', cli.args.keyboard)
return False

if not cli.args.keymap:
cli.log.error('Missing parameter: --keymap')
cli.subcommands['rgb-effects'].print_help()
return False

keymap = cli.args.keymap
keyboard = cli.args.keyboard
keyboard_filesafe = keyboard.replace('/', '_')
rgb_matrix_path = Path(f'{KEYBOARD_OUTPUT_PREFIX}{keyboard_filesafe}_{keymap}/quantum/rgb_matrix/rgb_matrix.i')

if not os.path.exists(rgb_matrix_path):
cli.log.error(f"{rgb_matrix_path} does not exist.")
cli.log.error("Please compile the image first.")
return False

with open(rgb_matrix_path, 'r') as f:
file_contents = f.read()
enum_as_dict = {key: value for (key, value) in extract_enum_from_c_file_as_dict(file_contents, "rgb_matrix_effects").items() if key != "RGB_MATRIX_EFFECT_MAX"}

max_value = max(enum_as_dict.values())

def enum_name_to_effect_name(x):
return x.replace("RGB_MATRIX_", "")

formatted = [[f"{str(idx).zfill(len(str(max_value)))}. {enum_name_to_effect_name(enum_name)}", value] for idx, [enum_name, value] in enumerate(enum_as_dict.items())]
print(json.dumps(formatted, cls=InfoJSONEncoder))
94 changes: 94 additions & 0 deletions lib/python/qmk/tests/c_parse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
from qmk.c_parse import extract_enum_from_c_file_as_dict


def test_extract_enum_from_c_file_as_dict():
test_cases = [
{
'input_1': 'enum my_enum {};',
'input_2': 'my_enum',
'expected_output': {}
},
{
'input_1': 'enum my_enum { A };',
'input_2': 'my_enum',
'expected_output': {
'A': 0
}
},
{
'input_1': 'enum my_enum { A = 5, B = 10, C = 15 };',
'input_2': 'my_enum',
'expected_output': {
'A': 5,
'B': 10,
'C': 15
}
},
{
'input_1': 'enum my_enum { A, B = 10, C };',
'input_2': 'my_enum',
'expected_output': {
'A': 0,
'B': 10,
'C': 11
}
},
{
'input_1': 'enum my_enum {\n A,\n B, // This comment refers to letter B\n C\n};',
'input_2': 'my_enum',
'expected_output': {
'A': 0,
'B': 1,
'C': 2
}
},
{
'input_1': 'enum my_enum { A = 0, B = 0, C = 1 };',
'input_2': 'my_enum',
'expected_output': {
'A': 0,
'B': 0,
'C': 1
}
},
{
'input_1': 'enum my_enum {\n A,\n B,\n C\n};\n\nenum my_other_enum {\n D\n};',
'input_2': 'my_enum',
'expected_output': {
'A': 0,
'B': 1,
'C': 2
}
},
{
'input_1': '/* Valid C comments */\nenum my_enum {\n A,\n B,\n C\n};',
'input_2': 'my_enum',
'expected_output': {
'A': 0,
'B': 1,
'C': 2
}
},
{
'input_1': 'enum my_enum {\n A,\n B,\n C\n};',
'input_2': 'my_enum',
'expected_output': {
'A': 0,
'B': 1,
'C': 2
}
},
{
'input_1': 'enum my_enum {\n A = 42,\n B,\n C\n};',
'input_2': 'my_enum',
'expected_output': {
'A': 42,
'B': 43,
'C': 44
}
},
]

for test_case in test_cases:
result = extract_enum_from_c_file_as_dict(test_case["input_1"], test_case["input_2"])
assert test_case["expected_output"] == result