Exemplo n.º 1
0
    def setup_keyplus_label(self):
        try:
            self.device.open()
            settingsInfo = protocol.get_device_info(self.device)
            firmwareInfo = protocol.get_firmware_info(self.device)
            self.device.close()
        except TimeoutError as err:
            # Incase opening the device fails
            raise Exception ("Error Opening Device: {} | {}:{}"
                    .format(
                        self.device.path,
                        self.device.vendor_id,
                        self.device.product_id
                    ),
                  file=sys.stderr
            )

        if settingsInfo.crc == settingsInfo.computed_crc:
            build_time_str = protocol.timestamp_to_str(settingsInfo.timestamp)
            self.label = QLabel('{} | {} | Firmware v{}.{}.{}\n'
                                'Device id: {}\n'
                                'Serial number: {}\n'
                                'Last time updated: {}'
                .format(
                    self.device.manufacturer_string,
                    self.device.product_string,
                    firmwareInfo.version_major,
                    firmwareInfo.version_minor,
                    firmwareInfo.version_patch,
                    settingsInfo.id,
                    self.device.serial_number,
                    build_time_str
                )
            )
        else:
            # CRC doesn't match
            if settingsInfo.is_empty:
                self.label = QLabel('??? | ??? | Firmware v{}.{}.{}\n'
                                    'Warning: Empty settings!\n'
                                    'Serial number: {}\n'
                    .format(
                        firmwareInfo.version_major,
                        firmwareInfo.version_minor,
                        firmwareInfo.version_patch,
                        self.device.serial_number,
                    )
                )
            else:
                # corrupt settings in the flash
                build_time_str = protocol.timestamp_to_str(settingsInfo.timestamp)
                self.label = QLabel('??? | ??? | Firmware v{}.{}.{}\n'
                                    'WARNING: Settings are uninitialized\n'
                                    'Serial number: {}\n'
                    .format(
                        firmwareInfo.version_major,
                        firmwareInfo.version_minor,
                        firmwareInfo.version_patch,
                        self.device.serial_number,
                    )
                )
Exemplo n.º 2
0
def print_device_info(device, indent="  "):
    dev_info = protocol.get_device_info(device)
    print(indent, "id: ", dev_info.id)
    print(indent, "name: '{}'".format(dev_info.device_name_str()) )
    print(indent, "layout last updated: ", dev_info.timestamp_str())
    print(indent, "default_report_mode: ", dev_info.default_report_mode_str())
    print(indent, "scan_mode: ", dev_info.scan_mode)
    print(indent, "row_count: ", dev_info.row_count)
    print(indent, "col_count: ", dev_info.col_count)
Exemplo n.º 3
0
    def setup_keyplus_label(self):
        self.device.open()
        settingsInfo = protocol.get_device_info(self.device)
        firmwareInfo = protocol.get_firmware_info(self.device)
        errorInfo = protocol.get_error_info(self.device)
        self.has_critical_error = errorInfo.has_critical_error()
        self.device.close()

        if settingsInfo.crc == settingsInfo.computed_crc:
            build_time_str = protocol.timestamp_to_str(settingsInfo.timestamp)
            device_name = settingsInfo.device_name_str()
            device_name = device_name.strip('\x00').strip('\xff').strip()

            self.label.setText('{} | {} | Firmware v{}.{}.{}\n'
                               'Device id: {}\n'
                               'Serial number: {}\n'
                               'Last time updated: {}'.format(
                                   self.device.manufacturer_string,
                                   device_name, firmwareInfo.version_major,
                                   firmwareInfo.version_minor,
                                   firmwareInfo.version_patch, settingsInfo.id,
                                   self.device.serial_number, build_time_str))
        else:
            # CRC doesn't match
            if settingsInfo.is_empty:
                self.label.setText('??? | ??? | Firmware v{}.{}.{}\n'
                                   'Warning: Empty settings!\n'
                                   'Serial number: {}\n'.format(
                                       firmwareInfo.version_major,
                                       firmwareInfo.version_minor,
                                       firmwareInfo.version_patch,
                                       self.device.serial_number,
                                   ))
            else:
                # corrupt settings in the flash
                build_time_str = protocol.timestamp_to_str(
                    settingsInfo.timestamp)
                self.label.setText('??? | ??? | Firmware v{}.{}.{}\n'
                                   'WARNING: Settings are uninitialized\n'
                                   'Serial number: {}\n'.format(
                                       firmwareInfo.version_major,
                                       firmwareInfo.version_minor,
                                       firmwareInfo.version_patch,
                                       self.device.serial_number,
                                   ))
