diff --git a/configure.ac b/configure.ac index 75fd274..8e32324 100644 --- a/configure.ac +++ b/configure.ac @@ -1,8 +1,9 @@ -AC_INIT([x52pro-linux], [0.1.0], [nirenjan@gmail.com]) +AC_INIT([x52pro-linux], [0.1.1], [nirenjan@gmail.com]) AC_CONFIG_MACRO_DIR([m4]) AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects]) AC_PROG_CC AM_PROG_AR +AM_PATH_PYTHON([2.6]) LT_INIT # Check for libusb-1.0 diff --git a/util/Makefile.am b/util/Makefile.am index 61f929f..9272779 100644 --- a/util/Makefile.am +++ b/util/Makefile.am @@ -10,17 +10,14 @@ libx52util_la_LDFLAGS = -version-info 1:0:0 libx52util_la_LIBADD = ../libx52/libx52.la # Header files that need to be copied -# pkginclude_HEADERS = +pkginclude_HEADERS = libx52util.h # Extra files that need to be in the distribution EXTRA_DIST = x52_char_map.cfg \ - x52_char_map.h - -# Character map generator -noinst_PROGRAMS = x52charmapgen -x52charmapgen_SOURCES = char_map_parser_gen.c + x52_char_map.h \ + x52_char_map_gen.py # Autogenerated file that needs to be cleaned up CLEANFILES = util_char_map.c -util_char_map.c: $(srcdir)/x52_char_map.cfg x52charmapgen$(EXEEXT) - $(AM_V_GEN) ./x52charmapgen$(EXEEXT) $(srcdir)/x52_char_map.cfg $@ +util_char_map.c: $(srcdir)/x52_char_map.cfg x52_char_map_gen.py + $(AM_V_GEN) ./x52_char_map_gen.py $(srcdir)/x52_char_map.cfg $@ diff --git a/util/char_map_parser_gen.c b/util/char_map_parser_gen.c deleted file mode 100644 index be75a6c..0000000 --- a/util/char_map_parser_gen.c +++ /dev/null @@ -1,309 +0,0 @@ -/* - * Saitek X52 Pro Character Map Parser and Generator - * - * This file takes in an input map and converts it to a C file which implements - * a lookup table to match the input UTF-8 character to the corresponding - * character map value. - * - * Copyright (C) 2015 Nirenjan Krishnan (nirenjan@nirenjan.org) - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2. - * - */ - -#include -#include -#include -#include -#include - -#include "x52_char_map.h" - -struct map_table { - struct map_table *next; - uint32_t value_so_far; - uint16_t type; - uint8_t value; -}; - -struct map_table *root; - -struct stack_node { - struct stack_node *next; - struct map_table *entry; - uint32_t value; -}; - -struct stack_node *stack; - -/* - * Convert Unicode code point to UTF-8 bytearray. The bytearray should be - * at least 8 bytes long to accomodate for the longest possible value of - * Unicode code point and a terminating NUL byte. - */ -static void convert_unichr_to_utf8(int unicode_cp, char *utf8_bytearray) -{ - if (unicode_cp <= 0x7F) { - utf8_bytearray[0] = unicode_cp; - utf8_bytearray[1] = 0; - } else if (unicode_cp <= 0x7FF) { - utf8_bytearray[0] = 0xC0 | ((unicode_cp & 0x7C0) >> 6); - utf8_bytearray[1] = 0x80 | (unicode_cp & 0x03F); - utf8_bytearray[2] = 0; - } else if (unicode_cp <= 0xFFFF) { - utf8_bytearray[0] = 0xE0 | ((unicode_cp & 0xF000) >> 12); - utf8_bytearray[1] = 0x80 | ((unicode_cp & 0x0FC0) >> 6); - utf8_bytearray[2] = 0x80 | (unicode_cp & 0x003F); - utf8_bytearray[3] = 0; - } else if (unicode_cp <= 0x1FFFFF) { - utf8_bytearray[0] = 0xF0 | ((unicode_cp & 0x1C0000) >> 18); - utf8_bytearray[1] = 0x80 | ((unicode_cp & 0x03F000) >> 12); - utf8_bytearray[2] = 0x80 | ((unicode_cp & 0x000FC0) >> 6); - utf8_bytearray[3] = 0x80 | (unicode_cp & 0x00003F); - utf8_bytearray[4] = 0; - } else if (unicode_cp <= 0x3FFFFFF) { - utf8_bytearray[0] = 0xF8 | ((unicode_cp & 0x3000000) >> 24); - utf8_bytearray[1] = 0x80 | ((unicode_cp & 0x0FC0000) >> 18); - utf8_bytearray[2] = 0x80 | ((unicode_cp & 0x003F000) >> 12); - utf8_bytearray[3] = 0x80 | ((unicode_cp & 0x0000FC0) >> 6); - utf8_bytearray[4] = 0x80 | (unicode_cp & 0x000003F); - utf8_bytearray[5] = 0; - } else /* if (unicode_cp <= 0x7FFFFFFF) */ { - utf8_bytearray[0] = 0xFC | ((unicode_cp & 0x40000000) >> 30); - utf8_bytearray[1] = 0x80 | ((unicode_cp & 0x3F000000) >> 24); - utf8_bytearray[2] = 0x80 | ((unicode_cp & 0x00FC0000) >> 18); - utf8_bytearray[3] = 0x80 | ((unicode_cp & 0x0003F000) >> 12); - utf8_bytearray[4] = 0x80 | ((unicode_cp & 0x00000FC0) >> 6); - utf8_bytearray[5] = 0x80 | (unicode_cp & 0x0000003F); - utf8_bytearray[6] = 0; - } -} - -static int stack_push(struct map_table *entry, int value) -{ - struct stack_node *node = calloc(1, sizeof(*node)); - - if (!node) { - fprintf(stderr, "Cannot allocate memory for stack node!\n"); - return 0; - } - - node->entry = entry; - node->value = value; - node->next = stack; - stack = node; - - return 1; -} - -static int stack_pop(struct stack_node *node) -{ - struct stack_node *temp; - - if (stack) { - *node = *stack; - - temp = stack; - stack = stack->next; - free(temp); - return 1; - } - - return 0; -} - -static int add_to_mem(int input_val, int map_val) -{ - uint8_t bytearray[8]; - int i; - int value_so_far = 0; - uint8_t c; - struct map_table *level; - struct map_table *temp; - - if (!root) { - root = calloc(256, sizeof(*root)); - if (!root) { - fprintf(stderr, "Cannot allocate memory for root node!\n"); - return 0; - } - - if (!stack_push(root, 0)) { - return 0; - } - } - - convert_unichr_to_utf8(input_val, bytearray); - level = root; - for (i = 0; bytearray[i]; i++) { - c = bytearray[i]; - value_so_far <<= 8; - value_so_far |= c; - if (bytearray[i+1]) { - level[c].type = TYPE_POINTER; - if (!level[c].next) { - temp = calloc(256, sizeof(*temp)); - if (!temp) { - fprintf(stderr, "Cannot allocate memory for entry table!\n"); - return 0; - } - if (!stack_push(temp, value_so_far)) { - return 0; - } - level[c].value_so_far = value_so_far; - level[c].next = temp; - } - - level = level[c].next; - } else { - level[c].type = TYPE_ENTRY; - level[c].value = map_val; - } - } - - return 1; -} - -/* - * Parse an input line and return the input value and map value - * For comment lines, or invalid lines, returned input value is 0. - * - * For invalid lines, return value is 0 - * For valid lines, return value is 1 - * For comment lines, return value is 2 - */ -static int parse_line(char *line, int *input_value, int *map_value) -{ - const char *format_str1 = " 0x%x 0x%x"; - const char *format_str2 = " 0x%x %c"; - int ret; - int val1; - int val2; - char c; - - /* Strip off leading whitespace */ - while (*line && isspace(*line)) line++; - - /* If line begins with # or is empty, it's a comment line */ - if (line[0] == '#' || line[0] == '\0') { - /* Comment line */ - *input_value = 0; - return 2; - } - - /* Try to match input against format_str1 */ - ret = sscanf(line, format_str1, &val1, &val2); - if (ret == 2) { - /* We have a match! */ - *input_value = val1; - *map_value = val2; - return 1; - } - - /* Try to match input against format_str2 */ - ret = sscanf(line, format_str2, &val1, &c); - if (ret == 2) { - /* We have a match! */ - *input_value = val1; - *map_value = c; - return 1; - } - - /* Neither format string matched, and it's not a comment line. Abort */ - return 0; -} - -static void write_output(FILE *dest) -{ - struct stack_node node; - int i; - int start; - int end; - - while (stack_pop(&node)) { - if (node.value) { - fprintf(dest, "static struct map_entry table_%x[64] = {\n", node.value); - start = 0x80; - end = 0xC0; - } else { - fprintf(dest, "struct map_entry map_root[256] = {\n"); - start = 0; - end = 256; - } - - for (i = start; i < end; i++) { - if (node.entry[i].next) { - fprintf(dest, "\t[0x%02x] = { table_%x, TYPE_POINTER, 0 },\n", - i - start, node.entry[i].value_so_far); - } else if (node.entry[i].type == TYPE_ENTRY) { - fprintf(dest, "\t[0x%02x] = { NULL, TYPE_ENTRY, 0x%02x },\n", - i - start, node.entry[i].value); - } - } - - fprintf(dest, "};\n\n"); - } -} - -void dummy(void) -{ - printf("Dummy function for breakpoint trap\n"); -} - -int main(int argc, char *argv[]) -{ - FILE *input = NULL; - FILE *output = NULL; - char buffer[512]; - int rc = 0; - - #define EXIT(v) do { rc = (v); goto exit_handler; } while (0) - - if (argc < 3) { - fprintf(stderr, "Usage: %s \n", argv[0]); - return 1; - } - - input = fopen(argv[1], "r"); - if (!input) { - fprintf(stderr, "Unable to read input file %s\n", argv[1]); - EXIT(2); - } - - output = fopen(argv[2], "w"); - if (!output) { - fprintf(stderr, "Unable to write output file %s\n", argv[2]); - EXIT(2); - } - - while (fgets(buffer, sizeof(buffer), input)) { - int input_val; - int map_val; - if (!parse_line(buffer, &input_val, &map_val)) { - fprintf(stderr, "Invalid line: %s\n", buffer); - EXIT(3); - } - - if (!add_to_mem(input_val, map_val)) { - EXIT(4); - } - } - - /* Write header for output file */ - fputs("/*\n", output); - fputs(" * Autogenerated character map file for Saitek X52 Pro\n", output); - fputs(" * Generated from ", output); - fputs(argv[1], output); - fputs("\n */\n\n", output); - fputs("#include \"x52_char_map.h\"\n", output); - fputs("\n\n", output); - - write_output(output); - -exit_handler: - if (output) fclose(output); - if (input) fclose(input); - return rc; -} diff --git a/util/x52_char_map.cfg b/util/x52_char_map.cfg index 9f9db5a..d1bf359 100644 --- a/util/x52_char_map.cfg +++ b/util/x52_char_map.cfg @@ -26,7 +26,7 @@ 0x0020 0x20 0x0021 ! 0x0022 " -0x0023 # +0x0023 0x23 # Can't really use the # character as the parser strips it out 0x0024 $ 0x0025 % 0x0026 & diff --git a/util/x52_char_map_gen.py b/util/x52_char_map_gen.py new file mode 100755 index 0000000..6a79bd0 --- /dev/null +++ b/util/x52_char_map_gen.py @@ -0,0 +1,166 @@ +#!/usr/bin/python +# Character map generator + +import sys +import re +import inspect + +autogen_header = """ +/* + * Autogenerated character map file for Saitek X52 Pro + * Generated from %s + */ + +#include "x52_char_map.h" + +""" + +class MapTable(): + """ + Defines a MapTable entry, with each entry storing the value seen so far, + the type of the entry, and the value, if it's a value node. + """ + # Empty list + root = [None] * 256 + + def __init__(self, value_so_far, map_value = None): + self.next_level = [None] * 256 + self.value_so_far = value_so_far + self.map_value = map_value + + def output_nodes(self): + output_lines = [] + output_count = 0 + for node in self.next_level: + if node is not None: + output_lines.extend(node.output_nodes()) + output_count += 1 + + if output_count != 0: + struct_header = 'static struct map_entry table_%x[64] = {' % self.value_so_far + output_lines.append(struct_header) + + for node_index in range(0,256): + node = self.next_level[node_index] + if node is not None: + output_lines.append(self.dump_entry_line(0x80, node_index, + node.value_so_far, node.map_value)) + + output_lines.extend(['};', '']) + + return output_lines + + @staticmethod + def dump_entry_line(offset, node_index, value_so_far, map_value): + if map_value is None: + node_entry_line = '\t[0x%02x] = { table_%x, TYPE_POINTER, 0 },' % \ + (node_index - offset, value_so_far) + else: + node_entry_line = '\t[0x%02x] = { NULL, TYPE_ENTRY, 0x%02x },' % \ + (node_index - offset, map_value) + + return node_entry_line + + @classmethod + def add_to_table(cls, input_val, map_val): + utf8_str = unichr(input_val).encode('utf-8') + utf8_vals = [ord(c) for c in utf8_str] + + value_so_far = 0 + level = cls.root + for i in range(len(utf8_vals)): + c = utf8_vals[i] + value_so_far = (value_so_far << 8) | c + if i < (len(utf8_vals) - 1): + node = level[c] + if node is None: + node = cls(value_so_far) + level[c] = node + + level = level[c].next_level + else: + node = cls(value_so_far, map_val) + level[c] = node + + @classmethod + def output_table_as_list(cls): + output_lines = [] + for node in cls.root: + if node is not None: + output_lines.extend(node.output_nodes()) + + output_lines.append('struct map_entry map_root[256] = {') + + for node_index in range(0,256): + node = cls.root[node_index] + if node is not None: + output_lines.append(cls.dump_entry_line(0x0, node_index, + node.value_so_far, node.map_value)) + + output_lines.extend(['};', '']) + + return output_lines + +class LineFormatError(ValueError): + pass + +def parse_line(line): + """ + Parse a line containing a mapping descriptor. The mapping descriptor + must start with a hexadecimal unicode code point, followed by either a + single character, or a hexadecimal integer that corresponds to the map + value. + """ + # Strip off comments + line = re.sub(re.compile('#.*$'), '', line) + + # Strip off leading and trailing whitespace + line = line.strip() + + # If the line is empty, it is a comment line + if len(line) == 0: + return None, None + + # Find the code point and the target value + try: + code_point, target = line.strip().split() + except ValueError: + # Raised when there are either too many, or not enough values in + # the string + raise LineFormatError('Invalid descriptor format "%s"' % line) + + # Convert the string to its equivalent numeric value + try: + code_point = int(code_point, 0) + except ValueError: + raise LineFormatError('Invalid code point "%s"' % code_point) + + # Check if the target is a single character + if len(target) == 1: + target = ord(target) + else: + # Try parsing the target as an integer + try: + target = int(target, 0) + except ValueError: + raise LineFormatError('Invalid map value "%s"' % target) + + return code_point, target + +if __name__ == "__main__": + if len(sys.argv) != 3: + sys.stderr.write('Usage: %s \n' % sys.argv[0]) + sys.exit(1) + + with open(sys.argv[1], 'r') as infile: + for line in infile: + src, dst = parse_line(line) + if src is not None: + MapTable.add_to_table(src,dst) + + with open(sys.argv[2], 'w') as outfile: + outfile.write(autogen_header % sys.argv[1]) + + for line in MapTable.output_table_as_list(): + outfile.write(line + '\n') +