示例#1
0
    def __init__(self):
        self.settings = {}
        self.rf_settings = None

        self._devices = {}
        self._layouts = {}
        self._layout_id_map = {}

        self.user_keycodes = UserKeycodes()
        self.ekc_data = EKCDataTable()

        self.kc_mapper = KeycodeMapper()
        self.kc_mapper.set_user_keycodes(self.user_keycodes)
示例#2
0
class KeyplusLayout(object):
    def __init__(self):
        self.settings = {}
        self.rf_settings = None

        self._devices = {}
        self._layouts = {}
        self._layout_id_map = {}

        self.user_keycodes = UserKeycodes()
        self.ekc_data = EKCDataTable()

        self.kc_mapper = KeycodeMapper()
        self.kc_mapper.set_user_keycodes(self.user_keycodes)


    def _from_file_common(self, layout_file=None, rf_file=None, print_warnings=False,
                           load_method=yaml.load, warnings=None):
        basename = os.path.basename(layout_file)
        if layout_file:
            with open(layout_file) as f:
                layout_json_obj = load_method(f.read())
            parser_info = KeyplusParserInfo(
                "<{}>".format(basename),
                layout_json_obj,
                print_warnings = print_warnings,
            )
        else:
            parser_info = None

        if rf_file:
            rf_basename = os.path.basename(rf_file)
            with open(rf_file) as f:
                rf_json_obj = load_method(f.read())
            rf_parser_info = KeyplusParserInfo(
                "<{}>".format(rf_basename),
                rf_json_obj,
                print_warnings = print_warnings,
            )
        else:
            rf_parser_info = None

        result = self.parse_json(
            parser_info =  parser_info,
            rf_parser_info = rf_parser_info,
        )

        if warnings != None:
            # print(warnings, parser_info.warnings, rf_parser_info.warnings)
            if parser_info != None:
                warnings.extend(parser_info.warnings)
            if rf_parser_info != None:
                warnings.extend(rf_parser_info.warnings)

        return result

    def from_json_file(self, layout_file=None, rf_file=None,
                       print_warnings=False, warnings=None):
        return self._from_file_common(
            layout_file,
            rf_file,
            print_warnings,
            json.loads,
            warnings,
        )

    def from_yaml_file(self, layout_file=None, rf_file=None,
                       print_warnings=False, warnings=None):
        return self._from_file_common(
            layout_file,
            rf_file,
            print_warnings,
            yaml.load,
            warnings,
        )

    def add_device(self, device):
        dev_id = device.device_id
        if dev_id in self._devices:
            raise KeyplusSettingsError(
                "Failed to add device '{}' because the device '{}' "
                "is already using the device id '{}'."
                .format(
                    device.name,
                    self._devices[dev_id].name,
                    dev_id
                )
            )
        else:
            self._devices[dev_id] = device

    def del_device(self, device):
        dev_id = device.device_id
        if dev_id in self._devices:
            del self._devices[dev_id]
        else:
            raise KeyplusError(
                "Tried to delete non-existent device '{}' with id '{}'."
                .format(device.name, layout_id)
            )

    def get_layout_by_id(self, layout_id):
        return self._layouts[layout_id]

    def get_layout_by_name(self, name):
        layout_id = self._layout_id_map[name]
        return self._layouts[layout_id]

    def add_layout(self, layout):
        layout_id = layout.layout_id
        if layout_id in self._layouts:
            raise KeyplusSettingsError(
                "Failed to add layout '{}' because the layout '{}' has the "
                "same name"
                .format(
                    layout.name,
                    self._devices[layout_id].name,
                )
            )
        else:
            self._layouts[layout_id] = layout
            if layout.name != None:
                self._layout_id_map[layout.name] = layout_id

    def del_layout(self, layout):
        layout_id = device.layout_id
        if layout_id in self._layouts:
            del self._layouts[layout_id]
            if layout.name != None:
                del self._layout_id_map[layout.name]
        else:
            raise KeyplusError(
                "Tried to delete non-existent layout '{}'."
                .format(layout_id)
            )

    def _parse_devices(self, parser_info):
        parser_info.enter("devices")
        for field in parser_info.iter_fields():
            device = LayoutDevice()
            device.parse_json(
                device_name = field,
                parser_info = parser_info
            )
            self.add_device(device)
        parser_info.exit()

    def _parse_keycodes(self, parser_info):
        if not parser_info.has_field('keycodes'):
            return
        self.user_keycodes.parse_json(parser_info = parser_info)
        self.ekc_data = self.user_keycodes.generate_ekc_data()

    def _parse_layouts(self, parser_info):
        parser_info.enter("layouts")
        for (layout_num, field) in enumerate(parser_info.iter_fields()):
            layout = LayoutKeyboard(field)
            layout.set_keycode_mapper(self.kc_mapper)

            layout.parse_json(
                name = field,
                parser_info = parser_info
            )
            if layout.layout_id == None:
                layout.layout_id = layout_num
            self.add_layout(layout)
        parser_info.exit()

    def parse_json(self, layout_json=None, rf_json=None, parser_info=None, rf_parser_info=None):
        if parser_info == None:
            assert(layout_json != None)
            parser_info = KeyplusParserInfo(
                "<KeyplusLayout(layout)>",
                layout_json,
                print_warnings = True
            )
        if not rf_parser_info and rf_json:
            rf_parser_info = KeyplusParserInfo(
                "<KeyplusLayout(rf)>",
                rf_json,
                print_warnings = True
            )

        self.settings["name"] = parser_info.try_get(
            field = "name",
            field_type = str,
            default = "Keyplus Layout",
        )

        self.settings["version"] = parser_info.try_get(
            field = "version",
            field_type = [str, int],
            optional = True
        )

        self.settings["default_report_mode"] = parser_info.try_get(
            field = "default_report_mode",
            field_type = str,
            default = KEYBOARD_REPORT_MODE_AUTO,
            remap_table = REPORT_MODE_MAP,
        )

        self._parse_devices(parser_info)
        self._parse_keycodes(parser_info)
        self._parse_layouts(parser_info)

        for dev in six.itervalues(self._devices):
            if dev.scan_mode.mode == MATRIX_SCANNER_MODE_NO_MATRIX:
                continue

            layout = self.get_layout_by_name(dev.layout)
            dev_sizes = layout.layer_list[0].device_sizes

            if dev.split_device_num >= len(dev_sizes):
                raise KeyplusParseError(
                    "Layout doesn't have the given split device number. The "
                    "device \"{}\" maps to split device {}, but "
                    "the layout \"{}\" only has {} split devices."
                    .format(
                        dev.name,
                        dev.split_device_num,
                        layout.name,
                        len(dev_sizes),
                    )
                )
            layout_size = dev_sizes[dev.split_device_num]

            if dev.scan_mode.number_mapped_keys != layout_size:
                raise KeyplusParseError(
                    "Layout size doesn't match device matrix_map size. The "
                    "device \"{}\" has a matrix map of length {}, but "
                    "the layout \"{}\" has length {} for split device {}."
                    .format(
                        dev.name,
                        dev.scan_mode.number_mapped_keys,
                        layout.name,
                        layout_size,
                        dev.split_device_num
                    )
                )

        if rf_parser_info:
            self.rf_settings = LayoutRFSettings()
            self.rf_settings.parse_json(rf_json, rf_parser_info)

        if DEBUG.parsing_extra:
            for device in six.itervalues(self._devices):
                print("LayoutDevice({})".format(device.name))
                print(device.to_json())
                print()
            for layout in six.itervalues(self._layouts):
                print("LayoutKeyboard({}, {})".format(layout.layout_id, layout.name))
                print(layout.to_json())
                print()
            if self.rf_settings and rf_parser_info:
                print("LayoutRFSettings({})".format(rf_parser_info.current_field))
                print(self.rf_settings.to_json())
                print()

    def pack_settings_data(self, device_target):
        pass

    def _build_layouts(self):
        result = bytearray()
        num_layouts = len(self._layouts)

        for layout_id in range(num_layouts):
            if layout_id not in self._layouts:
                raise KeyplusSettingsError(
                    "Layout ids cannot skip values. TODO(remove this requirement)"
                )
            result += self._layouts[layout_id].to_bytes()
        return result

    @property
    def number_layouts(self):
        return len(self._layouts)

    @property
    def number_devices(self):
        return len(self._devices)

    def build_layout_settings(self):
    # uint8_t number_layouts;
    # uint8_t number_devices;
    # uint8_t _reserved[30]; // 32
    # keyboard_info_t layouts[MAX_NUM_KEYBOARDS];
    # device_info_t devices[MAX_NUM_DEVICES];
        layout_info = KeyboardLayoutInfo()
        layout_info.number_layouts = self.number_layouts
        layout_info.number_devices = max(self._devices)

        # struct keyboard_info_t layouts[MAX_NUM_KEYBOARDS];

        for layout_id in self._layouts:
            layout = self._layouts[layout_id]
            layer = layout.layer_list[0]

            size = 0
            for (split_device_number, _) in enumerate(layer.device_list):
                size += layer.get_device_size(split_device_number)
            layout_info.layouts[layout_id].matrix_size = size
            layout_info.layouts[layout_id].layer_count = layout.number_layers

        for device_id in range(MAX_NUMBER_DEVICES):
            dev = layout_info.devices[device_id]
            if device_id in self._devices:
                this_dev = self._devices[device_id]
                if this_dev.scan_mode.mode == MATRIX_SCANNER_MODE_NO_MATRIX:
                    dev.layout_id = LAYOUT_ID_NONE
                else:
                    if this_dev.layout in self._layout_id_map:
                        dev.layout_id = self._layout_id_map[this_dev.layout]
                        target_layout = self._layouts[dev.layout_id]
                        target_layer = target_layout.layer_list[0]
                        dev.matrix_offset = target_layer.get_device_offset(this_dev.split_device_num)
                        # TODO: Change this to a key number
                        dev.matrix_size = target_layer.get_device_size(this_dev.split_device_num)
                    else:
                        raise KeyplusSettingsError(
                            "Device '{}' is set to use layout '{}', but that "
                            "layout does not exist."
                            .format(this_dev.name, this_dev.layout)
                        )
                    # dev.layout_id =
                    pass
            else:
                dev.layout_id = LAYOUT_ID_INVALID

        return layout_info

    def get_device(self, dev_id):
        if dev_id not in self._devices:
            raise KeyplusSettingsError(
                "Couldn't find any device in the layout with device id {}"
                .format(dev_id)
            )
        return self._devices[dev_id]

    def build_settings_section(self, device_target):
        device = self.get_device(device_target.device_id)

        settings = settings_t()

        settings_header = device.build_settings_header(device_target)
        settings_header.timestamp_raw = int(time.time())
        settings_header.default_report_mode = self.settings['default_report_mode']
        settings_header.crc = settings_header.compute_crc()

        settings.header = settings_header

        settings.layout = self.build_layout_settings()

        if self.rf_settings == None:
            settings.rf = rf_settings_t()
        else:
            settings.rf = self.rf_settings.generate_rf_settings()

        return settings.to_bytes()

    def build_layout_section(self, device_target):
        device = self.get_device(device_target.device_id)
        result = bytearray()

        pin_map = device.scan_mode.generate_pin_mapping(device_target)

        result += pin_map.to_bytes()
        result += self.ekc_data.to_bytes()
        result += self._build_layouts()

        return result