Exemplo n.º 4
0
 def setup_keyplus_label(self):
     try:
         self.device.open()
         settingsInfo = protocol.get_device_info(self.device)
         firmwareInfo = protocol.get_firmware_info(self.device)
         self.device.close()
     except TimeoutError as err:
         # Incase opening the device fails
         raise Exception("Error Opening Device: {} | {}:{}".format(
             self.device.path, self.device.vendor_id,
             self.device.product_id),
                         file=sys.stderr)
     self.valid = True
     build_time_str = protocol.timestamp_to_str(settingsInfo.timestamp)
     self.label = QLabel('{} | {} | Firmware v{}.{}.{}\n'
                         'Device id: {}\n'
                         'Serial number: {}\n'
                         'Last time updated: {}'.format(
                             self.device.manufacturer_string,
                             self.device.product_string,
                             firmwareInfo.version_major,
                             firmwareInfo.version_minor,
                             firmwareInfo.version_patch, settingsInfo.id,
                             self.device.serial_number, build_time_str))
Exemplo n.º 5
0
    def programDeviceHandler(self, device_path):
        target_device = self.tryOpenDevicePath(device_path)

        if target_device == None:
            self.abort_update(target_device)
            return

        programmingMode = self.fileSelectorWidget.getProgramingInfo()

        if is_bootloader_device(
                target_device
        ) and programmingMode != FileSelector.ScopeFirmware:
            error_msg_box(
                "Can only upload firmware while bootloader is running. "
                "Either reset it, or upload a firmware hex instead")
            self.abort_update(target_device)
            return

        if programmingMode == FileSelector.ScopeLayout:
            self.statusBar().showMessage("Started updating layout",
                                         timeout=STATUS_BAR_TIMEOUT)

            layout_file = self.fileSelectorWidget.getLayoutFile()

            if layout_file == '':
                error_msg_box("No layout file given.")
                self.abort_update(target_device)
                return
            else:
                pass

            layout_json_obj = None
            with open(layout_file) as file_obj:
                try:
                    layout_json_obj = yaml.safe_load(file_obj.read())
                except Exception as err:
                    error_msg_box("Syntax error in yaml file: " + str(err))
                    self.abort_update(target_device)
                    return

            device_info = protocol.get_device_info(target_device)
            layout_data, settings_data = self.process_layout(
                layout_json_obj, layout_file, device_info.id)
            if layout_data == None or settings_data == None:
                return

            protocol.update_layout_section(target_device, layout_data)
            protocol.update_settings_section(target_device,
                                             settings_data,
                                             keep_rf=True)
            protocol.reset_device(target_device)

            self.statusBar().showMessage("Finished updating layout",
                                         timeout=STATUS_BAR_TIMEOUT)
        elif programmingMode == FileSelector.ScopeDevice:
            layout_file = self.fileSelectorWidget.getRFLayoutFile()
            rf_file = self.fileSelectorWidget.getRFFile()
            target_id = self.fileSelectorWidget.getTargetID()

            self.statusBar().showMessage("Started updating RF settings",
                                         timeout=STATUS_BAR_TIMEOUT)

            if layout_file == '':
                error_msg_box("No layout file given.")
                self.abort_update(target_device)
                return
            elif rf_file == '':
                error_msg_box("No RF settings file given.")
                self.abort_update(target_device)
                return
            elif target_id == None:
                error_msg_box("No device id file given.")
                self.abort_update(target_device)
                return

            layout_json_obj = None
            rf_json_obj = None
            with open(layout_file) as file_obj:
                try:
                    layout_json_obj = yaml.safe_load(file_obj.read())
                except Exception as err:
                    error_msg_box("Syntax error in yaml file: " + str(err))
                    self.abort_update(target_device)
                    return
            with open(rf_file) as file_obj:
                try:
                    rf_json_obj = yaml.safe_load(file_obj.read())
                except Exception as err:
                    error_msg_box("Syntax error in yaml file: " + str(err))
                    self.abort_update(target_device)
                    return

            try:
                settings_gen = layout.parser.SettingsGenerator(
                    layout_json_obj, rf_json_obj)
            except ParseError as err:
                error_msg_box("Error Generating RF settings data: " + str(err))
                self.abort_update(target_device)
                return

            layout_data = settings_gen.gen_layout_section(target_id)
            settings_data = settings_gen.gen_settings_section(target_id)

            protocol.update_settings_section(target_device, settings_data)
            protocol.update_layout_section(target_device, layout_data)
            protocol.reset_device(target_device)

            self.statusBar().showMessage("Finished updating RF settings",
                                         timeout=STATUS_BAR_TIMEOUT)

        elif programmingMode == FileSelector.ScopeFirmware:
            fw_file = self.fileSelectorWidget.getFirmwareFile()

            self.statusBar().showMessage("Starting update firmware",
                                         timeout=STATUS_BAR_TIMEOUT)

            if fw_file == '':
                error_msg_box("No firmware file given.")
            else:

                if is_xusb_bootloader_device(target_device):
                    self.program_xusb_boot_firmware_hex(target_device, fw_file)
                elif is_keyplus_device(target_device):
                    try:
                        serial_num = target_device.serial_number
                        boot_vid, boot_pid = protocol.enter_bootloader(
                            target_device)

                        self.bootloaderProgramTimer = QTimer()
                        self.bootloaderProgramTimer.setInterval(3000)
                        self.bootloaderProgramTimer.setSingleShot(True)
                        self.bootloaderProgramTimer.timeout.connect(
                            lambda: self.programFirmwareHex(
                                boot_vid, boot_pid, serial_num, fw_file))
                        self.bootloaderProgramTimer.start()
                    except (easyhid.HIDException,
                            protocol.KBProtocolException):
                        error_msg_box(
                            "Programming hex file failed: '{}'".format(
                                fw_file))
        else:
            try:
                target_device.close()
            except:
                pass
            raise Exception("Unimplementend programming mode")
Exemplo n.º 6
0
    def infoDeviceHandler(self, device_path):
        device = self.tryOpenDevicePath(device_path)
        if device == None: return

        settingsInfo = protocol.get_device_info(device)
        firmwareInfo = protocol.get_firmware_info(device)
        rfInfo = protocol.get_rf_info(device)
        if firmwareInfo.has_at_least_version('0.2.2'):
            errorInfo = protocol.get_error_info(device)
        else:
            errorInfo = None
        device.close()

        header = ["Attribute", "Value"]
        device_settings = [
            ("Device ID", settingsInfo.id),
            ("Device name", settingsInfo.device_name_str()),
            ("Device serial number", device.serial_number),
            ("Last layout update", settingsInfo.timestamp_str()),
            ("Default report mode", settingsInfo.default_report_mode_str()),
            ("Matrix scan mode", settingsInfo.scan_mode_str()),
            ("Matrix columns", settingsInfo.col_count),
            ("Matrix rows", settingsInfo.row_count),
            ("Settings stored CRC", hex(settingsInfo.crc)),
            ("Settings computed CRC", hex(settingsInfo.computed_crc)),
            ("USB", not (settingsInfo.has_usb_disabled()
                         or not firmwareInfo.has_fw_support_usb())),
            ("I2C", not (settingsInfo.has_i2c_disabled()
                         or not firmwareInfo.has_fw_support_i2c())),
            ("nRF24 wireless",
             not (settingsInfo.has_nrf24_disabled()
                  or not firmwareInfo.has_fw_support_nrf24())),
            ("Unifying mouse",
             not (settingsInfo.has_unifying_mouse_disabled()
                  or not firmwareInfo.has_fw_support_unifying())),
            ("Bluetooth",
             not (settingsInfo.has_bluetooth_disabled()
                  or not firmwareInfo.has_fw_support_bluetooth())),
            ("RF pipe0", binascii.hexlify(rfInfo.pipe0).decode('ascii')),
            ("RF pipe1", binascii.hexlify(rfInfo.pipe1).decode('ascii')),
            ("RF pipe2", "{:02x}".format(rfInfo.pipe2)),
            ("RF pipe3", "{:02x}".format(rfInfo.pipe3)),
            ("RF pipe4", "{:02x}".format(rfInfo.pipe4)),
            ("RF pipe5", "{:02x}".format(rfInfo.pipe5)),
            ("RF channel", str(rfInfo.channel)),
            ("RF auto retransmit count", str(rfInfo.arc)),
            ("RF data rate", protocol.data_rate_to_str(rfInfo.data_rate)),
        ]

        firmware_settings = [
            ("Firmware version",
             "{}.{}.{}".format(firmwareInfo.version_major,
                               firmwareInfo.version_minor,
                               firmwareInfo.version_patch)),
            ("Firmware build date",
             str(datetime.datetime.fromtimestamp(firmwareInfo.timestamp))),
            ("Firmware git hash", "{:08x}".format(firmwareInfo.git_hash)),
            ("Layout storage size", firmwareInfo.layout_flash_size),
            ("Bootloader VID", "{:04x}".format(firmwareInfo.bootloader_vid)),
            ("Bootloader PID", "{:04x}".format(firmwareInfo.bootloader_pid)),
            ("Support scanning", firmwareInfo.has_fw_support_scanning()),
            ("Support scanning col to row",
             firmwareInfo.has_fw_support_scanning_col_row()),
            ("Support scanning row to col",
             firmwareInfo.has_fw_support_scanning_row_col()),
            ("Media keys", firmwareInfo.has_fw_support_key_media()),
            ("Mouse keys", firmwareInfo.has_fw_support_key_mouse()),
            ("Layer keys", firmwareInfo.has_fw_support_key_layers()),
            ("Sticky keys", firmwareInfo.has_fw_support_key_sticky()),
            ("Tap keys", firmwareInfo.has_fw_support_key_tap()),
            ("Hold keys", firmwareInfo.has_fw_support_key_hold()),
            ("Support 6KRO", firmwareInfo.has_fw_support_6kro()),
            ("Support NKRO", firmwareInfo.has_fw_support_key_hold()),
            ("Support indicator LEDs",
             firmwareInfo.has_fw_support_led_indicators()),
            ("Support LED backlighting",
             firmwareInfo.has_fw_support_led_backlighting()),
            ("Support ws2812 LEDs", firmwareInfo.has_fw_support_led_ws2812()),
            ("Support USB", firmwareInfo.has_fw_support_usb()),
            ("Support nRF24 wireless", firmwareInfo.has_fw_support_nrf24()),
            ("Support Unifying", firmwareInfo.has_fw_support_unifying()),
            ("Support I2C", firmwareInfo.has_fw_support_i2c()),
            ("Support Bluetooth", firmwareInfo.has_fw_support_bluetooth()),
        ]

        if errorInfo:
            error_codes = []
            for code in errorInfo.get_error_codes():
                error_codes.append((errorInfo.error_code_to_name(code), code))
        else:
            error_codes = [
                ('Error codes require firmware version 0.2.2 or greater', )
            ]

        self.info_window = DeviceInformationWindow(
            self,
            header,
            device_settings,
            firmware_settings,
            error_codes,
        )
        self.info_window.setModal(True)
        self.info_window.exec_()

        self.deviceListWidget.updateList()