示例#3
0
class KeyplusLayout(object):
    def __init__(self):
        self.settings = {}
        self.rf_settings = None

        self._devices = {}
        self._layouts = {}
        self._layout_id_map = {}

        self.user_keycodes = UserKeycodes()
        self.ekc_data = EKCDataTable()

        self.kc_mapper = KeycodeMapper()
        self.kc_mapper.set_user_keycodes(self.user_keycodes)

    def _from_file_common(self,
                          layout_file=None,
                          rf_file=None,
                          parser_info=None,
                          rf_parser_info=None,
                          load_method=yaml.safe_load):
        basename = os.path.basename(layout_file)
        if layout_file != None:
            with io.open(layout_file, encoding='utf8') as f:
                layout_json_obj = load_method(f.read())

                if parser_info and layout_json_obj:
                    parser_info.set_parse_object("<{}>".format(basename),
                                                 layout_json_obj)
                self._parser_info = parser_info
        if rf_file != None:
            rf_basename = os.path.basename(rf_file)
            with io.open(rf_file, encoding='utf8') as f:
                rf_json_obj = load_method(f.read())

                if rf_parser_info and rf_json_obj:
                    rf_parser_info.set_parse_object("<{}>".format(rf_basename),
                                                    rf_json_obj)

        self.kc_mapper.attach_parser(parser_info)

        result = self.parse_json(
            parser_info=parser_info,
            rf_parser_info=rf_parser_info,
        )

        if False:
            if warnings != None:
                if parser_info != None:
                    warnings.extend(parser_info.warnings)
                if rf_parser_info != None:
                    warnings.extend(rf_parser_info.warnings)

        return result

    def from_json_file(self,
                       layout_file=None,
                       rf_file=None,
                       parser_info=None,
                       rf_parser_info=None):
        return self._from_file_common(
            layout_file,
            rf_file,
            parser_info=parser_info,
            rf_parser_info=rf_parser_info,
            load_method=json.loads,
        )

    def from_yaml_file(self,
                       layout_file=None,
                       rf_file=None,
                       parser_info=None,
                       rf_parser_info=None):
        return self._from_file_common(
            layout_file,
            rf_file,
            parser_info=parser_info,
            rf_parser_info=rf_parser_info,
            load_method=yaml.safe_load,
        )

    def add_device(self, device):
        dev_id = device.device_id
        if dev_id in self._devices:
            raise KeyplusSettingsError(
                "Failed to add device '{}' because the device '{}' "
                "is already using the device id '{}'.".format(
                    device.name, self._devices[dev_id].name, dev_id))
        else:
            self._devices[dev_id] = device

    def del_device(self, device):
        dev_id = device.device_id
        if dev_id in self._devices:
            del self._devices[dev_id]
        else:
            raise KeyplusError(
                "Tried to delete non-existent device '{}' with id '{}'.".
                format(device.name, layout_id))

    def get_layout_by_id(self, layout_id):
        return self._layouts[layout_id]

    def get_layout_by_name(self, name):
        if name in self._layout_id_map:
            layout_id = self._layout_id_map[name]
            return self._layouts[layout_id]
        else:
            return None

    def add_layout(self, layout):
        layout_id = layout.layout_id
        if layout_id in self._layouts:
            raise KeyplusSettingsError(
                "Failed to add layout '{}' because the layout '{}' has the "
                "same name".format(
                    layout.name,
                    self._devices[layout_id].name,
                ))
        else:
            self._layouts[layout_id] = layout
            if layout.name != None:
                self._layout_id_map[layout.name] = layout_id

    def del_layout(self, layout):
        layout_id = device.layout_id
        if layout_id in self._layouts:
            del self._layouts[layout_id]
            if layout.name != None:
                del self._layout_id_map[layout.name]
        else:
            raise KeyplusError(
                "Tried to delete non-existent layout '{}'.".format(layout_id))

    def _parse_devices(self, parser_info):
        parser_info.enter("devices")
        for field in parser_info.iter_fields():
            device = LayoutDevice()
            device.parse_json(device_name=field, parser_info=parser_info)
            self.add_device(device)
        parser_info.exit()

    def _parse_keycodes(self, parser_info):
        if not parser_info.has_field('keycodes'):
            return
        self.user_keycodes.parse_json(parser_info=parser_info)
        self.ekc_data = self.user_keycodes.generate_ekc_data()

    def get_default_layout_id(self):
        """
       Gets the default layout id. Return 0 if defualt layout not set
       """
        layout = self.get_layout_by_name(self.default_layout_name)
        if layout != None:
            return layout.layout_id
        elif self.default_layout_name == None:
            return 0
        else:
            self._parser_info.raise_exception(
                "Default layout '{}' not found".format(
                    self.default_layout_name))

    def _parse_layouts(self, parser_info):
        parser_info.enter("layouts")
        self.default_layout_name = None

        layout_id = 0

        for layout_name in parser_info.iter_fields():
            if layout_name == 'default':
                if isinstance(parser_info.peek_field(layout_name), str):
                    # default layout is an alias to another layout, so don't parse it
                    self.default_layout_name = parser_info.try_get(layout_name)
                    continue
                else:
                    self.default_layout_name = 'default'

            layout = LayoutKeyboard(layout_name)
            layout.set_keycode_mapper(self.kc_mapper)

            layout.parse_json(name=layout_name, parser_info=parser_info)
            if layout.layout_id == None:
                layout.layout_id = layout_id
            layout_id += 1
            self.add_layout(layout)
        parser_info.exit()

    def parse_json(self,
                   layout_json=None,
                   rf_json=None,
                   parser_info=None,
                   rf_parser_info=None):
        if parser_info == None:
            assert (layout_json != None)
            parser_info = KeyplusParserInfo(
                "<KeyplusLayout(layout)>",
                layout_json,
            )
        if not rf_parser_info and rf_json:
            rf_parser_info = KeyplusParserInfo(
                "<KeyplusLayout(rf)>",
                rf_json,
            )

        self.kc_mapper.attach_parser(parser_info)

        self.settings["name"] = parser_info.try_get(
            field="name",
            field_type=str,
            default="Keyplus Layout",
        )

        self.settings["version"] = parser_info.try_get(field="version",
                                                       field_type=[str, int],
                                                       optional=True)

        self.settings["report_mode"] = parser_info.try_get(
            field="report_mode",
            field_type=str,
            default=KEYBOARD_REPORT_MODE_AUTO,
            remap_table=REPORT_MODE_MAP,
        )

        self._parse_devices(parser_info)
        self._parse_keycodes(parser_info)
        self._parse_layouts(parser_info)

        parser_info.exit()

        for dev in self._devices.values():
            if dev.scan_mode.mode == MATRIX_SCANNER_MODE_NO_MATRIX:
                continue

            layout = self.get_layout_by_name(dev.layout)
            if layout == None:
                raise KeyplusParseError(
                    "Device '{}' references non-existent layout '{}'".format(
                        dev.name, dev.layout))
            dev_sizes = layout.layer_list[0].device_sizes

            if dev.split_device_num >= len(dev_sizes):
                raise KeyplusParseError(
                    "Layout doesn't have the given split device number. The "
                    "device \"{}\" maps to split device {}, but "
                    "the layout \"{}\" only has {} split devices.".format(
                        dev.name,
                        dev.split_device_num,
                        layout.name,
                        len(dev_sizes),
                    ))
            layout_size = dev_sizes[dev.split_device_num]

            if dev.scan_mode.number_mapped_keys != layout_size:
                raise KeyplusParseError(
                    "Layout size doesn't match device matrix_map size. The "
                    "device \"{}\" has a matrix map of length {}, but "
                    "the layout \"{}\" has length {} for split device {}.".
                    format(dev.name, dev.scan_mode.number_mapped_keys,
                           layout.name, layout_size, dev.split_device_num))

        if rf_parser_info:
            self.rf_settings = LayoutRFSettings()
            self.rf_settings.parse_json(rf_json, rf_parser_info)
            rf_parser_info.exit()

        if DEBUG.parsing_extra:
            for device in self._devices.values():
                print("LayoutDevice({})".format(device.name))
                print(device.to_json())
                print()
            for layout in self._layouts.values():
                print("LayoutKeyboard({}, {})".format(layout.layout_id,
                                                      layout.name))
                print(layout.to_json())
                print()
            if self.rf_settings and rf_parser_info:
                print("LayoutRFSettings({})".format(
                    rf_parser_info.current_field))
                print(self.rf_settings.to_json())
                print()

    def pack_settings_data(self, device_target):
        pass

    def _build_layouts(self):
        result = bytearray()
        num_layouts = len(self._layouts)

        for layout_id in range(num_layouts):
            if layout_id not in self._layouts:
                raise KeyplusSettingsError(
                    "Layout ids cannot skip values. TODO(remove this requirement)"
                )
            result += self._layouts[layout_id].to_bytes()
        return result

    @property
    def number_layouts(self):
        return len(self._layouts)

    @property
    def number_devices(self):
        return len(self._devices)

    def build_layout_settings(self):
        # uint8_t number_layouts;
        # uint8_t number_devices;
        # uint8_t default_layout_id;
        # uint8_t _reserved[29]; // 32
        # keyboard_info_t layouts[MAX_NUM_KEYBOARDS];
        # device_info_t devices[MAX_NUM_DEVICES];
        layout_info = KeyboardLayoutInfo()
        layout_info.number_layouts = self.number_layouts
        # need to store the max device ID used by the device
        layout_info.number_devices = max(self._devices) + 1
        layout_info.default_layout_id = self.get_default_layout_id()

        # struct keyboard_info_t layouts[MAX_NUM_KEYBOARDS];

        # Build the layout table
        # The layout table holds precomputed values for layout sizes
        # and their number of layers
        for layout_id in self._layouts:
            layout = self._layouts[layout_id]
            layer = layout.layer_list[0]

            size = 0
            for (split_device_number, _) in enumerate(layer.device_list):
                size += layer.get_layout_component_size(split_device_number)
            layout_info.layouts[layout_id].matrix_size = size
            layout_info.layouts[layout_id].layer_count = layout.number_layers

        # Build the device map, need to store:
        # * layout_id: the layout this device maps to
        # * matrix_offset: the offset of this device into the layout with the
        #   given id
        # * matrix_size:
        # NOTE: matrix_offset and matrix_size are given in bytes, i.e.
        # ceil(num_keys/8)
        for device_id in range(MAX_NUMBER_DEVICES):
            dev = layout_info.devices[device_id]
            if device_id in self._devices:
                this_dev = self._devices[device_id]
                if this_dev.scan_mode.mode == MATRIX_SCANNER_MODE_NO_MATRIX:
                    dev.layout_id = LAYOUT_ID_NONE
                else:
                    if this_dev.layout in self._layout_id_map:
                        dev.layout_id = self._layout_id_map[this_dev.layout]
                        target_layout = self._layouts[dev.layout_id]
                        target_layer = target_layout.layer_list[0]
                        dev.matrix_offset = target_layer.get_layout_component_offset(
                            this_dev.split_device_num)
                        # TODO: Change this to a key number
                        dev.matrix_size = target_layer.get_layout_component_size(
                            this_dev.split_device_num)
                    else:
                        raise KeyplusSettingsError(
                            "Device '{}' is set to use layout '{}', but that "
                            "layout does not exist.".format(
                                this_dev.name, this_dev.layout))
                    # dev.layout_id =
                    pass
            else:
                dev.layout_id = LAYOUT_ID_INVALID

        return layout_info

    def get_device(self, dev_id):
        if dev_id not in self._devices:
            raise KeyplusSettingsError(
                "Couldn't find any device in the layout with device id {}".
                format(dev_id))
        return self._devices[dev_id]

    def build_settings_section(self, device_target):
        if device_target.is_virtual():
            device = LayoutDevice()
        else:
            device = self.get_device(device_target.device_id)

        settings = settings_t()

        settings_header = device.build_settings_header(device_target)
        settings_header.timestamp_raw = int(time.time())
        settings_header.default_report_mode = self.settings['report_mode']
        settings_header.crc = settings_header.compute_crc()

        settings.header = settings_header

        settings.layout = self.build_layout_settings()

        if self.rf_settings == None:
            settings.rf = rf_settings_t()
        else:
            settings.rf = self.rf_settings.generate_rf_settings()

        return settings.to_bytes()

    def build_layout_section(self, device_target):
        result = bytearray()

        if device_target.is_virtual():
            device_maps = bytearray()
            for (dev_id, dev) in self._devices.items():
                if dev.scan_mode.mode == MATRIX_SCANNER_MODE_VIRTUAL:
                    device_maps += dev.scan_mode.virtual_device_to_bytes(
                        device_target, dev_id)
            result += struct.pack("<I", len(device_maps))
            result += device_maps
        else:
            device = self.get_device(device_target.device_id)
            pin_map = device.scan_mode.generate_pin_mapping(device_target)
            result += pin_map.to_bytes()

        result += self.ekc_data.to_bytes()
        result += self._build_layouts()

        return result