Exemplo n.º 7
0
    def task(self, args):
        layout_file = args.layout_file
        rf_file = args.rf_file
        hex_file = args.hex_file
        new_id = args.new_id

        if args.merge_hex:
            if (new_id == None or layout_file == None or rf_file == None or \
                    new_id == None):
                print("Error: To generate a merged hex file, need all settings"
                        " files.", file=sys.stderr)
                exit(EXIT_COMMAND_ERROR)
            if len(args.merge_hex) != 3:
                print("Error: To generate a merged hex file, need to provide "
                      "[settings_addr, layout_addr, layout_size] as arguments",
                      file=sys.stderr)
                exit(EXIT_COMMAND_ERROR)


        if new_id != None and (layout_file == None or rf_file == None):
            print("Error: when providing a new ID, a layout and RF file "
                  "must be provided", file=sys.stderr)
            exit(EXIT_COMMAND_ERROR)

        if layout_file == None and hex_file == None and rf_file == None:
            self.arg_parser.print_help()
            exit(0)


        if not args.merge_hex:
            device = self.find_matching_device(args)

            device.open()
            print("Programing start...")
            print_hid_info(device)
            print_device_info(device)
            print_layout_info(device)
            print("")


        if layout_file != None:
            with open(layout_file) as file_obj:
                try:
                    layout_json_obj = yaml.safe_load(file_obj.read())
                # except yaml.YAMLError as err:
                except Exception as err:
                    print("Error in Layout Settings YAML file: " + str(err), file=sys.stderr)
                    device.close()
                    exit(EXIT_BAD_FILE)
        else:
            layout_json_obj = None

        if rf_file != None:
            with open(rf_file) as file_obj:
                try:
                    rf_json_obj = yaml.safe_load(file_obj.read())
                # except yaml.YAMLError as err:
                except Exception as err:
                    print("Error in RF Settings YAML file: " + str(err), file=sys.stderr)
                    device.close()
                    exit(EXIT_BAD_FILE)
        else:
            rf_json_obj = None

        if layout_file != None:
            print("Parsing files...")

            if not args.merge_hex:
                device_info = protocol.get_device_info(device)

            if new_id == None:
                target_id = device_info.id
            else:
                target_id = new_id

            layout_data, settings_data = self.process_layout(
                layout_json_obj,
                rf_json_obj,
                layout_file,
                target_id
            )
            if layout_data == None or settings_data == None:
                exit(EXIT_BAD_FILE)
            print("Parsing finished...")

        if args.merge_hex:
            # don't want to program the device, instead we want to build a
            # hexfile with the settings preprogrammed
            with open(hex_file) as f:
                fw_hex = intelhex.IntelHex(f)

            settings_addr = args.merge_hex[0]
            layout_addr = args.merge_hex[1]
            layout_size = args.merge_hex[2]

            if len(layout_data) > layout_size:
                print("Error: layout data to large. Got {} bytes, but only "
                      "{} bytes available".format(
                          len(layout_data),
                          layout_size
                      ), file=sys.stderr)
                exit(EXIT_INSUFFICIENT_SPACE)

            settings_hex = intelhex.IntelHex()
            settings_hex.frombytes(
                settings_data,
                offset = settings_addr
            )

            layout_hex = intelhex.IntelHex()
            layout_hex.frombytes(
                layout_data,
                offset = layout_addr
            )


            fw_hex.merge(settings_hex, overlap='replace')

            # first erase anything that is in the layout section
            for i in range(layout_addr, layout_addr+layout_size):
                fw_hex[i] = 0 # dummy, write so del works
                del fw_hex[i]

            fw_hex.merge(layout_hex, overlap='replace')

            if args.outfile:
                with open(args.outfile, 'w') as outfile:
                    fw_hex.write_hex_file(outfile)
            else:
                    fw_hex.write_hex_file(sys.stdout)
            exit(0)
        elif layout_file and not rf_file:
            print("Updating layout only...")
            protocol.update_settings_section(device, settings_data, keep_rf=True)
            protocol.update_layout_section(device, layout_data)
        elif layout_file and rf_file:
            print("Updating layout and rf settings...")
            protocol.update_settings_section(device, settings_data)
            protocol.update_layout_section(device, layout_data)
        elif layout_file and rf_file and hex_file:
            print("TODO: not implemented", file=sys.stderr)
        elif hex_file and not layout_file and not rf_file:
            print("TODO: not implemented", file=sys.stderr)
        else:
            pass

        print("Done!")

        protocol.reset_device(device)
        device.close()
Exemplo n.º 8
0
    def find_matching_device(self, args):
        hid_list = easyhid.Enumeration()

        target_vid = protocol.DEFAULT_VID
        target_pid = None

        if args.vid_pid:
            matches = args.vid_pid.split(":")
            if len(matches) == 1:
                try:
                    target_vid = int(matches[0], base=16)
                    if target_vid > 0xffff: raise Exception
                except:
                    print("Bad VID/PID pair: " + args.vid_pid, file=sys.stderr)
                    exit(EXIT_MATCH_DEVICE)
            elif len(matches) == 2:
                try:
                    if matches[0] == '':
                        target_vid = None
                    else:
                        target_vid = target_vid = int(matches[0], base=16)
                    if matches[1] == '':
                        target_pid = None
                    else:
                        target_pid = target_pid = int(matches[1], base=16)
                    if target_vid and target_vid > 0xffff: raise Exception
                    if target_pid and target_pid > 0xffff: raise Exception
                except:
                    print("Bad VID/PID pair: " + args.vid_pid, file=sys.stderr)
                    exit(EXIT_MATCH_DEVICE)

        if args.serial != None:
            args.serial = self.get_similar_serial_number(hid_list, args.serial)

        matching_devices = hid_list.find(
            vid=target_vid,
            pid=target_pid,
            serial=args.serial,
            interface=3
        )

        if len(matching_devices) == 0:
            print("Couldn't find a matching device to open", file=sys.stderr)
            exit(EXIT_MATCH_DEVICE)

        if args.dev_id != None:
            matching_id_list = []
            for dev in matching_devices:
                try:
                    dev.open()
                    dev_info = protocol.get_device_info(dev)
                    dev.close()
                    if dev_info.id == args.dev_id:
                        matching_id_list.append(dev)
                except:
                    print("Warning: couldn't open device: " + str(dev), file=sys.stderr)
                    dev.close()
            matching_devices = matching_id_list

        num_matches = len(matching_devices)

        if num_matches== 0:
            print("Couldn't find any matching devices.", file=sys.stderr)
            exit(EXIT_MATCH_DEVICE)
        elif num_matches == 1:
            return matching_devices[0]
        elif num_matches > 1:
            print("Error: found {} matching devices, select a specifc device or "
                  "disconnect the other devices".format(num_matches), file=sys.stderr)
            exit(EXIT_MATCH_DEVICE)
Exemplo n.º 9
0
    def infoDeviceHandler(self, device_path):
        device = self.tryOpenDevicePath(device_path)
        if device == None: return

        settingsInfo = protocol.get_device_info(device)
        firmwareInfo = protocol.get_firmware_info(device)
        rfInfo = protocol.get_rf_info(device)
        device.close()

        header = ["Attribute", "Value"]
        device_settings = [
            ("Device ID", settingsInfo.id),
            ("Device name", settingsInfo.name.decode('utf-8')),
            ("Device serial number", device.serial_number),
            ("Last layout update",
             protocol.timestamp_to_str(settingsInfo.timestamp)),
            ("Default report mode",
             protocol.report_mode_to_str(settingsInfo.default_report_mode)),
            ("Matrix scan mode",
             protocol.scan_mode_to_str(settingsInfo.scan_mode)),
            ("Matrix columns", settingsInfo.col_count),
            ("Matrix rows", settingsInfo.row_count),
            ("RF pipe0", binascii.hexlify(rfInfo.pipe0).decode('ascii')),
            ("RF pipe1", binascii.hexlify(rfInfo.pipe1).decode('ascii')),
            ("RF pipe2", "{:02x}".format(rfInfo.pipe2)),
            ("RF pipe3", "{:02x}".format(rfInfo.pipe3)),
            ("RF pipe4", "{:02x}".format(rfInfo.pipe4)),
            ("RF pipe5", "{:02x}".format(rfInfo.pipe5)),
            ("RF channel", str(rfInfo.channel)),
            ("RF auto retransmit count", str(rfInfo.arc)),
            ("RF data rate", protocol.data_rate_to_str(rfInfo.data_rate)),
        ]
        firmware_settings = [
            ("Firmware version",
             "{}.{}.{}".format(firmwareInfo.version_major,
                               firmwareInfo.version_minor,
                               firmwareInfo.version_patch)),
            ("Firmware build date",
             str(datetime.datetime.fromtimestamp(firmwareInfo.timestamp))),
            ("Firmware git hash", "{:08x}".format(firmwareInfo.git_hash)),
            ("Layout storage size", firmwareInfo.layout_flash_size),
            ("Bootloader VID", "{:04x}".format(firmwareInfo.bootloader_vid)),
            ("Bootloader PID", "{:04x}".format(firmwareInfo.bootloader_pid)),
            ("Support scanning",
             protocol.has_fw_support_scanning(firmwareInfo)),
            ("Support scanning col to row",
             protocol.has_fw_support_scanning_col_row(firmwareInfo)),
            ("Support scanning row to col",
             protocol.has_fw_support_scanning_row_col(firmwareInfo)),
            ("Media keys", protocol.has_fw_support_key_media(firmwareInfo)),
            ("Mouse keys", protocol.has_fw_support_key_mouse(firmwareInfo)),
            ("Layer keys", protocol.has_fw_support_key_layers(firmwareInfo)),
            ("Sticky keys", protocol.has_fw_support_key_sticky(firmwareInfo)),
            ("Tap keys", protocol.has_fw_support_key_tap(firmwareInfo)),
            ("Hold keys", protocol.has_fw_support_key_hold(firmwareInfo)),
            ("Support 6KRO", protocol.has_fw_support_6kro(firmwareInfo)),
            ("Support NKRO", protocol.has_fw_support_key_hold(firmwareInfo)),
            ("Support indicator LEDs",
             protocol.has_fw_support_led_indicators(firmwareInfo)),
            ("Support LED backlighting",
             protocol.has_fw_support_led_backlighting(firmwareInfo)),
            ("Support ws2812 LEDs",
             protocol.has_fw_support_led_ws2812(firmwareInfo)),
            ("Support USB", protocol.has_fw_support_usb(firmwareInfo)),
            ("Support nRF24 wireless",
             protocol.has_fw_support_wireless(firmwareInfo)),
            ("Support Unifying",
             protocol.has_fw_support_unifying(firmwareInfo)),
            ("Support I2C", protocol.has_fw_support_i2c(firmwareInfo)),
            ("Support Bluetooth",
             protocol.has_fw_support_bluetooth(firmwareInfo)),
        ]
        self.info_window = DeviceInformationWindow(self, header,
                                                   device_settings,
                                                   firmware_settings)
        self.info_window.setModal(True)
        self.info_window.exec_()

        self.deviceListWidget.updateList()