From eba91c6e286984459ea82ac9bce3e8bb91794764 Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Wed, 11 Aug 2021 21:08:32 +1000 Subject: [PATCH 01/77] Initial implementation of XAP protocol. --- build_keyboard.mk | 24 ++ common_features.mk | 12 + data/xap/xap_0.0.1.hjson | 178 +++++++++++++ data/xap/xap_0.1.0.hjson | 248 ++++++++++++++++++ data/xap/xap_0.2.0.hjson | 80 ++++++ docs/xap_0.0.1.md | 62 +++++ docs/xap_0.1.0.md | 78 ++++++ docs/xap_0.2.0.md | 78 ++++++ docs/xap_protocol.md | 5 + lib/python/qmk/casing.py | 65 +++++ lib/python/qmk/cli/__init__.py | 3 + lib/python/qmk/cli/xap/__init__.py | 0 lib/python/qmk/cli/xap/generate_docs.py | 11 + lib/python/qmk/cli/xap/generate_json.py | 13 + lib/python/qmk/cli/xap/generate_qmk.py | 24 ++ lib/python/qmk/commands.py | 7 +- lib/python/qmk/constants.py | 90 +++++++ lib/python/qmk/xap/__init__.py | 0 lib/python/qmk/xap/common.py | 81 ++++++ lib/python/qmk/xap/gen_client_js/__init__.py | 0 lib/python/qmk/xap/gen_docs/__init__.py | 0 lib/python/qmk/xap/gen_docs/generator.py | 103 ++++++++ lib/python/qmk/xap/gen_firmware/__init__.py | 0 .../qmk/xap/gen_firmware/header_generator.py | 136 ++++++++++ .../qmk/xap/gen_firmware/inline_generator.py | 222 ++++++++++++++++ quantum/quantum.h | 4 + quantum/xap/xap.c | 116 ++++++++ quantum/xap/xap.h | 44 ++++ quantum/xap/xap_handlers.c | 39 +++ requirements.txt | 1 + tmk_core/common/report.h | 3 +- tmk_core/protocol/lufa/lufa.c | 96 +++++++ tmk_core/protocol/usb_descriptor.c | 91 +++++++ tmk_core/protocol/usb_descriptor.h | 24 +- 34 files changed, 1934 insertions(+), 4 deletions(-) create mode 100755 data/xap/xap_0.0.1.hjson create mode 100755 data/xap/xap_0.1.0.hjson create mode 100755 data/xap/xap_0.2.0.hjson create mode 100644 docs/xap_0.0.1.md create mode 100644 docs/xap_0.1.0.md create mode 100644 docs/xap_0.2.0.md create mode 100644 docs/xap_protocol.md create mode 100755 lib/python/qmk/casing.py create mode 100755 lib/python/qmk/cli/xap/__init__.py create mode 100755 lib/python/qmk/cli/xap/generate_docs.py create mode 100755 lib/python/qmk/cli/xap/generate_json.py create mode 100755 lib/python/qmk/cli/xap/generate_qmk.py create mode 100644 lib/python/qmk/xap/__init__.py create mode 100755 lib/python/qmk/xap/common.py create mode 100644 lib/python/qmk/xap/gen_client_js/__init__.py create mode 100644 lib/python/qmk/xap/gen_docs/__init__.py create mode 100755 lib/python/qmk/xap/gen_docs/generator.py create mode 100644 lib/python/qmk/xap/gen_firmware/__init__.py create mode 100755 lib/python/qmk/xap/gen_firmware/header_generator.py create mode 100755 lib/python/qmk/xap/gen_firmware/inline_generator.py create mode 100644 quantum/xap/xap.c create mode 100644 quantum/xap/xap.h create mode 100644 quantum/xap/xap_handlers.c diff --git a/build_keyboard.mk b/build_keyboard.mk index 38ca2aaa99..79bd87326c 100644 --- a/build_keyboard.mk +++ b/build_keyboard.mk @@ -352,6 +352,30 @@ VPATH += $(KEYBOARD_PATHS) VPATH += $(COMMON_VPATH) include common_features.mk + +# XAP embedded info.json +ifeq ($(strip $(XAP_ENABLE)), yes) + +$(KEYMAP_OUTPUT)/src/info_json_gz.h: $(INFO_JSON_FILES) + mkdir -p $(KEYMAP_OUTPUT)/src + $(QMK_BIN) info -f json -kb $(KEYBOARD) -km $(KEYMAP) | gzip -c9 > $(KEYMAP_OUTPUT)/src/info.json.gz + cd $(KEYMAP_OUTPUT)/src >/dev/null 2>&1 \ + && xxd -i info.json.gz info_json_gz.h \ + && cd - >/dev/null 2>&1 + +XAP_FILES := $(shell ls -1 data/xap/* | sort | xargs echo) + +$(KEYMAP_OUTPUT)/src/xap_generated.inl: $(XAP_FILES) + $(QMK_BIN) xap-generate-qmk-inc -o "$(KEYMAP_OUTPUT)/src/xap_generated.inl" + +$(KEYMAP_OUTPUT)/src/xap_generated.h: $(XAP_FILES) + $(QMK_BIN) xap-generate-qmk-h -o "$(KEYMAP_OUTPUT)/src/xap_generated.h" -kb $(KEYBOARD) + +generated-files: $(KEYMAP_OUTPUT)/src/info_json_gz.h $(KEYMAP_OUTPUT)/src/xap_generated.inl $(KEYMAP_OUTPUT)/src/xap_generated.h + +VPATH += $(KEYMAP_OUTPUT)/src +endif + include $(TMK_PATH)/protocol.mk include $(TMK_PATH)/common.mk include bootloader.mk diff --git a/common_features.mk b/common_features.mk index e442222eae..9b5cfc5520 100644 --- a/common_features.mk +++ b/common_features.mk @@ -718,3 +718,15 @@ ifeq ($(strip $(USBPD_ENABLE)), yes) endif endif endif + +ifeq ($(strip $(XAP_ENABLE)), yes) + ifeq ($(strip $(VIA_ENABLE)), yes) + $(error 'XAP_ENABLE = $(XAP_ENABLE)' deprecates 'VIA_ENABLE = $(VIA_ENABLE)'. Please set 'VIA_ENABLE = no') + endif + + OPT_DEFS += -DXAP_ENABLE + DYNAMIC_KEYMAP_ENABLE := yes + EMBED_INFO_JSON := yes + VPATH += $(QUANTUM_DIR)/xap + SRC += $(QUANTUM_DIR)/xap/xap.c $(QUANTUM_DIR)/xap/xap_handlers.c +endif diff --git a/data/xap/xap_0.0.1.hjson b/data/xap/xap_0.0.1.hjson new file mode 100755 index 0000000000..34fd21d623 --- /dev/null +++ b/data/xap/xap_0.0.1.hjson @@ -0,0 +1,178 @@ +{ + version: 0.0.1 + + // Needed for table generation + define: XAP_ROUTE + + // Documentation section is used purely for `qmk xap-generate-docs`. + documentation: { + order: [ + page_header + type_docs + !type_docs! + term_definitions + !term_definitions! + request_response + reserved_tokens + response_flags + !response_flags! + example_conversation + ] + + page_header: + ''' + # QMK Firmware XAP Specs + + This document describes the requirements of the QMK XAP ("extensible application protocol") API. + ''' + + type_docs: + ''' + ## Types + + **All integral types are little-endian.** + ''' + + term_definitions: + ''' + ## Definitions + + This list defines the terms used across the entire set of XAP protocol documentation. + ''' + + request_response: + ''' + ## Requests and Responses + + Communication generally follows a request/response pattern. + + Each request needs to include a _token_ -- this `u16` value prefixes each outbound request from the host application and its corresponding response, allowing repsonse messages to be correlated with their request, even if multiple host applications are communicating with the firmware simultaneously. Host applications should randomly generate a token ID for **every** outbound request, unless using a reserved token defined below. + + This token is followed by a `u8` signifying the length of data in the request. + ''' + + // This documentation section reserved for next version + reserved_tokens: '' + + response_flags: + ''' + Response messages will always be prefixed by the originating request _token_, directly followed by that request's _response flags_, then the response payload length: + ''' + + example_conversation: + ''' + ### Example "conversation": + + **Request** -- version query: + | Byte | 0 | 1 | 2 | 3 | 4 | + | --- | --- | --- | --- | --- | --- | + | **Purpose** | Token | Token | Payload Length | Route | Route | + | **Value** | `0x43` | `0x2B` | `0x02` | `0x00` | `0x00` | + + **Response** -- matching token, successful flag, payload of `0x03170192` = 3.17.192: + | Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | + | --- | --- | --- | --- | --- | --- | --- | --- | --- | + | **Purpose** | Token | Token | Response Flags | Payload Length | Payload | Payload | Payload | Payload | + | **Value** | `0x43` | `0x2B` | `0x01` | `0x04` | `0x92` | `0x01` | `0x17` | `0x03` | + ''' + } + + type_docs: { + u8: + ''' + An unsigned 8-bit integral (octet, or byte), commonly seen as `uint8_t` from _stdint.h_. + ''' + u16: + ''' + An unsigned 16-bit integral, commonly seen as `uint16_t` from _stdint.h_. + ''' + u32: + ''' + An unsigned 32-bit integral, commonly seen as `uint32_t` from _stdint.h_. + ''' + "type[n]": + ''' + An array of `type`, with array extent of `N` -- e.g. `u8[2]` signifies two consecutive octets. + ''' + } + + term_definitions: { + Subsystem: + ''' + A high-level area of functionality within XAP. + ''' + ID: + ''' + A single octet / 8-bit byte. + ''' + Route: + ''' + A sequence of _IDs_ describing the route to invoke a _handler_. + ''' + Handler: + ''' + A piece of code that is executed when a specific _route_ is received. + ''' + Token: + ''' + A `u16` associated with a specific request as well as its corresponding response. + ''' + Response: + ''' + The data sent back to the host during execution of a _handler_. + ''' + "Response Flags": + ''' + An `u8` containing the status of the request. + ''' + Payload: + ''' + Any received data appended to the _route_, which gets delivered to the _handler_ when received. + ''' + } + + response_flags: { + define_prefix: XAP_RESP + bits: { + 0: { + name: Success + define: SUCCESS + description: + ''' + When this bit is set, the request was successfully handled. If not set, all payload data should be disregarded, and the request retried if appropriate (with a new token). + ''' + } + } + } + + routes: { + 0x00: { + type: router + name: XAP + define: XAP + description: + ''' + This subsystem is always present, and provides the ability to query information about the XAP protocol of the connected device. + ''' + routes: { + 0x00: { + type: command + name: Version Query + define: VERSION_QUERY + description: + ''' + XAP protocol version query. + + * Returns the BCD-encoded version in the format of XX.YY.ZZZZ => `0xXXYYZZZZ` + * e.g. 3.2.115 will match `0x03020115`, or bytes {0x15,0x01,0x02,0x03}. + * Response: + * `u32` value. + ''' + return_type: u32 + return_purpose: bcd-version + return_constant: XAP_BCD_VERSION + } + } + } + } +} diff --git a/data/xap/xap_0.1.0.hjson b/data/xap/xap_0.1.0.hjson new file mode 100755 index 0000000000..e24978f459 --- /dev/null +++ b/data/xap/xap_0.1.0.hjson @@ -0,0 +1,248 @@ +{ + version: 0.1.0 + + documentation: { + order: [ + broadcast_messages + ] + + reserved_tokens: + ''' + Two token values are reserved: `0x0000` and `0xFFFF`: + * `0x0000`: A message sent by a host application may use this token if no response is to be sent -- a "fire and forget" message. + * `0xFFFF`: Signifies a "broadcast" message sent by the firmware without prompting from the host application. Broadcast messages are defined later in this document. + + Any request will generate at least one corresponding response, with the exception of messages using reserved tokens. Maximum total message length is 128 bytes due to RAM constraints. + ''' + + broadcast_messages: + ''' + ## Broadcast messages + + Broadcast messages may be sent by the firmware to the host, without a corresponding inbound request. Each broadcast message uses the token `0xFFFF`, and does not expect a response from the host. Tokens are followed by an _ID_ signifying the type of broadcast, with corresponding _payload_. + ''' + } + + response_flags: { + bits: { + 1: { + name: Secure Failure + define: SECURE_FAILURE + description: + ''' + When this bit is set, the requested _route_ was marked _secure_ but an _unlock sequence_ has not completed. + ''' + } + 6: { + name: Unlocking + define: UNLOCK_IN_PROGRESS + description: + ''' + When this bit is set, an _unlock sequence_ is in progress. + ''' + } + 7: { + name: Unlocked + define: UNLOCKED + description: + ''' + When this bit is set, an _unlock sequence_ has completed, and _secure routes_ may be invoked. + ''' + } + } + } + + type_docs: { + u64: + ''' + An unsigned 64-bit integral, commonly seen as `uint64_t` from _stdint.h_. + ''' + "struct{}": + ''' + A structure of data, packing different objects together. Data is "compacted" -- there are no padding bytes between fields. Equivalent to a C-style `struct`. The order in which they're defined matches the order of the data in the response packet. + ''' + } + + term_definitions: { + Capability: + ''' + A way to determine if certain functionality is enabled in the firmware. Any _subsystem_ that provides build-time restriction of functionality must provide a _route_ for a _capabilities query_. + ''' + "Secure Route": + ''' + A _route_ which has potentially destructive consequences, necessitating prior approval by the user before executing. + ''' + "Unlock sequence": + ''' + A physical sequence initiated by the user to enable execution of _secure routes_. + ''' + } + + broadcast_messages: { + define_prefix: XAP_BROADCAST + messages: { + 0x00: { + name: Log message + define: LOG_MESSAGE + description: + ''' + Replicates and replaces the same functionality as if using the standard QMK `CONSOLE_ENABLE = yes` in `rules.mk`. Normal prints within the firmware will manifest as log messages broadcast to the host. `hid_listen` will not be functional with XAP enabled. + + Log message payloads include a `u8` signifying the length of the text, followed by the `u8[Length]` containing the text itself. + + **Example Log Broadcast** -- log message "Hello QMK!" + | Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | + | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | + | **Purpose** | Token | Token | Broadcast Type | Length | Payload | Payload | Payload | Payload | Payload | Payload | Payload | Payload | Payload | Payload | + | **Value** | `0xFF` | `0xFF` | `0x00` | `0x0A`(10) | `0x48`(H) | `0x65`(e) | `0x6C`(l) | `0x6C`(l) | `0x6F`(o) | `0x20`( ) | `0x51`(Q) | `0x4D`(M) | `0x4B`(K) | `0x21`(!) | + ''' + } + } + } + + routes: { + 0x00: { + routes: { + 0x01: { + type: command + name: Capabilities Query + define: CAPABILITIES_QUERY + description: + ''' + XAP subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem. + ''' + return_type: u32 + return_purpose: capabilities + return_constant: XAP_ROUTE_XAP_CAPABILITIES + } + 0x02: { + type: command + name: Enabled subsystem query + define: SUBSYSTEM_QUERY + description: + ''' + XAP protocol subsystem query. Each bit should be considered as a "usable" subsystem. For example, checking `(value & (1 << XAP_ROUTE_QMK) != 0)` means the QMK subsystem is enabled and available for querying. + ''' + return_type: u32 + return_purpose: capabilities + return_constant: XAP_ROUTE_CAPABILITIES + } + } + }, + + 0x01: { + type: router + name: QMK + define: QMK + description: + ''' + This subsystem is always present, and provides the ability to address QMK-specific functionality. + ''' + routes: { + 0x00: { + type: command + name: Version Query + define: VERSION_QUERY + description: + ''' + QMK protocol version query. + + * Returns the BCD-encoded version in the format of XX.YY.ZZZZ => `0xXXYYZZZZ` + * e.g. 3.2.115 will match `0x03020115`, or bytes {0x15,0x01,0x02,0x03}. + * Response: + * `u32` value. + ''' + return_type: u32 + return_purpose: bcd-version + return_constant: QMK_BCD_VERSION + } + 0x01: { + type: command + name: Capabilities Query + define: CAPABILITIES_QUERY + description: + ''' + QMK subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem. + ''' + return_type: u32 + return_purpose: capabilities + return_constant: XAP_ROUTE_QMK_CAPABILITIES + } + 0x02: { + type: command + name: Board identifiers + define: BOARD_IDENTIFIERS + description: + ''' + Retrieves the set of identifying information for the board. + ''' + return_type: struct + return_struct_members: [ + { + type: u16 + name: Vendor ID + }, + { + type: u16 + name: Product ID + }, + { + type: u16 + name: Product Version + }, + { + type: u32 + name: QMK Unique Identifier + } + ] + return_constant: [ + VENDOR_ID + PRODUCT_ID + DEVICE_VER + XAP_KEYBOARD_IDENTIFIER + ] + } + 0x03: { + type: command + name: Board Manufacturer + define: BOARD_MANUFACTURER + description: Retrieves the name of the manufacturer + return_type: string + return_constant: QSTR(MANUFACTURER) + } + 0x04: { + type: command + name: Product Name + define: PRODUCT_NAME + description: Retrieves the product name + return_type: string + return_constant: QSTR(PRODUCT) + } + } + }, + + 0x02: { + type: router + name: Keyboard + define: KB + description: + ''' + This subsystem is always present, and reserved for user-specific functionality. No routes are defined by XAP. + ''' + routes: { + } + }, + + 0x03: { + type: router + name: User + define: USER + description: + ''' + This subsystem is always present, and reserved for user-specific functionality. No routes are defined by XAP. + ''' + routes: { + } + } + } +} diff --git a/data/xap/xap_0.2.0.hjson b/data/xap/xap_0.2.0.hjson new file mode 100755 index 0000000000..81c5187390 --- /dev/null +++ b/data/xap/xap_0.2.0.hjson @@ -0,0 +1,80 @@ +{ + version: 0.2.0 + + routes: { + 0x04: { + type: router + name: Dynamic Keymap + define: DYNAMIC_KEYMAP + description: + ''' + This subsystem allows for live modifications of the keymap, allowing keys to be reassigned without rebuilding the firmware. + ''' + enable_if_preprocessor: defined(DYNAMIC_KEYMAP_ENABLE) + routes: { + 0x00: { + type: command + name: Capabilities Query + define: CAPABILITIES_QUERY + description: + ''' + Dynamic Keymap subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem. + ''' + return_type: u32 + return_purpose: capabilities + return_constant: XAP_ROUTE_DYNAMIC_KEYMAP_CAPABILITIES + } + } + } + + 0x05: { + type: router + name: Dynamic Encoders + define: DYNAMIC_ENCODER + description: + ''' + This subsystem allows for live modifications of the keymap, allowing encoder functionality to be reassigned without rebuilding the firmware. + ''' + enable_if_preprocessor: defined(DYNAMIC_KEYMAP_ENABLE) && defined(ENCODER_MAP_ENABLE) + routes: { + 0x00: { + type: command + name: Capabilities Query + define: CAPABILITIES_QUERY + description: + ''' + Dynamic Encoders subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem. + ''' + return_type: u32 + return_purpose: capabilities + return_constant: XAP_ROUTE_DYNAMIC_ENCODER_CAPABILITIES + } + } + } + + 0x06: { + type: router + name: Lighting + define: LIGHTING + description: + ''' + This subsystem allows for control over the lighting subsystem. + ''' + enable_if_preprocessor: defined(RGBLIGHT_ENABLE) || defined(RGB_MATRIX_ENABLE) + routes: { + 0x00: { + type: command + name: Capabilities Query + define: CAPABILITIES_QUERY + description: + ''' + Lighting subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem. + ''' + return_type: u32 + return_purpose: capabilities + return_constant: XAP_ROUTE_LIGHTING_CAPABILITIES + } + } + } + } +} diff --git a/docs/xap_0.0.1.md b/docs/xap_0.0.1.md new file mode 100644 index 0000000000..ff45f1ae45 --- /dev/null +++ b/docs/xap_0.0.1.md @@ -0,0 +1,62 @@ +# QMK Firmware XAP Specs + +This document describes the requirements of the QMK XAP ("extensible application protocol") API. + +## Types + +**All integral types are little-endian.** + +| Name | Definition | +| -- | -- | +| _type[n]_ | An array of `type`, with array extent of `N` -- e.g. `u8[2]` signifies two consecutive octets. | +| _u16_ | An unsigned 16-bit integral, commonly seen as `uint16_t` from _stdint.h_. | +| _u32_ | An unsigned 32-bit integral, commonly seen as `uint32_t` from _stdint.h_. | +| _u8_ | An unsigned 8-bit integral (octet, or byte), commonly seen as `uint8_t` from _stdint.h_. | + +## Definitions + +This list defines the terms used across the entire set of XAP protocol documentation. + +| Name | Definition | +| -- | -- | +| _Handler_ | A piece of code that is executed when a specific _route_ is received. | +| _ID_ | A single octet / 8-bit byte. | +| _Payload_ | Any received data appended to the _route_, which gets delivered to the _handler_ when received. | +| _Response_ | The data sent back to the host during execution of a _handler_. | +| _Response Flags_ | An `u8` containing the status of the request. | +| _Route_ | A sequence of _IDs_ describing the route to invoke a _handler_. | +| _Subsystem_ | A high-level area of functionality within XAP. | +| _Token_ | A `u16` associated with a specific request as well as its corresponding response. | + +## Requests and Responses + +Communication generally follows a request/response pattern. + +Each request needs to include a _token_ -- this `u16` value prefixes each outbound request from the host application and its corresponding response, allowing repsonse messages to be correlated with their request, even if multiple host applications are communicating with the firmware simultaneously. Host applications should randomly generate a token ID for **every** outbound request, unless using a reserved token defined below. + +This token is followed by a `u8` signifying the length of data in the request. + + + +Response messages will always be prefixed by the originating request _token_, directly followed by that request's _response flags_, then the response payload length: + +| Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 | +|--|--|--|--|--|--|--|--| +| - | - | - | - | - | - | - | Success | + +* `Bit 0`: When this bit is set, the request was successfully handled. If not set, all payload data should be disregarded, and the request retried if appropriate (with a new token). + +### Example "conversation": + +**Request** -- version query: +| Byte | 0 | 1 | 2 | 3 | 4 | +| --- | --- | --- | --- | --- | --- | +| **Purpose** | Token | Token | Payload Length | Route | Route | +| **Value** | `0x43` | `0x2B` | `0x02` | `0x00` | `0x00` | + +**Response** -- matching token, successful flag, payload of `0x03170192` = 3.17.192: +| Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| **Purpose** | Token | Token | Response Flags | Payload Length | Payload | Payload | Payload | Payload | +| **Value** | `0x43` | `0x2B` | `0x01` | `0x04` | `0x92` | `0x01` | `0x17` | `0x03` | + diff --git a/docs/xap_0.1.0.md b/docs/xap_0.1.0.md new file mode 100644 index 0000000000..8dac167209 --- /dev/null +++ b/docs/xap_0.1.0.md @@ -0,0 +1,78 @@ +# QMK Firmware XAP Specs + +This document describes the requirements of the QMK XAP ("extensible application protocol") API. + +## Types + +**All integral types are little-endian.** + +| Name | Definition | +| -- | -- | +| _struct{}_ | A structure of data, packing different objects together. Data is "compacted" -- there are no padding bytes between fields. Equivalent to a C-style `struct`. The order in which they're defined matches the order of the data in the response packet. | +| _type[n]_ | An array of `type`, with array extent of `N` -- e.g. `u8[2]` signifies two consecutive octets. | +| _u16_ | An unsigned 16-bit integral, commonly seen as `uint16_t` from _stdint.h_. | +| _u32_ | An unsigned 32-bit integral, commonly seen as `uint32_t` from _stdint.h_. | +| _u64_ | An unsigned 64-bit integral, commonly seen as `uint64_t` from _stdint.h_. | +| _u8_ | An unsigned 8-bit integral (octet, or byte), commonly seen as `uint8_t` from _stdint.h_. | + +## Definitions + +This list defines the terms used across the entire set of XAP protocol documentation. + +| Name | Definition | +| -- | -- | +| _Capability_ | A way to determine if certain functionality is enabled in the firmware. Any _subsystem_ that provides build-time restriction of functionality must provide a _route_ for a _capabilities query_. | +| _Handler_ | A piece of code that is executed when a specific _route_ is received. | +| _ID_ | A single octet / 8-bit byte. | +| _Payload_ | Any received data appended to the _route_, which gets delivered to the _handler_ when received. | +| _Response_ | The data sent back to the host during execution of a _handler_. | +| _Response Flags_ | An `u8` containing the status of the request. | +| _Route_ | A sequence of _IDs_ describing the route to invoke a _handler_. | +| _Secure Route_ | A _route_ which has potentially destructive consequences, necessitating prior approval by the user before executing. | +| _Subsystem_ | A high-level area of functionality within XAP. | +| _Token_ | A `u16` associated with a specific request as well as its corresponding response. | +| _Unlock sequence_ | A physical sequence initiated by the user to enable execution of _secure routes_. | + +## Requests and Responses + +Communication generally follows a request/response pattern. + +Each request needs to include a _token_ -- this `u16` value prefixes each outbound request from the host application and its corresponding response, allowing repsonse messages to be correlated with their request, even if multiple host applications are communicating with the firmware simultaneously. Host applications should randomly generate a token ID for **every** outbound request, unless using a reserved token defined below. + +This token is followed by a `u8` signifying the length of data in the request. + +Two token values are reserved: `0x0000` and `0xFFFF`: +* `0x0000`: A message sent by a host application may use this token if no response is to be sent -- a "fire and forget" message. +* `0xFFFF`: Signifies a "broadcast" message sent by the firmware without prompting from the host application. Broadcast messages are defined later in this document. + +Any request will generate at least one corresponding response, with the exception of messages using reserved tokens. Maximum total message length is 128 bytes due to RAM constraints. + +Response messages will always be prefixed by the originating request _token_, directly followed by that request's _response flags_, then the response payload length: + +| Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 | +|--|--|--|--|--|--|--|--| +| Unlocked | Unlocking | - | - | - | - | Secure Failure | Success | + +* `Bit 7`: When this bit is set, an _unlock sequence_ has completed, and _secure routes_ may be invoked. +* `Bit 6`: When this bit is set, an _unlock sequence_ is in progress. +* `Bit 1`: When this bit is set, the requested _route_ was marked _secure_ but an _unlock sequence_ has not completed. +* `Bit 0`: When this bit is set, the request was successfully handled. If not set, all payload data should be disregarded, and the request retried if appropriate (with a new token). + +### Example "conversation": + +**Request** -- version query: +| Byte | 0 | 1 | 2 | 3 | 4 | +| --- | --- | --- | --- | --- | --- | +| **Purpose** | Token | Token | Payload Length | Route | Route | +| **Value** | `0x43` | `0x2B` | `0x02` | `0x00` | `0x00` | + +**Response** -- matching token, successful flag, payload of `0x03170192` = 3.17.192: +| Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| **Purpose** | Token | Token | Response Flags | Payload Length | Payload | Payload | Payload | Payload | +| **Value** | `0x43` | `0x2B` | `0x01` | `0x04` | `0x92` | `0x01` | `0x17` | `0x03` | + +## Broadcast messages + +Broadcast messages may be sent by the firmware to the host, without a corresponding inbound request. Each broadcast message uses the token `0xFFFF`, and does not expect a response from the host. Tokens are followed by an _ID_ signifying the type of broadcast, with corresponding _payload_. + diff --git a/docs/xap_0.2.0.md b/docs/xap_0.2.0.md new file mode 100644 index 0000000000..8dac167209 --- /dev/null +++ b/docs/xap_0.2.0.md @@ -0,0 +1,78 @@ +# QMK Firmware XAP Specs + +This document describes the requirements of the QMK XAP ("extensible application protocol") API. + +## Types + +**All integral types are little-endian.** + +| Name | Definition | +| -- | -- | +| _struct{}_ | A structure of data, packing different objects together. Data is "compacted" -- there are no padding bytes between fields. Equivalent to a C-style `struct`. The order in which they're defined matches the order of the data in the response packet. | +| _type[n]_ | An array of `type`, with array extent of `N` -- e.g. `u8[2]` signifies two consecutive octets. | +| _u16_ | An unsigned 16-bit integral, commonly seen as `uint16_t` from _stdint.h_. | +| _u32_ | An unsigned 32-bit integral, commonly seen as `uint32_t` from _stdint.h_. | +| _u64_ | An unsigned 64-bit integral, commonly seen as `uint64_t` from _stdint.h_. | +| _u8_ | An unsigned 8-bit integral (octet, or byte), commonly seen as `uint8_t` from _stdint.h_. | + +## Definitions + +This list defines the terms used across the entire set of XAP protocol documentation. + +| Name | Definition | +| -- | -- | +| _Capability_ | A way to determine if certain functionality is enabled in the firmware. Any _subsystem_ that provides build-time restriction of functionality must provide a _route_ for a _capabilities query_. | +| _Handler_ | A piece of code that is executed when a specific _route_ is received. | +| _ID_ | A single octet / 8-bit byte. | +| _Payload_ | Any received data appended to the _route_, which gets delivered to the _handler_ when received. | +| _Response_ | The data sent back to the host during execution of a _handler_. | +| _Response Flags_ | An `u8` containing the status of the request. | +| _Route_ | A sequence of _IDs_ describing the route to invoke a _handler_. | +| _Secure Route_ | A _route_ which has potentially destructive consequences, necessitating prior approval by the user before executing. | +| _Subsystem_ | A high-level area of functionality within XAP. | +| _Token_ | A `u16` associated with a specific request as well as its corresponding response. | +| _Unlock sequence_ | A physical sequence initiated by the user to enable execution of _secure routes_. | + +## Requests and Responses + +Communication generally follows a request/response pattern. + +Each request needs to include a _token_ -- this `u16` value prefixes each outbound request from the host application and its corresponding response, allowing repsonse messages to be correlated with their request, even if multiple host applications are communicating with the firmware simultaneously. Host applications should randomly generate a token ID for **every** outbound request, unless using a reserved token defined below. + +This token is followed by a `u8` signifying the length of data in the request. + +Two token values are reserved: `0x0000` and `0xFFFF`: +* `0x0000`: A message sent by a host application may use this token if no response is to be sent -- a "fire and forget" message. +* `0xFFFF`: Signifies a "broadcast" message sent by the firmware without prompting from the host application. Broadcast messages are defined later in this document. + +Any request will generate at least one corresponding response, with the exception of messages using reserved tokens. Maximum total message length is 128 bytes due to RAM constraints. + +Response messages will always be prefixed by the originating request _token_, directly followed by that request's _response flags_, then the response payload length: + +| Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 | +|--|--|--|--|--|--|--|--| +| Unlocked | Unlocking | - | - | - | - | Secure Failure | Success | + +* `Bit 7`: When this bit is set, an _unlock sequence_ has completed, and _secure routes_ may be invoked. +* `Bit 6`: When this bit is set, an _unlock sequence_ is in progress. +* `Bit 1`: When this bit is set, the requested _route_ was marked _secure_ but an _unlock sequence_ has not completed. +* `Bit 0`: When this bit is set, the request was successfully handled. If not set, all payload data should be disregarded, and the request retried if appropriate (with a new token). + +### Example "conversation": + +**Request** -- version query: +| Byte | 0 | 1 | 2 | 3 | 4 | +| --- | --- | --- | --- | --- | --- | +| **Purpose** | Token | Token | Payload Length | Route | Route | +| **Value** | `0x43` | `0x2B` | `0x02` | `0x00` | `0x00` | + +**Response** -- matching token, successful flag, payload of `0x03170192` = 3.17.192: +| Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| **Purpose** | Token | Token | Response Flags | Payload Length | Payload | Payload | Payload | Payload | +| **Value** | `0x43` | `0x2B` | `0x01` | `0x04` | `0x92` | `0x01` | `0x17` | `0x03` | + +## Broadcast messages + +Broadcast messages may be sent by the firmware to the host, without a corresponding inbound request. Each broadcast message uses the token `0xFFFF`, and does not expect a response from the host. Tokens are followed by an _ID_ signifying the type of broadcast, with corresponding _payload_. + diff --git a/docs/xap_protocol.md b/docs/xap_protocol.md new file mode 100644 index 0000000000..688d7dab3e --- /dev/null +++ b/docs/xap_protocol.md @@ -0,0 +1,5 @@ +# XAP Protocol Reference + +* [XAP Version 0.2.0](xap_0.2.0.md) +* [XAP Version 0.1.0](xap_0.1.0.md) +* [XAP Version 0.0.1](xap_0.0.1.md) diff --git a/lib/python/qmk/casing.py b/lib/python/qmk/casing.py new file mode 100755 index 0000000000..60b14cc540 --- /dev/null +++ b/lib/python/qmk/casing.py @@ -0,0 +1,65 @@ +"""This script handles conversion between snake and camel casing. +""" +import re + +_words_expr = re.compile(r"([a-zA-Z][^A-Z0-9]*|[0-9]+)") +_lower_snake_case_expr = re.compile(r'^[a-z][a-z0-9_]*$') +_upper_snake_case_expr = re.compile(r'^[A-Z][A-Z0-9_]*$') + + +def _is_snake_case(str): + """Checks if the supplied string is already in snake case. + """ + match = _lower_snake_case_expr.match(str) + if match: + return True + match = _upper_snake_case_expr.match(str) + if match: + return True + return False + + +def _split_snake_case(str): + """Splits up a string based on underscores, if it's in snake casing. + """ + if _is_snake_case(str): + return [s.lower() for s in str.split("_")] + return str + + +def _split_camel_case(str): + """Splits up a string based on capitalised camel casing. + """ + return _words_expr.findall(str) + + +def _split_cased_words(str): + return _split_snake_case(str) if _is_snake_case(str) else _split_camel_case(str) + + +def to_snake(str): + str = "_".join([word.strip().lower() for word in _split_cased_words(str)]) + + # Fix acronyms + str = str.replace('i_d', 'id') + str = str.replace('x_a_p', 'xap') + str = str.replace('q_m_k', 'qmk') + + return str + + +def to_upper_snake(str): + return to_snake(str).upper() + + +def to_camel(str): + def _acronym(w): + if w.strip().lower() == 'qmk': + return 'QMK' + elif w.strip().lower() == 'xap': + return 'XAP' + elif w.strip().lower() == 'id': + return 'ID' + return w.title() + + return "".join([_acronym(word) for word in _split_cased_words(str)]) diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py index dea0eaeaf9..fbad3b9329 100644 --- a/lib/python/qmk/cli/__init__.py +++ b/lib/python/qmk/cli/__init__.py @@ -66,6 +66,9 @@ subcommands = [ 'qmk.cli.new.keymap', 'qmk.cli.pyformat', 'qmk.cli.pytest', + 'qmk.cli.xap.generate_docs', + 'qmk.cli.xap.generate_json', + 'qmk.cli.xap.generate_qmk', ] diff --git a/lib/python/qmk/cli/xap/__init__.py b/lib/python/qmk/cli/xap/__init__.py new file mode 100755 index 0000000000..e69de29bb2 diff --git a/lib/python/qmk/cli/xap/generate_docs.py b/lib/python/qmk/cli/xap/generate_docs.py new file mode 100755 index 0000000000..bf2fdb86bb --- /dev/null +++ b/lib/python/qmk/cli/xap/generate_docs.py @@ -0,0 +1,11 @@ +"""This script generates the XAP protocol documentation. +""" +from milc import cli +from qmk.xap.gen_docs.generator import generate_docs + + +@cli.subcommand('Generates the XAP protocol documentation.', hidden=False if cli.config.user.developer else True) +def xap_generate_docs(cli): + """Generates the XAP protocol documentation by merging the definitions files, and producing the corresponding Markdown document under `/docs/`. + """ + generate_docs() \ No newline at end of file diff --git a/lib/python/qmk/cli/xap/generate_json.py b/lib/python/qmk/cli/xap/generate_json.py new file mode 100755 index 0000000000..e999f6ce87 --- /dev/null +++ b/lib/python/qmk/cli/xap/generate_json.py @@ -0,0 +1,13 @@ +"""This script generates the consolidated XAP protocol definitions. +""" +import hjson +from milc import cli +from qmk.xap.common import latest_xap_defs + + +@cli.subcommand('Generates the consolidated XAP protocol definitions.', hidden=False if cli.config.user.developer else True) +def xap_generate_json(cli): + """Generates the consolidated XAP protocol definitions. + """ + defs = latest_xap_defs() + print(hjson.dumps(defs)) diff --git a/lib/python/qmk/cli/xap/generate_qmk.py b/lib/python/qmk/cli/xap/generate_qmk.py new file mode 100755 index 0000000000..0cb2fec195 --- /dev/null +++ b/lib/python/qmk/cli/xap/generate_qmk.py @@ -0,0 +1,24 @@ +"""This script generates the XAP protocol generated sources to be compiled into QMK firmware. +""" +from milc import cli + +from qmk.path import normpath +from qmk.xap.gen_firmware.inline_generator import generate_inline +from qmk.xap.gen_firmware.header_generator import generate_header + + +@cli.argument('-o', '--output', type=normpath, help='File to write to') +@cli.subcommand('Generates the XAP protocol include.', hidden=False if cli.config.user.developer else True) +def xap_generate_qmk_inc(cli): + """Generates the XAP protocol inline codegen file, generated during normal build. + """ + generate_inline(cli.args.output) + + +@cli.argument('-o', '--output', type=normpath, help='File to write to') +@cli.argument('-kb', '--keyboard', help='Name of the keyboard') +@cli.subcommand('Generates the XAP protocol include.', hidden=False if cli.config.user.developer else True) +def xap_generate_qmk_h(cli): + """Generates the XAP protocol header file, generated during normal build. + """ + generate_header(cli.args.output, cli.args.keyboard) diff --git a/lib/python/qmk/commands.py b/lib/python/qmk/commands.py index 8c66228b2b..58d7a381d6 100644 --- a/lib/python/qmk/commands.py +++ b/lib/python/qmk/commands.py @@ -87,11 +87,14 @@ def create_make_command(keyboard, keymap, target=None, parallel=1, **env_vars): return create_make_target(':'.join(make_args), parallel, **env_vars) -def get_git_version(current_time, repo_dir='.', check_dir='.'): +def get_git_version(current_time=None, repo_dir='.', check_dir='.'): """Returns the current git version for a repo, or the current time. """ git_describe_cmd = ['git', 'describe', '--abbrev=6', '--dirty', '--always', '--tags'] + if current_time is None: + current_time = strftime(time_fmt) + if repo_dir != '.': repo_dir = Path('lib') / repo_dir @@ -118,7 +121,7 @@ def create_version_h(skip_git=False, skip_all=False): if skip_all: current_time = "1970-01-01-00:00:00" else: - current_time = strftime(time_fmt) + current_time = None if skip_git: git_version = "NA" diff --git a/lib/python/qmk/constants.py b/lib/python/qmk/constants.py index 71a6c91c77..c4bfc92a5f 100644 --- a/lib/python/qmk/constants.py +++ b/lib/python/qmk/constants.py @@ -1,6 +1,7 @@ """Information that should be available to the python library. """ from os import environ +from datetime import date from pathlib import Path # The root of the qmk_firmware tree. @@ -36,3 +37,92 @@ LED_INDICATORS = { # Constants that should match their counterparts in make BUILD_DIR = environ.get('BUILD_DIR', '.build') KEYBOARD_OUTPUT_PREFIX = f'{BUILD_DIR}/obj_' + +# Headers for generated files +this_year = date.today().year +GPL2_HEADER_C_LIKE = f'''\ +/* Copyright {this_year} QMK + * + * 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, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +''' + +GPL2_HEADER_SH_LIKE = f'''\ +# Copyright {this_year} QMK +# +# 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, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +''' + +GENERATED_HEADER_C_LIKE = '''\ +/******************************************************************************* + 88888888888 888 d8b .d888 d8b 888 d8b + 888 888 Y8P d88P" Y8P 888 Y8P + 888 888 888 888 + 888 88888b. 888 .d8888b 888888 888 888 .d88b. 888 .d8888b + 888 888 "88b 888 88K 888 888 888 d8P Y8b 888 88K + 888 888 888 888 "Y8888b. 888 888 888 88888888 888 "Y8888b. + 888 888 888 888 X88 888 888 888 Y8b. 888 X88 + 888 888 888 888 88888P' 888 888 888 "Y8888 888 88888P' + + 888 888 + 888 888 + 888 888 + .d88b. .d88b. 88888b. .d88b. 888d888 8888b. 888888 .d88b. .d88888 + d88P"88b d8P Y8b 888 "88b d8P Y8b 888P" "88b 888 d8P Y8b d88" 888 + 888 888 88888888 888 888 88888888 888 .d888888 888 88888888 888 888 + Y88b 888 Y8b. 888 888 Y8b. 888 888 888 Y88b. Y8b. Y88b 888 + "Y88888 "Y8888 888 888 "Y8888 888 "Y888888 "Y888 "Y8888 "Y88888 + 888 + Y8b d88P + "Y88P" +*******************************************************************************/ +''' + +GENERATED_HEADER_SH_LIKE = '''\ +################################################################################ +# +# 88888888888 888 d8b .d888 d8b 888 d8b +# 888 888 Y8P d88P" Y8P 888 Y8P +# 888 888 888 888 +# 888 88888b. 888 .d8888b 888888 888 888 .d88b. 888 .d8888b +# 888 888 "88b 888 88K 888 888 888 d8P Y8b 888 88K +# 888 888 888 888 "Y8888b. 888 888 888 88888888 888 "Y8888b. +# 888 888 888 888 X88 888 888 888 Y8b. 888 X88 +# 888 888 888 888 88888P' 888 888 888 "Y8888 888 88888P' +# +# 888 888 +# 888 888 +# 888 888 +# .d88b. .d88b. 88888b. .d88b. 888d888 8888b. 888888 .d88b. .d88888 +# d88P"88b d8P Y8b 888 "88b d8P Y8b 888P" "88b 888 d8P Y8b d88" 888 +# 888 888 88888888 888 888 88888888 888 .d888888 888 88888888 888 888 +# Y88b 888 Y8b. 888 888 Y8b. 888 888 888 Y88b. Y8b. Y88b 888 +# "Y88888 "Y8888 888 888 "Y8888 888 "Y888888 "Y888 "Y8888 "Y88888 +# 888 +# Y8b d88P +# "Y88P" +# +################################################################################ +''' diff --git a/lib/python/qmk/xap/__init__.py b/lib/python/qmk/xap/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/python/qmk/xap/common.py b/lib/python/qmk/xap/common.py new file mode 100755 index 0000000000..bec82d727b --- /dev/null +++ b/lib/python/qmk/xap/common.py @@ -0,0 +1,81 @@ +"""This script handles the XAP protocol data files. +""" +import re +import hjson +from typing import OrderedDict + +from qmk.constants import QMK_FIRMWARE + + +def _merge_ordered_dicts(dicts): + """Merges nested OrderedDict objects resulting from reading a hjson file. + + Later input dicts overrides earlier dicts for plain values. + Arrays will be appended. If the first entry of an array is "!reset!", the contents of the array will be cleared and replaced with RHS. + Dictionaries will be recursively merged. If any entry is "!reset!", the contents of the dictionary will be cleared and replaced with RHS. + """ + + result = OrderedDict() + + def add_entry(target, k, v): + if k in target and isinstance(v, OrderedDict): + if "!reset!" in v: + target[k] = v + else: + target[k] = _merge_ordered_dicts([target[k], v]) + if "!reset!" in target[k]: + del target[k]["!reset!"] + elif k in target and isinstance(v, list): + if v[0] == '!reset!': + target[k] = v[1:] + else: + target[k] = target[k] + v + else: + target[k] = v + + for d in dicts: + for (k, v) in d.items(): + add_entry(result, k, v) + + return result + + +def get_xap_definition_files(): + """Get the sorted list of XAP definition files, from /data/xap. + """ + xap_defs = QMK_FIRMWARE / "data" / "xap" + return list(sorted(xap_defs.glob('**/xap_*.hjson'))) + + +def update_xap_definitions(original, new): + """Creates a new XAP definition object based on an original and the new supplied object. + + Both inputs must be of type OrderedDict. + Later input dicts overrides earlier dicts for plain values. + Arrays will be appended. If the first entry of an array is "!reset!", the contents of the array will be cleared and replaced with RHS. + Dictionaries will be recursively merged. If any entry is "!reset!", the contents of the dictionary will be cleared and replaced with RHS. + """ + if original is None: + original = OrderedDict() + return _merge_ordered_dicts([original, new]) + + +def latest_xap_defs(): + """Gets the latest version of the XAP definitions. + """ + definitions = [hjson.load(file.open(encoding='utf-8')) for file in get_xap_definition_files()] + return _merge_ordered_dicts(definitions) + + +def route_conditions(route_stack): + """Handles building the C preprocessor conditional based on the current route. + """ + conditions = [] + for route in route_stack: + if 'enable_if_preprocessor' in route: + conditions.append(route['enable_if_preprocessor']) + + if len(conditions) == 0: + return None + + return "(" + ' && '.join([f'({c})' for c in conditions]) + ")" diff --git a/lib/python/qmk/xap/gen_client_js/__init__.py b/lib/python/qmk/xap/gen_client_js/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/python/qmk/xap/gen_docs/__init__.py b/lib/python/qmk/xap/gen_docs/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/python/qmk/xap/gen_docs/generator.py b/lib/python/qmk/xap/gen_docs/generator.py new file mode 100755 index 0000000000..3e0ce52896 --- /dev/null +++ b/lib/python/qmk/xap/gen_docs/generator.py @@ -0,0 +1,103 @@ +"""This script generates the XAP protocol documentation. +""" +import hjson +from qmk.constants import QMK_FIRMWARE +from qmk.xap.common import get_xap_definition_files, update_xap_definitions + + +def _update_type_docs(overall): + defs = overall['type_docs'] + + type_docs = [] + for (k, v) in sorted(defs.items(), key=lambda x: x[0]): + type_docs.append(f'| _{k}_ | {v} |') + + desc_str = "\n".join(type_docs) + + overall['documentation']['!type_docs!'] = f'''\ +| Name | Definition | +| -- | -- | +{desc_str} +''' + + +def _update_term_definitions(overall): + defs = overall['term_definitions'] + + term_descriptions = [] + for (k, v) in sorted(defs.items(), key=lambda x: x[0]): + term_descriptions.append(f'| _{k}_ | {v} |') + + desc_str = "\n".join(term_descriptions) + + overall['documentation']['!term_definitions!'] = f'''\ +| Name | Definition | +| -- | -- | +{desc_str} +''' + + +def _update_response_flags(overall): + flags = overall['response_flags']['bits'] + for n in range(0, 8): + if str(n) not in flags: + flags[str(n)] = {"name": "-", "description": "-"} + + header = '| ' + " | ".join([f'Bit {n}' for n in range(7, -1, -1)]) + ' |' + dividers = '|' + "|".join(['--' for n in range(7, -1, -1)]) + '|' + bit_names = '| ' + " | ".join([flags[str(n)]['name'] for n in range(7, -1, -1)]) + ' |' + + bit_descriptions = '' + for n in range(7, -1, -1): + bit_desc = flags[str(n)] + if bit_desc['name'] != '-': + desc = bit_desc['description'] + bit_descriptions = bit_descriptions + f'\n* `Bit {n}`: {desc}' + + overall['documentation']['!response_flags!'] = f'''\ +{header} +{dividers} +{bit_names} +{bit_descriptions} +''' + + +def generate_docs(): + """Generates the XAP protocol documentation by merging the definitions files, and producing the corresponding Markdown document under `/docs/`. + """ + docs_list = [] + + overall = None + for file in get_xap_definition_files(): + + overall = update_xap_definitions(overall, hjson.load(file.open(encoding='utf-8'))) + + try: + if 'type_docs' in overall: + _update_type_docs(overall) + if 'term_definitions' in overall: + _update_term_definitions(overall) + if 'response_flags' in overall: + _update_response_flags(overall) + except: + print(hjson.dumps(overall)) + exit(1) + + output_doc = QMK_FIRMWARE / "docs" / f"{file.stem}.md" + docs_list.append(output_doc) + + with open(output_doc, "w", encoding='utf-8') as out_file: + for e in overall['documentation']['order']: + out_file.write(overall['documentation'][e].strip()) + out_file.write('\n\n') + + output_doc = QMK_FIRMWARE / "docs" / f"xap_protocol.md" + with open(output_doc, "w", encoding='utf-8') as out_file: + out_file.write('''\ +# XAP Protocol Reference + +''') + + for file in reversed(sorted(docs_list)): + ver = file.stem[4:] + out_file.write(f'* [XAP Version {ver}]({file.name})\n') diff --git a/lib/python/qmk/xap/gen_firmware/__init__.py b/lib/python/qmk/xap/gen_firmware/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/python/qmk/xap/gen_firmware/header_generator.py b/lib/python/qmk/xap/gen_firmware/header_generator.py new file mode 100755 index 0000000000..9b295ed8f3 --- /dev/null +++ b/lib/python/qmk/xap/gen_firmware/header_generator.py @@ -0,0 +1,136 @@ +"""This script generates the XAP protocol generated header to be compiled into QMK. +""" +import re +import pyhash + +from qmk.commands import get_git_version +from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE +from qmk.xap.common import latest_xap_defs, route_conditions + + +def _append_route_defines(lines, container, container_id=None, route_stack=None): + """Handles building the list of the XAP routes, combining parent and child names together, as well as the route number. + """ + if route_stack is None: + route_stack = [container] + else: + route_stack.append(container) + + route_name = '_'.join([r['define'] for r in route_stack]) + + if container_id: + lines.append(f'#define {route_name} {container_id}') + + if 'routes' in container: + for route_id in container['routes']: + route = container['routes'][route_id] + _append_route_defines(lines, route, route_id, route_stack) + + route_stack.pop() + + +def _append_route_masks(lines, container, container_id=None, route_stack=None): + """Handles creating the equivalent XAP route masks, for capabilities checks. Forces value of `0` if disabled in the firmware. + """ + if route_stack is None: + route_stack = [container] + else: + route_stack.append(container) + + route_name = '_'.join([r['define'] for r in route_stack]) + condition = route_conditions(route_stack) + + if container_id: + if condition: + lines.append('') + lines.append(f'#if {condition}') + + lines.append(f'#define {route_name}_MASK (1ul << ({route_name}))') + + if condition: + lines.append(f'#else // {condition}') + lines.append(f'#define {route_name}_MASK 0') + lines.append(f'#endif // {condition}') + lines.append('') + + if 'routes' in container: + for route_id in container['routes']: + route = container['routes'][route_id] + _append_route_masks(lines, route, route_id, route_stack) + + route_stack.pop() + + +def _append_route_capabilities(lines, container, container_id=None, route_stack=None): + """Handles creating the equivalent XAP route masks, for capabilities checks. Forces value of `0` if disabled in the firmware. + """ + if route_stack is None: + route_stack = [container] + else: + route_stack.append(container) + + route_name = '_'.join([r['define'] for r in route_stack]) + + if 'routes' in container: + lines.append('') + lines.append(f'#define {route_name}_CAPABILITIES (0 \\') + + if 'routes' in container: + for route_id in container['routes']: + route = container['routes'][route_id] + route_stack.append(route) + child_name = '_'.join([r['define'] for r in route_stack]) + lines.append(f' | ({child_name}_MASK) \\') + route_stack.pop() + + lines.append(' )') + + if 'routes' in container: + for route_id in container['routes']: + route = container['routes'][route_id] + _append_route_capabilities(lines, route, route_id, route_stack) + + route_stack.pop() + + +def generate_header(output_file, keyboard): + """Generates the XAP protocol header file, generated during normal build. + """ + xap_defs = latest_xap_defs() + + # Preamble + lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once', ''] + + # Versions + prog = re.compile(r'^(\d+)\.(\d+)\.(\d+)') + b = prog.match(xap_defs['version']) + lines.append(f'#define XAP_BCD_VERSION 0x{int(b.group(1)):02d}{int(b.group(2)):02d}{int(b.group(3)):04d}ul') + b = prog.match(get_git_version()) + lines.append(f'#define QMK_BCD_VERSION 0x{int(b.group(1)):02d}{int(b.group(2)):02d}{int(b.group(3)):04d}ul') + keyboard_id = pyhash.murmur3_32()(keyboard) + lines.append(f'#define XAP_KEYBOARD_IDENTIFIER 0x{keyboard_id:08X}ul') + lines.append('') + + # Append the route and command defines + _append_route_defines(lines, xap_defs) + lines.append('') + _append_route_masks(lines, xap_defs) + lines.append('') + _append_route_capabilities(lines, xap_defs) + lines.append('') + + # Generate the full output + xap_generated_inl = '\n'.join(lines) + + # Clean up newlines + while "\n\n\n" in xap_generated_inl: + xap_generated_inl = xap_generated_inl.replace("\n\n\n", "\n\n") + + if output_file: + if output_file.name == '-': + print(xap_generated_inl) + else: + output_file.parent.mkdir(parents=True, exist_ok=True) + if output_file.exists(): + output_file.replace(output_file.parent / (output_file.name + '.bak')) + output_file.write_text(xap_generated_inl) diff --git a/lib/python/qmk/xap/gen_firmware/inline_generator.py b/lib/python/qmk/xap/gen_firmware/inline_generator.py new file mode 100755 index 0000000000..756b0e8666 --- /dev/null +++ b/lib/python/qmk/xap/gen_firmware/inline_generator.py @@ -0,0 +1,222 @@ +"""This script generates the XAP protocol generated header to be compiled into QMK. +""" +import pyhash + +from qmk.casing import to_snake +from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE +from qmk.xap.common import latest_xap_defs, route_conditions + + +def _get_c_type(xap_type): + if xap_type == 'bool': + return 'bool' + elif xap_type == 'u8': + return 'uint8_t' + elif xap_type == 'u16': + return 'uint16_t' + elif xap_type == 'u32': + return 'uint32_t' + elif xap_type == 'u64': + return 'uint64_t' + elif xap_type == 'struct': + return 'struct' + elif xap_type == 'string': + return 'const char *' + return 'unknown' + + +def _get_route_type(container): + if 'routes' in container: + return 'XAP_ROUTE' + elif 'return_constant' in container: + if container['return_type'] == 'u32': + return 'XAP_VALUE' + elif container['return_type'] == 'struct': + return 'XAP_CONST_MEM' + elif container['return_type'] == 'string': + return 'XAP_CONST_MEM' + elif 'return_getter' in container: + if container['return_type'] == 'u32': + return 'XAP_GETTER' + return 'UNSUPPORTED' + + +def _append_routing_table_declaration(lines, container, container_id, route_stack): + route_stack.append(container) + + route_name = to_snake('_'.join([r['define'] for r in route_stack])) + + if 'routes' in container: + pass + + elif 'return_constant' in container: + + if container['return_type'] == 'u32': + pass + + elif container['return_type'] == 'struct': + lines.append('') + lines.append(f'static const struct {route_name}_t {{') + + for member in container['return_struct_members']: + member_type = _get_c_type(member['type']) + member_name = to_snake(member['name']) + lines.append(f' const {member_type} {member_name};') + + lines.append(f'}} {route_name}_data PROGMEM = {{') + + for constant in container['return_constant']: + lines.append(f' {constant},') + + lines.append(f'}};') + + elif container['return_type'] == 'string': + constant = container['return_constant'] + lines.append('') + lines.append(f'static const char {route_name}_str[] PROGMEM = {constant};') + + elif 'return_getter' in container: + + if container['return_type'] == 'u32': + lines.append('') + lines.append(f'extern uint32_t {route_name}_getter(void);') + + elif container['return_type'] == 'struct': + pass + + route_stack.pop() + + +def _append_routing_table_entry_flags(lines, container, container_id, route_stack): + is_secure = 1 if ('secure' in container and container['secure'] is True) else 0 + lines.append(f' .flags = {{') + lines.append(f' .type = {_get_route_type(container)},') + lines.append(f' .is_secure = {is_secure},') + lines.append(f' }},') + + +def _append_routing_table_entry_route(lines, container, container_id, route_stack): + route_name = to_snake('_'.join([r['define'] for r in route_stack])) + lines.append(f' .child_routes = {route_name}_table,') + lines.append(f' .child_routes_len = sizeof({route_name}_table)/sizeof(xap_route_t),') + + +def _append_routing_table_entry_u32value(lines, container, container_id, route_stack): + value = container['return_constant'] + lines.append(f' .u32value = {value},') + + +def _append_routing_table_entry_u32getter(lines, container, container_id, route_stack): + route_name = to_snake('_'.join([r['define'] for r in route_stack])) + lines.append(f' .u32getter = &{route_name}_getter,') + + +def _append_routing_table_entry_const_data(lines, container, container_id, route_stack): + route_name = to_snake('_'.join([r['define'] for r in route_stack])) + lines.append(f' .const_data = &{route_name}_data,') + lines.append(f' .const_data_len = sizeof({route_name}_data),') + + +def _append_routing_table_entry_string(lines, container, container_id, route_stack): + route_name = to_snake('_'.join([r['define'] for r in route_stack])) + lines.append(f' .const_data = {route_name}_str,') + lines.append(f' .const_data_len = sizeof({route_name}_str) - 1,') + + +def _append_routing_table_entry(lines, container, container_id, route_stack): + route_stack.append(container) + route_name = '_'.join([r['define'] for r in route_stack]) + condition = route_conditions(route_stack) + + if condition: + lines.append(f'#if {condition}') + + lines.append(f' [{route_name}] = {{') + + _append_routing_table_entry_flags(lines, container, container_id, route_stack) + if 'routes' in container: + _append_routing_table_entry_route(lines, container, container_id, route_stack) + elif 'return_constant' in container: + if container['return_type'] == 'u32': + _append_routing_table_entry_u32value(lines, container, container_id, route_stack) + elif container['return_type'] == 'struct': + _append_routing_table_entry_const_data(lines, container, container_id, route_stack) + elif container['return_type'] == 'string': + _append_routing_table_entry_string(lines, container, container_id, route_stack) + elif 'return_getter' in container: + if container['return_type'] == 'u32': + _append_routing_table_entry_u32getter(lines, container, container_id, route_stack) + + lines.append(f' }},') + + if condition: + lines.append(f'#endif // {condition}') + + route_stack.pop() + + +def _append_routing_tables(lines, container, container_id=None, route_stack=None): + """Handles building the list of the XAP routes, combining parent and child names together, as well as the route number. + """ + if route_stack is None: + route_stack = [container] + else: + route_stack.append(container) + + route_name = to_snake('_'.join([r['define'] for r in route_stack])) + condition = route_conditions(route_stack) + + if 'routes' in container: + for route_id in container['routes']: + route = container['routes'][route_id] + _append_routing_tables(lines, route, route_id, route_stack) + + for route_id in container['routes']: + route = container['routes'][route_id] + _append_routing_table_declaration(lines, route, route_id, route_stack) + + lines.append('') + if condition: + lines.append(f'#if {condition}') + + lines.append(f'static const xap_route_t {route_name}_table[] PROGMEM = {{') + + for route_id in container['routes']: + route = container['routes'][route_id] + _append_routing_table_entry(lines, route, route_id, route_stack) + + lines.append('};') + + if condition: + lines.append(f'#endif // {condition}') + lines.append('') + + route_stack.pop() + + +def generate_inline(output_file): + """Generates the XAP protocol header file, generated during normal build. + """ + xap_defs = latest_xap_defs() + + # Preamble + lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, ''] + + # Add all the generated code + _append_routing_tables(lines, xap_defs) + + # Generate the full output + xap_generated_inl = '\n'.join(lines) + + # Clean up newlines + while "\n\n\n" in xap_generated_inl: + xap_generated_inl = xap_generated_inl.replace("\n\n\n", "\n\n") + + if output_file: + if output_file.name == '-': + print(xap_generated_inl) + else: + output_file.parent.mkdir(parents=True, exist_ok=True) + if output_file.exists(): + output_file.replace(output_file.parent / (output_file.name + '.bak')) + output_file.write_text(xap_generated_inl) diff --git a/quantum/quantum.h b/quantum/quantum.h index ffb5e0df45..d9114a0aef 100644 --- a/quantum/quantum.h +++ b/quantum/quantum.h @@ -184,6 +184,10 @@ extern layer_state_t layer_state; # include "dynamic_keymap.h" #endif +#ifdef XAP_ENABLE +# include "xap.h" +#endif + #ifdef VIA_ENABLE # include "via.h" #endif diff --git a/quantum/xap/xap.c b/quantum/xap/xap.c new file mode 100644 index 0000000000..df25f799d5 --- /dev/null +++ b/quantum/xap/xap.c @@ -0,0 +1,116 @@ +/* Copyright 2021 Nick Brassel (@tzarc) + * + * 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, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +#define QSTR2(z) #z +#define QSTR(z) QSTR2(z) + +typedef enum xap_route_type_t { + XAP_UNUSED = 0, // "Unused" needs to be zero -- undefined routes (through preprocessor) will be skipped + XAP_ROUTE, + XAP_EXECUTE, + XAP_VALUE, + XAP_GETTER, + XAP_CONST_MEM, + TOTAL_XAP_ROUTE_TYPES +} xap_route_type_t; + +#define XAP_ROUTE_TYPE_BIT_COUNT 3 + +typedef struct __attribute__((packed)) xap_route_flags_t { + xap_route_type_t type : XAP_ROUTE_TYPE_BIT_COUNT; + uint8_t is_secure : 1; +} xap_route_flags_t; + +_Static_assert(TOTAL_XAP_ROUTE_TYPES <= (1 << (XAP_ROUTE_TYPE_BIT_COUNT)), "Number of XAP route types is too large for XAP_ROUTE_TYPE_BITS."); +_Static_assert(sizeof(xap_route_flags_t) == 1, "xap_route_flags_t is not length of 1"); + +typedef struct xap_route_t xap_route_t; +struct __attribute__((packed)) xap_route_t { + const xap_route_flags_t flags; + union { + // XAP_ROUTE + struct { + const xap_route_t *child_routes; + const uint8_t child_routes_len; + }; + + // XAP_EXECUTE + bool (*handler)(xap_token_t token, const uint8_t *data, size_t data_len); + + // XAP_VALUE + uint32_t u32value; + + // XAP_GETTER + uint32_t (*u32getter)(void); + + // XAP_CONST_MEM + struct { + const void * const_data; + const uint8_t const_data_len; + }; + }; +}; + +#include + +void xap_execute_route(xap_token_t token, const xap_route_t *routes, size_t max_routes, const uint8_t *data, size_t data_len) { + if (data_len == 0) return; + xap_identifier_t id = data[0]; + + if (id < max_routes) { + xap_route_t route; + memcpy_P(&route, &routes[id], sizeof(xap_route_t)); + switch (route.flags.type) { + case XAP_ROUTE: + if (route.child_routes != NULL && route.child_routes_len > 0 && data_len > 0) { + xap_execute_route(token, route.child_routes, route.child_routes_len, &data[1], data_len - 1); + return; + } + break; + + case XAP_EXECUTE: + if (route.handler != NULL) { + bool ok = (route.handler)(token, data_len == 1 ? NULL : &data[1], data_len - 1); + if (ok) return; + } + break; + + case XAP_VALUE: + xap_respond_u32(token, route.u32value); + return; + + case XAP_GETTER: + xap_respond_u32(token, (route.u32getter)()); + return; + + case XAP_CONST_MEM: + xap_respond_data_P(token, route.const_data, route.const_data_len); + return; + + default: + break; + } + } + + // Nothing got handled, so we respond with failure. + xap_respond_failure(token, XAP_RESPONSE_FLAG_FAILED); +} + +void xap_receive(xap_token_t token, const uint8_t *data, size_t length) { xap_execute_route(token, xap_route_table, sizeof(xap_route_table) / sizeof(xap_route_t), data, length); } diff --git a/quantum/xap/xap.h b/quantum/xap/xap.h new file mode 100644 index 0000000000..f3cab6aef2 --- /dev/null +++ b/quantum/xap/xap.h @@ -0,0 +1,44 @@ +/* Copyright 2021 Nick Brassel (@tzarc) + * + * 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, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include + +typedef uint8_t xap_identifier_t; +typedef uint8_t xap_response_flags_t; +typedef uint16_t xap_token_t; + +#ifndef XAP_SUBSYSTEM_VERSION_KB +# define XAP_SUBSYSTEM_VERSION_KB 0 +#endif + +#ifndef XAP_SUBSYSTEM_VERSION_USER +# define XAP_SUBSYSTEM_VERSION_USER 0 +#endif + +#define XAP_RESPONSE_FLAG_FAILED 0 +#define XAP_RESPONSE_FLAG_SUCCESS (1 << 0) + +void xap_respond_failure(xap_token_t token, xap_response_flags_t response_flags); +bool xap_respond_u32(xap_token_t token, uint32_t value); +bool xap_respond_data(xap_token_t token, const void *data, size_t length); +bool xap_respond_data_P(xap_token_t token, const void *data, size_t length); + +void xap_send(xap_token_t token, xap_response_flags_t response_flags, const void *data, size_t length); + +#include diff --git a/quantum/xap/xap_handlers.c b/quantum/xap/xap_handlers.c new file mode 100644 index 0000000000..7c3d8127ce --- /dev/null +++ b/quantum/xap/xap_handlers.c @@ -0,0 +1,39 @@ +/* Copyright 2021 Nick Brassel (@tzarc) + * + * 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, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +void xap_respond_failure(xap_token_t token, xap_response_flags_t response_flags) { xap_send(token, response_flags, NULL, 0); } + +bool xap_respond_data(xap_token_t token, const void *data, size_t length) { + xap_send(token, XAP_RESPONSE_FLAG_SUCCESS, data, length); + return true; +} + +bool xap_respond_data_P(xap_token_t token, const void *data, size_t length) { + uint8_t blob[length]; + memcpy_P(blob, data, length); + return xap_respond_data(token, blob, length); +} + +bool xap_respond_u32(xap_token_t token, uint32_t value) { + xap_send(token, XAP_RESPONSE_FLAG_SUCCESS, &value, sizeof(value)); + return true; +} + +uint32_t xap_route_qmk_ffffffffffffffff_getter(void) { return 0x12345678; } diff --git a/requirements.txt b/requirements.txt index 92381d7d51..079dcdb8ac 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,5 +7,6 @@ hjson jsonschema>=3 milc>=1.4.2 pygments +pyhash pyusb qmk-dotty-dict diff --git a/tmk_core/common/report.h b/tmk_core/common/report.h index db6370657d..cf9f1df589 100644 --- a/tmk_core/common/report.h +++ b/tmk_core/common/report.h @@ -30,7 +30,8 @@ enum hid_report_ids { REPORT_ID_SYSTEM, REPORT_ID_CONSUMER, REPORT_ID_NKRO, - REPORT_ID_JOYSTICK + REPORT_ID_JOYSTICK, + REPORT_ID_XAP }; /* Mouse buttons */ diff --git a/tmk_core/protocol/lufa/lufa.c b/tmk_core/protocol/lufa/lufa.c index 4ac079e168..16ad71414c 100644 --- a/tmk_core/protocol/lufa/lufa.c +++ b/tmk_core/protocol/lufa/lufa.c @@ -85,6 +85,10 @@ extern keymap_config_t keymap_config; # include "raw_hid.h" #endif +#ifdef XAP_ENABLE +# include "xap.h" +#endif + #ifdef JOYSTICK_ENABLE # include "joystick.h" #endif @@ -249,6 +253,88 @@ static void raw_hid_task(void) { } #endif +#ifdef XAP_ENABLE +extern void xap_receive(xap_token_t token, const uint8_t *data, size_t length); + +void xap_send_base(uint8_t *data, uint8_t length) { + // TODO: implement variable size packet + if (length != XAP_EPSIZE) { + return; + } + + if (USB_DeviceState != DEVICE_STATE_Configured) { + return; + } + + // TODO: decide if we allow calls to raw_hid_send() in the middle + // of other endpoint usage. + uint8_t ep = Endpoint_GetCurrentEndpoint(); + + Endpoint_SelectEndpoint(XAP_IN_EPNUM); + + // Check to see if the host is ready to accept another packet + if (Endpoint_IsINReady()) { + // Write data + Endpoint_Write_Stream_LE(data, XAP_EPSIZE, NULL); + // Finalize the stream transfer to send the last packet + Endpoint_ClearIN(); + } + + Endpoint_SelectEndpoint(ep); +} + +void xap_send(xap_token_t token, uint8_t response_flags, const void *data, size_t length) { + uint8_t rdata[XAP_EPSIZE] = {0}; + *(xap_token_t *)&rdata[0] = token; + if (length > (XAP_EPSIZE - 4)) response_flags &= ~(XAP_RESPONSE_FLAG_SUCCESS); + rdata[2] = response_flags; + if (response_flags & (XAP_RESPONSE_FLAG_SUCCESS)) { + rdata[3] = (uint8_t)length; + if (data != NULL) { + memcpy(&rdata[4], data, length); + } + } + xap_send_base(rdata, sizeof(rdata)); +} + +void xap_receive_base(const void *data) { + const uint8_t *u8data = (const uint8_t *)data; + xap_token_t token = *(xap_token_t *)&u8data[0]; + uint8_t length = u8data[2]; + if (length <= (XAP_EPSIZE - 3)) { + xap_receive(token, &u8data[3], length); + } +} + +static void xap_task(void) { + // Create a temporary buffer to hold the read in data from the host + uint8_t data[XAP_EPSIZE]; + bool data_read = false; + + // Device must be connected and configured for the task to run + if (USB_DeviceState != DEVICE_STATE_Configured) return; + + Endpoint_SelectEndpoint(XAP_OUT_EPNUM); + + // Check to see if a packet has been sent from the host + if (Endpoint_IsOUTReceived()) { + // Check to see if the packet contains data + if (Endpoint_IsReadWriteAllowed()) { + /* Read data */ + Endpoint_Read_Stream_LE(data, sizeof(data), NULL); + data_read = true; + } + + // Finalize the stream transfer to receive the last packet + Endpoint_ClearOUT(); + + if (data_read) { + xap_receive_base(data); + } + } +} +#endif // XAP_ENABLE + /******************************************************************************* * Console ******************************************************************************/ @@ -500,6 +586,12 @@ void EVENT_USB_Device_ConfigurationChanged(void) { ConfigSuccess &= Endpoint_ConfigureEndpoint((RAW_OUT_EPNUM | ENDPOINT_DIR_OUT), EP_TYPE_INTERRUPT, RAW_EPSIZE, 1); #endif +#ifdef XAP_ENABLE + /* Setup XAP endpoints */ + ConfigSuccess &= Endpoint_ConfigureEndpoint((XAP_IN_EPNUM | ENDPOINT_DIR_IN), EP_TYPE_INTERRUPT, XAP_EPSIZE, 1); + ConfigSuccess &= Endpoint_ConfigureEndpoint((XAP_OUT_EPNUM | ENDPOINT_DIR_OUT), EP_TYPE_INTERRUPT, XAP_EPSIZE, 1); +#endif // XAP_ENABLE + #ifdef CONSOLE_ENABLE /* Setup console endpoint */ ConfigSuccess &= Endpoint_ConfigureEndpoint((CONSOLE_IN_EPNUM | ENDPOINT_DIR_IN), EP_TYPE_INTERRUPT, CONSOLE_EPSIZE, 1); @@ -1102,6 +1194,10 @@ int main(void) { raw_hid_task(); #endif +#ifdef XAP_ENABLE + xap_task(); +#endif + #if !defined(INTERRUPT_CONTROL_ENDPOINT) USB_USBTask(); #endif diff --git a/tmk_core/protocol/usb_descriptor.c b/tmk_core/protocol/usb_descriptor.c index 7a4a790315..0a15e0c6d1 100644 --- a/tmk_core/protocol/usb_descriptor.c +++ b/tmk_core/protocol/usb_descriptor.c @@ -227,6 +227,7 @@ const USB_Descriptor_HIDReport_Datatype_t PROGMEM SharedReport[] = { HID_RI_OUTPUT(8, HID_IOF_CONSTANT), HID_RI_END_COLLECTION(0), #endif + #ifdef SHARED_EP_ENABLE }; #endif @@ -255,6 +256,30 @@ const USB_Descriptor_HIDReport_Datatype_t PROGMEM RawReport[] = { }; #endif +#ifdef XAP_ENABLE +const USB_Descriptor_HIDReport_Datatype_t PROGMEM XapReport[] = { + HID_RI_USAGE_PAGE(16, 0xFF51), // Vendor Defined ('Q') + HID_RI_USAGE(8, 0x58), // Vendor Defined ('X') + HID_RI_COLLECTION(8, 0x01), // Application + // Data to host + HID_RI_USAGE(8, 0x62), // Vendor Defined + HID_RI_LOGICAL_MINIMUM(8, 0x00), + HID_RI_LOGICAL_MAXIMUM(16, 0x00FF), + HID_RI_REPORT_COUNT(8, XAP_EPSIZE), + HID_RI_REPORT_SIZE(8, 0x08), + HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + + // Data from host + HID_RI_USAGE(8, 0x63), // Vendor Defined + HID_RI_LOGICAL_MINIMUM(8, 0x00), + HID_RI_LOGICAL_MAXIMUM(16, 0x00FF), + HID_RI_REPORT_COUNT(8, XAP_EPSIZE), + HID_RI_REPORT_SIZE(8, 0x08), + HID_RI_OUTPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE | HID_IOF_NON_VOLATILE), + HID_RI_END_COLLECTION(0), +}; +#endif // XAP_ENABLE + #ifdef CONSOLE_ENABLE const USB_Descriptor_HIDReport_Datatype_t PROGMEM ConsoleReport[] = { HID_RI_USAGE_PAGE(16, 0xFF31), // Vendor Defined (PJRC Teensy compatible) @@ -492,6 +517,56 @@ const USB_Descriptor_Configuration_t PROGMEM ConfigurationDescriptor = { }, #endif +#ifdef XAP_ENABLE + /* + * QMK XAP + */ + .Xap_Interface = { + .Header = { + .Size = sizeof(USB_Descriptor_Interface_t), + .Type = DTYPE_Interface + }, + .InterfaceNumber = XAP_INTERFACE, + .AlternateSetting = 0x00, + .TotalEndpoints = 2, + .Class = HID_CSCP_HIDClass, + .SubClass = HID_CSCP_NonBootSubclass, + .Protocol = HID_CSCP_NonBootProtocol, + .InterfaceStrIndex = NO_DESCRIPTOR + }, + .Xap_HID = { + .Header = { + .Size = sizeof(USB_HID_Descriptor_HID_t), + .Type = HID_DTYPE_HID + }, + .HIDSpec = VERSION_BCD(1, 1, 1), + .CountryCode = 0x00, + .TotalReportDescriptors = 1, + .HIDReportType = HID_DTYPE_Report, + .HIDReportLength = sizeof(XapReport) + }, + .Xap_INEndpoint = { + .Header = { + .Size = sizeof(USB_Descriptor_Endpoint_t), + .Type = DTYPE_Endpoint + }, + .EndpointAddress = (ENDPOINT_DIR_IN | XAP_IN_EPNUM), + .Attributes = (EP_TYPE_INTERRUPT | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA), + .EndpointSize = XAP_EPSIZE, + .PollingIntervalMS = 0x01 + }, + .Xap_OUTEndpoint = { + .Header = { + .Size = sizeof(USB_Descriptor_Endpoint_t), + .Type = DTYPE_Endpoint + }, + .EndpointAddress = (ENDPOINT_DIR_OUT | XAP_OUT_EPNUM), + .Attributes = (EP_TYPE_INTERRUPT | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA), + .EndpointSize = XAP_EPSIZE, + .PollingIntervalMS = 0x01 + }, +#endif + #if defined(MOUSE_ENABLE) && !defined(MOUSE_SHARED_EP) /* * Mouse @@ -1046,6 +1121,14 @@ uint16_t get_usb_descriptor(const uint16_t wValue, const uint16_t wIndex, const break; #endif +#ifdef XAP_ENABLE + case XAP_INTERFACE: + Address = &ConfigurationDescriptor.Xap_HID; + Size = sizeof(USB_HID_Descriptor_HID_t); + + break; +#endif + #ifdef CONSOLE_ENABLE case CONSOLE_INTERFACE: Address = &ConfigurationDescriptor.Console_HID; @@ -1096,6 +1179,14 @@ uint16_t get_usb_descriptor(const uint16_t wValue, const uint16_t wIndex, const break; #endif +#ifdef XAP_ENABLE + case XAP_INTERFACE: + Address = &XapReport; + Size = sizeof(XapReport); + + break; +#endif + #ifdef CONSOLE_ENABLE case CONSOLE_INTERFACE: Address = &ConsoleReport; diff --git a/tmk_core/protocol/usb_descriptor.h b/tmk_core/protocol/usb_descriptor.h index 867e549b4f..e7afe68640 100644 --- a/tmk_core/protocol/usb_descriptor.h +++ b/tmk_core/protocol/usb_descriptor.h @@ -75,6 +75,14 @@ typedef struct { USB_Descriptor_Endpoint_t Raw_OUTEndpoint; #endif +#ifdef XAP_ENABLE + // Mouse HID Interface + USB_Descriptor_Interface_t Xap_Interface; + USB_HID_Descriptor_HID_t Xap_HID; + USB_Descriptor_Endpoint_t Xap_INEndpoint; + USB_Descriptor_Endpoint_t Xap_OUTEndpoint; +#endif + #if defined(MOUSE_ENABLE) && !defined(MOUSE_SHARED_EP) // Mouse HID Interface USB_Descriptor_Interface_t Mouse_Interface; @@ -155,6 +163,10 @@ enum usb_interfaces { RAW_INTERFACE, #endif +#ifdef XAP_ENABLE + XAP_INTERFACE, +#endif + #if defined(MOUSE_ENABLE) && !defined(MOUSE_SHARED_EP) MOUSE_INTERFACE, #endif @@ -212,6 +224,15 @@ enum usb_endpoints { # endif #endif +#ifdef XAP_ENABLE + XAP_IN_EPNUM = NEXT_EPNUM, +# if STM32_USB_USE_OTG1 +# define XAP_OUT_EPNUM XAP_IN_EPNUM +# else + XAP_OUT_EPNUM = NEXT_EPNUM, +# endif +#endif + #ifdef SHARED_EP_ENABLE SHARED_IN_EPNUM = NEXT_EPNUM, #endif @@ -272,7 +293,7 @@ enum usb_endpoints { // TODO - ARM_ATSAM #if (NEXT_EPNUM - 1) > MAX_ENDPOINTS -# error There are not enough available endpoints to support all functions. Please disable one or more of the following: Mouse Keys, Extra Keys, Console, NKRO, MIDI, Serial, Steno +# error There are not enough available endpoints to support all functions. Please disable one or more of the following: Mouse Keys, Extra Keys, Console, NKRO, MIDI, Serial, Steno, XAP #endif #define KEYBOARD_EPSIZE 8 @@ -284,5 +305,6 @@ enum usb_endpoints { #define CDC_NOTIFICATION_EPSIZE 8 #define CDC_EPSIZE 16 #define JOYSTICK_EPSIZE 8 +#define XAP_EPSIZE 64 uint16_t get_usb_descriptor(const uint16_t wValue, const uint16_t wIndex, const void** const DescriptorAddress); From 437559cd037a97103b5d898ddfe40d0145cbfc59 Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Wed, 15 Sep 2021 08:45:14 +1000 Subject: [PATCH 02/77] Swap to fnvhash due to deps. --- lib/python/qmk/xap/gen_firmware/header_generator.py | 4 ++-- requirements.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/python/qmk/xap/gen_firmware/header_generator.py b/lib/python/qmk/xap/gen_firmware/header_generator.py index 9b295ed8f3..66f01d1855 100755 --- a/lib/python/qmk/xap/gen_firmware/header_generator.py +++ b/lib/python/qmk/xap/gen_firmware/header_generator.py @@ -1,7 +1,7 @@ """This script generates the XAP protocol generated header to be compiled into QMK. """ import re -import pyhash +from fnvhash import fnv1a_32 from qmk.commands import get_git_version from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE @@ -107,7 +107,7 @@ def generate_header(output_file, keyboard): lines.append(f'#define XAP_BCD_VERSION 0x{int(b.group(1)):02d}{int(b.group(2)):02d}{int(b.group(3)):04d}ul') b = prog.match(get_git_version()) lines.append(f'#define QMK_BCD_VERSION 0x{int(b.group(1)):02d}{int(b.group(2)):02d}{int(b.group(3)):04d}ul') - keyboard_id = pyhash.murmur3_32()(keyboard) + keyboard_id = fnv1a_32(keyboard) lines.append(f'#define XAP_KEYBOARD_IDENTIFIER 0x{keyboard_id:08X}ul') lines.append('') diff --git a/requirements.txt b/requirements.txt index 079dcdb8ac..6357b4e2eb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,11 +2,11 @@ appdirs argcomplete colorama +fnvhash hid hjson jsonschema>=3 milc>=1.4.2 pygments -pyhash pyusb qmk-dotty-dict From 5aae5a767f0659649b629cd7c8184ed58fb1c7b8 Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Wed, 15 Sep 2021 08:49:51 +1000 Subject: [PATCH 03/77] Use the correct input type. --- lib/python/qmk/xap/gen_firmware/header_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/python/qmk/xap/gen_firmware/header_generator.py b/lib/python/qmk/xap/gen_firmware/header_generator.py index 66f01d1855..24dc8d3450 100755 --- a/lib/python/qmk/xap/gen_firmware/header_generator.py +++ b/lib/python/qmk/xap/gen_firmware/header_generator.py @@ -107,7 +107,7 @@ def generate_header(output_file, keyboard): lines.append(f'#define XAP_BCD_VERSION 0x{int(b.group(1)):02d}{int(b.group(2)):02d}{int(b.group(3)):04d}ul') b = prog.match(get_git_version()) lines.append(f'#define QMK_BCD_VERSION 0x{int(b.group(1)):02d}{int(b.group(2)):02d}{int(b.group(3)):04d}ul') - keyboard_id = fnv1a_32(keyboard) + keyboard_id = fnv1a_32(bytes(keyboard, 'utf-8')) lines.append(f'#define XAP_KEYBOARD_IDENTIFIER 0x{keyboard_id:08X}ul') lines.append('') From ec9a78cc4a7ad202a16d83b8673fc77b87eb327c Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Thu, 16 Sep 2021 11:43:19 +1000 Subject: [PATCH 04/77] Doco update. --- data/xap/xap_0.1.0.hjson | 2 +- docs/xap_0.1.0.md | 2 +- docs/xap_0.2.0.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/xap/xap_0.1.0.hjson b/data/xap/xap_0.1.0.hjson index e24978f459..f74f177339 100755 --- a/data/xap/xap_0.1.0.hjson +++ b/data/xap/xap_0.1.0.hjson @@ -59,7 +59,7 @@ ''' "struct{}": ''' - A structure of data, packing different objects together. Data is "compacted" -- there are no padding bytes between fields. Equivalent to a C-style `struct`. The order in which they're defined matches the order of the data in the response packet. + A structure of data, packing different objects together. Data is "compacted" -- there are no padding bytes between fields. Equivalent to a packed C-style `struct`. The order in which they're defined matches the order of the data in the response packet. ''' } diff --git a/docs/xap_0.1.0.md b/docs/xap_0.1.0.md index 8dac167209..8d53f276bf 100644 --- a/docs/xap_0.1.0.md +++ b/docs/xap_0.1.0.md @@ -8,7 +8,7 @@ This document describes the requirements of the QMK XAP ("extensible application | Name | Definition | | -- | -- | -| _struct{}_ | A structure of data, packing different objects together. Data is "compacted" -- there are no padding bytes between fields. Equivalent to a C-style `struct`. The order in which they're defined matches the order of the data in the response packet. | +| _struct{}_ | A structure of data, packing different objects together. Data is "compacted" -- there are no padding bytes between fields. Equivalent to a packed C-style `struct`. The order in which they're defined matches the order of the data in the response packet. | | _type[n]_ | An array of `type`, with array extent of `N` -- e.g. `u8[2]` signifies two consecutive octets. | | _u16_ | An unsigned 16-bit integral, commonly seen as `uint16_t` from _stdint.h_. | | _u32_ | An unsigned 32-bit integral, commonly seen as `uint32_t` from _stdint.h_. | diff --git a/docs/xap_0.2.0.md b/docs/xap_0.2.0.md index 8dac167209..8d53f276bf 100644 --- a/docs/xap_0.2.0.md +++ b/docs/xap_0.2.0.md @@ -8,7 +8,7 @@ This document describes the requirements of the QMK XAP ("extensible application | Name | Definition | | -- | -- | -| _struct{}_ | A structure of data, packing different objects together. Data is "compacted" -- there are no padding bytes between fields. Equivalent to a C-style `struct`. The order in which they're defined matches the order of the data in the response packet. | +| _struct{}_ | A structure of data, packing different objects together. Data is "compacted" -- there are no padding bytes between fields. Equivalent to a packed C-style `struct`. The order in which they're defined matches the order of the data in the response packet. | | _type[n]_ | An array of `type`, with array extent of `N` -- e.g. `u8[2]` signifies two consecutive octets. | | _u16_ | An unsigned 16-bit integral, commonly seen as `uint16_t` from _stdint.h_. | | _u32_ | An unsigned 32-bit integral, commonly seen as `uint32_t` from _stdint.h_. | From 1e723e66475324c800744739bf19153d561ddd67 Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Mon, 14 Feb 2022 09:46:17 +1100 Subject: [PATCH 05/77] Output logging. --- builddefs/build_keyboard.mk | 44 ++++++++++++------------------------ builddefs/common_features.mk | 4 ++++ builddefs/message.mk | 1 + builddefs/xap.mk | 28 +++++++++++++++++++++++ 4 files changed, 48 insertions(+), 29 deletions(-) create mode 100644 builddefs/xap.mk diff --git a/builddefs/build_keyboard.mk b/builddefs/build_keyboard.mk index 3e9e4bcb78..44acd964cc 100644 --- a/builddefs/build_keyboard.mk +++ b/builddefs/build_keyboard.mk @@ -159,10 +159,14 @@ ifneq ("$(wildcard $(KEYMAP_JSON))", "") # Add rules to generate the keymap files - indentation here is important $(KEYMAP_OUTPUT)/src/keymap.c: $(KEYMAP_JSON) - $(QMK_BIN) json2c --quiet --output $(KEYMAP_C) $(KEYMAP_JSON) + @$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD) + $(eval CMD=$(QMK_BIN) json2c --quiet --output $(KEYMAP_C) $(KEYMAP_JSON)) + @$(BUILD_CMD) $(KEYMAP_OUTPUT)/src/config.h: $(KEYMAP_JSON) - $(QMK_BIN) generate-config-h --quiet --keyboard $(KEYBOARD) --keymap $(KEYMAP) --output $(KEYMAP_H) + @$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD) + $(eval CMD=$(QMK_BIN) generate-config-h --quiet --keyboard $(KEYBOARD) --keymap $(KEYMAP) --output $(KEYMAP_H)) + @$(BUILD_CMD) generated-files: $(KEYMAP_OUTPUT)/src/config.h $(KEYMAP_OUTPUT)/src/keymap.c @@ -326,13 +330,19 @@ endif CONFIG_H += $(KEYBOARD_OUTPUT)/src/info_config.h $(KEYBOARD_OUTPUT)/src/layouts.h $(KEYBOARD_OUTPUT)/src/info_config.h: $(INFO_JSON_FILES) - $(QMK_BIN) generate-config-h --quiet --keyboard $(KEYBOARD) --output $(KEYBOARD_OUTPUT)/src/info_config.h + @$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD) + $(eval CMD=$(QMK_BIN) generate-config-h --quiet --keyboard $(KEYBOARD) --output $(KEYBOARD_OUTPUT)/src/info_config.h) + @$(BUILD_CMD) $(KEYBOARD_OUTPUT)/src/default_keyboard.h: $(INFO_JSON_FILES) - $(QMK_BIN) generate-keyboard-h --quiet --keyboard $(KEYBOARD) --output $(KEYBOARD_OUTPUT)/src/default_keyboard.h + @$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD) + $(eval CMD=$(QMK_BIN) generate-keyboard-h --quiet --keyboard $(KEYBOARD) --output $(KEYBOARD_OUTPUT)/src/default_keyboard.h) + @$(BUILD_CMD) $(KEYBOARD_OUTPUT)/src/layouts.h: $(INFO_JSON_FILES) - $(QMK_BIN) generate-layouts --quiet --keyboard $(KEYBOARD) --output $(KEYBOARD_OUTPUT)/src/layouts.h + @$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD) + $(eval CMD=$(QMK_BIN) generate-layouts --quiet --keyboard $(KEYBOARD) --output $(KEYBOARD_OUTPUT)/src/layouts.h) + @$(BUILD_CMD) generated-files: $(KEYBOARD_OUTPUT)/src/info_config.h $(KEYBOARD_OUTPUT)/src/default_keyboard.h $(KEYBOARD_OUTPUT)/src/layouts.h @@ -399,30 +409,6 @@ VPATH += $(KEYBOARD_OUTPUT)/src VPATH += $(KEYMAP_OUTPUT)/src include $(BUILDDEFS_PATH)/common_features.mk - -# XAP embedded info.json -ifeq ($(strip $(XAP_ENABLE)), yes) - -$(KEYMAP_OUTPUT)/src/info_json_gz.h: $(INFO_JSON_FILES) - mkdir -p $(KEYMAP_OUTPUT)/src - $(QMK_BIN) info -f json -kb $(KEYBOARD) -km $(KEYMAP) | gzip -c9 > $(KEYMAP_OUTPUT)/src/info.json.gz - cd $(KEYMAP_OUTPUT)/src >/dev/null 2>&1 \ - && xxd -i info.json.gz info_json_gz.h \ - && cd - >/dev/null 2>&1 - -XAP_FILES := $(shell ls -1 data/xap/* | sort | xargs echo) - -$(KEYMAP_OUTPUT)/src/xap_generated.inl: $(XAP_FILES) - $(QMK_BIN) xap-generate-qmk-inc -o "$(KEYMAP_OUTPUT)/src/xap_generated.inl" - -$(KEYMAP_OUTPUT)/src/xap_generated.h: $(XAP_FILES) - $(QMK_BIN) xap-generate-qmk-h -o "$(KEYMAP_OUTPUT)/src/xap_generated.h" -kb $(KEYBOARD) - -generated-files: $(KEYMAP_OUTPUT)/src/info_json_gz.h $(KEYMAP_OUTPUT)/src/xap_generated.inl $(KEYMAP_OUTPUT)/src/xap_generated.h - -VPATH += $(KEYMAP_OUTPUT)/src -endif - include $(BUILDDEFS_PATH)/generic_features.mk include $(TMK_PATH)/protocol.mk include $(PLATFORM_PATH)/common.mk diff --git a/builddefs/common_features.mk b/builddefs/common_features.mk index cbff9fa4fa..63a5d57ec2 100644 --- a/builddefs/common_features.mk +++ b/builddefs/common_features.mk @@ -45,6 +45,10 @@ else ifeq ($(strip $(DEBUG_MATRIX_SCAN_RATE_ENABLE)), api) OPT_DEFS += -DDEBUG_MATRIX_SCAN_RATE endif +ifeq ($(strip $(XAP_ENABLE)), yes) + include $(BUILDDEFS_PATH)/xap.mk +endif + AUDIO_ENABLE ?= no ifeq ($(strip $(AUDIO_ENABLE)), yes) ifeq ($(PLATFORM),CHIBIOS) diff --git a/builddefs/message.mk b/builddefs/message.mk index d441f560be..07d0e07ce8 100644 --- a/builddefs/message.mk +++ b/builddefs/message.mk @@ -63,6 +63,7 @@ MSG_COMPILING_CXX = Compiling: MSG_ASSEMBLING = Assembling: MSG_CLEANING = Cleaning project: MSG_CREATING_LIBRARY = Creating library: +MSG_GENERATING = Generating: MSG_SUBMODULE_DIRTY = $(WARN_COLOR)WARNING:$(NO_COLOR) Some git submodules are out of date or modified.\n\ Please consider running $(BOLD)make git-submodule$(NO_COLOR).\n\n MSG_NO_CMP = $(ERROR_COLOR)Error:$(NO_COLOR)$(BOLD) cmp command not found, please install diffutils\n$(NO_COLOR) diff --git a/builddefs/xap.mk b/builddefs/xap.mk new file mode 100644 index 0000000000..c2ccf14541 --- /dev/null +++ b/builddefs/xap.mk @@ -0,0 +1,28 @@ +# Copyright 2022 Nick Brassel (@tzarc) +# SPDX-License-Identifier: GPL-2.0-or-later + +# XAP embedded info.json +$(KEYMAP_OUTPUT)/src/info_json_gz.h: $(INFO_JSON_FILES) + @$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD) + mkdir -p $(KEYMAP_OUTPUT)/src + $(eval CMD=$(QMK_BIN) info -f json -kb $(KEYBOARD) -km $(KEYMAP) | gzip -c9 > $(KEYMAP_OUTPUT)/src/info.json.gz \ + && cd $(KEYMAP_OUTPUT)/src >/dev/null 2>&1 \ + && xxd -i info.json.gz info_json_gz.h \ + && cd - >/dev/null 2>&1) + @$(BUILD_CMD) + +XAP_FILES := $(shell ls -1 data/xap/* | sort | xargs echo) + +$(KEYMAP_OUTPUT)/src/xap_generated.inl: $(XAP_FILES) + @$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD) + $(eval CMD=$(QMK_BIN) xap-generate-qmk-inc -o "$(KEYMAP_OUTPUT)/src/xap_generated.inl") + @$(BUILD_CMD) + +$(KEYMAP_OUTPUT)/src/xap_generated.h: $(XAP_FILES) + @$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD) + $(eval CMD=$(QMK_BIN) xap-generate-qmk-h -o "$(KEYMAP_OUTPUT)/src/xap_generated.h" -kb $(KEYBOARD)) + @$(BUILD_CMD) + +generated-files: $(KEYMAP_OUTPUT)/src/info_json_gz.h $(KEYMAP_OUTPUT)/src/xap_generated.inl $(KEYMAP_OUTPUT)/src/xap_generated.h + +VPATH += $(KEYMAP_OUTPUT)/src From 69e9c80ec3e1af38a8a88b44737a138b39c45d21 Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Tue, 15 Feb 2022 05:19:13 +1100 Subject: [PATCH 06/77] Reworked docs rendering using jinja2. --- data/templates/xap/docs/docs.md.j2 | 7 ++ data/templates/xap/docs/response_flags.md.j2 | 9 +++ .../templates/xap/docs/term_definitions.md.j2 | 5 ++ data/templates/xap/docs/type_docs.md.j2 | 5 ++ data/xap/xap_0.0.1.hjson | 6 +- docs/xap_0.0.1.md | 7 +- docs/xap_0.1.0.md | 13 +-- docs/xap_0.2.0.md | 13 +-- lib/python/qmk/cli/__init__.py | 3 +- lib/python/qmk/xap/common.py | 15 +++- lib/python/qmk/xap/gen_docs/generator.py | 80 ++----------------- requirements.txt | 1 + 12 files changed, 70 insertions(+), 94 deletions(-) create mode 100644 data/templates/xap/docs/docs.md.j2 create mode 100644 data/templates/xap/docs/response_flags.md.j2 create mode 100644 data/templates/xap/docs/term_definitions.md.j2 create mode 100644 data/templates/xap/docs/type_docs.md.j2 diff --git a/data/templates/xap/docs/docs.md.j2 b/data/templates/xap/docs/docs.md.j2 new file mode 100644 index 0000000000..7d6225ff7b --- /dev/null +++ b/data/templates/xap/docs/docs.md.j2 @@ -0,0 +1,7 @@ +{%- for item in xap.documentation.order -%} +{%- if not item[0:1] == '!' -%} +{{ xap.documentation.get(item) }} +{% else %} +{%- include item[1:] %} +{% endif %} +{% endfor %} \ No newline at end of file diff --git a/data/templates/xap/docs/response_flags.md.j2 b/data/templates/xap/docs/response_flags.md.j2 new file mode 100644 index 0000000000..852db16fd9 --- /dev/null +++ b/data/templates/xap/docs/response_flags.md.j2 @@ -0,0 +1,9 @@ +|{% for bitnum, bitinfo in xap.response_flags.bits | dictsort | reverse %} Bit {{ bitnum }} |{% endfor %} +|{% for bitnum, bitinfo in xap.response_flags.bits | dictsort | reverse %} -- |{% endfor %} +|{% for bitnum, bitinfo in xap.response_flags.bits | dictsort | reverse %} `{{ bitinfo.define }}` |{%- endfor %} + +{% for bitnum, bitinfo in xap.response_flags.bits | dictsort | reverse %} +{%- if bitinfo.define != "-" -%} +* Bit {{ bitnum }} (`{{ bitinfo.define }}`): {{ bitinfo.description }} +{% endif %} +{%- endfor %} \ No newline at end of file diff --git a/data/templates/xap/docs/term_definitions.md.j2 b/data/templates/xap/docs/term_definitions.md.j2 new file mode 100644 index 0000000000..8afdb9c3e2 --- /dev/null +++ b/data/templates/xap/docs/term_definitions.md.j2 @@ -0,0 +1,5 @@ +| Name | Definition | +| -- | -- | +{%- for type, definition in xap.term_definitions | dictsort %} +| _{{ type }}_ | {{ definition }} | +{%- endfor %} \ No newline at end of file diff --git a/data/templates/xap/docs/type_docs.md.j2 b/data/templates/xap/docs/type_docs.md.j2 new file mode 100644 index 0000000000..787d942853 --- /dev/null +++ b/data/templates/xap/docs/type_docs.md.j2 @@ -0,0 +1,5 @@ +| Name | Definition | +| -- | -- | +{%- for type, definition in xap.type_docs | dictsort %} +| _{{ type }}_ | {{ definition }} | +{%- endfor %} \ No newline at end of file diff --git a/data/xap/xap_0.0.1.hjson b/data/xap/xap_0.0.1.hjson index 34fd21d623..9b19526244 100755 --- a/data/xap/xap_0.0.1.hjson +++ b/data/xap/xap_0.0.1.hjson @@ -9,13 +9,13 @@ order: [ page_header type_docs - !type_docs! + !type_docs.md.j2 term_definitions - !term_definitions! + !term_definitions.md.j2 request_response reserved_tokens response_flags - !response_flags! + !response_flags.md.j2 example_conversation ] diff --git a/docs/xap_0.0.1.md b/docs/xap_0.0.1.md index ff45f1ae45..336176c33c 100644 --- a/docs/xap_0.0.1.md +++ b/docs/xap_0.0.1.md @@ -41,10 +41,11 @@ This token is followed by a `u8` signifying the length of data in the request. Response messages will always be prefixed by the originating request _token_, directly followed by that request's _response flags_, then the response payload length: | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 | -|--|--|--|--|--|--|--|--| -| - | - | - | - | - | - | - | Success | +| -- | -- | -- | -- | -- | -- | -- | -- | +| `-` | `-` | `-` | `-` | `-` | `-` | `-` | `SUCCESS` | + +* Bit 0 (`SUCCESS`): When this bit is set, the request was successfully handled. If not set, all payload data should be disregarded, and the request retried if appropriate (with a new token). -* `Bit 0`: When this bit is set, the request was successfully handled. If not set, all payload data should be disregarded, and the request retried if appropriate (with a new token). ### Example "conversation": diff --git a/docs/xap_0.1.0.md b/docs/xap_0.1.0.md index 8d53f276bf..f3911d7220 100644 --- a/docs/xap_0.1.0.md +++ b/docs/xap_0.1.0.md @@ -50,13 +50,14 @@ Any request will generate at least one corresponding response, with the exceptio Response messages will always be prefixed by the originating request _token_, directly followed by that request's _response flags_, then the response payload length: | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 | -|--|--|--|--|--|--|--|--| -| Unlocked | Unlocking | - | - | - | - | Secure Failure | Success | +| -- | -- | -- | -- | -- | -- | -- | -- | +| `UNLOCKED` | `UNLOCK_IN_PROGRESS` | `-` | `-` | `-` | `-` | `SECURE_FAILURE` | `SUCCESS` | + +* Bit 7 (`UNLOCKED`): When this bit is set, an _unlock sequence_ has completed, and _secure routes_ may be invoked. +* Bit 6 (`UNLOCK_IN_PROGRESS`): When this bit is set, an _unlock sequence_ is in progress. +* Bit 1 (`SECURE_FAILURE`): When this bit is set, the requested _route_ was marked _secure_ but an _unlock sequence_ has not completed. +* Bit 0 (`SUCCESS`): When this bit is set, the request was successfully handled. If not set, all payload data should be disregarded, and the request retried if appropriate (with a new token). -* `Bit 7`: When this bit is set, an _unlock sequence_ has completed, and _secure routes_ may be invoked. -* `Bit 6`: When this bit is set, an _unlock sequence_ is in progress. -* `Bit 1`: When this bit is set, the requested _route_ was marked _secure_ but an _unlock sequence_ has not completed. -* `Bit 0`: When this bit is set, the request was successfully handled. If not set, all payload data should be disregarded, and the request retried if appropriate (with a new token). ### Example "conversation": diff --git a/docs/xap_0.2.0.md b/docs/xap_0.2.0.md index 8d53f276bf..f3911d7220 100644 --- a/docs/xap_0.2.0.md +++ b/docs/xap_0.2.0.md @@ -50,13 +50,14 @@ Any request will generate at least one corresponding response, with the exceptio Response messages will always be prefixed by the originating request _token_, directly followed by that request's _response flags_, then the response payload length: | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 | -|--|--|--|--|--|--|--|--| -| Unlocked | Unlocking | - | - | - | - | Secure Failure | Success | +| -- | -- | -- | -- | -- | -- | -- | -- | +| `UNLOCKED` | `UNLOCK_IN_PROGRESS` | `-` | `-` | `-` | `-` | `SECURE_FAILURE` | `SUCCESS` | + +* Bit 7 (`UNLOCKED`): When this bit is set, an _unlock sequence_ has completed, and _secure routes_ may be invoked. +* Bit 6 (`UNLOCK_IN_PROGRESS`): When this bit is set, an _unlock sequence_ is in progress. +* Bit 1 (`SECURE_FAILURE`): When this bit is set, the requested _route_ was marked _secure_ but an _unlock sequence_ has not completed. +* Bit 0 (`SUCCESS`): When this bit is set, the request was successfully handled. If not set, all payload data should be disregarded, and the request retried if appropriate (with a new token). -* `Bit 7`: When this bit is set, an _unlock sequence_ has completed, and _secure routes_ may be invoked. -* `Bit 6`: When this bit is set, an _unlock sequence_ is in progress. -* `Bit 1`: When this bit is set, the requested _route_ was marked _secure_ but an _unlock sequence_ has not completed. -* `Bit 0`: When this bit is set, the request was successfully handled. If not set, all payload data should be disregarded, and the request retried if appropriate (with a new token). ### Example "conversation": diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py index 5bc809d503..3197d9d7f2 100644 --- a/lib/python/qmk/cli/__init__.py +++ b/lib/python/qmk/cli/__init__.py @@ -16,7 +16,8 @@ import_names = { # A mapping of package name to importable name 'pep8-naming': 'pep8ext_naming', 'pyusb': 'usb.core', - 'qmk-dotty-dict': 'dotty_dict' + 'qmk-dotty-dict': 'dotty_dict', + 'Jinja2': 'jinja2' } safe_commands = [ diff --git a/lib/python/qmk/xap/common.py b/lib/python/qmk/xap/common.py index bec82d727b..d1e0d5dbb0 100755 --- a/lib/python/qmk/xap/common.py +++ b/lib/python/qmk/xap/common.py @@ -1,10 +1,21 @@ """This script handles the XAP protocol data files. """ -import re +import os import hjson +from jinja2 import Environment, FileSystemLoader, select_autoescape +from qmk.constants import QMK_FIRMWARE from typing import OrderedDict -from qmk.constants import QMK_FIRMWARE + +def _get_jinja2_env(data_templates_xap_subdir: str): + templates_dir = os.path.join(QMK_FIRMWARE, 'data', 'templates', 'xap', data_templates_xap_subdir) + j2 = Environment(loader=FileSystemLoader(templates_dir), autoescape=select_autoescape()) + return j2 + + +def render_xap_output(data_templates_xap_subdir, file_to_render, defs): + j2 = _get_jinja2_env(data_templates_xap_subdir) + return j2.get_template(file_to_render).render(xap=defs, xap_str=hjson.dumps(defs)) def _merge_ordered_dicts(dicts): diff --git a/lib/python/qmk/xap/gen_docs/generator.py b/lib/python/qmk/xap/gen_docs/generator.py index 3e0ce52896..bcb90351e9 100755 --- a/lib/python/qmk/xap/gen_docs/generator.py +++ b/lib/python/qmk/xap/gen_docs/generator.py @@ -2,64 +2,7 @@ """ import hjson from qmk.constants import QMK_FIRMWARE -from qmk.xap.common import get_xap_definition_files, update_xap_definitions - - -def _update_type_docs(overall): - defs = overall['type_docs'] - - type_docs = [] - for (k, v) in sorted(defs.items(), key=lambda x: x[0]): - type_docs.append(f'| _{k}_ | {v} |') - - desc_str = "\n".join(type_docs) - - overall['documentation']['!type_docs!'] = f'''\ -| Name | Definition | -| -- | -- | -{desc_str} -''' - - -def _update_term_definitions(overall): - defs = overall['term_definitions'] - - term_descriptions = [] - for (k, v) in sorted(defs.items(), key=lambda x: x[0]): - term_descriptions.append(f'| _{k}_ | {v} |') - - desc_str = "\n".join(term_descriptions) - - overall['documentation']['!term_definitions!'] = f'''\ -| Name | Definition | -| -- | -- | -{desc_str} -''' - - -def _update_response_flags(overall): - flags = overall['response_flags']['bits'] - for n in range(0, 8): - if str(n) not in flags: - flags[str(n)] = {"name": "-", "description": "-"} - - header = '| ' + " | ".join([f'Bit {n}' for n in range(7, -1, -1)]) + ' |' - dividers = '|' + "|".join(['--' for n in range(7, -1, -1)]) + '|' - bit_names = '| ' + " | ".join([flags[str(n)]['name'] for n in range(7, -1, -1)]) + ' |' - - bit_descriptions = '' - for n in range(7, -1, -1): - bit_desc = flags[str(n)] - if bit_desc['name'] != '-': - desc = bit_desc['description'] - bit_descriptions = bit_descriptions + f'\n* `Bit {n}`: {desc}' - - overall['documentation']['!response_flags!'] = f'''\ -{header} -{dividers} -{bit_names} -{bit_descriptions} -''' +from qmk.xap.common import get_xap_definition_files, update_xap_definitions, render_xap_output def generate_docs(): @@ -69,27 +12,18 @@ def generate_docs(): overall = None for file in get_xap_definition_files(): - overall = update_xap_definitions(overall, hjson.load(file.open(encoding='utf-8'))) - try: - if 'type_docs' in overall: - _update_type_docs(overall) - if 'term_definitions' in overall: - _update_term_definitions(overall) - if 'response_flags' in overall: - _update_response_flags(overall) - except: - print(hjson.dumps(overall)) - exit(1) + # Inject dummy bits for unspecified response flags + for n in range(0, 8): + if str(n) not in overall['response_flags']['bits']: + overall['response_flags']['bits'][str(n)] = {'name': '', 'description': '', 'define': '-'} output_doc = QMK_FIRMWARE / "docs" / f"{file.stem}.md" docs_list.append(output_doc) - + output = render_xap_output('docs', 'docs.md.j2', overall) with open(output_doc, "w", encoding='utf-8') as out_file: - for e in overall['documentation']['order']: - out_file.write(overall['documentation'][e].strip()) - out_file.write('\n\n') + out_file.write(output) output_doc = QMK_FIRMWARE / "docs" / f"xap_protocol.md" with open(output_doc, "w", encoding='utf-8') as out_file: diff --git a/requirements.txt b/requirements.txt index 6357b4e2eb..7cf2c0b128 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,7 @@ colorama fnvhash hid hjson +Jinja2 jsonschema>=3 milc>=1.4.2 pygments From 6c7afbb859a9b3557268802ac2fcea2450260b5c Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Wed, 16 Feb 2022 10:53:35 +1100 Subject: [PATCH 07/77] Migrate XAP docs generator into CLI now that most logic is in Jinja2 files. --- lib/python/qmk/cli/xap/generate_docs.py | 32 ++++++++++++++++++-- lib/python/qmk/xap/gen_docs/__init__.py | 0 lib/python/qmk/xap/gen_docs/generator.py | 37 ------------------------ 3 files changed, 30 insertions(+), 39 deletions(-) delete mode 100644 lib/python/qmk/xap/gen_docs/__init__.py delete mode 100755 lib/python/qmk/xap/gen_docs/generator.py diff --git a/lib/python/qmk/cli/xap/generate_docs.py b/lib/python/qmk/cli/xap/generate_docs.py index bf2fdb86bb..d77a418bf7 100755 --- a/lib/python/qmk/cli/xap/generate_docs.py +++ b/lib/python/qmk/cli/xap/generate_docs.py @@ -1,11 +1,39 @@ """This script generates the XAP protocol documentation. """ +import hjson +from qmk.constants import QMK_FIRMWARE +from qmk.xap.common import get_xap_definition_files, update_xap_definitions, render_xap_output from milc import cli -from qmk.xap.gen_docs.generator import generate_docs @cli.subcommand('Generates the XAP protocol documentation.', hidden=False if cli.config.user.developer else True) def xap_generate_docs(cli): """Generates the XAP protocol documentation by merging the definitions files, and producing the corresponding Markdown document under `/docs/`. """ - generate_docs() \ No newline at end of file + docs_list = [] + + overall = None + for file in get_xap_definition_files(): + overall = update_xap_definitions(overall, hjson.load(file.open(encoding='utf-8'))) + + # Inject dummy bits for unspecified response flags + for n in range(0, 8): + if str(n) not in overall['response_flags']['bits']: + overall['response_flags']['bits'][str(n)] = {'name': '', 'description': '', 'define': '-'} + + output_doc = QMK_FIRMWARE / "docs" / f"{file.stem}.md" + docs_list.append(output_doc) + output = render_xap_output('docs', 'docs.md.j2', overall) + with open(output_doc, "w", encoding='utf-8') as out_file: + out_file.write(output) + + output_doc = QMK_FIRMWARE / "docs" / f"xap_protocol.md" + with open(output_doc, "w", encoding='utf-8') as out_file: + out_file.write('''\ +# XAP Protocol Reference + +''') + + for file in reversed(sorted(docs_list)): + ver = file.stem[4:] + out_file.write(f'* [XAP Version {ver}]({file.name})\n') diff --git a/lib/python/qmk/xap/gen_docs/__init__.py b/lib/python/qmk/xap/gen_docs/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/lib/python/qmk/xap/gen_docs/generator.py b/lib/python/qmk/xap/gen_docs/generator.py deleted file mode 100755 index bcb90351e9..0000000000 --- a/lib/python/qmk/xap/gen_docs/generator.py +++ /dev/null @@ -1,37 +0,0 @@ -"""This script generates the XAP protocol documentation. -""" -import hjson -from qmk.constants import QMK_FIRMWARE -from qmk.xap.common import get_xap_definition_files, update_xap_definitions, render_xap_output - - -def generate_docs(): - """Generates the XAP protocol documentation by merging the definitions files, and producing the corresponding Markdown document under `/docs/`. - """ - docs_list = [] - - overall = None - for file in get_xap_definition_files(): - overall = update_xap_definitions(overall, hjson.load(file.open(encoding='utf-8'))) - - # Inject dummy bits for unspecified response flags - for n in range(0, 8): - if str(n) not in overall['response_flags']['bits']: - overall['response_flags']['bits'][str(n)] = {'name': '', 'description': '', 'define': '-'} - - output_doc = QMK_FIRMWARE / "docs" / f"{file.stem}.md" - docs_list.append(output_doc) - output = render_xap_output('docs', 'docs.md.j2', overall) - with open(output_doc, "w", encoding='utf-8') as out_file: - out_file.write(output) - - output_doc = QMK_FIRMWARE / "docs" / f"xap_protocol.md" - with open(output_doc, "w", encoding='utf-8') as out_file: - out_file.write('''\ -# XAP Protocol Reference - -''') - - for file in reversed(sorted(docs_list)): - ver = file.stem[4:] - out_file.write(f'* [XAP Version {ver}]({file.name})\n') From c3ac89d1c97640311350203b0d1b85557daca8b1 Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Wed, 9 Mar 2022 20:01:20 +1100 Subject: [PATCH 08/77] `qmk format-c`, `qmk format-python` --- lib/python/qmk/xap/gen_firmware/inline_generator.py | 2 -- quantum/xap/xap.c | 6 ++++-- quantum/xap/xap_handlers.c | 8 ++++++-- tmk_core/protocol/lufa/lufa.c | 6 +++--- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/python/qmk/xap/gen_firmware/inline_generator.py b/lib/python/qmk/xap/gen_firmware/inline_generator.py index 756b0e8666..5d8f10a12b 100755 --- a/lib/python/qmk/xap/gen_firmware/inline_generator.py +++ b/lib/python/qmk/xap/gen_firmware/inline_generator.py @@ -1,7 +1,5 @@ """This script generates the XAP protocol generated header to be compiled into QMK. """ -import pyhash - from qmk.casing import to_snake from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE from qmk.xap.common import latest_xap_defs, route_conditions diff --git a/quantum/xap/xap.c b/quantum/xap/xap.c index df25f799d5..185213c833 100644 --- a/quantum/xap/xap.c +++ b/quantum/xap/xap.c @@ -22,7 +22,7 @@ #define QSTR(z) QSTR2(z) typedef enum xap_route_type_t { - XAP_UNUSED = 0, // "Unused" needs to be zero -- undefined routes (through preprocessor) will be skipped + XAP_UNUSED = 0, // "Unused" needs to be zero -- undefined routes (through preprocessor) will be skipped XAP_ROUTE, XAP_EXECUTE, XAP_VALUE, @@ -113,4 +113,6 @@ void xap_execute_route(xap_token_t token, const xap_route_t *routes, size_t max_ xap_respond_failure(token, XAP_RESPONSE_FLAG_FAILED); } -void xap_receive(xap_token_t token, const uint8_t *data, size_t length) { xap_execute_route(token, xap_route_table, sizeof(xap_route_table) / sizeof(xap_route_t), data, length); } +void xap_receive(xap_token_t token, const uint8_t *data, size_t length) { + xap_execute_route(token, xap_route_table, sizeof(xap_route_table) / sizeof(xap_route_t), data, length); +} diff --git a/quantum/xap/xap_handlers.c b/quantum/xap/xap_handlers.c index 7c3d8127ce..492d3d25bc 100644 --- a/quantum/xap/xap_handlers.c +++ b/quantum/xap/xap_handlers.c @@ -18,7 +18,9 @@ #include #include -void xap_respond_failure(xap_token_t token, xap_response_flags_t response_flags) { xap_send(token, response_flags, NULL, 0); } +void xap_respond_failure(xap_token_t token, xap_response_flags_t response_flags) { + xap_send(token, response_flags, NULL, 0); +} bool xap_respond_data(xap_token_t token, const void *data, size_t length) { xap_send(token, XAP_RESPONSE_FLAG_SUCCESS, data, length); @@ -36,4 +38,6 @@ bool xap_respond_u32(xap_token_t token, uint32_t value) { return true; } -uint32_t xap_route_qmk_ffffffffffffffff_getter(void) { return 0x12345678; } +uint32_t xap_route_qmk_ffffffffffffffff_getter(void) { + return 0x12345678; +} diff --git a/tmk_core/protocol/lufa/lufa.c b/tmk_core/protocol/lufa/lufa.c index 47c3d30c3c..0dcf1036c4 100644 --- a/tmk_core/protocol/lufa/lufa.c +++ b/tmk_core/protocol/lufa/lufa.c @@ -293,7 +293,7 @@ static void xap_task(void) { } } } -#endif // XAP_ENABLE +#endif // XAP_ENABLE /******************************************************************************* * Console @@ -561,7 +561,7 @@ void EVENT_USB_Device_ConfigurationChanged(void) { /* Setup XAP endpoints */ ConfigSuccess &= Endpoint_ConfigureEndpoint((XAP_IN_EPNUM | ENDPOINT_DIR_IN), EP_TYPE_INTERRUPT, XAP_EPSIZE, 1); ConfigSuccess &= Endpoint_ConfigureEndpoint((XAP_OUT_EPNUM | ENDPOINT_DIR_OUT), EP_TYPE_INTERRUPT, XAP_EPSIZE, 1); -#endif // XAP_ENABLE +#endif // XAP_ENABLE #ifdef CONSOLE_ENABLE /* Setup console endpoint */ @@ -1189,7 +1189,7 @@ void protocol_post_task(void) { #endif #ifdef XAP_ENABLE - xap_task(); + xap_task(); #endif #if !defined(INTERRUPT_CONTROL_ENDPOINT) From 2b4724bd83c13c0abfe867ae69334a756babeaa9 Mon Sep 17 00:00:00 2001 From: zvecr Date: Tue, 15 Mar 2022 17:59:12 +0000 Subject: [PATCH 09/77] Convert info_json_gz.h generation to CLI --- builddefs/xap.mk | 6 +-- lib/python/qmk/cli/format/text.py | 10 +---- lib/python/qmk/cli/xap/generate_qmk.py | 57 +++++++++++++++++++++++++- lib/python/qmk/commands.py | 23 +++++++++++ 4 files changed, 82 insertions(+), 14 deletions(-) diff --git a/builddefs/xap.mk b/builddefs/xap.mk index c2ccf14541..e599c81221 100644 --- a/builddefs/xap.mk +++ b/builddefs/xap.mk @@ -4,11 +4,7 @@ # XAP embedded info.json $(KEYMAP_OUTPUT)/src/info_json_gz.h: $(INFO_JSON_FILES) @$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD) - mkdir -p $(KEYMAP_OUTPUT)/src - $(eval CMD=$(QMK_BIN) info -f json -kb $(KEYBOARD) -km $(KEYMAP) | gzip -c9 > $(KEYMAP_OUTPUT)/src/info.json.gz \ - && cd $(KEYMAP_OUTPUT)/src >/dev/null 2>&1 \ - && xxd -i info.json.gz info_json_gz.h \ - && cd - >/dev/null 2>&1) + $(eval CMD=$(QMK_BIN) xap-generate-info-h -o "$(KEYMAP_OUTPUT)/src/info_json_gz.h" -kb $(KEYBOARD) -km $(KEYMAP)) @$(BUILD_CMD) XAP_FILES := $(shell ls -1 data/xap/* | sort | xargs echo) diff --git a/lib/python/qmk/cli/format/text.py b/lib/python/qmk/cli/format/text.py index 6dd4511896..fa86b4379a 100644 --- a/lib/python/qmk/cli/format/text.py +++ b/lib/python/qmk/cli/format/text.py @@ -6,19 +6,13 @@ from subprocess import DEVNULL from milc import cli from qmk.path import normpath - - -def _get_chunks(it, size): - """Break down a collection into smaller parts - """ - it = iter(it) - return iter(lambda: tuple(islice(it, size)), ()) +from qmk.commands import get_chunks def dos2unix_run(files): """Spawn multiple dos2unix subprocess avoiding too long commands on formatting everything """ - for chunk in _get_chunks(files, 10): + for chunk in get_chunks(files, 10): dos2unix = cli.run(['dos2unix', *chunk]) if dos2unix.returncode: diff --git a/lib/python/qmk/cli/xap/generate_qmk.py b/lib/python/qmk/cli/xap/generate_qmk.py index 0cb2fec195..ff43e2863d 100755 --- a/lib/python/qmk/cli/xap/generate_qmk.py +++ b/lib/python/qmk/cli/xap/generate_qmk.py @@ -1,11 +1,19 @@ """This script generates the XAP protocol generated sources to be compiled into QMK firmware. """ +import json +import gzip + from milc import cli from qmk.path import normpath +from qmk.info import info_json +from qmk.commands import get_chunks, dump_lines +from qmk.keyboard import keyboard_completer, keyboard_folder from qmk.xap.gen_firmware.inline_generator import generate_inline from qmk.xap.gen_firmware.header_generator import generate_header +from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE + @cli.argument('-o', '--output', type=normpath, help='File to write to') @cli.subcommand('Generates the XAP protocol include.', hidden=False if cli.config.user.developer else True) @@ -16,9 +24,56 @@ def xap_generate_qmk_inc(cli): @cli.argument('-o', '--output', type=normpath, help='File to write to') -@cli.argument('-kb', '--keyboard', help='Name of the keyboard') +@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Name of the keyboard') @cli.subcommand('Generates the XAP protocol include.', hidden=False if cli.config.user.developer else True) def xap_generate_qmk_h(cli): """Generates the XAP protocol header file, generated during normal build. """ + # Determine our keyboard + if not cli.args.keyboard: + cli.log.error('Missing parameter: --keyboard') + cli.subcommands['xap-generate-qmk-h'].print_help() + return False generate_header(cli.args.output, cli.args.keyboard) + + +@cli.argument('-o', '--output', type=normpath, help='File to write to') +@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Name of the keyboard') +@cli.argument('-km', '--keymap', help='The keymap\'s name') +@cli.subcommand('Generates the XAP info.json payload include.', hidden=False if cli.config.user.developer else True) +def xap_generate_info_h(cli): + """Generates the XAP info.json payload header file, generated during normal build. + """ + # Determine our keyboard + if not cli.args.keyboard: + cli.log.error('Missing parameter: --keyboard') + cli.subcommands['xap-generate-info-h'].print_help() + return False + + # TODO: merge in keymap level content + # Build the info.json file + kb_info_json = info_json(cli.args.keyboard) + + # Minify + str_data = json.dumps(kb_info_json, separators=(',', ':')) + + # Compress + compressed = gzip.compress(str_data.encode("utf-8"), compresslevel=9) + + # split into lines to match xxd output + hex_array = ["0x{:02X}".format(b) for b in compressed] + data_len = len(hex_array) + + data = "" + for chunk in get_chunks(hex_array, 12): + data += f' {", ".join(chunk)},\n' + + lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once', ''] + + # Gen output file + lines.append('unsigned char info_json_gz[] = {') + lines.append(data) + lines.append('};') + lines.append(f'unsigned int info_json_gz_len = {data_len};') + + dump_lines(cli.args.output, lines) diff --git a/lib/python/qmk/commands.py b/lib/python/qmk/commands.py index 5216bcdba5..56b2ccc50c 100644 --- a/lib/python/qmk/commands.py +++ b/lib/python/qmk/commands.py @@ -6,6 +6,7 @@ import shutil from pathlib import Path from subprocess import DEVNULL from time import strftime +from itertools import islice from milc import cli import jsonschema @@ -360,3 +361,25 @@ def in_virtualenv(): """ active_prefix = getattr(sys, "base_prefix", None) or getattr(sys, "real_prefix", None) or sys.prefix return active_prefix != sys.prefix + + +def get_chunks(it, size): + """Break down a collection into smaller parts + """ + it = iter(it) + return iter(lambda: tuple(islice(it, size)), ()) + + +def dump_lines(output_file, lines): + """Handle dumping to stdout or file + Creates parent folders if required + """ + generated = '\n'.join(lines) + if output_file: + if output_file.name == '-': + print(generated) + else: + output_file.parent.mkdir(parents=True, exist_ok=True) + if output_file.exists(): + output_file.replace(output_file.parent / (output_file.name + '.bak')) + output_file.write_text(generated) From 7e8f0d49ea511385476746c977e7086e35fc4a55 Mon Sep 17 00:00:00 2001 From: zvecr Date: Tue, 15 Mar 2022 23:57:41 +0000 Subject: [PATCH 10/77] Temp bodge for unit tests? --- .github/workflows/unit_test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml index 26bcb2f511..726ce19f0c 100644 --- a/.github/workflows/unit_test.yml +++ b/.github/workflows/unit_test.yml @@ -26,5 +26,7 @@ jobs: - uses: actions/checkout@v2 with: submodules: recursive + - name: Install dependencies + run: pip3 install -r requirements-dev.txt - name: Run tests run: make test:all From 52d3b9dcc5b5342ae1a682da50406fcead2ee3cd Mon Sep 17 00:00:00 2001 From: zvecr Date: Wed, 16 Mar 2022 00:10:30 +0000 Subject: [PATCH 11/77] fix up for pytest --- lib/python/qmk/cli/format/text.py | 1 - lib/python/qmk/cli/xap/generate_docs.py | 2 +- lib/python/qmk/xap/gen_firmware/inline_generator.py | 8 ++++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/python/qmk/cli/format/text.py b/lib/python/qmk/cli/format/text.py index fa86b4379a..ff44cf2cd9 100644 --- a/lib/python/qmk/cli/format/text.py +++ b/lib/python/qmk/cli/format/text.py @@ -1,6 +1,5 @@ """Ensure text files have the proper line endings. """ -from itertools import islice from subprocess import DEVNULL from milc import cli diff --git a/lib/python/qmk/cli/xap/generate_docs.py b/lib/python/qmk/cli/xap/generate_docs.py index d77a418bf7..e399b6ddb1 100755 --- a/lib/python/qmk/cli/xap/generate_docs.py +++ b/lib/python/qmk/cli/xap/generate_docs.py @@ -27,7 +27,7 @@ def xap_generate_docs(cli): with open(output_doc, "w", encoding='utf-8') as out_file: out_file.write(output) - output_doc = QMK_FIRMWARE / "docs" / f"xap_protocol.md" + output_doc = QMK_FIRMWARE / "docs" / "xap_protocol.md" with open(output_doc, "w", encoding='utf-8') as out_file: out_file.write('''\ # XAP Protocol Reference diff --git a/lib/python/qmk/xap/gen_firmware/inline_generator.py b/lib/python/qmk/xap/gen_firmware/inline_generator.py index 5d8f10a12b..357a4ab722 100755 --- a/lib/python/qmk/xap/gen_firmware/inline_generator.py +++ b/lib/python/qmk/xap/gen_firmware/inline_generator.py @@ -66,7 +66,7 @@ def _append_routing_table_declaration(lines, container, container_id, route_stac for constant in container['return_constant']: lines.append(f' {constant},') - lines.append(f'}};') + lines.append('}};') elif container['return_type'] == 'string': constant = container['return_constant'] @@ -87,10 +87,10 @@ def _append_routing_table_declaration(lines, container, container_id, route_stac def _append_routing_table_entry_flags(lines, container, container_id, route_stack): is_secure = 1 if ('secure' in container and container['secure'] is True) else 0 - lines.append(f' .flags = {{') + lines.append(' .flags = {{') lines.append(f' .type = {_get_route_type(container)},') lines.append(f' .is_secure = {is_secure},') - lines.append(f' }},') + lines.append(' }},') def _append_routing_table_entry_route(lines, container, container_id, route_stack): @@ -145,7 +145,7 @@ def _append_routing_table_entry(lines, container, container_id, route_stack): if container['return_type'] == 'u32': _append_routing_table_entry_u32getter(lines, container, container_id, route_stack) - lines.append(f' }},') + lines.append(' }},') if condition: lines.append(f'#endif // {condition}') From 9fd4db1fc7f635afae796a126e99fcc29f86eddc Mon Sep 17 00:00:00 2001 From: zvecr Date: Wed, 16 Mar 2022 10:58:21 +0000 Subject: [PATCH 12/77] fix up for pytest - remove fstring escaping --- lib/python/qmk/xap/gen_firmware/inline_generator.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/python/qmk/xap/gen_firmware/inline_generator.py b/lib/python/qmk/xap/gen_firmware/inline_generator.py index 357a4ab722..a40e40ec98 100755 --- a/lib/python/qmk/xap/gen_firmware/inline_generator.py +++ b/lib/python/qmk/xap/gen_firmware/inline_generator.py @@ -66,7 +66,7 @@ def _append_routing_table_declaration(lines, container, container_id, route_stac for constant in container['return_constant']: lines.append(f' {constant},') - lines.append('}};') + lines.append('};') elif container['return_type'] == 'string': constant = container['return_constant'] @@ -87,10 +87,10 @@ def _append_routing_table_declaration(lines, container, container_id, route_stac def _append_routing_table_entry_flags(lines, container, container_id, route_stack): is_secure = 1 if ('secure' in container and container['secure'] is True) else 0 - lines.append(' .flags = {{') + lines.append(' .flags = {') lines.append(f' .type = {_get_route_type(container)},') lines.append(f' .is_secure = {is_secure},') - lines.append(' }},') + lines.append(' },') def _append_routing_table_entry_route(lines, container, container_id, route_stack): @@ -145,7 +145,7 @@ def _append_routing_table_entry(lines, container, container_id, route_stack): if container['return_type'] == 'u32': _append_routing_table_entry_u32getter(lines, container, container_id, route_stack) - lines.append(' }},') + lines.append(' },') if condition: lines.append(f'#endif // {condition}') From 31c486470591409767d797dba6ffcfe456ca1f13 Mon Sep 17 00:00:00 2001 From: zvecr Date: Wed, 16 Mar 2022 19:45:37 +0000 Subject: [PATCH 13/77] Crude CLI device discovery --- lib/python/qmk/cli/__init__.py | 1 + lib/python/qmk/cli/xap/__init__.py | 79 ++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py index 3197d9d7f2..f0e86dd124 100644 --- a/lib/python/qmk/cli/__init__.py +++ b/lib/python/qmk/cli/__init__.py @@ -70,6 +70,7 @@ subcommands = [ 'qmk.cli.new.keymap', 'qmk.cli.pyformat', 'qmk.cli.pytest', + 'qmk.cli.xap', 'qmk.cli.xap.generate_docs', 'qmk.cli.xap.generate_json', 'qmk.cli.xap.generate_qmk', diff --git a/lib/python/qmk/cli/xap/__init__.py b/lib/python/qmk/cli/xap/__init__.py index e69de29bb2..963617a6ee 100755 --- a/lib/python/qmk/cli/xap/__init__.py +++ b/lib/python/qmk/cli/xap/__init__.py @@ -0,0 +1,79 @@ +"""Interactions with compatible XAP devices +""" +import json +import random + +from milc import cli + + +def _is_xap_usage(x): + return x['usage_page'] == 0xFF51 and x['usage'] == 0x0058 + + +def _is_filtered_device(x): + name = "%04x:%04x" % (x['vendor_id'], x['product_id']) + return name.lower().startswith(cli.args.device.lower()) + + +def _search(): + devices = filter(_is_xap_usage, hid.enumerate()) + if cli.args.device: + devices = filter(_is_filtered_device, devices) + + return list(devices) + + +def _list_devices(): + """Dump out available devices + """ + cli.log.info('Available devices:') + devices = _search() + for dev in devices: + device = hid.Device(path=dev['path']) + + data = _query_device(device) + cli.log.info(" %04x:%04x %s %s [API:%s]", dev['vendor_id'], dev['product_id'], dev['manufacturer_string'], dev['product_string'], data['ver']) + + if cli.config.general.verbose: + # TODO: better formatting like "lsusb -v" + cli.log.info(" " + json.dumps(data)) + + +def _query_device(device): + # gen token + tok = random.getrandbits(16) + temp = tok.to_bytes(2, byteorder='big') + + # send with padding + padding = b"\x00" * 59 + device.write(temp + b'\x02\x00\x00' + padding) + + # get resp + array_alpha = device.read(8, 100) + # hex_string = " ".join("%02x" % b for b in array_alpha) + + # validate tok sent == resp + ver = "UNKNOWN" + if str(temp) == str(array_alpha[:2]): + # to BCD string + a = (array_alpha[7] << 24) + (array_alpha[6] << 16) + (array_alpha[5] << 8) + (array_alpha[4]) + ver = f'{a>>24}.{a>>16 & 0xFF}.{a & 0xFFFF}' + + return {'ver': ver} + + +@cli.argument('-d', '--device', help='device to select - uses format :.') +@cli.argument('-i', '--index', default=0, help='device index to select.') +@cli.argument('-l', '--list', arg_only=True, action='store_true', help='List available devices.') +@cli.subcommand('Acquire debugging information from usb XAP devices.', hidden=False if cli.config.user.developer else True) +def xap(cli): + """Acquire debugging information from XAP devices + """ + # Lazy load to avoid issues + global hid + import hid + + if cli.args.list: + return _list_devices() + + cli.log.warn("TODO: Device specific stuff") From 72602a344330b50cd746ca8d8b96eafd76fbf473 Mon Sep 17 00:00:00 2001 From: zvecr Date: Fri, 18 Mar 2022 01:53:39 +0000 Subject: [PATCH 14/77] Fixup after merge --- lib/python/qmk/commands.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/python/qmk/commands.py b/lib/python/qmk/commands.py index eec347d260..5def790644 100644 --- a/lib/python/qmk/commands.py +++ b/lib/python/qmk/commands.py @@ -4,6 +4,8 @@ import os import sys import shutil from pathlib import Path +from time import strftime +from itertools import islice from subprocess import DEVNULL from milc import cli @@ -96,7 +98,7 @@ def get_git_version(current_time=None, repo_dir='.', check_dir='.'): git_describe_cmd = ['git', 'describe', '--abbrev=6', '--dirty', '--always', '--tags'] if current_time is None: - current_time = strftime(time_fmt) + current_time = strftime('%Y-%m-%d-%H:%M:%S') if repo_dir != '.': repo_dir = Path('lib') / repo_dir From 5bb6173cc7c0a56c9baac401385e5ac14565738f Mon Sep 17 00:00:00 2001 From: zvecr Date: Fri, 18 Mar 2022 17:01:02 +0000 Subject: [PATCH 15/77] Fixup after merge --- lib/python/qmk/xap/gen_firmware/header_generator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/python/qmk/xap/gen_firmware/header_generator.py b/lib/python/qmk/xap/gen_firmware/header_generator.py index 24dc8d3450..ea299b18f5 100755 --- a/lib/python/qmk/xap/gen_firmware/header_generator.py +++ b/lib/python/qmk/xap/gen_firmware/header_generator.py @@ -3,7 +3,7 @@ import re from fnvhash import fnv1a_32 -from qmk.commands import get_git_version +from qmk.git import git_get_version from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE from qmk.xap.common import latest_xap_defs, route_conditions @@ -105,7 +105,7 @@ def generate_header(output_file, keyboard): prog = re.compile(r'^(\d+)\.(\d+)\.(\d+)') b = prog.match(xap_defs['version']) lines.append(f'#define XAP_BCD_VERSION 0x{int(b.group(1)):02d}{int(b.group(2)):02d}{int(b.group(3)):04d}ul') - b = prog.match(get_git_version()) + b = prog.match(git_get_version() or "") lines.append(f'#define QMK_BCD_VERSION 0x{int(b.group(1)):02d}{int(b.group(2)):02d}{int(b.group(3)):04d}ul') keyboard_id = fnv1a_32(bytes(keyboard, 'utf-8')) lines.append(f'#define XAP_KEYBOARD_IDENTIFIER 0x{keyboard_id:08X}ul') From 4d4b013e5bef5b011b2edd4c77958b50d355cd62 Mon Sep 17 00:00:00 2001 From: zvecr Date: Fri, 18 Mar 2022 17:03:54 +0000 Subject: [PATCH 16/77] Fixup after merge --- lib/python/qmk/commands.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/python/qmk/commands.py b/lib/python/qmk/commands.py index 7021e01b32..db1a12c4d9 100644 --- a/lib/python/qmk/commands.py +++ b/lib/python/qmk/commands.py @@ -3,6 +3,7 @@ import os import sys import shutil +from itertools import islice from pathlib import Path from milc import cli From aaf4fcbe5a69529d38d95121a072ce508425f596 Mon Sep 17 00:00:00 2001 From: zvecr Date: Fri, 18 Mar 2022 19:51:04 +0000 Subject: [PATCH 17/77] Merge chibios support --- tmk_core/protocol/chibios/chibios.c | 7 ++++ tmk_core/protocol/chibios/usb_main.c | 61 ++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/tmk_core/protocol/chibios/chibios.c b/tmk_core/protocol/chibios/chibios.c index c9a480c325..4f4ad2b655 100644 --- a/tmk_core/protocol/chibios/chibios.c +++ b/tmk_core/protocol/chibios/chibios.c @@ -74,6 +74,10 @@ void virtser_task(void); void raw_hid_task(void); #endif +#ifdef XAP_ENABLE +void xap_task(void); +#endif + #ifdef CONSOLE_ENABLE void console_task(void); #endif @@ -218,4 +222,7 @@ void protocol_post_task(void) { #ifdef RAW_ENABLE raw_hid_task(); #endif +#ifdef XAP_ENABLE + xap_task(); +#endif } diff --git a/tmk_core/protocol/chibios/usb_main.c b/tmk_core/protocol/chibios/usb_main.c index d9aa351ecb..a95ca79da9 100644 --- a/tmk_core/protocol/chibios/usb_main.c +++ b/tmk_core/protocol/chibios/usb_main.c @@ -53,6 +53,10 @@ extern keymap_config_t keymap_config; # include "joystick.h" #endif +#ifdef XAP_ENABLE +# include "xap.h" +#endif + /* --------------------------------------------------------- * Global interface variables and declarations * --------------------------------------------------------- @@ -313,6 +317,9 @@ typedef struct { #ifdef RAW_ENABLE usb_driver_config_t raw_driver; #endif +#ifdef XAP_ENABLE + usb_driver_config_t xap_driver; +#endif #ifdef MIDI_ENABLE usb_driver_config_t midi_driver; #endif @@ -346,6 +353,14 @@ static usb_driver_configs_t drivers = { .raw_driver = QMK_USB_DRIVER_CONFIG(RAW, 0, false), #endif +#ifdef XAP_ENABLE +# define XAP_IN_CAPACITY 4 +# define XAP_OUT_CAPACITY 4 +# define XAP_IN_MODE USB_EP_MODE_TYPE_INTR +# define XAP_OUT_MODE USB_EP_MODE_TYPE_INTR + .xap_driver = QMK_USB_DRIVER_CONFIG(XAP, 0, false), +#endif + #ifdef MIDI_ENABLE # define MIDI_STREAM_IN_CAPACITY 4 # define MIDI_STREAM_OUT_CAPACITY 4 @@ -1111,6 +1126,52 @@ void raw_hid_task(void) { #endif +#ifdef XAP_ENABLE +extern void xap_receive(xap_token_t token, const uint8_t *data, size_t length); + +void xap_send_base(uint8_t *data, uint8_t length) { + // TODO: implement variable size packet + if (length != XAP_EPSIZE) { + return; + } + chnWrite(&drivers.xap_driver.driver, data, length); +} + +void xap_send(xap_token_t token, uint8_t response_flags, const void *data, size_t length) { + uint8_t rdata[XAP_EPSIZE] = {0}; + *(xap_token_t *)&rdata[0] = token; + if (length > (XAP_EPSIZE - 4)) response_flags &= ~(XAP_RESPONSE_FLAG_SUCCESS); + rdata[2] = response_flags; + if (response_flags & (XAP_RESPONSE_FLAG_SUCCESS)) { + rdata[3] = (uint8_t)length; + if (data != NULL) { + memcpy(&rdata[4], data, length); + } + } + xap_send_base(rdata, sizeof(rdata)); +} + +void xap_receive_base(const void *data) { + const uint8_t *u8data = (const uint8_t *)data; + xap_token_t token = *(xap_token_t *)&u8data[0]; + uint8_t length = u8data[2]; + if (length <= (XAP_EPSIZE - 3)) { + xap_receive(token, &u8data[3], length); + } +} + +void xap_task(void) { + uint8_t buffer[XAP_EPSIZE]; + size_t size = 0; + do { + size_t size = chnReadTimeout(&drivers.xap_driver.driver, buffer, sizeof(buffer), TIME_IMMEDIATE); + if (size > 0) { + xap_receive_base(buffer); + } + } while (size > 0); +} +#endif // XAP_ENABLE + #ifdef MIDI_ENABLE void send_midi_packet(MIDI_EventPacket_t *event) { From c27edf4e64241d7823c5427d480b3962c3d87905 Mon Sep 17 00:00:00 2001 From: zvecr Date: Fri, 18 Mar 2022 20:23:28 +0000 Subject: [PATCH 18/77] vusb prep --- quantum/xap/xap.c | 1 - tmk_core/protocol/report.h | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/quantum/xap/xap.c b/quantum/xap/xap.c index 185213c833..b348d366fb 100644 --- a/quantum/xap/xap.c +++ b/quantum/xap/xap.c @@ -16,7 +16,6 @@ #include #include -#include #define QSTR2(z) #z #define QSTR(z) QSTR2(z) diff --git a/tmk_core/protocol/report.h b/tmk_core/protocol/report.h index e71e63d81c..7bbeb78af7 100644 --- a/tmk_core/protocol/report.h +++ b/tmk_core/protocol/report.h @@ -32,8 +32,7 @@ enum hid_report_ids { REPORT_ID_PROGRAMMABLE_BUTTON, REPORT_ID_NKRO, REPORT_ID_JOYSTICK, - REPORT_ID_DIGITIZER, - REPORT_ID_XAP + REPORT_ID_DIGITIZER }; /* Mouse buttons */ From a5204887a8533987640b61d82e5f597dd9145e76 Mon Sep 17 00:00:00 2001 From: zvecr Date: Fri, 18 Mar 2022 21:37:23 +0000 Subject: [PATCH 19/77] maybe vusb --- tmk_core/protocol/vusb/protocol.c | 14 ++- tmk_core/protocol/vusb/vusb.c | 178 +++++++++++++++++++++++++++++- tmk_core/protocol/vusb/vusb.h | 7 ++ 3 files changed, 197 insertions(+), 2 deletions(-) diff --git a/tmk_core/protocol/vusb/protocol.c b/tmk_core/protocol/vusb/protocol.c index 20d0530483..596735f400 100644 --- a/tmk_core/protocol/vusb/protocol.c +++ b/tmk_core/protocol/vusb/protocol.c @@ -39,6 +39,10 @@ void console_task(void); void raw_hid_task(void); #endif +#ifdef XAP_ENABLE +void xap_task(void); +#endif + /* This is from main.c of USBaspLoader */ static void initForUsbConnectivity(void) { uint8_t i = 0; @@ -158,11 +162,19 @@ void protocol_task(void) { #ifdef RAW_ENABLE usbPoll(); - if (usbConfiguration && usbInterruptIsReady3()) { + if (usbConfiguration && usbInterruptIsReady4()) { raw_hid_task(); } #endif +#ifdef XAP_ENABLE + usbPoll(); + + if (usbConfiguration && usbInterruptIsReady4()) { + xap_task(); + } +#endif + #ifdef CONSOLE_ENABLE usbPoll(); diff --git a/tmk_core/protocol/vusb/vusb.c b/tmk_core/protocol/vusb/vusb.c index ebde955d3b..ab5f447fd8 100644 --- a/tmk_core/protocol/vusb/vusb.c +++ b/tmk_core/protocol/vusb/vusb.c @@ -35,6 +35,11 @@ along with this program. If not, see . # include "raw_hid.h" #endif +#ifdef XAP_ENABLE +# include "xap.h" +# include +#endif + #if defined(CONSOLE_ENABLE) # define RBUF_SIZE 128 # include "ring_buffer.h" @@ -60,6 +65,10 @@ enum usb_interfaces { RAW_INTERFACE = NEXT_INTERFACE, #endif +#ifdef XAP_ENABLE + XAP_INTERFACE = NEXT_INTERFACE, +#endif + #if defined(SHARED_EP_ENABLE) && !defined(KEYBOARD_SHARED_EP) SHARED_INTERFACE = NEXT_INTERFACE, #endif @@ -137,7 +146,7 @@ void raw_hid_send(uint8_t *data, uint8_t length) { } uint8_t *temp = data; - for (uint8_t i = 0; i < 4; i++) { + for (uint8_t i = 0; i < (RAW_BUFFER_SIZE / RAW_EPSIZE); i++) { while (!usbInterruptIsReady4()) { usbPoll(); } @@ -164,6 +173,68 @@ void raw_hid_task(void) { } #endif +/*------------------------------------------------------------------* + * XAP + *------------------------------------------------------------------*/ +#ifdef XAP_ENABLE +# define XAP_BUFFER_SIZE 64 +# define XAP_EPSIZE 8 + +static uint8_t xap_output_buffer[XAP_BUFFER_SIZE]; +static uint8_t xap_output_received_bytes = 0; + +extern void xap_receive(xap_token_t token, const uint8_t *data, size_t length); + +void xap_send_base(uint8_t *data, uint8_t length) { + if (length != XAP_BUFFER_SIZE) { + return; + } + + uint8_t *temp = data; + for (uint8_t i = 0; i < (XAP_BUFFER_SIZE / XAP_EPSIZE); i++) { + while (!usbInterruptIsReady4()) { + usbPoll(); + } + usbSetInterrupt4(temp, 8); + temp += 8; + } + while (!usbInterruptIsReady4()) { + usbPoll(); + } + usbSetInterrupt4(0, 0); +} + +void xap_send(xap_token_t token, uint8_t response_flags, const void *data, size_t length) { + uint8_t rdata[XAP_BUFFER_SIZE] = {0}; + *(xap_token_t *)&rdata[0] = token; + if (length > (XAP_BUFFER_SIZE - 4)) response_flags &= ~(XAP_RESPONSE_FLAG_SUCCESS); + rdata[2] = response_flags; + if (response_flags & (XAP_RESPONSE_FLAG_SUCCESS)) { + rdata[3] = (uint8_t)length; + if (data != NULL) { + memcpy(&rdata[4], data, length); + } + } + xap_send_base(rdata, sizeof(rdata)); +} + +void xap_receive_base(const void *data) { + const uint8_t *u8data = (const uint8_t *)data; + xap_token_t token = *(xap_token_t *)&u8data[0]; + uint8_t length = u8data[2]; + if (length <= (XAP_BUFFER_SIZE - 3)) { + xap_receive(token, &u8data[3], length); + } +} + +void xap_task(void) { + if (xap_output_received_bytes == XAP_BUFFER_SIZE) { + xap_receive_base(xap_output_buffer); + xap_output_received_bytes = 0; + } +} +#endif + /*------------------------------------------------------------------* * Console *------------------------------------------------------------------*/ @@ -402,6 +473,24 @@ void usbFunctionWriteOut(uchar *data, uchar len) { raw_output_received_bytes += len; } #endif +#ifdef XAP_ENABLE + // Data from host must be divided every 8bytes + if (len != 8) { + dprint("XAP: invalid length\n"); + xap_output_received_bytes = 0; + return; + } + + if (xap_output_received_bytes + len > XAP_BUFFER_SIZE) { + dprint("XAP: buffer full\n"); + xap_output_received_bytes = 0; + } else { + for (uint8_t i = 0; i < 8; i++) { + xap_output_buffer[xap_output_received_bytes + i] = data[i]; + } + xap_output_received_bytes += len; + } +#endif } /*------------------------------------------------------------------* @@ -624,6 +713,29 @@ const PROGMEM uchar raw_hid_report[] = { }; #endif +#ifdef XAP_ENABLE +const PROGMEM uchar xap_report[] = { + 0x06, 0x51, 0xFF, // Usage Page (Vendor Defined) + 0x09, 0x58, // Usage (Vendor Defined) + 0xA1, 0x01, // Collection (Application) + // Data to host + 0x09, 0x62, // Usage (Vendor Defined) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x95, XAP_BUFFER_SIZE, // Report Count + 0x75, 0x08, // Report Size (8) + 0x81, 0x02, // Input (Data, Variable, Absolute) + // Data from host + 0x09, 0x63, // Usage (Vendor Defined) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x95, XAP_BUFFER_SIZE, // Report Count + 0x75, 0x08, // Report Size (8) + 0x91, 0x02, // Output (Data, Variable, Absolute) + 0xC0 // End Collection +}; +#endif + #if defined(CONSOLE_ENABLE) const PROGMEM uchar console_hid_report[] = { 0x06, 0x31, 0xFF, // Usage Page (Vendor Defined - PJRC Teensy compatible) @@ -823,6 +935,56 @@ const PROGMEM usbConfigurationDescriptor_t usbConfigurationDescriptor = { }, # endif +# if defined(XAP_ENABLE) + /* + * XAP + */ + .xapInterface = { + .header = { + .bLength = sizeof(usbInterfaceDescriptor_t), + .bDescriptorType = USBDESCR_INTERFACE + }, + .bInterfaceNumber = XAP_INTERFACE, + .bAlternateSetting = 0x00, + .bNumEndpoints = 2, + .bInterfaceClass = 0x03, + .bInterfaceSubClass = 0x00, + .bInterfaceProtocol = 0x00, + .iInterface = 0x00 + }, + .xapHID = { + .header = { + .bLength = sizeof(usbHIDDescriptor_t), + .bDescriptorType = USBDESCR_HID + }, + .bcdHID = 0x0101, + .bCountryCode = 0x00, + .bNumDescriptors = 1, + .bDescriptorType = USBDESCR_HID_REPORT, + .wDescriptorLength = sizeof(xap_report) + }, + .xapINEndpoint = { + .header = { + .bLength = sizeof(usbEndpointDescriptor_t), + .bDescriptorType = USBDESCR_ENDPOINT + }, + .bEndpointAddress = (USBRQ_DIR_DEVICE_TO_HOST | USB_CFG_EP4_NUMBER), + .bmAttributes = 0x03, + .wMaxPacketSize = XAP_EPSIZE, + .bInterval = USB_POLLING_INTERVAL_MS + }, + .xapOUTEndpoint = { + .header = { + .bLength = sizeof(usbEndpointDescriptor_t), + .bDescriptorType = USBDESCR_ENDPOINT + }, + .bEndpointAddress = (USBRQ_DIR_HOST_TO_DEVICE | USB_CFG_EP4_NUMBER), + .bmAttributes = 0x03, + .wMaxPacketSize = XAP_EPSIZE, + .bInterval = USB_POLLING_INTERVAL_MS + }, +# endif + # ifdef SHARED_EP_ENABLE /* * Shared @@ -975,6 +1137,13 @@ USB_PUBLIC usbMsgLen_t usbFunctionDescriptor(struct usbRequest *rq) { break; #endif +#if defined(XAP_ENABLE) + case XAP_INTERFACE: + usbMsgPtr = (usbMsgPtr_t)&usbConfigurationDescriptor.xapHID; + len = sizeof(usbHIDDescriptor_t); + break; +#endif + #ifdef SHARED_EP_ENABLE case SHARED_INTERFACE: usbMsgPtr = (usbMsgPtr_t)&usbConfigurationDescriptor.sharedHID; @@ -1007,6 +1176,13 @@ USB_PUBLIC usbMsgLen_t usbFunctionDescriptor(struct usbRequest *rq) { break; #endif +#if defined(XAP_ENABLE) + case XAP_INTERFACE: + usbMsgPtr = (usbMsgPtr_t)xap_report; + len = sizeof(xap_report); + break; +#endif + #ifdef SHARED_EP_ENABLE case SHARED_INTERFACE: usbMsgPtr = (usbMsgPtr_t)shared_hid_report; diff --git a/tmk_core/protocol/vusb/vusb.h b/tmk_core/protocol/vusb/vusb.h index b1ecc98f37..152d3fafe2 100644 --- a/tmk_core/protocol/vusb/vusb.h +++ b/tmk_core/protocol/vusb/vusb.h @@ -104,6 +104,13 @@ typedef struct usbConfigurationDescriptor { usbEndpointDescriptor_t rawOUTEndpoint; #endif +#if defined(XAP_ENABLE) + usbInterfaceDescriptor_t xapInterface; + usbHIDDescriptor_t xapHID; + usbEndpointDescriptor_t xapINEndpoint; + usbEndpointDescriptor_t xapOUTEndpoint; +#endif + #if defined(SHARED_EP_ENABLE) && !defined(KEYBOARD_SHARED_EP) usbInterfaceDescriptor_t sharedInterface; usbHIDDescriptor_t sharedHID; From 70c9905cb647c345b7d8dda479c1c16f6776b1b5 Mon Sep 17 00:00:00 2001 From: zvecr Date: Fri, 18 Mar 2022 21:43:01 +0000 Subject: [PATCH 20/77] clang --- tmk_core/protocol/vusb/vusb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tmk_core/protocol/vusb/vusb.c b/tmk_core/protocol/vusb/vusb.c index ab5f447fd8..8e120b4ca9 100644 --- a/tmk_core/protocol/vusb/vusb.c +++ b/tmk_core/protocol/vusb/vusb.c @@ -206,7 +206,7 @@ void xap_send_base(uint8_t *data, uint8_t length) { void xap_send(xap_token_t token, uint8_t response_flags, const void *data, size_t length) { uint8_t rdata[XAP_BUFFER_SIZE] = {0}; - *(xap_token_t *)&rdata[0] = token; + *(xap_token_t *)&rdata[0] = token; if (length > (XAP_BUFFER_SIZE - 4)) response_flags &= ~(XAP_RESPONSE_FLAG_SUCCESS); rdata[2] = response_flags; if (response_flags & (XAP_RESPONSE_FLAG_SUCCESS)) { From b365cbce15f0532312f49a09ef782ed081561f3d Mon Sep 17 00:00:00 2001 From: zvecr Date: Sun, 20 Mar 2022 01:25:04 +0000 Subject: [PATCH 21/77] Merge in keymap level to XAP info.json payload --- lib/python/qmk/cli/xap/generate_qmk.py | 42 +++------------ lib/python/qmk/info.py | 53 +++++++++++++++---- .../qmk/xap/gen_firmware/info_generator.py | 40 ++++++++++++++ 3 files changed, 91 insertions(+), 44 deletions(-) create mode 100644 lib/python/qmk/xap/gen_firmware/info_generator.py diff --git a/lib/python/qmk/cli/xap/generate_qmk.py b/lib/python/qmk/cli/xap/generate_qmk.py index ff43e2863d..26476dba46 100755 --- a/lib/python/qmk/cli/xap/generate_qmk.py +++ b/lib/python/qmk/cli/xap/generate_qmk.py @@ -1,19 +1,13 @@ """This script generates the XAP protocol generated sources to be compiled into QMK firmware. """ -import json -import gzip - from milc import cli from qmk.path import normpath -from qmk.info import info_json -from qmk.commands import get_chunks, dump_lines from qmk.keyboard import keyboard_completer, keyboard_folder +from qmk.xap.gen_firmware.info_generator import generate_info from qmk.xap.gen_firmware.inline_generator import generate_inline from qmk.xap.gen_firmware.header_generator import generate_header -from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE - @cli.argument('-o', '--output', type=normpath, help='File to write to') @cli.subcommand('Generates the XAP protocol include.', hidden=False if cli.config.user.developer else True) @@ -44,36 +38,14 @@ def xap_generate_qmk_h(cli): def xap_generate_info_h(cli): """Generates the XAP info.json payload header file, generated during normal build. """ - # Determine our keyboard + # Determine our keyboard/keymap if not cli.args.keyboard: cli.log.error('Missing parameter: --keyboard') cli.subcommands['xap-generate-info-h'].print_help() return False + if not cli.args.keymap: + cli.log.error('Missing parameter: --keymap') + cli.subcommands['xap-generate-info-h'].print_help() + return False - # TODO: merge in keymap level content - # Build the info.json file - kb_info_json = info_json(cli.args.keyboard) - - # Minify - str_data = json.dumps(kb_info_json, separators=(',', ':')) - - # Compress - compressed = gzip.compress(str_data.encode("utf-8"), compresslevel=9) - - # split into lines to match xxd output - hex_array = ["0x{:02X}".format(b) for b in compressed] - data_len = len(hex_array) - - data = "" - for chunk in get_chunks(hex_array, 12): - data += f' {", ".join(chunk)},\n' - - lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once', ''] - - # Gen output file - lines.append('unsigned char info_json_gz[] = {') - lines.append(data) - lines.append('};') - lines.append(f'unsigned int info_json_gz_len = {data_len};') - - dump_lines(cli.args.output, lines) + generate_info(cli.args.output, cli.args.keyboard, cli.args.keymap) diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index c399a9f321..4a8a64a40a 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -8,10 +8,10 @@ from dotty_dict import dotty from milc import cli from qmk.constants import CHIBIOS_PROCESSORS, LUFA_PROCESSORS, VUSB_PROCESSORS -from qmk.c_parse import find_layouts +from qmk.c_parse import find_layouts, parse_config_h_file from qmk.json_schema import deep_update, json_load, validate from qmk.keyboard import config_h, rules_mk -from qmk.keymap import list_keymaps +from qmk.keymap import list_keymaps, locate_keymap from qmk.makefile import parse_rules_mk_file from qmk.math import compute @@ -68,8 +68,8 @@ def info_json(keyboard): # Merge in the data from info.json, config.h, and rules.mk info_data = merge_info_jsons(keyboard, info_data) - info_data = _extract_rules_mk(info_data) - info_data = _extract_config_h(info_data) + info_data = _extract_rules_mk(info_data, rules_mk(str(keyboard))) + info_data = _extract_config_h(info_data, config_h(str(keyboard))) # Ensure that we have matrix row and column counts info_data = _matrix_size(info_data) @@ -257,6 +257,10 @@ def _extract_split_main(info_data, config_c): def _extract_split_transport(info_data, config_c): + # TODO: Ignore? + if 'split' not in info_data: + return + # Figure out the transport method if config_c.get('USE_I2C') is True: if 'split' not in info_data: @@ -400,11 +404,9 @@ def _extract_device_version(info_data): info_data['usb']['device_version'] = f'{major}.{minor}.{revision}' -def _extract_config_h(info_data): +def _extract_config_h(info_data, config_c): """Pull some keyboard information from existing config.h files """ - config_c = config_h(info_data['keyboard_folder']) - # Pull in data from the json map dotty_info = dotty(info_data) info_config_map = json_load(Path('data/mappings/info_config.json')) @@ -472,10 +474,9 @@ def _extract_config_h(info_data): return info_data -def _extract_rules_mk(info_data): +def _extract_rules_mk(info_data, rules): """Pull some keyboard information from existing rules.mk files """ - rules = rules_mk(info_data['keyboard_folder']) info_data['processor'] = rules.get('MCU', info_data.get('processor', 'atmega32u4')) if info_data['processor'] in CHIBIOS_PROCESSORS: @@ -766,3 +767,37 @@ def find_info_json(keyboard): # Return a list of the info.json files that actually exist return [info_json for info_json in info_jsons if info_json.exists()] + + +def parse_keymap_json_file(file): + """load a valid keymap.json + """ + if not file.exists(): + return {} + km_info_json = json_load(file) + validate(km_info_json, 'qmk.keymap.v1') + return km_info_json + + +def keymap_json(keyboard, keymap): + """Generate the info.json data for a specific keymap. + """ + keymap_folder = locate_keymap(keyboard, keymap).parent + + # Files to scan + keymap_config = keymap_folder / 'config.h' + keymap_rules = keymap_folder / 'rules.mk' + keymap_file = keymap_folder / 'keymap.json' + + # Build the info.json file + kb_info_json = info_json(keyboard) + + # Merge in the data from keymap.json + km_info_json = parse_keymap_json_file(keymap_file).get('config', {}) + deep_update(kb_info_json, km_info_json) + + # Merge in the data from config.h, and rules.mk + _extract_rules_mk(kb_info_json, parse_rules_mk_file(keymap_rules)) + _extract_config_h(kb_info_json, parse_config_h_file(keymap_config)) + + return kb_info_json diff --git a/lib/python/qmk/xap/gen_firmware/info_generator.py b/lib/python/qmk/xap/gen_firmware/info_generator.py new file mode 100644 index 0000000000..b90bd57672 --- /dev/null +++ b/lib/python/qmk/xap/gen_firmware/info_generator.py @@ -0,0 +1,40 @@ +"""This script generates the XAP info.json payload header to be compiled into QMK. +""" +import json +import gzip + +from qmk.info import keymap_json +from qmk.commands import get_chunks, dump_lines + +from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE + + +def generate_info(output_file, keyboard, keymap): + # Build the info.json file + km_info_json = keymap_json(keyboard, keymap) + + # TODO: Munge to XAP requirements + + # Minify + str_data = json.dumps(km_info_json, separators=(',', ':')) + + # Compress + compressed = gzip.compress(str_data.encode("utf-8"), compresslevel=9) + + # split into lines to match xxd output + hex_array = ["0x{:02X}".format(b) for b in compressed] + data_len = len(hex_array) + + data = "" + for chunk in get_chunks(hex_array, 12): + data += f' {", ".join(chunk)},\n' + + lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once', ''] + + # Gen output file + lines.append('unsigned char info_json_gz[] = {') + lines.append(data) + lines.append('};') + lines.append(f'unsigned int info_json_gz_len = {data_len};') + + dump_lines(output_file, lines) From e31c605bf75826e5a5a243a264faa21e8e5bc601 Mon Sep 17 00:00:00 2001 From: zvecr Date: Tue, 22 Mar 2022 00:04:12 +0000 Subject: [PATCH 22/77] revert split logic --- lib/python/qmk/info.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index 137a488bc8..d104d655ef 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -258,10 +258,6 @@ def _extract_split_main(info_data, config_c): def _extract_split_transport(info_data, config_c): - # TODO: Ignore? - if 'split' not in info_data: - return - # Figure out the transport method if config_c.get('USE_I2C') is True: if 'split' not in info_data: From ff1bb7653756a6997f255af26686cf32f6cc5388 Mon Sep 17 00:00:00 2001 From: zvecr Date: Mon, 28 Mar 2022 21:06:11 +0100 Subject: [PATCH 23/77] basic info.json handling --- data/xap/xap_0.1.0.hjson | 18 +++ lib/python/qmk/cli/xap/__init__.py | 80 +--------- lib/python/qmk/cli/xap/xap.py | 149 ++++++++++++++++++ .../qmk/xap/gen_firmware/info_generator.py | 5 +- .../qmk/xap/gen_firmware/inline_generator.py | 14 ++ quantum/xap/xap.c | 10 ++ quantum/xap/xap_handlers.c | 16 +- 7 files changed, 210 insertions(+), 82 deletions(-) create mode 100644 lib/python/qmk/cli/xap/xap.py diff --git a/data/xap/xap_0.1.0.hjson b/data/xap/xap_0.1.0.hjson index f74f177339..8fdbcc02a6 100755 --- a/data/xap/xap_0.1.0.hjson +++ b/data/xap/xap_0.1.0.hjson @@ -218,6 +218,24 @@ return_type: string return_constant: QSTR(PRODUCT) } + 0x05: { + type: command + name: info.json length + define: INFO_LEN_QUERY + description: Retrieves the length of info.json + return_type: u32 + return_constant: info_json_gz_len + } + 0x06: { + type: command + name: info.json + define: INFO_QUERY + description: Retrieves a chunk of info.json + request_type: u16 + request_purpose: offset + return_type: u8[32] + return_execute: info_json_gz + } } }, diff --git a/lib/python/qmk/cli/xap/__init__.py b/lib/python/qmk/cli/xap/__init__.py index 963617a6ee..190f3607ec 100755 --- a/lib/python/qmk/cli/xap/__init__.py +++ b/lib/python/qmk/cli/xap/__init__.py @@ -1,79 +1 @@ -"""Interactions with compatible XAP devices -""" -import json -import random - -from milc import cli - - -def _is_xap_usage(x): - return x['usage_page'] == 0xFF51 and x['usage'] == 0x0058 - - -def _is_filtered_device(x): - name = "%04x:%04x" % (x['vendor_id'], x['product_id']) - return name.lower().startswith(cli.args.device.lower()) - - -def _search(): - devices = filter(_is_xap_usage, hid.enumerate()) - if cli.args.device: - devices = filter(_is_filtered_device, devices) - - return list(devices) - - -def _list_devices(): - """Dump out available devices - """ - cli.log.info('Available devices:') - devices = _search() - for dev in devices: - device = hid.Device(path=dev['path']) - - data = _query_device(device) - cli.log.info(" %04x:%04x %s %s [API:%s]", dev['vendor_id'], dev['product_id'], dev['manufacturer_string'], dev['product_string'], data['ver']) - - if cli.config.general.verbose: - # TODO: better formatting like "lsusb -v" - cli.log.info(" " + json.dumps(data)) - - -def _query_device(device): - # gen token - tok = random.getrandbits(16) - temp = tok.to_bytes(2, byteorder='big') - - # send with padding - padding = b"\x00" * 59 - device.write(temp + b'\x02\x00\x00' + padding) - - # get resp - array_alpha = device.read(8, 100) - # hex_string = " ".join("%02x" % b for b in array_alpha) - - # validate tok sent == resp - ver = "UNKNOWN" - if str(temp) == str(array_alpha[:2]): - # to BCD string - a = (array_alpha[7] << 24) + (array_alpha[6] << 16) + (array_alpha[5] << 8) + (array_alpha[4]) - ver = f'{a>>24}.{a>>16 & 0xFF}.{a & 0xFFFF}' - - return {'ver': ver} - - -@cli.argument('-d', '--device', help='device to select - uses format :.') -@cli.argument('-i', '--index', default=0, help='device index to select.') -@cli.argument('-l', '--list', arg_only=True, action='store_true', help='List available devices.') -@cli.subcommand('Acquire debugging information from usb XAP devices.', hidden=False if cli.config.user.developer else True) -def xap(cli): - """Acquire debugging information from XAP devices - """ - # Lazy load to avoid issues - global hid - import hid - - if cli.args.list: - return _list_devices() - - cli.log.warn("TODO: Device specific stuff") +from .xap import xap diff --git a/lib/python/qmk/cli/xap/xap.py b/lib/python/qmk/cli/xap/xap.py new file mode 100644 index 0000000000..8aab20864c --- /dev/null +++ b/lib/python/qmk/cli/xap/xap.py @@ -0,0 +1,149 @@ +"""Interactions with compatible XAP devices +""" +import json +import random +import gzip + +from milc import cli + + +def _is_xap_usage(x): + return x['usage_page'] == 0xFF51 and x['usage'] == 0x0058 + + +def _is_filtered_device(x): + name = "%04x:%04x" % (x['vendor_id'], x['product_id']) + return name.lower().startswith(cli.args.device.lower()) + + +def _search(): + devices = filter(_is_xap_usage, hid.enumerate()) + if cli.args.device: + devices = filter(_is_filtered_device, devices) + + return list(devices) + + +def print_dotted_output(kb_info_json, prefix=''): + """Print the info.json in a plain text format with dot-joined keys. + """ + for key in sorted(kb_info_json): + new_prefix = f'{prefix}.{key}' if prefix else key + + if key in ['parse_errors', 'parse_warnings']: + continue + elif key == 'layouts' and prefix == '': + cli.echo(' {fg_blue}layouts{fg_reset}: %s', ', '.join(sorted(kb_info_json['layouts'].keys()))) + elif isinstance(kb_info_json[key], dict): + print_dotted_output(kb_info_json[key], new_prefix) + elif isinstance(kb_info_json[key], list): + cli.echo(' {fg_blue}%s{fg_reset}: %s', new_prefix, ', '.join(map(str, sorted(kb_info_json[key])))) + else: + cli.echo(' {fg_blue}%s{fg_reset}: %s', new_prefix, kb_info_json[key]) + + +def _list_devices(): + """Dump out available devices + """ + cli.log.info('Available devices:') + devices = _search() + for dev in devices: + device = hid.Device(path=dev['path']) + + data = _query_device_version(device) + cli.log.info(" %04x:%04x %s %s [API:%s]", dev['vendor_id'], dev['product_id'], dev['manufacturer_string'], dev['product_string'], data['ver']) + + if cli.config.general.verbose: + # TODO: better formatting like "lsusb -v" + datalen = _query_device_info_len(device) + + data = [] + offset = 0 + while offset < datalen: + data += _query_device_info(device, offset) + offset += 32 + str_data = gzip.decompress(bytearray(data[:datalen])) + print_dotted_output(json.loads(str_data)) + + +def _query_device_version(device): + # gen token + tok = random.getrandbits(16) + temp = tok.to_bytes(2, byteorder='big') + + # send with padding + padding = b"\x00" * 59 + device.write(temp + b'\x02\x00\x00' + padding) + + # get resp + array_alpha = device.read(8, 100) + # hex_string = " ".join("%02x" % b for b in array_alpha) + + # validate tok sent == resp + ver = "UNKNOWN" + if str(temp) == str(array_alpha[:2]): + # to BCD string + a = (array_alpha[7] << 24) + (array_alpha[6] << 16) + (array_alpha[5] << 8) + (array_alpha[4]) + ver = f'{a>>24}.{a>>16 & 0xFF}.{a & 0xFFFF}' + + return {'ver': ver} + + +def _query_device_info_len(device): + # gen token + tok = random.getrandbits(16) + temp = tok.to_bytes(2, byteorder='big') + + # send with padding + padding = b"\x00" * 59 + device.write(temp + b'\x02\x01\x05' + padding) + + # get resp + array_alpha = device.read(8, 100) + # hex_string = " ".join("%02x" % b for b in array_alpha) + + # validate tok sent == resp + datalen = "UNKNOWN" + if str(temp) == str(array_alpha[:2]): + # to BCD string + a = (array_alpha[7] << 24) + (array_alpha[6] << 16) + (array_alpha[5] << 8) + (array_alpha[4]) + datalen = f'{a & 0xFFFF}' + + return int(datalen) + + +def _query_device_info(device, offset): + # gen token + tok = random.getrandbits(16) + temp = tok.to_bytes(2, byteorder='big') + + # send with padding + padding = b"\x00" * 57 + device.write(temp + b'\x04\x01\x06' + (offset).to_bytes(2, byteorder='big') + padding) + + # get resp + array_alpha = device.read(4 + 32, 100) + + # hex_string = " ".join("%02x" % b for b in array_alpha) + + # validate tok sent == resp + if str(temp) == str(array_alpha[:2]): + return array_alpha[4:] + return None + + +@cli.argument('-d', '--device', help='device to select - uses format :.') +@cli.argument('-i', '--index', default=0, help='device index to select.') +@cli.argument('-l', '--list', arg_only=True, action='store_true', help='List available devices.') +@cli.subcommand('Acquire debugging information from usb XAP devices.', hidden=False if cli.config.user.developer else True) +def xap(cli): + """Acquire debugging information from XAP devices + """ + # Lazy load to avoid issues + global hid + import hid + + if cli.args.list: + return _list_devices() + + cli.log.warn("TODO: Device specific stuff") diff --git a/lib/python/qmk/xap/gen_firmware/info_generator.py b/lib/python/qmk/xap/gen_firmware/info_generator.py index b90bd57672..c25d82181b 100644 --- a/lib/python/qmk/xap/gen_firmware/info_generator.py +++ b/lib/python/qmk/xap/gen_firmware/info_generator.py @@ -14,6 +14,7 @@ def generate_info(output_file, keyboard, keymap): km_info_json = keymap_json(keyboard, keymap) # TODO: Munge to XAP requirements + del km_info_json['config_h_features'] # Minify str_data = json.dumps(km_info_json, separators=(',', ':')) @@ -32,9 +33,9 @@ def generate_info(output_file, keyboard, keymap): lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once', ''] # Gen output file - lines.append('unsigned char info_json_gz[] = {') + lines.append('static const unsigned char info_json_gz[] PROGMEM = {') lines.append(data) lines.append('};') - lines.append(f'unsigned int info_json_gz_len = {data_len};') + lines.append(f'static const unsigned int info_json_gz_len = {data_len};') dump_lines(output_file, lines) diff --git a/lib/python/qmk/xap/gen_firmware/inline_generator.py b/lib/python/qmk/xap/gen_firmware/inline_generator.py index a40e40ec98..a78d2f357e 100755 --- a/lib/python/qmk/xap/gen_firmware/inline_generator.py +++ b/lib/python/qmk/xap/gen_firmware/inline_generator.py @@ -26,6 +26,8 @@ def _get_c_type(xap_type): def _get_route_type(container): if 'routes' in container: return 'XAP_ROUTE' + elif 'return_execute' in container: + return 'XAP_EXECUTE' elif 'return_constant' in container: if container['return_type'] == 'u32': return 'XAP_VALUE' @@ -47,6 +49,11 @@ def _append_routing_table_declaration(lines, container, container_id, route_stac if 'routes' in container: pass + elif 'return_execute' in container: + execute = container['return_execute'] + lines.append('') + lines.append(f'bool xap_respond_{execute}(xap_token_t token, const uint8_t *data, size_t data_len);') + elif 'return_constant' in container: if container['return_type'] == 'u32': @@ -99,6 +106,11 @@ def _append_routing_table_entry_route(lines, container, container_id, route_stac lines.append(f' .child_routes_len = sizeof({route_name}_table)/sizeof(xap_route_t),') +def _append_routing_table_entry_execute(lines, container, container_id, route_stack): + value = container['return_execute'] + lines.append(f' .handler = xap_respond_{value},') + + def _append_routing_table_entry_u32value(lines, container, container_id, route_stack): value = container['return_constant'] lines.append(f' .u32value = {value},') @@ -134,6 +146,8 @@ def _append_routing_table_entry(lines, container, container_id, route_stack): _append_routing_table_entry_flags(lines, container, container_id, route_stack) if 'routes' in container: _append_routing_table_entry_route(lines, container, container_id, route_stack) + elif 'return_execute' in container: + _append_routing_table_entry_execute(lines, container, container_id, route_stack) elif 'return_constant' in container: if container['return_type'] == 'u32': _append_routing_table_entry_u32value(lines, container, container_id, route_stack) diff --git a/quantum/xap/xap.c b/quantum/xap/xap.c index b348d366fb..f468eac9b4 100644 --- a/quantum/xap/xap.c +++ b/quantum/xap/xap.c @@ -17,6 +17,16 @@ #include #include +#include "info_json_gz.h" +void get_info_json_chunk(uint8_t *data, size_t offset) { + uint8_t len = 32; + if (offset + len > info_json_gz_len) { + len = info_json_gz_len - offset; + } + + memcpy_P(data, &info_json_gz[offset], len); +} + #define QSTR2(z) #z #define QSTR(z) QSTR2(z) diff --git a/quantum/xap/xap_handlers.c b/quantum/xap/xap_handlers.c index 492d3d25bc..f21cdab316 100644 --- a/quantum/xap/xap_handlers.c +++ b/quantum/xap/xap_handlers.c @@ -16,7 +16,6 @@ #include #include -#include void xap_respond_failure(xap_token_t token, xap_response_flags_t response_flags) { xap_send(token, response_flags, NULL, 0); @@ -41,3 +40,18 @@ bool xap_respond_u32(xap_token_t token, uint32_t value) { uint32_t xap_route_qmk_ffffffffffffffff_getter(void) { return 0x12345678; } + +bool xap_respond_info_json_gz(xap_token_t token, const uint8_t *data, size_t data_len) { + if (data_len != 2) { + xap_respond_failure(token, 0); + return false; + } + + uint8_t blob[32] = {0}; + uint16_t offset = ((uint16_t)data[0]) << 8 | data[1]; + + void get_info_json_chunk(uint8_t * data, size_t offset); + get_info_json_chunk(blob, offset); + + return xap_respond_data(token, blob, 32); +} From 7262333857e2c006c108e075d4b76f088b27a379 Mon Sep 17 00:00:00 2001 From: zvecr Date: Mon, 28 Mar 2022 21:18:17 +0100 Subject: [PATCH 24/77] Use generic 'dump_lines' --- .../qmk/xap/gen_firmware/header_generator.py | 16 ++-------------- .../qmk/xap/gen_firmware/inline_generator.py | 17 ++--------------- 2 files changed, 4 insertions(+), 29 deletions(-) diff --git a/lib/python/qmk/xap/gen_firmware/header_generator.py b/lib/python/qmk/xap/gen_firmware/header_generator.py index ea299b18f5..bb0df05959 100755 --- a/lib/python/qmk/xap/gen_firmware/header_generator.py +++ b/lib/python/qmk/xap/gen_firmware/header_generator.py @@ -3,6 +3,7 @@ import re from fnvhash import fnv1a_32 +from qmk.commands import dump_lines from qmk.git import git_get_version from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE from qmk.xap.common import latest_xap_defs, route_conditions @@ -119,18 +120,5 @@ def generate_header(output_file, keyboard): _append_route_capabilities(lines, xap_defs) lines.append('') - # Generate the full output - xap_generated_inl = '\n'.join(lines) + dump_lines(output_file, lines) - # Clean up newlines - while "\n\n\n" in xap_generated_inl: - xap_generated_inl = xap_generated_inl.replace("\n\n\n", "\n\n") - - if output_file: - if output_file.name == '-': - print(xap_generated_inl) - else: - output_file.parent.mkdir(parents=True, exist_ok=True) - if output_file.exists(): - output_file.replace(output_file.parent / (output_file.name + '.bak')) - output_file.write_text(xap_generated_inl) diff --git a/lib/python/qmk/xap/gen_firmware/inline_generator.py b/lib/python/qmk/xap/gen_firmware/inline_generator.py index a78d2f357e..8563bfdc69 100755 --- a/lib/python/qmk/xap/gen_firmware/inline_generator.py +++ b/lib/python/qmk/xap/gen_firmware/inline_generator.py @@ -1,6 +1,7 @@ """This script generates the XAP protocol generated header to be compiled into QMK. """ from qmk.casing import to_snake +from qmk.commands import dump_lines from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE from qmk.xap.common import latest_xap_defs, route_conditions @@ -217,18 +218,4 @@ def generate_inline(output_file): # Add all the generated code _append_routing_tables(lines, xap_defs) - # Generate the full output - xap_generated_inl = '\n'.join(lines) - - # Clean up newlines - while "\n\n\n" in xap_generated_inl: - xap_generated_inl = xap_generated_inl.replace("\n\n\n", "\n\n") - - if output_file: - if output_file.name == '-': - print(xap_generated_inl) - else: - output_file.parent.mkdir(parents=True, exist_ok=True) - if output_file.exists(): - output_file.replace(output_file.parent / (output_file.name + '.bak')) - output_file.write_text(xap_generated_inl) + dump_lines(output_file, lines) From 05911e9908404f46b2df2324d669fbadadb05a49 Mon Sep 17 00:00:00 2001 From: zvecr Date: Tue, 29 Mar 2022 18:36:08 +0100 Subject: [PATCH 25/77] bodge 'qmk xap -l' for windows --- lib/python/qmk/cli/xap/xap.py | 154 +++++++++++++++++----------------- 1 file changed, 77 insertions(+), 77 deletions(-) diff --git a/lib/python/qmk/cli/xap/xap.py b/lib/python/qmk/cli/xap/xap.py index 8aab20864c..bfd9511711 100644 --- a/lib/python/qmk/cli/xap/xap.py +++ b/lib/python/qmk/cli/xap/xap.py @@ -3,6 +3,7 @@ import json import random import gzip +from platform import platform from milc import cli @@ -42,6 +43,78 @@ def print_dotted_output(kb_info_json, prefix=''): cli.echo(' {fg_blue}%s{fg_reset}: %s', new_prefix, kb_info_json[key]) +def _xap_transaction(device, sub, route, ret_len, *args): + # gen token + tok = random.getrandbits(16) + token = tok.to_bytes(2, byteorder='big') + + # send with padding + # TODO: this code is total garbage + args_data = [] + args_len = 2 + if len(args) == 1: + args_len += 2 + args_data = args[0].to_bytes(2, byteorder='big') + + padding = b"\x00" * (64 - 3 - args_len) + if args_data: + padding = args_data + padding + buffer = token + args_len.to_bytes(1, byteorder='big') + sub.to_bytes(1, byteorder='big') + route.to_bytes(1, byteorder='big') + padding + + # prepend 0 on windows because reasons... + if 'windows' in platform().lower(): + buffer = b"\x00" + buffer + + device.write(buffer) + + # get resp + array_alpha = device.read(4 + ret_len, 100) + + # validate tok sent == resp + if str(token) != str(array_alpha[:2]): + return None + return array_alpha[4:] + + +def _query_device_version(device): + ver_data = _xap_transaction(device, 0x00, 0x00, 4) + if not ver_data: + return {'xap': 'UNKNOWN'} + + # to u32 to BCD string + a = (ver_data[3] << 24) + (ver_data[2] << 16) + (ver_data[1] << 8) + (ver_data[0]) + ver = f'{a>>24}.{a>>16 & 0xFF}.{a & 0xFFFF}' + + return {'xap': ver} + + +def _query_device_info_len(device): + len_data = _xap_transaction(device, 0x01, 0x05, 4) + if not len_data: + return 0 + + # to u32 + return (len_data[3] << 24) + (len_data[2] << 16) + (len_data[1] << 8) + (len_data[0]) + + +def _query_device_info_chunk(device, offset): + return _xap_transaction(device, 0x01, 0x06, 32, offset) + + +def _query_device_info(device): + datalen = _query_device_info_len(device) + if not datalen: + return {} + + data = [] + offset = 0 + while offset < datalen: + data += _query_device_info_chunk(device, offset) + offset += 32 + str_data = gzip.decompress(bytearray(data[:datalen])) + return json.loads(str_data) + + def _list_devices(): """Dump out available devices """ @@ -51,85 +124,12 @@ def _list_devices(): device = hid.Device(path=dev['path']) data = _query_device_version(device) - cli.log.info(" %04x:%04x %s %s [API:%s]", dev['vendor_id'], dev['product_id'], dev['manufacturer_string'], dev['product_string'], data['ver']) + cli.log.info(" %04x:%04x %s %s [API:%s]", dev['vendor_id'], dev['product_id'], dev['manufacturer_string'], dev['product_string'], data['xap']) if cli.config.general.verbose: - # TODO: better formatting like "lsusb -v" - datalen = _query_device_info_len(device) - - data = [] - offset = 0 - while offset < datalen: - data += _query_device_info(device, offset) - offset += 32 - str_data = gzip.decompress(bytearray(data[:datalen])) - print_dotted_output(json.loads(str_data)) - - -def _query_device_version(device): - # gen token - tok = random.getrandbits(16) - temp = tok.to_bytes(2, byteorder='big') - - # send with padding - padding = b"\x00" * 59 - device.write(temp + b'\x02\x00\x00' + padding) - - # get resp - array_alpha = device.read(8, 100) - # hex_string = " ".join("%02x" % b for b in array_alpha) - - # validate tok sent == resp - ver = "UNKNOWN" - if str(temp) == str(array_alpha[:2]): - # to BCD string - a = (array_alpha[7] << 24) + (array_alpha[6] << 16) + (array_alpha[5] << 8) + (array_alpha[4]) - ver = f'{a>>24}.{a>>16 & 0xFF}.{a & 0xFFFF}' - - return {'ver': ver} - - -def _query_device_info_len(device): - # gen token - tok = random.getrandbits(16) - temp = tok.to_bytes(2, byteorder='big') - - # send with padding - padding = b"\x00" * 59 - device.write(temp + b'\x02\x01\x05' + padding) - - # get resp - array_alpha = device.read(8, 100) - # hex_string = " ".join("%02x" % b for b in array_alpha) - - # validate tok sent == resp - datalen = "UNKNOWN" - if str(temp) == str(array_alpha[:2]): - # to BCD string - a = (array_alpha[7] << 24) + (array_alpha[6] << 16) + (array_alpha[5] << 8) + (array_alpha[4]) - datalen = f'{a & 0xFFFF}' - - return int(datalen) - - -def _query_device_info(device, offset): - # gen token - tok = random.getrandbits(16) - temp = tok.to_bytes(2, byteorder='big') - - # send with padding - padding = b"\x00" * 57 - device.write(temp + b'\x04\x01\x06' + (offset).to_bytes(2, byteorder='big') + padding) - - # get resp - array_alpha = device.read(4 + 32, 100) - - # hex_string = " ".join("%02x" % b for b in array_alpha) - - # validate tok sent == resp - if str(temp) == str(array_alpha[:2]): - return array_alpha[4:] - return None + # TODO: better formatting like "lsusb -v"? + data = _query_device_info(device) + print_dotted_output(data) @cli.argument('-d', '--device', help='device to select - uses format :.') From 56c9f7b7ff23196afba65b66f632fcc10070175d Mon Sep 17 00:00:00 2001 From: zvecr Date: Tue, 29 Mar 2022 19:25:16 +0100 Subject: [PATCH 26/77] format --- lib/python/qmk/cli/xap/xap.py | 5 +++-- lib/python/qmk/xap/gen_firmware/header_generator.py | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/python/qmk/cli/xap/xap.py b/lib/python/qmk/cli/xap/xap.py index bfd9511711..7826ed33d6 100644 --- a/lib/python/qmk/cli/xap/xap.py +++ b/lib/python/qmk/cli/xap/xap.py @@ -56,11 +56,12 @@ def _xap_transaction(device, sub, route, ret_len, *args): args_len += 2 args_data = args[0].to_bytes(2, byteorder='big') - padding = b"\x00" * (64 - 3 - args_len) + padding_len = 64 - 3 - args_len + padding = b"\x00" * padding_len if args_data: padding = args_data + padding buffer = token + args_len.to_bytes(1, byteorder='big') + sub.to_bytes(1, byteorder='big') + route.to_bytes(1, byteorder='big') + padding - + # prepend 0 on windows because reasons... if 'windows' in platform().lower(): buffer = b"\x00" + buffer diff --git a/lib/python/qmk/xap/gen_firmware/header_generator.py b/lib/python/qmk/xap/gen_firmware/header_generator.py index bb0df05959..42d54e1960 100755 --- a/lib/python/qmk/xap/gen_firmware/header_generator.py +++ b/lib/python/qmk/xap/gen_firmware/header_generator.py @@ -121,4 +121,3 @@ def generate_header(output_file, keyboard): lines.append('') dump_lines(output_file, lines) - From 13ee88dd21f9cfc051ad064a1c90019db1159f0e Mon Sep 17 00:00:00 2001 From: zvecr Date: Fri, 25 Mar 2022 02:13:31 +0000 Subject: [PATCH 27/77] Data driven g_led config --- builddefs/build_keyboard.mk | 8 +- data/schemas/keyboard.jsonschema | 56 +++++++++++ lib/python/qmk/c_parse.py | 116 ++++++++++++++++++++++ lib/python/qmk/cli/__init__.py | 1 + lib/python/qmk/cli/generate/keyboard_c.py | 65 ++++++++++++ lib/python/qmk/info.py | 45 ++++++++- lib/python/qmk/json_encoders.py | 4 +- 7 files changed, 291 insertions(+), 4 deletions(-) create mode 100755 lib/python/qmk/cli/generate/keyboard_c.py diff --git a/builddefs/build_keyboard.mk b/builddefs/build_keyboard.mk index 44acd964cc..7c8f966637 100644 --- a/builddefs/build_keyboard.mk +++ b/builddefs/build_keyboard.mk @@ -328,12 +328,18 @@ ifneq ("$(wildcard $(KEYBOARD_PATH_5)/info.json)","") endif CONFIG_H += $(KEYBOARD_OUTPUT)/src/info_config.h $(KEYBOARD_OUTPUT)/src/layouts.h +KEYBOARD_SRC += $(KEYBOARD_OUTPUT)/src/default_keyboard.c $(KEYBOARD_OUTPUT)/src/info_config.h: $(INFO_JSON_FILES) @$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD) $(eval CMD=$(QMK_BIN) generate-config-h --quiet --keyboard $(KEYBOARD) --output $(KEYBOARD_OUTPUT)/src/info_config.h) @$(BUILD_CMD) +$(KEYBOARD_OUTPUT)/src/default_keyboard.c: $(INFO_JSON_FILES) + @$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD) + $(eval CMD=$(QMK_BIN) generate-keyboard-c --quiet --keyboard $(KEYBOARD) --output $(KEYBOARD_OUTPUT)/src/default_keyboard.c) + @$(BUILD_CMD) + $(KEYBOARD_OUTPUT)/src/default_keyboard.h: $(INFO_JSON_FILES) @$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD) $(eval CMD=$(QMK_BIN) generate-keyboard-h --quiet --keyboard $(KEYBOARD) --output $(KEYBOARD_OUTPUT)/src/default_keyboard.h) @@ -344,7 +350,7 @@ $(KEYBOARD_OUTPUT)/src/layouts.h: $(INFO_JSON_FILES) $(eval CMD=$(QMK_BIN) generate-layouts --quiet --keyboard $(KEYBOARD) --output $(KEYBOARD_OUTPUT)/src/layouts.h) @$(BUILD_CMD) -generated-files: $(KEYBOARD_OUTPUT)/src/info_config.h $(KEYBOARD_OUTPUT)/src/default_keyboard.h $(KEYBOARD_OUTPUT)/src/layouts.h +generated-files: $(KEYBOARD_OUTPUT)/src/info_config.h $(KEYBOARD_OUTPUT)/src/default_keyboard.c $(KEYBOARD_OUTPUT)/src/default_keyboard.h $(KEYBOARD_OUTPUT)/src/layouts.h .INTERMEDIATE : generated-files diff --git a/data/schemas/keyboard.jsonschema b/data/schemas/keyboard.jsonschema index a8b3d06933..2820741691 100644 --- a/data/schemas/keyboard.jsonschema +++ b/data/schemas/keyboard.jsonschema @@ -193,6 +193,62 @@ "timeout": {"$ref": "qmk.definitions.v1#/unsigned_int"} } }, + "led_matrix": { + "type": "object", + "properties": { + "driver": {"type": "string"}, + "layout": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "matrix": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": { + "type": "number", + "min": 0, + "multipleOf": 1 + } + }, + "x": {"$ref": "qmk.definitions.v1#/key_unit"}, + "y": {"$ref": "qmk.definitions.v1#/key_unit"}, + "flags": {"$ref": "qmk.definitions.v1#/unsigned_decimal"} + } + } + } + } + }, + "rgb_matrix": { + "type": "object", + "properties": { + "driver": {"type": "string"}, + "layout": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "matrix": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": { + "type": "number", + "min": 0, + "multipleOf": 1 + } + }, + "x": {"$ref": "qmk.definitions.v1#/key_unit"}, + "y": {"$ref": "qmk.definitions.v1#/key_unit"}, + "flags": {"$ref": "qmk.definitions.v1#/unsigned_decimal"} + } + } + } + } + }, "rgblight": { "type": "object", "additionalProperties": false, diff --git a/lib/python/qmk/c_parse.py b/lib/python/qmk/c_parse.py index 72be690019..dcee19cbdf 100644 --- a/lib/python/qmk/c_parse.py +++ b/lib/python/qmk/c_parse.py @@ -1,5 +1,9 @@ """Functions for working with config.h files. """ +from pygments.lexers.c_cpp import CLexer +from pygments.token import Token +from pygments import lex +from itertools import islice from pathlib import Path import re @@ -13,6 +17,13 @@ multi_comment_regex = re.compile(r'/\*(.|\n)*?\*/', re.MULTILINE) layout_macro_define_regex = re.compile(r'^#\s*define') +def _get_chunks(it, size): + """Break down a collection into smaller parts + """ + it = iter(it) + return iter(lambda: tuple(islice(it, size)), ()) + + def strip_line_comment(string): """Removes comments from a single line string. """ @@ -170,3 +181,108 @@ def _parse_matrix_locations(matrix, file, macro_name): matrix_locations[identifier] = [row_num, col_num] return matrix_locations + + +def _coerce_led_token(_type, value): + """ Convert token to valid info.json content + """ + value_map = { + 'NO_LED': None, + 'LED_FLAG_ALL': 0xFF, + 'LED_FLAG_NONE': 0x00, + 'LED_FLAG_MODIFIER': 0x01, + 'LED_FLAG_UNDERGLOW': 0x02, + 'LED_FLAG_KEYLIGHT': 0x04, + 'LED_FLAG_INDICATOR': 0x08, + } + if _type is Token.Literal.Number.Integer: + return int(value) + if _type is Token.Literal.Number.Hex: + return int(value, 0) + if _type is Token.Name and value in value_map.keys(): + return value_map[value] + + +def _parse_led_config(file, matrix_cols, matrix_rows): + """Return any 'raw' led/rgb matrix config + """ + file_contents = file.read_text(encoding='utf-8') + file_contents = comment_remover(file_contents) + file_contents = file_contents.replace('\\\n', '') + + matrix_raw = [] + position_raw = [] + flags = [] + + found_led_config = False + bracket_count = 0 + section = 0 + for _type, value in lex(file_contents, CLexer()): + # Assume g_led_config..stuff..; + if value == 'g_led_config': + found_led_config = True + elif value == ';': + found_led_config = False + elif found_led_config: + # Assume bracket count hints to section of config we are within + if value == '{': + bracket_count += 1 + if bracket_count == 2: + section += 1 + elif value == '}': + bracket_count -= 1 + else: + # Assume any non whitespace value here is important enough to stash + if _type in [Token.Literal.Number.Integer, Token.Literal.Number.Hex, Token.Name]: + if section == 1 and bracket_count == 3: + matrix_raw.append(_coerce_led_token(_type, value)) + if section == 2 and bracket_count == 3: + position_raw.append(_coerce_led_token(_type, value)) + if section == 3 and bracket_count == 2: + flags.append(_coerce_led_token(_type, value)) + + # Slightly better intrim format + matrix = list(_get_chunks(matrix_raw, matrix_cols)) + position = list(_get_chunks(position_raw, 2)) + matrix_indexes = list(filter(lambda x: x is not None, matrix_raw)) + + # If we have not found anything - bail + if not section: + return None + + # TODO: Improve crude parsing/validation + if len(matrix) != matrix_rows and len(matrix) != (matrix_rows / 2): + raise ValueError("Unable to parse g_led_config matrix data") + if len(position) != len(flags): + raise ValueError("Unable to parse g_led_config position data") + if len(matrix_indexes) and (max(matrix_indexes) >= len(flags)): + raise ValueError("OOB within g_led_config matrix data") + + return (matrix, position, flags) + + +def find_led_config(file, matrix_cols, matrix_rows): + """Search file for led/rgb matrix config + """ + found = _parse_led_config(file, matrix_cols, matrix_rows) + if not found: + return None + + # Expand collected content + (matrix, position, flags) = found + + # Align to output format + led_config = [] + for index, item in enumerate(position, start=0): + led_config.append({ + 'x': item[0], + 'y': item[1], + 'flags': flags[index], + }) + for r in range(len(matrix)): + for c in range(len(matrix[r])): + index = matrix[r][c] + if index is not None: + led_config[index]['matrix'] = [r, c] + + return led_config diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py index ee4321a2bb..6e25c89780 100644 --- a/lib/python/qmk/cli/__init__.py +++ b/lib/python/qmk/cli/__init__.py @@ -52,6 +52,7 @@ subcommands = [ 'qmk.cli.generate.dfu_header', 'qmk.cli.generate.docs', 'qmk.cli.generate.info_json', + 'qmk.cli.generate.keyboard_c', 'qmk.cli.generate.keyboard_h', 'qmk.cli.generate.layouts', 'qmk.cli.generate.rgb_breathe_table', diff --git a/lib/python/qmk/cli/generate/keyboard_c.py b/lib/python/qmk/cli/generate/keyboard_c.py new file mode 100755 index 0000000000..7f74eb9b42 --- /dev/null +++ b/lib/python/qmk/cli/generate/keyboard_c.py @@ -0,0 +1,65 @@ +"""Used by the make system to generate keyboard.c from info.json. +""" +from milc import cli + +from qmk.info import info_json +from qmk.commands import dump_lines +from qmk.keyboard import keyboard_completer, keyboard_folder +from qmk.path import normpath +from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE + + +def _gen_led_config(info_data): + """Convert info.json content to g_led_config + """ + cols = info_data['matrix_size']['cols'] + rows = info_data['matrix_size']['rows'] + + if 'layout' in info_data.get('rgb_matrix', {}): + led_config = info_data['rgb_matrix']['layout'] + elif 'layout' in info_data.get('led_matrix', {}): + led_config = info_data['led_matrix']['layout'] + + lines = [] + if not led_config: + return lines + + matrix = [['NO_PIN'] * cols for i in range(rows)] + pos = [] + flags = [] + + for index, item in enumerate(led_config, start=0): + if 'matrix' in item: + (x, y) = item['matrix'] + matrix[x][y] = str(index) + pos.append(f'{{ {item.get("x", 0)},{item.get("y", 0)} }}') + flags.append(str(item.get('flags', 0))) + + lines.append('__attribute__ ((weak)) led_config_t g_led_config = {') + lines.append(' {') + for line in matrix: + lines.append(f' {{ {",".join(line)} }},') + lines.append(' },') + lines.append(f' {{ {",".join(pos)} }},') + lines.append(f' {{ {",".join(flags)} }},') + lines.append('};') + + return lines + + +@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to') +@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") +@cli.argument('-kb', '--keyboard', arg_only=True, type=keyboard_folder, completer=keyboard_completer, required=True, help='Keyboard to generate keyboard.c for.') +@cli.subcommand('Used by the make system to generate keyboard.c from info.json', hidden=True) +def generate_keyboard_c(cli): + """Generates the keyboard.h file. + """ + kb_info_json = info_json(cli.args.keyboard) + + # Build the layouts.h file. + keyboard_h_lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#include QMK_KEYBOARD_H', ''] + + keyboard_h_lines.extend(_gen_led_config(kb_info_json)) + + # Show the results + dump_lines(cli.args.output, keyboard_h_lines, cli.args.quiet) diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index 1d81b3e94b..3e896b90ee 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -8,7 +8,7 @@ from dotty_dict import dotty from milc import cli from qmk.constants import CHIBIOS_PROCESSORS, LUFA_PROCESSORS, VUSB_PROCESSORS -from qmk.c_parse import find_layouts, parse_config_h_file +from qmk.c_parse import find_layouts, parse_config_h_file, find_led_config from qmk.json_schema import deep_update, json_load, validate from qmk.keyboard import config_h, rules_mk from qmk.keymap import list_keymaps, locate_keymap @@ -75,6 +75,9 @@ def info_json(keyboard): # Ensure that we have matrix row and column counts info_data = _matrix_size(info_data) + # Merge in data from + info_data = _extract_led_config(info_data, str(keyboard)) + # Validate against the jsonschema try: validate(info_data, 'qmk.api.keyboard.v1') @@ -543,6 +546,46 @@ def _extract_rules_mk(info_data, rules): return info_data +def find_keyboard_c(keyboard): + """Find all .c files + """ + keyboard = Path(keyboard) + current_path = Path('keyboards/') + + files = [] + for directory in keyboard.parts: + current_path = current_path / directory + keyboard_c_path = current_path / f'{directory}.c' + if keyboard_c_path.exists(): + files.append(keyboard_c_path) + + return files + + +def _extract_led_config(info_data, keyboard): + """Scan all .c files for led config + """ + cols = info_data['matrix_size']['cols'] + rows = info_data['matrix_size']['rows'] + + # Assume what feature owns g_led_config + feature = "rgb_matrix" + if info_data.get("features", {}).get("led_matrix", False): + feature = "led_matrix" + + # Process + for file in find_keyboard_c(keyboard): + try: + ret = find_led_config(file, cols, rows) + if ret: + info_data[feature] = info_data.get(feature, {}) + info_data[feature]["layout"] = ret + except Exception as e: + _log_warning(info_data, f'led_config: {file.name}: {e}') + + return info_data + + def _matrix_size(info_data): """Add info_data['matrix_size'] if it doesn't exist. """ diff --git a/lib/python/qmk/json_encoders.py b/lib/python/qmk/json_encoders.py index 40a5c1dea8..f968b3dbb2 100755 --- a/lib/python/qmk/json_encoders.py +++ b/lib/python/qmk/json_encoders.py @@ -75,8 +75,8 @@ class InfoJSONEncoder(QMKJSONEncoder): """Encode info.json dictionaries. """ if obj: - if self.indentation_level == 4: - # These are part of a layout, put them on a single line. + if set(("x", "y")).issubset(obj.keys()): + # These are part of a layout/led_config, put them on a single line. return "{ " + ", ".join(f"{self.encode(key)}: {self.encode(element)}" for key, element in sorted(obj.items())) + " }" else: From a65ea1a71158797e349416749ce4d48443ce9a5a Mon Sep 17 00:00:00 2001 From: zvecr Date: Sun, 27 Mar 2022 15:26:48 +0100 Subject: [PATCH 28/77] Fix codegen for non led boards --- lib/python/qmk/cli/generate/keyboard_c.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/python/qmk/cli/generate/keyboard_c.py b/lib/python/qmk/cli/generate/keyboard_c.py index 7f74eb9b42..c03c569c16 100755 --- a/lib/python/qmk/cli/generate/keyboard_c.py +++ b/lib/python/qmk/cli/generate/keyboard_c.py @@ -15,6 +15,7 @@ def _gen_led_config(info_data): cols = info_data['matrix_size']['cols'] rows = info_data['matrix_size']['rows'] + led_config = None if 'layout' in info_data.get('rgb_matrix', {}): led_config = info_data['rgb_matrix']['layout'] elif 'layout' in info_data.get('led_matrix', {}): From 22b82992303f2edf9fc95abefc35d423fe80f144 Mon Sep 17 00:00:00 2001 From: zvecr Date: Wed, 30 Mar 2022 00:43:25 +0100 Subject: [PATCH 29/77] Fix up print_dotted_output dict handling --- lib/python/qmk/cli/xap/xap.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/python/qmk/cli/xap/xap.py b/lib/python/qmk/cli/xap/xap.py index 7826ed33d6..13fc33de1a 100644 --- a/lib/python/qmk/cli/xap/xap.py +++ b/lib/python/qmk/cli/xap/xap.py @@ -38,7 +38,12 @@ def print_dotted_output(kb_info_json, prefix=''): elif isinstance(kb_info_json[key], dict): print_dotted_output(kb_info_json[key], new_prefix) elif isinstance(kb_info_json[key], list): - cli.echo(' {fg_blue}%s{fg_reset}: %s', new_prefix, ', '.join(map(str, sorted(kb_info_json[key])))) + data = kb_info_json[key] + if len(data) and isinstance(data[0], dict): + for index, item in enumerate(data, start=0): + cli.echo(' {fg_blue}%s.%s{fg_reset}: %s', new_prefix, index, str(item)) + else: + cli.echo(' {fg_blue}%s{fg_reset}: %s', new_prefix, ', '.join(sorted(map(str, data)))) else: cli.echo(' {fg_blue}%s{fg_reset}: %s', new_prefix, kb_info_json[key]) From 7f128c52865d6aa978ad74e0d96e3fd6ff78fdd7 Mon Sep 17 00:00:00 2001 From: zvecr Date: Wed, 30 Mar 2022 02:09:19 +0100 Subject: [PATCH 30/77] Remove some assumptions on packet format --- quantum/xap/xap.h | 12 ++++++++++++ tmk_core/protocol/chibios/usb_main.c | 26 ++++++++++++++------------ tmk_core/protocol/lufa/lufa.c | 24 +++++++++++++----------- tmk_core/protocol/vusb/vusb.c | 24 +++++++++++++----------- 4 files changed, 52 insertions(+), 34 deletions(-) diff --git a/quantum/xap/xap.h b/quantum/xap/xap.h index f3cab6aef2..67e41d31ea 100644 --- a/quantum/xap/xap.h +++ b/quantum/xap/xap.h @@ -41,4 +41,16 @@ bool xap_respond_data_P(xap_token_t token, const void *data, size_t length); void xap_send(xap_token_t token, xap_response_flags_t response_flags, const void *data, size_t length); +// TODO: gen from xap defs? +typedef struct { + xap_token_t token; + uint8_t length; +} xap_request_header_t; + +typedef struct { + xap_token_t token; + xap_response_flags_t flags; + uint8_t length; +} xap_response_header_t; + #include diff --git a/tmk_core/protocol/chibios/usb_main.c b/tmk_core/protocol/chibios/usb_main.c index a95ca79da9..32de4622c1 100644 --- a/tmk_core/protocol/chibios/usb_main.c +++ b/tmk_core/protocol/chibios/usb_main.c @@ -113,7 +113,7 @@ static const USBDescriptor *usb_get_descriptor_cb(USBDriver *usbp, uint8_t dtype static USBDescriptor desc; uint16_t wValue = ((uint16_t)dtype << 8) | dindex; desc.ud_string = NULL; - desc.ud_size = get_usb_descriptor(wValue, wIndex, (const void **const) & desc.ud_string); + desc.ud_size = get_usb_descriptor(wValue, wIndex, (const void **const)&desc.ud_string); if (desc.ud_string == NULL) return NULL; else @@ -1138,25 +1138,27 @@ void xap_send_base(uint8_t *data, uint8_t length) { } void xap_send(xap_token_t token, uint8_t response_flags, const void *data, size_t length) { - uint8_t rdata[XAP_EPSIZE] = {0}; - *(xap_token_t *)&rdata[0] = token; - if (length > (XAP_EPSIZE - 4)) response_flags &= ~(XAP_RESPONSE_FLAG_SUCCESS); - rdata[2] = response_flags; + uint8_t rdata[XAP_EPSIZE] = {0}; + xap_response_header_t *header = (xap_response_header_t *)&rdata[0]; + header->token = token; + + if (length > (XAP_EPSIZE - sizeof(xap_response_header_t))) response_flags &= ~(XAP_RESPONSE_FLAG_SUCCESS); + header->flags = response_flags; + if (response_flags & (XAP_RESPONSE_FLAG_SUCCESS)) { - rdata[3] = (uint8_t)length; + header->length = (uint8_t)length; if (data != NULL) { - memcpy(&rdata[4], data, length); + memcpy(&rdata[sizeof(xap_response_header_t)], data, length); } } xap_send_base(rdata, sizeof(rdata)); } void xap_receive_base(const void *data) { - const uint8_t *u8data = (const uint8_t *)data; - xap_token_t token = *(xap_token_t *)&u8data[0]; - uint8_t length = u8data[2]; - if (length <= (XAP_EPSIZE - 3)) { - xap_receive(token, &u8data[3], length); + const uint8_t *u8data = (const uint8_t *)data; + xap_request_header_t *header = (xap_request_header_t *)&u8data[0]; + if (header->length <= (XAP_EPSIZE - sizeof(xap_request_header_t))) { + xap_receive(header->token, &u8data[sizeof(xap_request_header_t)], header->length); } } diff --git a/tmk_core/protocol/lufa/lufa.c b/tmk_core/protocol/lufa/lufa.c index 0dcf1036c4..b3fe89c319 100644 --- a/tmk_core/protocol/lufa/lufa.c +++ b/tmk_core/protocol/lufa/lufa.c @@ -244,25 +244,27 @@ void xap_send_base(uint8_t *data, uint8_t length) { } void xap_send(xap_token_t token, uint8_t response_flags, const void *data, size_t length) { - uint8_t rdata[XAP_EPSIZE] = {0}; - *(xap_token_t *)&rdata[0] = token; - if (length > (XAP_EPSIZE - 4)) response_flags &= ~(XAP_RESPONSE_FLAG_SUCCESS); - rdata[2] = response_flags; + uint8_t rdata[XAP_EPSIZE] = {0}; + xap_response_header_t *header = (xap_response_header_t *)&rdata[0]; + header->token = token; + + if (length > (XAP_EPSIZE - sizeof(xap_response_header_t))) response_flags &= ~(XAP_RESPONSE_FLAG_SUCCESS); + header->flags = response_flags; + if (response_flags & (XAP_RESPONSE_FLAG_SUCCESS)) { - rdata[3] = (uint8_t)length; + header->length = (uint8_t)length; if (data != NULL) { - memcpy(&rdata[4], data, length); + memcpy(&rdata[sizeof(xap_response_header_t)], data, length); } } xap_send_base(rdata, sizeof(rdata)); } void xap_receive_base(const void *data) { - const uint8_t *u8data = (const uint8_t *)data; - xap_token_t token = *(xap_token_t *)&u8data[0]; - uint8_t length = u8data[2]; - if (length <= (XAP_EPSIZE - 3)) { - xap_receive(token, &u8data[3], length); + const uint8_t *u8data = (const uint8_t *)data; + xap_request_header_t *header = (xap_request_header_t *)&u8data[0]; + if (header->length <= (XAP_EPSIZE - sizeof(xap_request_header_t))) { + xap_receive(header->token, &u8data[sizeof(xap_request_header_t)], header->length); } } diff --git a/tmk_core/protocol/vusb/vusb.c b/tmk_core/protocol/vusb/vusb.c index 8e120b4ca9..6cf451144f 100644 --- a/tmk_core/protocol/vusb/vusb.c +++ b/tmk_core/protocol/vusb/vusb.c @@ -205,25 +205,27 @@ void xap_send_base(uint8_t *data, uint8_t length) { } void xap_send(xap_token_t token, uint8_t response_flags, const void *data, size_t length) { - uint8_t rdata[XAP_BUFFER_SIZE] = {0}; - *(xap_token_t *)&rdata[0] = token; - if (length > (XAP_BUFFER_SIZE - 4)) response_flags &= ~(XAP_RESPONSE_FLAG_SUCCESS); - rdata[2] = response_flags; + uint8_t rdata[XAP_BUFFER_SIZE] = {0}; + xap_response_header_t *header = (xap_response_header_t *)&rdata[0]; + header->token = token; + + if (length > (XAP_BUFFER_SIZE - sizeof(xap_response_header_t))) response_flags &= ~(XAP_RESPONSE_FLAG_SUCCESS); + header->flags = response_flags; + if (response_flags & (XAP_RESPONSE_FLAG_SUCCESS)) { - rdata[3] = (uint8_t)length; + header->length = (uint8_t)length; if (data != NULL) { - memcpy(&rdata[4], data, length); + memcpy(&rdata[sizeof(xap_response_header_t)], data, length); } } xap_send_base(rdata, sizeof(rdata)); } void xap_receive_base(const void *data) { - const uint8_t *u8data = (const uint8_t *)data; - xap_token_t token = *(xap_token_t *)&u8data[0]; - uint8_t length = u8data[2]; - if (length <= (XAP_BUFFER_SIZE - 3)) { - xap_receive(token, &u8data[3], length); + const uint8_t *u8data = (const uint8_t *)data; + xap_request_header_t *header = (xap_request_header_t *)&u8data[0]; + if (header->length <= (XAP_BUFFER_SIZE - sizeof(xap_request_header_t))) { + xap_receive(header->token, &u8data[sizeof(xap_request_header_t)], header->length); } } From 51e09235a2f477ec53972e15c70b439914d8d50d Mon Sep 17 00:00:00 2001 From: zvecr Date: Wed, 30 Mar 2022 02:28:03 +0100 Subject: [PATCH 31/77] clang --- tmk_core/protocol/chibios/usb_main.c | 4 ++-- tmk_core/protocol/lufa/lufa.c | 2 +- tmk_core/protocol/vusb/vusb.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tmk_core/protocol/chibios/usb_main.c b/tmk_core/protocol/chibios/usb_main.c index 32de4622c1..53116d1562 100644 --- a/tmk_core/protocol/chibios/usb_main.c +++ b/tmk_core/protocol/chibios/usb_main.c @@ -113,7 +113,7 @@ static const USBDescriptor *usb_get_descriptor_cb(USBDriver *usbp, uint8_t dtype static USBDescriptor desc; uint16_t wValue = ((uint16_t)dtype << 8) | dindex; desc.ud_string = NULL; - desc.ud_size = get_usb_descriptor(wValue, wIndex, (const void **const)&desc.ud_string); + desc.ud_size = get_usb_descriptor(wValue, wIndex, (const void **const) & desc.ud_string); if (desc.ud_string == NULL) return NULL; else @@ -1155,7 +1155,7 @@ void xap_send(xap_token_t token, uint8_t response_flags, const void *data, size_ } void xap_receive_base(const void *data) { - const uint8_t *u8data = (const uint8_t *)data; + const uint8_t * u8data = (const uint8_t *)data; xap_request_header_t *header = (xap_request_header_t *)&u8data[0]; if (header->length <= (XAP_EPSIZE - sizeof(xap_request_header_t))) { xap_receive(header->token, &u8data[sizeof(xap_request_header_t)], header->length); diff --git a/tmk_core/protocol/lufa/lufa.c b/tmk_core/protocol/lufa/lufa.c index b3fe89c319..969bf1ab21 100644 --- a/tmk_core/protocol/lufa/lufa.c +++ b/tmk_core/protocol/lufa/lufa.c @@ -261,7 +261,7 @@ void xap_send(xap_token_t token, uint8_t response_flags, const void *data, size_ } void xap_receive_base(const void *data) { - const uint8_t *u8data = (const uint8_t *)data; + const uint8_t * u8data = (const uint8_t *)data; xap_request_header_t *header = (xap_request_header_t *)&u8data[0]; if (header->length <= (XAP_EPSIZE - sizeof(xap_request_header_t))) { xap_receive(header->token, &u8data[sizeof(xap_request_header_t)], header->length); diff --git a/tmk_core/protocol/vusb/vusb.c b/tmk_core/protocol/vusb/vusb.c index 6cf451144f..49d59f1574 100644 --- a/tmk_core/protocol/vusb/vusb.c +++ b/tmk_core/protocol/vusb/vusb.c @@ -222,7 +222,7 @@ void xap_send(xap_token_t token, uint8_t response_flags, const void *data, size_ } void xap_receive_base(const void *data) { - const uint8_t *u8data = (const uint8_t *)data; + const uint8_t * u8data = (const uint8_t *)data; xap_request_header_t *header = (xap_request_header_t *)&u8data[0]; if (header->length <= (XAP_BUFFER_SIZE - sizeof(xap_request_header_t))) { xap_receive(header->token, &u8data[sizeof(xap_request_header_t)], header->length); From 6269c6b51c0f1bcedd226a65f5efeeadb4b51815 Mon Sep 17 00:00:00 2001 From: zvecr Date: Wed, 30 Mar 2022 23:19:56 +0100 Subject: [PATCH 32/77] partial gen for return_execute --- data/xap/xap_0.1.0.hjson | 2 +- lib/python/qmk/cli/xap/xap.py | 6 ++-- .../qmk/xap/gen_firmware/inline_generator.py | 36 +++++++++++++++++-- quantum/xap/xap.c | 10 +++--- quantum/xap/xap_handlers.c | 15 -------- 5 files changed, 43 insertions(+), 26 deletions(-) diff --git a/data/xap/xap_0.1.0.hjson b/data/xap/xap_0.1.0.hjson index 8fdbcc02a6..38d36dddfa 100755 --- a/data/xap/xap_0.1.0.hjson +++ b/data/xap/xap_0.1.0.hjson @@ -234,7 +234,7 @@ request_type: u16 request_purpose: offset return_type: u8[32] - return_execute: info_json_gz + return_execute: get_info_json_chunk } } }, diff --git a/lib/python/qmk/cli/xap/xap.py b/lib/python/qmk/cli/xap/xap.py index 13fc33de1a..3eb107d784 100644 --- a/lib/python/qmk/cli/xap/xap.py +++ b/lib/python/qmk/cli/xap/xap.py @@ -51,7 +51,7 @@ def print_dotted_output(kb_info_json, prefix=''): def _xap_transaction(device, sub, route, ret_len, *args): # gen token tok = random.getrandbits(16) - token = tok.to_bytes(2, byteorder='big') + token = tok.to_bytes(2, byteorder='little') # send with padding # TODO: this code is total garbage @@ -59,13 +59,13 @@ def _xap_transaction(device, sub, route, ret_len, *args): args_len = 2 if len(args) == 1: args_len += 2 - args_data = args[0].to_bytes(2, byteorder='big') + args_data = args[0].to_bytes(2, byteorder='little') padding_len = 64 - 3 - args_len padding = b"\x00" * padding_len if args_data: padding = args_data + padding - buffer = token + args_len.to_bytes(1, byteorder='big') + sub.to_bytes(1, byteorder='big') + route.to_bytes(1, byteorder='big') + padding + buffer = token + args_len.to_bytes(1, byteorder='little') + sub.to_bytes(1, byteorder='little') + route.to_bytes(1, byteorder='little') + padding # prepend 0 on windows because reasons... if 'windows' in platform().lower(): diff --git a/lib/python/qmk/xap/gen_firmware/inline_generator.py b/lib/python/qmk/xap/gen_firmware/inline_generator.py index 8563bfdc69..914e31d0c4 100755 --- a/lib/python/qmk/xap/gen_firmware/inline_generator.py +++ b/lib/python/qmk/xap/gen_firmware/inline_generator.py @@ -24,6 +24,20 @@ def _get_c_type(xap_type): return 'unknown' +def _get_c_size(xap_type): + if xap_type == 'u8': + return 1 + elif xap_type == 'u16': + return 2 + elif xap_type == 'u32': + return 4 + elif xap_type == 'u64': + return 8 + elif xap_type == 'u8[32]': + return 32 + return -1 + + def _get_route_type(container): if 'routes' in container: return 'XAP_ROUTE' @@ -52,8 +66,26 @@ def _append_routing_table_declaration(lines, container, container_id, route_stac elif 'return_execute' in container: execute = container['return_execute'] - lines.append('') - lines.append(f'bool xap_respond_{execute}(xap_token_t token, const uint8_t *data, size_t data_len);') + request_type = container['request_type'] + return_type = container['return_type'] + lines.append(f''' +bool xap_respond_{execute}(xap_token_t token, const uint8_t *data, size_t data_len) {{ + if (data_len != sizeof({_get_c_type(request_type)})) {{ + xap_respond_failure(token, 0); + return false; + }} + + uint8_t ret[{_get_c_size(return_type)}] = {{0}}; + {_get_c_type(request_type)} *argp = ({_get_c_type(request_type)} *)&data[0]; + + bool {execute}({_get_c_type(request_type)} arg, uint8_t *ret, uint8_t ret_len); + if(!{execute}(*argp, ret, sizeof(ret))) {{ + xap_respond_failure(token, 0); + return false; + }} + + return xap_respond_data(token, ret, sizeof(ret)); +}}''') elif 'return_constant' in container: diff --git a/quantum/xap/xap.c b/quantum/xap/xap.c index f468eac9b4..fce5014c62 100644 --- a/quantum/xap/xap.c +++ b/quantum/xap/xap.c @@ -18,13 +18,13 @@ #include #include "info_json_gz.h" -void get_info_json_chunk(uint8_t *data, size_t offset) { - uint8_t len = 32; - if (offset + len > info_json_gz_len) { - len = info_json_gz_len - offset; +bool get_info_json_chunk(uint16_t offset, uint8_t *data, uint8_t data_len) { + if (offset + data_len > info_json_gz_len) { + data_len = info_json_gz_len - offset; } - memcpy_P(data, &info_json_gz[offset], len); + memcpy_P(data, &info_json_gz[offset], data_len); + return true; } #define QSTR2(z) #z diff --git a/quantum/xap/xap_handlers.c b/quantum/xap/xap_handlers.c index f21cdab316..811e5439d4 100644 --- a/quantum/xap/xap_handlers.c +++ b/quantum/xap/xap_handlers.c @@ -40,18 +40,3 @@ bool xap_respond_u32(xap_token_t token, uint32_t value) { uint32_t xap_route_qmk_ffffffffffffffff_getter(void) { return 0x12345678; } - -bool xap_respond_info_json_gz(xap_token_t token, const uint8_t *data, size_t data_len) { - if (data_len != 2) { - xap_respond_failure(token, 0); - return false; - } - - uint8_t blob[32] = {0}; - uint16_t offset = ((uint16_t)data[0]) << 8 | data[1]; - - void get_info_json_chunk(uint8_t * data, size_t offset); - get_info_json_chunk(blob, offset); - - return xap_respond_data(token, blob, 32); -} From 73d2228524ff9efc774a6d15f78f571c3560d205 Mon Sep 17 00:00:00 2001 From: zvecr Date: Wed, 30 Mar 2022 23:25:33 +0100 Subject: [PATCH 33/77] format --- lib/python/qmk/xap/gen_firmware/inline_generator.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/python/qmk/xap/gen_firmware/inline_generator.py b/lib/python/qmk/xap/gen_firmware/inline_generator.py index 914e31d0c4..9cee29b455 100755 --- a/lib/python/qmk/xap/gen_firmware/inline_generator.py +++ b/lib/python/qmk/xap/gen_firmware/inline_generator.py @@ -68,7 +68,8 @@ def _append_routing_table_declaration(lines, container, container_id, route_stac execute = container['return_execute'] request_type = container['request_type'] return_type = container['return_type'] - lines.append(f''' + lines.append( + f''' bool xap_respond_{execute}(xap_token_t token, const uint8_t *data, size_t data_len) {{ if (data_len != sizeof({_get_c_type(request_type)})) {{ xap_respond_failure(token, 0); @@ -85,7 +86,8 @@ bool xap_respond_{execute}(xap_token_t token, const uint8_t *data, size_t data_l }} return xap_respond_data(token, ret, sizeof(ret)); -}}''') +}}''' + ) elif 'return_constant' in container: From 646fdc7d170d76ccb6885434814191b2012683b8 Mon Sep 17 00:00:00 2001 From: zvecr Date: Thu, 31 Mar 2022 01:18:26 +0100 Subject: [PATCH 34/77] Rework code gen for return of dynamic variables --- data/xap/xap_0.1.0.hjson | 16 ++++++++++++ lib/python/qmk/cli/xap/xap.py | 11 +++++--- .../qmk/xap/gen_firmware/inline_generator.py | 26 ++++++++++++++----- quantum/xap/xap.c | 23 +++++++++------- quantum/xap/xap.h | 1 + 5 files changed, 58 insertions(+), 19 deletions(-) diff --git a/data/xap/xap_0.1.0.hjson b/data/xap/xap_0.1.0.hjson index 38d36dddfa..82dffb192b 100755 --- a/data/xap/xap_0.1.0.hjson +++ b/data/xap/xap_0.1.0.hjson @@ -127,6 +127,22 @@ return_purpose: capabilities return_constant: XAP_ROUTE_CAPABILITIES } + 0x03: { + type: command + name: Secure Status + define: SECURE_STATUS + description: + ''' + Query secure route status + + * 0 means secure routes are disabled + * 1 means unlock sequence initiated but incomplete + * 2 means secure routes are allowed + * any other value should be interpreted as disabled + ''' + return_type: u8 + return_value: secure_status + } } }, diff --git a/lib/python/qmk/cli/xap/xap.py b/lib/python/qmk/cli/xap/xap.py index 3eb107d784..3626512829 100644 --- a/lib/python/qmk/cli/xap/xap.py +++ b/lib/python/qmk/cli/xap/xap.py @@ -82,7 +82,7 @@ def _xap_transaction(device, sub, route, ret_len, *args): return array_alpha[4:] -def _query_device_version(device): +def _query_device(device): ver_data = _xap_transaction(device, 0x00, 0x00, 4) if not ver_data: return {'xap': 'UNKNOWN'} @@ -91,7 +91,10 @@ def _query_device_version(device): a = (ver_data[3] << 24) + (ver_data[2] << 16) + (ver_data[1] << 8) + (ver_data[0]) ver = f'{a>>24}.{a>>16 & 0xFF}.{a & 0xFFFF}' - return {'xap': ver} + secure = int.from_bytes(_xap_transaction(device, 0x00, 0x03, 1), 'little') + secure = 'unlocked' if secure == 2 else 'LOCKED' + + return {'xap': ver, 'secure': secure} def _query_device_info_len(device): @@ -129,8 +132,8 @@ def _list_devices(): for dev in devices: device = hid.Device(path=dev['path']) - data = _query_device_version(device) - cli.log.info(" %04x:%04x %s %s [API:%s]", dev['vendor_id'], dev['product_id'], dev['manufacturer_string'], dev['product_string'], data['xap']) + data = _query_device(device) + cli.log.info(" %04x:%04x %s %s [API:%s] %s", dev['vendor_id'], dev['product_id'], dev['manufacturer_string'], dev['product_string'], data['xap'], data['secure']) if cli.config.general.verbose: # TODO: better formatting like "lsusb -v"? diff --git a/lib/python/qmk/xap/gen_firmware/inline_generator.py b/lib/python/qmk/xap/gen_firmware/inline_generator.py index 9cee29b455..93bb6c7afd 100755 --- a/lib/python/qmk/xap/gen_firmware/inline_generator.py +++ b/lib/python/qmk/xap/gen_firmware/inline_generator.py @@ -43,9 +43,12 @@ def _get_route_type(container): return 'XAP_ROUTE' elif 'return_execute' in container: return 'XAP_EXECUTE' + elif 'return_value' in container: + if container['return_type'] == 'u8': + return 'XAP_VALUE' elif 'return_constant' in container: if container['return_type'] == 'u32': - return 'XAP_VALUE' + return 'XAP_CONST_MEM' elif container['return_type'] == 'struct': return 'XAP_CONST_MEM' elif container['return_type'] == 'string': @@ -89,10 +92,18 @@ bool xap_respond_{execute}(xap_token_t token, const uint8_t *data, size_t data_l }}''' ) + # elif 'return_value' in container: + # value = container['return_value'] + # return_type = container['return_type'] + # lines.append('') + # lines.append(f'{_get_c_type(return_type)} {value} = 0;') + elif 'return_constant' in container: if container['return_type'] == 'u32': - pass + constant = container['return_constant'] + lines.append('') + lines.append(f'static const uint32_t {route_name}_data PROGMEM = {constant};') elif container['return_type'] == 'struct': lines.append('') @@ -146,9 +157,10 @@ def _append_routing_table_entry_execute(lines, container, container_id, route_st lines.append(f' .handler = xap_respond_{value},') -def _append_routing_table_entry_u32value(lines, container, container_id, route_stack): - value = container['return_constant'] - lines.append(f' .u32value = {value},') +def _append_routing_table_entry_value(lines, container, container_id, route_stack): + value = container['return_value'] + lines.append(f' .const_data = &{value},') + lines.append(f' .const_data_len = sizeof({value}),') def _append_routing_table_entry_u32getter(lines, container, container_id, route_stack): @@ -183,9 +195,11 @@ def _append_routing_table_entry(lines, container, container_id, route_stack): _append_routing_table_entry_route(lines, container, container_id, route_stack) elif 'return_execute' in container: _append_routing_table_entry_execute(lines, container, container_id, route_stack) + elif 'return_value' in container: + _append_routing_table_entry_value(lines, container, container_id, route_stack) elif 'return_constant' in container: if container['return_type'] == 'u32': - _append_routing_table_entry_u32value(lines, container, container_id, route_stack) + _append_routing_table_entry_const_data(lines, container, container_id, route_stack) elif container['return_type'] == 'struct': _append_routing_table_entry_const_data(lines, container, container_id, route_stack) elif container['return_type'] == 'string': diff --git a/quantum/xap/xap.c b/quantum/xap/xap.c index fce5014c62..333e53e845 100644 --- a/quantum/xap/xap.c +++ b/quantum/xap/xap.c @@ -27,6 +27,8 @@ bool get_info_json_chunk(uint16_t offset, uint8_t *data, uint8_t data_len) { return true; } +uint8_t secure_status = 2; + #define QSTR2(z) #z #define QSTR(z) QSTR2(z) @@ -63,15 +65,12 @@ struct __attribute__((packed)) xap_route_t { // XAP_EXECUTE bool (*handler)(xap_token_t token, const uint8_t *data, size_t data_len); - // XAP_VALUE - uint32_t u32value; - // XAP_GETTER uint32_t (*u32getter)(void); - // XAP_CONST_MEM + // XAP_VALUE / XAP_CONST_MEM struct { - const void * const_data; + const void *const_data; const uint8_t const_data_len; }; }; @@ -86,6 +85,12 @@ void xap_execute_route(xap_token_t token, const xap_route_t *routes, size_t max_ if (id < max_routes) { xap_route_t route; memcpy_P(&route, &routes[id], sizeof(xap_route_t)); + + if (route.flags.is_secure && secure_status != 2) { + xap_respond_failure(token, XAP_RESPONSE_FLAG_SECURE_FAILURE); + return; + } + switch (route.flags.type) { case XAP_ROUTE: if (route.child_routes != NULL && route.child_routes_len > 0 && data_len > 0) { @@ -101,14 +106,14 @@ void xap_execute_route(xap_token_t token, const xap_route_t *routes, size_t max_ } break; - case XAP_VALUE: - xap_respond_u32(token, route.u32value); - return; - case XAP_GETTER: xap_respond_u32(token, (route.u32getter)()); return; + case XAP_VALUE: + xap_respond_data(token, route.const_data, route.const_data_len); + return; + case XAP_CONST_MEM: xap_respond_data_P(token, route.const_data, route.const_data_len); return; diff --git a/quantum/xap/xap.h b/quantum/xap/xap.h index 67e41d31ea..828531a92b 100644 --- a/quantum/xap/xap.h +++ b/quantum/xap/xap.h @@ -33,6 +33,7 @@ typedef uint16_t xap_token_t; #define XAP_RESPONSE_FLAG_FAILED 0 #define XAP_RESPONSE_FLAG_SUCCESS (1 << 0) +#define XAP_RESPONSE_FLAG_SECURE_FAILURE (1 << 1) void xap_respond_failure(xap_token_t token, xap_response_flags_t response_flags); bool xap_respond_u32(xap_token_t token, uint32_t value); From fe1a4a52d421a4c9cbcd972e1aa284063dad485d Mon Sep 17 00:00:00 2001 From: zvecr Date: Thu, 31 Mar 2022 01:23:19 +0100 Subject: [PATCH 35/77] clang --- quantum/xap/xap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quantum/xap/xap.c b/quantum/xap/xap.c index 333e53e845..98a6c9902a 100644 --- a/quantum/xap/xap.c +++ b/quantum/xap/xap.c @@ -70,7 +70,7 @@ struct __attribute__((packed)) xap_route_t { // XAP_VALUE / XAP_CONST_MEM struct { - const void *const_data; + const void * const_data; const uint8_t const_data_len; }; }; From 0f5ced0521fb54095cc88249a8ffebf10ca17221 Mon Sep 17 00:00:00 2001 From: zvecr Date: Thu, 31 Mar 2022 19:14:35 +0100 Subject: [PATCH 36/77] claim back a few bytes --- quantum/xap/xap.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/quantum/xap/xap.c b/quantum/xap/xap.c index 98a6c9902a..a5d8b3cc0d 100644 --- a/quantum/xap/xap.c +++ b/quantum/xap/xap.c @@ -107,8 +107,12 @@ void xap_execute_route(xap_token_t token, const xap_route_t *routes, size_t max_ break; case XAP_GETTER: - xap_respond_u32(token, (route.u32getter)()); - return; + if (route.u32getter != NULL) { + const uint32_t ret = (route.u32getter)(); + xap_respond_data(token, &ret, sizeof(ret)); + return; + } + break; case XAP_VALUE: xap_respond_data(token, route.const_data, route.const_data_len); From 81a53ac5b624c90c27310d1c6b9daabe1a8ad565 Mon Sep 17 00:00:00 2001 From: zvecr Date: Thu, 31 Mar 2022 21:08:18 +0100 Subject: [PATCH 37/77] gen RESPONSE_FLAG defines --- data/xap/xap_0.0.1.hjson | 2 +- .../qmk/xap/gen_firmware/header_generator.py | 36 +++++++++++++++++++ quantum/xap/xap.h | 22 +----------- 3 files changed, 38 insertions(+), 22 deletions(-) diff --git a/data/xap/xap_0.0.1.hjson b/data/xap/xap_0.0.1.hjson index 9b19526244..328207768e 100755 --- a/data/xap/xap_0.0.1.hjson +++ b/data/xap/xap_0.0.1.hjson @@ -132,7 +132,7 @@ } response_flags: { - define_prefix: XAP_RESP + define_prefix: XAP_RESPONSE_FLAG bits: { 0: { name: Success diff --git a/lib/python/qmk/xap/gen_firmware/header_generator.py b/lib/python/qmk/xap/gen_firmware/header_generator.py index 42d54e1960..6cc9ff8816 100755 --- a/lib/python/qmk/xap/gen_firmware/header_generator.py +++ b/lib/python/qmk/xap/gen_firmware/header_generator.py @@ -94,6 +94,38 @@ def _append_route_capabilities(lines, container, container_id=None, route_stack= route_stack.pop() +def _append_types(lines, container): + """Handles creating the various constants, types, defines, etc. + """ + response_flags = container.get('response_flags', {}) + prefix = response_flags['define_prefix'] + for key, value in response_flags['bits'].items(): + define = value.get('define') + lines.append(f'#define {prefix}_{define} (1ul << ({key}))') + + # Add special + lines.append(f'#define {prefix}_FAILED 0x00') + + # TODO: define this in xap_*.hjson + lines.append( + ''' +typedef uint8_t xap_identifier_t; +typedef uint8_t xap_response_flags_t; +typedef uint16_t xap_token_t; + +typedef struct { + xap_token_t token; + uint8_t length; +} xap_request_header_t; + +typedef struct { + xap_token_t token; + xap_response_flags_t flags; + uint8_t length; +} xap_response_header_t;''' + ) + + def generate_header(output_file, keyboard): """Generates the XAP protocol header file, generated during normal build. """ @@ -112,6 +144,10 @@ def generate_header(output_file, keyboard): lines.append(f'#define XAP_KEYBOARD_IDENTIFIER 0x{keyboard_id:08X}ul') lines.append('') + # Types + _append_types(lines, xap_defs) + lines.append('') + # Append the route and command defines _append_route_defines(lines, xap_defs) lines.append('') diff --git a/quantum/xap/xap.h b/quantum/xap/xap.h index 828531a92b..7c43d90e27 100644 --- a/quantum/xap/xap.h +++ b/quantum/xap/xap.h @@ -19,9 +19,7 @@ #include #include -typedef uint8_t xap_identifier_t; -typedef uint8_t xap_response_flags_t; -typedef uint16_t xap_token_t; +#include #ifndef XAP_SUBSYSTEM_VERSION_KB # define XAP_SUBSYSTEM_VERSION_KB 0 @@ -31,27 +29,9 @@ typedef uint16_t xap_token_t; # define XAP_SUBSYSTEM_VERSION_USER 0 #endif -#define XAP_RESPONSE_FLAG_FAILED 0 -#define XAP_RESPONSE_FLAG_SUCCESS (1 << 0) -#define XAP_RESPONSE_FLAG_SECURE_FAILURE (1 << 1) - void xap_respond_failure(xap_token_t token, xap_response_flags_t response_flags); bool xap_respond_u32(xap_token_t token, uint32_t value); bool xap_respond_data(xap_token_t token, const void *data, size_t length); bool xap_respond_data_P(xap_token_t token, const void *data, size_t length); void xap_send(xap_token_t token, xap_response_flags_t response_flags, const void *data, size_t length); - -// TODO: gen from xap defs? -typedef struct { - xap_token_t token; - uint8_t length; -} xap_request_header_t; - -typedef struct { - xap_token_t token; - xap_response_flags_t flags; - uint8_t length; -} xap_response_header_t; - -#include From ffcdfc6c032578e89b7c409c8afb15f1b907fd1e Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Fri, 1 Apr 2022 08:15:15 +1100 Subject: [PATCH 38/77] Swap info.json.gz length to #define. --- data/xap/xap_0.1.0.hjson | 2 +- lib/python/qmk/xap/gen_firmware/info_generator.py | 2 +- quantum/xap/xap.c | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/xap/xap_0.1.0.hjson b/data/xap/xap_0.1.0.hjson index 82dffb192b..42b0476f7a 100755 --- a/data/xap/xap_0.1.0.hjson +++ b/data/xap/xap_0.1.0.hjson @@ -240,7 +240,7 @@ define: INFO_LEN_QUERY description: Retrieves the length of info.json return_type: u32 - return_constant: info_json_gz_len + return_constant: INFO_JSON_GZ_LEN } 0x06: { type: command diff --git a/lib/python/qmk/xap/gen_firmware/info_generator.py b/lib/python/qmk/xap/gen_firmware/info_generator.py index c25d82181b..a1a6f1b40c 100644 --- a/lib/python/qmk/xap/gen_firmware/info_generator.py +++ b/lib/python/qmk/xap/gen_firmware/info_generator.py @@ -36,6 +36,6 @@ def generate_info(output_file, keyboard, keymap): lines.append('static const unsigned char info_json_gz[] PROGMEM = {') lines.append(data) lines.append('};') - lines.append(f'static const unsigned int info_json_gz_len = {data_len};') + lines.append(f'#define INFO_JSON_GZ_LEN {data_len}') dump_lines(output_file, lines) diff --git a/quantum/xap/xap.c b/quantum/xap/xap.c index a5d8b3cc0d..00901b0423 100644 --- a/quantum/xap/xap.c +++ b/quantum/xap/xap.c @@ -19,8 +19,8 @@ #include "info_json_gz.h" bool get_info_json_chunk(uint16_t offset, uint8_t *data, uint8_t data_len) { - if (offset + data_len > info_json_gz_len) { - data_len = info_json_gz_len - offset; + if (offset + data_len > INFO_JSON_GZ_LEN) { + data_len = INFO_JSON_GZ_LEN - offset; } memcpy_P(data, &info_json_gz[offset], data_len); From 53052228df2e0cbf1154878679f7f06f93ea37c7 Mon Sep 17 00:00:00 2001 From: zvecr Date: Thu, 31 Mar 2022 22:34:01 +0100 Subject: [PATCH 39/77] Add types codegen --- data/xap/xap_0.0.1.hjson | 68 +++++++++++++++---- .../qmk/xap/gen_firmware/header_generator.py | 54 ++++++++++----- 2 files changed, 94 insertions(+), 28 deletions(-) diff --git a/data/xap/xap_0.0.1.hjson b/data/xap/xap_0.0.1.hjson index 328207768e..31aa5fdafc 100755 --- a/data/xap/xap_0.0.1.hjson +++ b/data/xap/xap_0.0.1.hjson @@ -101,10 +101,6 @@ ''' A high-level area of functionality within XAP. ''' - ID: - ''' - A single octet / 8-bit byte. - ''' Route: ''' A sequence of _IDs_ describing the route to invoke a _handler_. @@ -113,24 +109,72 @@ ''' A piece of code that is executed when a specific _route_ is received. ''' - Token: - ''' - A `u16` associated with a specific request as well as its corresponding response. - ''' Response: ''' The data sent back to the host during execution of a _handler_. ''' - "Response Flags": - ''' - An `u8` containing the status of the request. - ''' Payload: ''' Any received data appended to the _route_, which gets delivered to the _handler_ when received. ''' } + types: { + identifier: { + name: ID + description: Subsystem/Route ID + type: u8 + } + + response_flags: { + name: Response Flags + description: Contains the status of the request + type: u8 + } + + token: { + name: Token + description: ID associated with a specific request as well as its corresponding response + type: u16 + } + + request_header: { + name: Request Header + description: asdf + type: struct + struct_members: [ + { + type: token + name: token + }, + { + type: u8 + name: length + } + ] + } + + response_header: { + name: Response Header + description: asdf + type: struct + struct_members: [ + { + type: token + name: token + }, + { + type: response_flags + name: flags + }, + { + type: u8 + name: length + } + ] + } + } + response_flags: { define_prefix: XAP_RESPONSE_FLAG bits: { diff --git a/lib/python/qmk/xap/gen_firmware/header_generator.py b/lib/python/qmk/xap/gen_firmware/header_generator.py index 6cc9ff8816..e8e2465e4d 100755 --- a/lib/python/qmk/xap/gen_firmware/header_generator.py +++ b/lib/python/qmk/xap/gen_firmware/header_generator.py @@ -9,6 +9,24 @@ from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE from qmk.xap.common import latest_xap_defs, route_conditions +def _get_c_type(xap_type): + if xap_type == 'bool': + return 'bool' + elif xap_type == 'u8': + return 'uint8_t' + elif xap_type == 'u16': + return 'uint16_t' + elif xap_type == 'u32': + return 'uint32_t' + elif xap_type == 'u64': + return 'uint64_t' + elif xap_type == 'struct': + return 'struct' + elif xap_type == 'string': + return 'const char *' + return 'unknown' + + def _append_route_defines(lines, container, container_id=None, route_stack=None): """Handles building the list of the XAP routes, combining parent and child names together, as well as the route number. """ @@ -105,25 +123,29 @@ def _append_types(lines, container): # Add special lines.append(f'#define {prefix}_FAILED 0x00') + lines.append('') - # TODO: define this in xap_*.hjson - lines.append( - ''' -typedef uint8_t xap_identifier_t; -typedef uint8_t xap_response_flags_t; -typedef uint16_t xap_token_t; + additional_types = {} + types = container.get('types', {}) + for key, value in types.items(): + data_type = _get_c_type(value['type']) + additional_types[key] = f'xap_{key}_t' -typedef struct { - xap_token_t token; - uint8_t length; -} xap_request_header_t; + for key, value in types.items(): + data_type = _get_c_type(value['type']) + if data_type == 'struct': + members = value['struct_members'] -typedef struct { - xap_token_t token; - xap_response_flags_t flags; - uint8_t length; -} xap_response_header_t;''' - ) + lines.append(f'typedef {data_type} {{') + for member in members: + member_name = member["name"] + member_type = _get_c_type(member["type"]) + if member_type == 'unknown': + member_type = additional_types[member["type"]] + lines.append(f' {member_type} {member_name};') + lines.append(f'}} xap_{key}_t;') + else: + lines.append(f'typedef {data_type} xap_{key}_t;') def generate_header(output_file, keyboard): From 2c8c9c9928b4880bc91884451cbbc4b9afc6d705 Mon Sep 17 00:00:00 2001 From: zvecr Date: Thu, 31 Mar 2022 23:16:56 +0100 Subject: [PATCH 40/77] And sort out docs gen too --- data/templates/xap/docs/term_definitions.md.j2 | 3 +++ data/xap/xap_0.0.1.hjson | 10 +++++----- docs/xap_0.0.1.md | 6 ++++-- docs/xap_0.1.0.md | 8 +++++--- docs/xap_0.2.0.md | 8 +++++--- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/data/templates/xap/docs/term_definitions.md.j2 b/data/templates/xap/docs/term_definitions.md.j2 index 8afdb9c3e2..39587edb49 100644 --- a/data/templates/xap/docs/term_definitions.md.j2 +++ b/data/templates/xap/docs/term_definitions.md.j2 @@ -2,4 +2,7 @@ | -- | -- | {%- for type, definition in xap.term_definitions | dictsort %} | _{{ type }}_ | {{ definition }} | +{%- endfor %} +{%- for type, definition in xap.types | dictsort %} +| _{{ definition.name }}_ | {{ definition.description }}{% if 'struct' == definition.type %} Takes the format:{% for item in definition.struct_members %}
`{{ item.type }}` - {{ item.name }}{%- endfor %}{% endif %} | {%- endfor %} \ No newline at end of file diff --git a/data/xap/xap_0.0.1.hjson b/data/xap/xap_0.0.1.hjson index 31aa5fdafc..a2abcb5fd6 100755 --- a/data/xap/xap_0.0.1.hjson +++ b/data/xap/xap_0.0.1.hjson @@ -122,25 +122,25 @@ types: { identifier: { name: ID - description: Subsystem/Route ID + description: A single octet / 8-bit byte, representing Subsystem or Route index. type: u8 } response_flags: { name: Response Flags - description: Contains the status of the request + description: An `u8` containing the status of the request. type: u8 } token: { name: Token - description: ID associated with a specific request as well as its corresponding response + description: A `u16` associated with a specific request as well as its corresponding response. type: u16 } request_header: { name: Request Header - description: asdf + description: Packet format for inbound data. type: struct struct_members: [ { @@ -156,7 +156,7 @@ response_header: { name: Response Header - description: asdf + description: Packet format for inbound data. type: struct struct_members: [ { diff --git a/docs/xap_0.0.1.md b/docs/xap_0.0.1.md index 336176c33c..afa23c85c3 100644 --- a/docs/xap_0.0.1.md +++ b/docs/xap_0.0.1.md @@ -20,12 +20,14 @@ This list defines the terms used across the entire set of XAP protocol documenta | Name | Definition | | -- | -- | | _Handler_ | A piece of code that is executed when a specific _route_ is received. | -| _ID_ | A single octet / 8-bit byte. | | _Payload_ | Any received data appended to the _route_, which gets delivered to the _handler_ when received. | | _Response_ | The data sent back to the host during execution of a _handler_. | -| _Response Flags_ | An `u8` containing the status of the request. | | _Route_ | A sequence of _IDs_ describing the route to invoke a _handler_. | | _Subsystem_ | A high-level area of functionality within XAP. | +| _ID_ | A single octet / 8-bit byte, representing Subsystem or Route index. | +| _Request Header_ | Packet format for inbound data. Takes the format:
`token` - token
`u8` - length | +| _Response Flags_ | An `u8` containing the status of the request. | +| _Response Header_ | Packet format for inbound data. Takes the format:
`token` - token
`response_flags` - flags
`u8` - length | | _Token_ | A `u16` associated with a specific request as well as its corresponding response. | ## Requests and Responses diff --git a/docs/xap_0.1.0.md b/docs/xap_0.1.0.md index f3911d7220..c61ee1df60 100644 --- a/docs/xap_0.1.0.md +++ b/docs/xap_0.1.0.md @@ -23,15 +23,17 @@ This list defines the terms used across the entire set of XAP protocol documenta | -- | -- | | _Capability_ | A way to determine if certain functionality is enabled in the firmware. Any _subsystem_ that provides build-time restriction of functionality must provide a _route_ for a _capabilities query_. | | _Handler_ | A piece of code that is executed when a specific _route_ is received. | -| _ID_ | A single octet / 8-bit byte. | | _Payload_ | Any received data appended to the _route_, which gets delivered to the _handler_ when received. | | _Response_ | The data sent back to the host during execution of a _handler_. | -| _Response Flags_ | An `u8` containing the status of the request. | | _Route_ | A sequence of _IDs_ describing the route to invoke a _handler_. | | _Secure Route_ | A _route_ which has potentially destructive consequences, necessitating prior approval by the user before executing. | | _Subsystem_ | A high-level area of functionality within XAP. | -| _Token_ | A `u16` associated with a specific request as well as its corresponding response. | | _Unlock sequence_ | A physical sequence initiated by the user to enable execution of _secure routes_. | +| _ID_ | A single octet / 8-bit byte, representing Subsystem or Route index. | +| _Request Header_ | Packet format for inbound data. Takes the format:
`token` - token
`u8` - length | +| _Response Flags_ | An `u8` containing the status of the request. | +| _Response Header_ | Packet format for inbound data. Takes the format:
`token` - token
`response_flags` - flags
`u8` - length | +| _Token_ | A `u16` associated with a specific request as well as its corresponding response. | ## Requests and Responses diff --git a/docs/xap_0.2.0.md b/docs/xap_0.2.0.md index f3911d7220..c61ee1df60 100644 --- a/docs/xap_0.2.0.md +++ b/docs/xap_0.2.0.md @@ -23,15 +23,17 @@ This list defines the terms used across the entire set of XAP protocol documenta | -- | -- | | _Capability_ | A way to determine if certain functionality is enabled in the firmware. Any _subsystem_ that provides build-time restriction of functionality must provide a _route_ for a _capabilities query_. | | _Handler_ | A piece of code that is executed when a specific _route_ is received. | -| _ID_ | A single octet / 8-bit byte. | | _Payload_ | Any received data appended to the _route_, which gets delivered to the _handler_ when received. | | _Response_ | The data sent back to the host during execution of a _handler_. | -| _Response Flags_ | An `u8` containing the status of the request. | | _Route_ | A sequence of _IDs_ describing the route to invoke a _handler_. | | _Secure Route_ | A _route_ which has potentially destructive consequences, necessitating prior approval by the user before executing. | | _Subsystem_ | A high-level area of functionality within XAP. | -| _Token_ | A `u16` associated with a specific request as well as its corresponding response. | | _Unlock sequence_ | A physical sequence initiated by the user to enable execution of _secure routes_. | +| _ID_ | A single octet / 8-bit byte, representing Subsystem or Route index. | +| _Request Header_ | Packet format for inbound data. Takes the format:
`token` - token
`u8` - length | +| _Response Flags_ | An `u8` containing the status of the request. | +| _Response Header_ | Packet format for inbound data. Takes the format:
`token` - token
`response_flags` - flags
`u8` - length | +| _Token_ | A `u16` associated with a specific request as well as its corresponding response. | ## Requests and Responses From e111b9d01743c4e6c8f6772f0a71e7127dd344cf Mon Sep 17 00:00:00 2001 From: zvecr Date: Thu, 31 Mar 2022 23:31:01 +0100 Subject: [PATCH 41/77] Use slightly more unique data name --- data/templates/xap/docs/term_definitions.md.j2 | 2 +- data/xap/xap_0.0.1.hjson | 2 +- lib/python/qmk/xap/gen_firmware/header_generator.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/templates/xap/docs/term_definitions.md.j2 b/data/templates/xap/docs/term_definitions.md.j2 index 39587edb49..9b2863eaa5 100644 --- a/data/templates/xap/docs/term_definitions.md.j2 +++ b/data/templates/xap/docs/term_definitions.md.j2 @@ -3,6 +3,6 @@ {%- for type, definition in xap.term_definitions | dictsort %} | _{{ type }}_ | {{ definition }} | {%- endfor %} -{%- for type, definition in xap.types | dictsort %} +{%- for type, definition in xap.type_definitions | dictsort %} | _{{ definition.name }}_ | {{ definition.description }}{% if 'struct' == definition.type %} Takes the format:{% for item in definition.struct_members %}
`{{ item.type }}` - {{ item.name }}{%- endfor %}{% endif %} | {%- endfor %} \ No newline at end of file diff --git a/data/xap/xap_0.0.1.hjson b/data/xap/xap_0.0.1.hjson index a2abcb5fd6..b8a479f01b 100755 --- a/data/xap/xap_0.0.1.hjson +++ b/data/xap/xap_0.0.1.hjson @@ -119,7 +119,7 @@ ''' } - types: { + type_definitions: { identifier: { name: ID description: A single octet / 8-bit byte, representing Subsystem or Route index. diff --git a/lib/python/qmk/xap/gen_firmware/header_generator.py b/lib/python/qmk/xap/gen_firmware/header_generator.py index e8e2465e4d..a4b7d7af95 100755 --- a/lib/python/qmk/xap/gen_firmware/header_generator.py +++ b/lib/python/qmk/xap/gen_firmware/header_generator.py @@ -126,7 +126,7 @@ def _append_types(lines, container): lines.append('') additional_types = {} - types = container.get('types', {}) + types = container.get('type_definitions', {}) for key, value in types.items(): data_type = _get_c_type(value['type']) additional_types[key] = f'xap_{key}_t' From c5842ab9b503ea65729c3d3c3cb547fbfa288d33 Mon Sep 17 00:00:00 2001 From: zvecr Date: Fri, 1 Apr 2022 00:44:27 +0100 Subject: [PATCH 42/77] stub out return_execute with zero args --- data/xap/xap_0.1.0.hjson | 18 +++++++++++ lib/python/qmk/cli/xap/xap.py | 1 + .../qmk/xap/gen_firmware/inline_generator.py | 32 +++++++++++++------ quantum/xap/xap.c | 15 +++++++++ 4 files changed, 57 insertions(+), 9 deletions(-) diff --git a/data/xap/xap_0.1.0.hjson b/data/xap/xap_0.1.0.hjson index 42b0476f7a..132fc421bc 100755 --- a/data/xap/xap_0.1.0.hjson +++ b/data/xap/xap_0.1.0.hjson @@ -252,6 +252,24 @@ return_type: u8[32] return_execute: get_info_json_chunk } + 0x07: { + type: command + name: Jump to bootloader + define: BOOTLOADER_JUMP + secure: true + enable_if_preprocessor: defined(BOOTLOADER_JUMP_SUPPORTED) + description: + ''' + Jump to bootloader + + May not be present – if QMK capabilities query returns “true”, then jump to bootloader is supported + + * 0 means secure routes are disabled, and should be considered as a failure + * 1 means successful, board will jump to bootloader + ''' + return_type: u8 + return_execute: request_bootloader_jump + } } }, diff --git a/lib/python/qmk/cli/xap/xap.py b/lib/python/qmk/cli/xap/xap.py index 3626512829..1ba144a644 100644 --- a/lib/python/qmk/cli/xap/xap.py +++ b/lib/python/qmk/cli/xap/xap.py @@ -139,6 +139,7 @@ def _list_devices(): # TODO: better formatting like "lsusb -v"? data = _query_device_info(device) print_dotted_output(data) + # _xap_transaction(device, 0x01, 0x07, 1) @cli.argument('-d', '--device', help='device to select - uses format :.') diff --git a/lib/python/qmk/xap/gen_firmware/inline_generator.py b/lib/python/qmk/xap/gen_firmware/inline_generator.py index 93bb6c7afd..2ae2221c7b 100755 --- a/lib/python/qmk/xap/gen_firmware/inline_generator.py +++ b/lib/python/qmk/xap/gen_firmware/inline_generator.py @@ -26,16 +26,16 @@ def _get_c_type(xap_type): def _get_c_size(xap_type): if xap_type == 'u8': - return 1 + return 'sizeof(uint8_t)' elif xap_type == 'u16': - return 2 + return 'sizeof(uint16_t)' elif xap_type == 'u32': - return 4 + return 'sizeof(uint32_t)' elif xap_type == 'u64': return 8 elif xap_type == 'u8[32]': return 32 - return -1 + return 0 def _get_route_type(container): @@ -69,17 +69,30 @@ def _append_routing_table_declaration(lines, container, container_id, route_stac elif 'return_execute' in container: execute = container['return_execute'] - request_type = container['request_type'] + request_type = container.get('request_type', None) return_type = container['return_type'] lines.append( f''' bool xap_respond_{execute}(xap_token_t token, const uint8_t *data, size_t data_len) {{ - if (data_len != sizeof({_get_c_type(request_type)})) {{ + if (data_len != {_get_c_size(request_type)}) {{ xap_respond_failure(token, 0); return false; }} uint8_t ret[{_get_c_size(return_type)}] = {{0}}; +''' + ) + if not request_type: + lines.append(f''' + bool {execute}(uint8_t *ret, uint8_t ret_len); + if(!{execute}(ret, sizeof(ret))) {{ + xap_respond_failure(token, 0); + return false; + }} +''') + else: + lines.append( + f''' {_get_c_type(request_type)} *argp = ({_get_c_type(request_type)} *)&data[0]; bool {execute}({_get_c_type(request_type)} arg, uint8_t *ret, uint8_t ret_len); @@ -87,10 +100,11 @@ bool xap_respond_{execute}(xap_token_t token, const uint8_t *data, size_t data_l xap_respond_failure(token, 0); return false; }} - +''' + ) + lines.append(''' return xap_respond_data(token, ret, sizeof(ret)); -}}''' - ) +}''') # elif 'return_value' in container: # value = container['return_value'] diff --git a/quantum/xap/xap.c b/quantum/xap/xap.c index 00901b0423..1eb3786cc1 100644 --- a/quantum/xap/xap.c +++ b/quantum/xap/xap.c @@ -29,6 +29,21 @@ bool get_info_json_chunk(uint16_t offset, uint8_t *data, uint8_t data_len) { uint8_t secure_status = 2; +// TODO: how to set this if "custom" is just an empty stub +#ifndef BOOTLOADER_JUMP_SUPPORTED +# define BOOTLOADER_JUMP_SUPPORTED +#endif + +#ifdef BOOTLOADER_JUMP_SUPPORTED +bool request_bootloader_jump(uint8_t *data, uint8_t data_len) { + data[0] = secure_status == 2; + + // TODO: post to deferred queue so this request can return? + reset_keyboard(); + return true; +} +#endif + #define QSTR2(z) #z #define QSTR(z) QSTR2(z) From e7d9d6675cec0ebca5c40700875f2de2db726d56 Mon Sep 17 00:00:00 2001 From: zvecr Date: Fri, 1 Apr 2022 23:37:58 +0100 Subject: [PATCH 43/77] Implement codegen for more data types - codegen for return_execute stubs removed --- data/xap/xap_0.2.0.hjson | 58 +++++++++++++++ lib/python/qmk/cli/xap/xap.py | 29 +++++++- .../qmk/xap/gen_firmware/header_generator.py | 58 ++++++++++++++- .../qmk/xap/gen_firmware/inline_generator.py | 70 ++++++------------- quantum/xap/xap.c | 15 ---- quantum/xap/xap_handlers.c | 60 ++++++++++++++++ 6 files changed, 223 insertions(+), 67 deletions(-) diff --git a/data/xap/xap_0.2.0.hjson b/data/xap/xap_0.2.0.hjson index 81c5187390..f2fce07b84 100755 --- a/data/xap/xap_0.2.0.hjson +++ b/data/xap/xap_0.2.0.hjson @@ -24,6 +24,64 @@ return_purpose: capabilities return_constant: XAP_ROUTE_DYNAMIC_KEYMAP_CAPABILITIES } + 0x01: { + type: command + name: Get Layer Count + define: GET_LAYER_COUNT + description: TODO + return_type: u8 + return_constant: DYNAMIC_KEYMAP_LAYER_COUNT + } + 0x02: { + type: command + name: Get Keycode + define: GET_KEYMAP_KEYCODE + description: TODO + request_type: struct + request_struct_members: [ + { + type: u8 + name: Layer + }, + { + type: u8 + name: Row + }, + { + type: u8 + name: Column + } + ] + return_type: u16 + return_execute: dynamic_keymap_get_keycode + } + 0x03: { + type: command + name: Set Keycode + define: SET_KEYMAP_KEYCODE + description: TODO + request_type: struct + request_struct_members: [ + { + type: u8 + name: Layer + }, + { + type: u8 + name: Row + }, + { + type: u8 + name: Column + }, + { + type: u16 + name: Keycode + } + ] + return_type: u8 + return_execute: dynamic_keymap_set_keycode + } } } diff --git a/lib/python/qmk/cli/xap/xap.py b/lib/python/qmk/cli/xap/xap.py index 1ba144a644..fa24a23ede 100644 --- a/lib/python/qmk/cli/xap/xap.py +++ b/lib/python/qmk/cli/xap/xap.py @@ -58,8 +58,12 @@ def _xap_transaction(device, sub, route, ret_len, *args): args_data = [] args_len = 2 if len(args) == 1: - args_len += 2 - args_data = args[0].to_bytes(2, byteorder='little') + if isinstance(args[0], (bytes, bytearray)): + args_len += len(args[0]) + args_data = args[0] + else: + args_len += 2 + args_data = args[0].to_bytes(2, byteorder='little') padding_len = 64 - 3 - args_len padding = b"\x00" * padding_len @@ -156,4 +160,23 @@ def xap(cli): if cli.args.list: return _list_devices() - cli.log.warn("TODO: Device specific stuff") + # Connect to first available device + dev = _search()[0] + device = hid.Device(path=dev['path']) + cli.log.info("Connected to:%04x:%04x %s %s", dev['vendor_id'], dev['product_id'], dev['manufacturer_string'], dev['product_string']) + + # get layer count + layers = _xap_transaction(device, 0x04, 0x01, 1) + layers = int.from_bytes(layers, "little") + print(f'layers:{layers}') + + # get keycode [layer:0, row:0, col:0] + keycode = _xap_transaction(device, 0x04, 0x02, 2, b"\x00\x00\x00") + keycode = int.from_bytes(keycode, "little") + keycode_map = { + 0x29: 'KC_ESCAPE' + } + print('keycode:' + keycode_map.get(keycode, 'unknown')) + + # Reboot + # _xap_transaction(device, 0x01, 0x07, 1) diff --git a/lib/python/qmk/xap/gen_firmware/header_generator.py b/lib/python/qmk/xap/gen_firmware/header_generator.py index a4b7d7af95..3abfeaf4e6 100755 --- a/lib/python/qmk/xap/gen_firmware/header_generator.py +++ b/lib/python/qmk/xap/gen_firmware/header_generator.py @@ -3,6 +3,7 @@ import re from fnvhash import fnv1a_32 +from qmk.casing import to_snake from qmk.commands import dump_lines from qmk.git import git_get_version from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE @@ -112,7 +113,58 @@ def _append_route_capabilities(lines, container, container_id=None, route_stack= route_stack.pop() -def _append_types(lines, container): +def _append_route_types(lines, container, container_id=None, route_stack=None): + """Handles creating + """ + if route_stack is None: + route_stack = [container] + else: + route_stack.append(container) + + route_name = to_snake('_'.join([r['define'] for r in route_stack])) + + # Inbound + if 'request_struct_members' in container: + request_struct_members = container['request_struct_members'] + lines.append(f'typedef struct {{') + for member in request_struct_members: + member_type = _get_c_type(member['type']) + member_name = to_snake(member['name']) + lines.append(f' {member_type} {member_name};') + lines.append(f'}} {route_name}_arg_t;') + + elif 'request_type' in container: + request_type = container['request_type'] + lines.append(f'typedef {_get_c_type(request_type)} {route_name}_arg_t;') + + # Outbound + qualifier = 'const' if 'return_constant' in container else '' + if 'return_struct_members' in container: + return_struct_members = container['return_struct_members'] + lines.append(f'typedef struct {{') + for member in return_struct_members: + member_type = _get_c_type(member['type']) + member_name = f'{qualifier} {to_snake(member["name"])}' + lines.append(f' {member_type} {member_name};') + lines.append(f'}} {route_name}_t;') + + elif 'return_type' in container: + return_type = container['return_type'] + if return_type == 'u8[32]': + lines.append(f'typedef struct {{ uint8_t x[32]; }} {route_name}_t;') + else: + lines.append(f'typedef {_get_c_type(return_type)} {route_name}_t;') + + # Recurse + if 'routes' in container: + for route_id in container['routes']: + route = container['routes'][route_id] + _append_route_types(lines, route, route_id, route_stack) + + route_stack.pop() + + +def _append_internal_types(lines, container): """Handles creating the various constants, types, defines, etc. """ response_flags = container.get('response_flags', {}) @@ -167,7 +219,9 @@ def generate_header(output_file, keyboard): lines.append('') # Types - _append_types(lines, xap_defs) + _append_internal_types(lines, xap_defs) + lines.append('') + _append_route_types(lines, xap_defs) lines.append('') # Append the route and command defines diff --git a/lib/python/qmk/xap/gen_firmware/inline_generator.py b/lib/python/qmk/xap/gen_firmware/inline_generator.py index 2ae2221c7b..5b81176202 100755 --- a/lib/python/qmk/xap/gen_firmware/inline_generator.py +++ b/lib/python/qmk/xap/gen_firmware/inline_generator.py @@ -47,7 +47,11 @@ def _get_route_type(container): if container['return_type'] == 'u8': return 'XAP_VALUE' elif 'return_constant' in container: - if container['return_type'] == 'u32': + if container['return_type'] == 'u8': + return 'XAP_CONST_MEM' + elif container['return_type'] == 'u16': + return 'XAP_CONST_MEM' + elif container['return_type'] == 'u32': return 'XAP_CONST_MEM' elif container['return_type'] == 'struct': return 'XAP_CONST_MEM' @@ -69,42 +73,7 @@ def _append_routing_table_declaration(lines, container, container_id, route_stac elif 'return_execute' in container: execute = container['return_execute'] - request_type = container.get('request_type', None) - return_type = container['return_type'] - lines.append( - f''' -bool xap_respond_{execute}(xap_token_t token, const uint8_t *data, size_t data_len) {{ - if (data_len != {_get_c_size(request_type)}) {{ - xap_respond_failure(token, 0); - return false; - }} - - uint8_t ret[{_get_c_size(return_type)}] = {{0}}; -''' - ) - if not request_type: - lines.append(f''' - bool {execute}(uint8_t *ret, uint8_t ret_len); - if(!{execute}(ret, sizeof(ret))) {{ - xap_respond_failure(token, 0); - return false; - }} -''') - else: - lines.append( - f''' - {_get_c_type(request_type)} *argp = ({_get_c_type(request_type)} *)&data[0]; - - bool {execute}({_get_c_type(request_type)} arg, uint8_t *ret, uint8_t ret_len); - if(!{execute}(*argp, ret, sizeof(ret))) {{ - xap_respond_failure(token, 0); - return false; - }} -''' - ) - lines.append(''' - return xap_respond_data(token, ret, sizeof(ret)); -}''') + lines.append(f'bool xap_respond_{execute}(xap_token_t token, const uint8_t *data, size_t data_len);') # elif 'return_value' in container: # value = container['return_value'] @@ -114,21 +83,24 @@ bool xap_respond_{execute}(xap_token_t token, const uint8_t *data, size_t data_l elif 'return_constant' in container: - if container['return_type'] == 'u32': + if container['return_type'] == 'u8': + constant = container['return_constant'] + lines.append('') + lines.append(f'static const uint8_t {route_name}_data PROGMEM = {constant};') + + elif container['return_type'] == 'u16': + constant = container['return_constant'] + lines.append('') + lines.append(f'static const uint16_t {route_name}_data PROGMEM = {constant};') + + elif container['return_type'] == 'u32': constant = container['return_constant'] lines.append('') lines.append(f'static const uint32_t {route_name}_data PROGMEM = {constant};') elif container['return_type'] == 'struct': lines.append('') - lines.append(f'static const struct {route_name}_t {{') - - for member in container['return_struct_members']: - member_type = _get_c_type(member['type']) - member_name = to_snake(member['name']) - lines.append(f' const {member_type} {member_name};') - - lines.append(f'}} {route_name}_data PROGMEM = {{') + lines.append(f'static const {route_name}_t {route_name}_data PROGMEM = {{') for constant in container['return_constant']: lines.append(f' {constant},') @@ -212,7 +184,11 @@ def _append_routing_table_entry(lines, container, container_id, route_stack): elif 'return_value' in container: _append_routing_table_entry_value(lines, container, container_id, route_stack) elif 'return_constant' in container: - if container['return_type'] == 'u32': + if container['return_type'] == 'u8': + _append_routing_table_entry_const_data(lines, container, container_id, route_stack) + elif container['return_type'] == 'u16': + _append_routing_table_entry_const_data(lines, container, container_id, route_stack) + elif container['return_type'] == 'u32': _append_routing_table_entry_const_data(lines, container, container_id, route_stack) elif container['return_type'] == 'struct': _append_routing_table_entry_const_data(lines, container, container_id, route_stack) diff --git a/quantum/xap/xap.c b/quantum/xap/xap.c index 1eb3786cc1..00901b0423 100644 --- a/quantum/xap/xap.c +++ b/quantum/xap/xap.c @@ -29,21 +29,6 @@ bool get_info_json_chunk(uint16_t offset, uint8_t *data, uint8_t data_len) { uint8_t secure_status = 2; -// TODO: how to set this if "custom" is just an empty stub -#ifndef BOOTLOADER_JUMP_SUPPORTED -# define BOOTLOADER_JUMP_SUPPORTED -#endif - -#ifdef BOOTLOADER_JUMP_SUPPORTED -bool request_bootloader_jump(uint8_t *data, uint8_t data_len) { - data[0] = secure_status == 2; - - // TODO: post to deferred queue so this request can return? - reset_keyboard(); - return true; -} -#endif - #define QSTR2(z) #z #define QSTR(z) QSTR2(z) diff --git a/quantum/xap/xap_handlers.c b/quantum/xap/xap_handlers.c index 811e5439d4..adee30862f 100644 --- a/quantum/xap/xap_handlers.c +++ b/quantum/xap/xap_handlers.c @@ -17,6 +17,10 @@ #include #include +void xap_respond_success(xap_token_t token) { + xap_send(token, XAP_RESPONSE_FLAG_SUCCESS, NULL, 0); +} + void xap_respond_failure(xap_token_t token, xap_response_flags_t response_flags) { xap_send(token, response_flags, NULL, 0); } @@ -40,3 +44,59 @@ bool xap_respond_u32(xap_token_t token, uint32_t value) { uint32_t xap_route_qmk_ffffffffffffffff_getter(void) { return 0x12345678; } + +bool xap_respond_get_info_json_chunk(xap_token_t token, const void *data, size_t length) { + if(length != sizeof(uint16_t)){ + return false; + } + + uint16_t offset = *((uint16_t*)data); + xap_route_qmk_info_query_t ret = {0}; + + bool get_info_json_chunk(uint16_t offset, uint8_t *data, uint8_t data_len); + get_info_json_chunk(offset, (uint8_t *)&ret, sizeof(ret)); + + return xap_respond_data(token, &ret, sizeof(ret)); +} + +// TODO: how to set this if "custom" is just an empty stub +#ifndef BOOTLOADER_JUMP_SUPPORTED +# define BOOTLOADER_JUMP_SUPPORTED +#endif + +#ifdef BOOTLOADER_JUMP_SUPPORTED +bool xap_respond_request_bootloader_jump(xap_token_t token, const void *data, size_t length) { + extern uint8_t secure_status; + uint8_t ret = secure_status == 2; + + // TODO: post to deferred queue so this request can return? + bool res = xap_respond_data(token, &ret, sizeof(ret)); + reset_keyboard(); + return res; +} +#endif + +#if ((defined(DYNAMIC_KEYMAP_ENABLE))) +bool xap_respond_dynamic_keymap_get_keycode(xap_token_t token, const void *data, size_t length) { + if(length != sizeof(xap_route_dynamic_keymap_get_keymap_keycode_arg_t)){ + return false; + } + + xap_route_dynamic_keymap_get_keymap_keycode_arg_t* arg = (xap_route_dynamic_keymap_get_keymap_keycode_arg_t*)data; + + uint16_t keycode = dynamic_keymap_get_keycode(arg->layer, arg->row, arg->column); + return xap_respond_data(token, &keycode, sizeof(keycode)); +} + +bool xap_respond_dynamic_keymap_set_keycode(xap_token_t token, const void *data, size_t length) { + if(length != sizeof(xap_route_dynamic_keymap_set_keymap_keycode_arg_t)){ + return false; + } + + xap_route_dynamic_keymap_set_keymap_keycode_arg_t* arg = (xap_route_dynamic_keymap_set_keymap_keycode_arg_t*)data; + + dynamic_keymap_set_keycode(arg->layer, arg->row, arg->column, arg->keycode); + xap_respond_success(token); + return true; +} +#endif From c9eae1d3842f43855dd763996d2a44becfc18f42 Mon Sep 17 00:00:00 2001 From: zvecr Date: Sat, 2 Apr 2022 00:11:22 +0100 Subject: [PATCH 44/77] format --- lib/python/qmk/cli/xap/xap.py | 3 +++ .../qmk/xap/gen_firmware/header_generator.py | 6 +++--- quantum/xap/xap_handlers.c | 18 +++++++++--------- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/lib/python/qmk/cli/xap/xap.py b/lib/python/qmk/cli/xap/xap.py index fa24a23ede..5765bfeb8a 100644 --- a/lib/python/qmk/cli/xap/xap.py +++ b/lib/python/qmk/cli/xap/xap.py @@ -174,6 +174,9 @@ def xap(cli): keycode = _xap_transaction(device, 0x04, 0x02, 2, b"\x00\x00\x00") keycode = int.from_bytes(keycode, "little") keycode_map = { + # TODO: this should be data driven... + 0x04: 'KC_A', + 0x05: 'KC_B', 0x29: 'KC_ESCAPE' } print('keycode:' + keycode_map.get(keycode, 'unknown')) diff --git a/lib/python/qmk/xap/gen_firmware/header_generator.py b/lib/python/qmk/xap/gen_firmware/header_generator.py index 3abfeaf4e6..6b58947471 100755 --- a/lib/python/qmk/xap/gen_firmware/header_generator.py +++ b/lib/python/qmk/xap/gen_firmware/header_generator.py @@ -114,7 +114,7 @@ def _append_route_capabilities(lines, container, container_id=None, route_stack= def _append_route_types(lines, container, container_id=None, route_stack=None): - """Handles creating + """Handles creating typedefs used by routes """ if route_stack is None: route_stack = [container] @@ -126,7 +126,7 @@ def _append_route_types(lines, container, container_id=None, route_stack=None): # Inbound if 'request_struct_members' in container: request_struct_members = container['request_struct_members'] - lines.append(f'typedef struct {{') + lines.append('typedef struct {') for member in request_struct_members: member_type = _get_c_type(member['type']) member_name = to_snake(member['name']) @@ -141,7 +141,7 @@ def _append_route_types(lines, container, container_id=None, route_stack=None): qualifier = 'const' if 'return_constant' in container else '' if 'return_struct_members' in container: return_struct_members = container['return_struct_members'] - lines.append(f'typedef struct {{') + lines.append('typedef struct {') for member in return_struct_members: member_type = _get_c_type(member['type']) member_name = f'{qualifier} {to_snake(member["name"])}' diff --git a/quantum/xap/xap_handlers.c b/quantum/xap/xap_handlers.c index adee30862f..70cd2fb272 100644 --- a/quantum/xap/xap_handlers.c +++ b/quantum/xap/xap_handlers.c @@ -46,14 +46,14 @@ uint32_t xap_route_qmk_ffffffffffffffff_getter(void) { } bool xap_respond_get_info_json_chunk(xap_token_t token, const void *data, size_t length) { - if(length != sizeof(uint16_t)){ + if (length != sizeof(uint16_t)) { return false; } - uint16_t offset = *((uint16_t*)data); - xap_route_qmk_info_query_t ret = {0}; + uint16_t offset = *((uint16_t *)data); + xap_route_qmk_info_query_t ret = {0}; - bool get_info_json_chunk(uint16_t offset, uint8_t *data, uint8_t data_len); + bool get_info_json_chunk(uint16_t offset, uint8_t * data, uint8_t data_len); get_info_json_chunk(offset, (uint8_t *)&ret, sizeof(ret)); return xap_respond_data(token, &ret, sizeof(ret)); @@ -67,7 +67,7 @@ bool xap_respond_get_info_json_chunk(xap_token_t token, const void *data, size_t #ifdef BOOTLOADER_JUMP_SUPPORTED bool xap_respond_request_bootloader_jump(xap_token_t token, const void *data, size_t length) { extern uint8_t secure_status; - uint8_t ret = secure_status == 2; + uint8_t ret = secure_status == 2; // TODO: post to deferred queue so this request can return? bool res = xap_respond_data(token, &ret, sizeof(ret)); @@ -78,22 +78,22 @@ bool xap_respond_request_bootloader_jump(xap_token_t token, const void *data, si #if ((defined(DYNAMIC_KEYMAP_ENABLE))) bool xap_respond_dynamic_keymap_get_keycode(xap_token_t token, const void *data, size_t length) { - if(length != sizeof(xap_route_dynamic_keymap_get_keymap_keycode_arg_t)){ + if (length != sizeof(xap_route_dynamic_keymap_get_keymap_keycode_arg_t)) { return false; } - xap_route_dynamic_keymap_get_keymap_keycode_arg_t* arg = (xap_route_dynamic_keymap_get_keymap_keycode_arg_t*)data; + xap_route_dynamic_keymap_get_keymap_keycode_arg_t *arg = (xap_route_dynamic_keymap_get_keymap_keycode_arg_t *)data; uint16_t keycode = dynamic_keymap_get_keycode(arg->layer, arg->row, arg->column); return xap_respond_data(token, &keycode, sizeof(keycode)); } bool xap_respond_dynamic_keymap_set_keycode(xap_token_t token, const void *data, size_t length) { - if(length != sizeof(xap_route_dynamic_keymap_set_keymap_keycode_arg_t)){ + if (length != sizeof(xap_route_dynamic_keymap_set_keymap_keycode_arg_t)) { return false; } - xap_route_dynamic_keymap_set_keymap_keycode_arg_t* arg = (xap_route_dynamic_keymap_set_keymap_keycode_arg_t*)data; + xap_route_dynamic_keymap_set_keymap_keycode_arg_t *arg = (xap_route_dynamic_keymap_set_keymap_keycode_arg_t *)data; dynamic_keymap_set_keycode(arg->layer, arg->row, arg->column, arg->keycode); xap_respond_success(token); From c1b57354f61e1e1456b46d448108a8ebeaaf49ae Mon Sep 17 00:00:00 2001 From: zvecr Date: Tue, 5 Apr 2022 18:54:28 +0100 Subject: [PATCH 45/77] Stub out more of broadcast messages --- data/xap/xap_0.1.0.hjson | 31 +++++++ lib/python/qmk/cli/xap/xap.py | 82 +++++++++++++------ .../qmk/xap/gen_firmware/header_generator.py | 10 +++ .../qmk/xap/gen_firmware/inline_generator.py | 17 ++++ quantum/xap/xap.h | 1 + tmk_core/protocol/chibios/usb_main.c | 17 +++- tmk_core/protocol/lufa/lufa.c | 17 +++- tmk_core/protocol/vusb/vusb.c | 17 +++- 8 files changed, 162 insertions(+), 30 deletions(-) diff --git a/data/xap/xap_0.1.0.hjson b/data/xap/xap_0.1.0.hjson index 132fc421bc..e9506b5c7b 100755 --- a/data/xap/xap_0.1.0.hjson +++ b/data/xap/xap_0.1.0.hjson @@ -78,6 +78,28 @@ ''' } + type_definitions: { + broadcast_header: { + name: Broadcast Header + description: Packet format for broadcast messages. + type: struct + struct_members: [ + { + type: token + name: token + }, + { + type: u8 + name: type + }, + { + type: u8 + name: length + } + ] + } + } + broadcast_messages: { define_prefix: XAP_BROADCAST messages: { @@ -97,6 +119,15 @@ | **Value** | `0xFF` | `0xFF` | `0x00` | `0x0A`(10) | `0x48`(H) | `0x65`(e) | `0x6C`(l) | `0x6C`(l) | `0x6F`(o) | `0x20`( ) | `0x51`(Q) | `0x4D`(M) | `0x4B`(K) | `0x21`(!) | ''' } + 0x01: { + name: Secure Status + define: SECURE_STATUS + description: + ''' + Secure status has changed. + ''' + return_type: u8 + } } } diff --git a/lib/python/qmk/cli/xap/xap.py b/lib/python/qmk/cli/xap/xap.py index 5765bfeb8a..e61f7fe48b 100644 --- a/lib/python/qmk/cli/xap/xap.py +++ b/lib/python/qmk/cli/xap/xap.py @@ -48,7 +48,7 @@ def print_dotted_output(kb_info_json, prefix=''): cli.echo(' {fg_blue}%s{fg_reset}: %s', new_prefix, kb_info_json[key]) -def _xap_transaction(device, sub, route, ret_len, *args): +def _xap_transaction(device, sub, route, *args): # gen token tok = random.getrandbits(16) token = tok.to_bytes(2, byteorder='little') @@ -78,16 +78,20 @@ def _xap_transaction(device, sub, route, ret_len, *args): device.write(buffer) # get resp - array_alpha = device.read(4 + ret_len, 100) + array_alpha = device.read(64, 100) # validate tok sent == resp if str(token) != str(array_alpha[:2]): return None - return array_alpha[4:] + if int(array_alpha[2]) != 0x01: + return None + + payload_len = int(array_alpha[3]) + return array_alpha[4:4 + payload_len] def _query_device(device): - ver_data = _xap_transaction(device, 0x00, 0x00, 4) + ver_data = _xap_transaction(device, 0x00, 0x00) if not ver_data: return {'xap': 'UNKNOWN'} @@ -95,14 +99,14 @@ def _query_device(device): a = (ver_data[3] << 24) + (ver_data[2] << 16) + (ver_data[1] << 8) + (ver_data[0]) ver = f'{a>>24}.{a>>16 & 0xFF}.{a & 0xFFFF}' - secure = int.from_bytes(_xap_transaction(device, 0x00, 0x03, 1), 'little') + secure = int.from_bytes(_xap_transaction(device, 0x00, 0x03), 'little') secure = 'unlocked' if secure == 2 else 'LOCKED' return {'xap': ver, 'secure': secure} def _query_device_info_len(device): - len_data = _xap_transaction(device, 0x01, 0x05, 4) + len_data = _xap_transaction(device, 0x01, 0x05) if not len_data: return 0 @@ -111,7 +115,7 @@ def _query_device_info_len(device): def _query_device_info_chunk(device, offset): - return _xap_transaction(device, 0x01, 0x06, 32, offset) + return _xap_transaction(device, 0x01, 0x06, offset) def _query_device_info(device): @@ -143,7 +147,42 @@ def _list_devices(): # TODO: better formatting like "lsusb -v"? data = _query_device_info(device) print_dotted_output(data) - # _xap_transaction(device, 0x01, 0x07, 1) + + +def xap_doit(): + print("xap_doit") + # get layer count + # layers = _xap_transaction(device, 0x04, 0x01) + # layers = int.from_bytes(layers, "little") + # print(f'layers:{layers}') + + # get keycode [layer:0, row:0, col:0] + # keycode = _xap_transaction(device, 0x04, 0x02, b"\x00\x00\x00") + # keycode = int.from_bytes(keycode, "little") + # keycode_map = { + # # TODO: this should be data driven... + # 0x04: 'KC_A', + # 0x05: 'KC_B', + # 0x29: 'KC_ESCAPE' + # } + # print('keycode:' + keycode_map.get(keycode, 'unknown')) + + # Reboot + # _xap_transaction(device, 0x01, 0x07) + + +def xap_broadcast_listen(device): + try: + cli.log.info("Listening for XAP broadcasts...") + while 1: + array_alpha = device.read(64, 100) + if str(b"\xFF\xFF") == str(array_alpha[:2]): + if array_alpha[2] == 1: + cli.log.info(" Broadcast: Secure[%02x]", array_alpha[4]) + else: + cli.log.info(" Broadcast: type[%02x] data:[%02x]", array_alpha[2], array_alpha[4]) + except KeyboardInterrupt: + cli.log.info("Stopping...") @cli.argument('-d', '--device', help='device to select - uses format :.') @@ -161,25 +200,14 @@ def xap(cli): return _list_devices() # Connect to first available device - dev = _search()[0] + devices = _search() + if not devices: + cli.log.error("No devices found!") + return False + + dev = devices[0] device = hid.Device(path=dev['path']) cli.log.info("Connected to:%04x:%04x %s %s", dev['vendor_id'], dev['product_id'], dev['manufacturer_string'], dev['product_string']) - # get layer count - layers = _xap_transaction(device, 0x04, 0x01, 1) - layers = int.from_bytes(layers, "little") - print(f'layers:{layers}') - - # get keycode [layer:0, row:0, col:0] - keycode = _xap_transaction(device, 0x04, 0x02, 2, b"\x00\x00\x00") - keycode = int.from_bytes(keycode, "little") - keycode_map = { - # TODO: this should be data driven... - 0x04: 'KC_A', - 0x05: 'KC_B', - 0x29: 'KC_ESCAPE' - } - print('keycode:' + keycode_map.get(keycode, 'unknown')) - - # Reboot - # _xap_transaction(device, 0x01, 0x07, 1) + # xap_doit(device) + xap_broadcast_listen(device) diff --git a/lib/python/qmk/xap/gen_firmware/header_generator.py b/lib/python/qmk/xap/gen_firmware/header_generator.py index 6b58947471..0af3b6d7c4 100755 --- a/lib/python/qmk/xap/gen_firmware/header_generator.py +++ b/lib/python/qmk/xap/gen_firmware/header_generator.py @@ -177,6 +177,16 @@ def _append_internal_types(lines, container): lines.append(f'#define {prefix}_FAILED 0x00') lines.append('') + broadcast_messages = container.get('broadcast_messages', {}) + broadcast_prefix = broadcast_messages['define_prefix'] + for key, value in broadcast_messages['messages'].items(): + define = value.get('define') + lines.append(f'#define {broadcast_prefix}_{define} {key}') + + # Add special + lines.append(f'#define {broadcast_prefix}_TOKEN 0xFFFF') + lines.append('') + additional_types = {} types = container.get('type_definitions', {}) for key, value in types.items(): diff --git a/lib/python/qmk/xap/gen_firmware/inline_generator.py b/lib/python/qmk/xap/gen_firmware/inline_generator.py index 5b81176202..074a37729d 100755 --- a/lib/python/qmk/xap/gen_firmware/inline_generator.py +++ b/lib/python/qmk/xap/gen_firmware/inline_generator.py @@ -245,6 +245,22 @@ def _append_routing_tables(lines, container, container_id=None, route_stack=None route_stack.pop() +def _append_broadcast_messages(lines, container): + """TODO: + """ + broadcast_messages = container.get('broadcast_messages', {}) + broadcast_prefix = broadcast_messages['define_prefix'] + for key, value in broadcast_messages['messages'].items(): + define = value.get('define') + name = to_snake(f'{broadcast_prefix}_{define}') + + if 'return_type' in value: + ret_type = _get_c_type(value['return_type']) + lines.append(f'void {name}({ret_type} value) {{ xap_broadcast({key}, &value, sizeof(value)); }}') + else: + lines.append(f'void {name}(const void *data, size_t length){{ xap_broadcast({key}, data, length); }}') + + def generate_inline(output_file): """Generates the XAP protocol header file, generated during normal build. """ @@ -254,6 +270,7 @@ def generate_inline(output_file): lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, ''] # Add all the generated code + _append_broadcast_messages(lines, xap_defs) _append_routing_tables(lines, xap_defs) dump_lines(output_file, lines) diff --git a/quantum/xap/xap.h b/quantum/xap/xap.h index 7c43d90e27..76e5095492 100644 --- a/quantum/xap/xap.h +++ b/quantum/xap/xap.h @@ -35,3 +35,4 @@ bool xap_respond_data(xap_token_t token, const void *data, size_t length); bool xap_respond_data_P(xap_token_t token, const void *data, size_t length); void xap_send(xap_token_t token, xap_response_flags_t response_flags, const void *data, size_t length); +void xap_broadcast(uint8_t type, const void *data, size_t length); diff --git a/tmk_core/protocol/chibios/usb_main.c b/tmk_core/protocol/chibios/usb_main.c index 53116d1562..af4eca4da0 100644 --- a/tmk_core/protocol/chibios/usb_main.c +++ b/tmk_core/protocol/chibios/usb_main.c @@ -1137,7 +1137,7 @@ void xap_send_base(uint8_t *data, uint8_t length) { chnWrite(&drivers.xap_driver.driver, data, length); } -void xap_send(xap_token_t token, uint8_t response_flags, const void *data, size_t length) { +void xap_send(xap_token_t token, xap_response_flags_t response_flags, const void *data, size_t length) { uint8_t rdata[XAP_EPSIZE] = {0}; xap_response_header_t *header = (xap_response_header_t *)&rdata[0]; header->token = token; @@ -1154,6 +1154,21 @@ void xap_send(xap_token_t token, uint8_t response_flags, const void *data, size_ xap_send_base(rdata, sizeof(rdata)); } +void xap_broadcast(uint8_t type, const void *data, size_t length) { + uint8_t rdata[XAP_EPSIZE] = {0}; + xap_broadcast_header_t *header = (xap_broadcast_header_t *)&rdata[0]; + header->token = XAP_BROADCAST_TOKEN; + header->type = type; + + if (length > (XAP_EPSIZE - sizeof(xap_broadcast_header_t))) return; + + header->length = (uint8_t)length; + if (data != NULL) { + memcpy(&rdata[sizeof(xap_broadcast_header_t)], data, length); + } + xap_send_base(rdata, sizeof(rdata)); +} + void xap_receive_base(const void *data) { const uint8_t * u8data = (const uint8_t *)data; xap_request_header_t *header = (xap_request_header_t *)&u8data[0]; diff --git a/tmk_core/protocol/lufa/lufa.c b/tmk_core/protocol/lufa/lufa.c index 969bf1ab21..0353ec7d4d 100644 --- a/tmk_core/protocol/lufa/lufa.c +++ b/tmk_core/protocol/lufa/lufa.c @@ -243,7 +243,7 @@ void xap_send_base(uint8_t *data, uint8_t length) { Endpoint_SelectEndpoint(ep); } -void xap_send(xap_token_t token, uint8_t response_flags, const void *data, size_t length) { +void xap_send(xap_token_t token, xap_response_flags_t response_flags, const void *data, size_t length) { uint8_t rdata[XAP_EPSIZE] = {0}; xap_response_header_t *header = (xap_response_header_t *)&rdata[0]; header->token = token; @@ -260,6 +260,21 @@ void xap_send(xap_token_t token, uint8_t response_flags, const void *data, size_ xap_send_base(rdata, sizeof(rdata)); } +void xap_broadcast(uint8_t type, const void *data, size_t length) { + uint8_t rdata[XAP_EPSIZE] = {0}; + xap_broadcast_header_t *header = (xap_broadcast_header_t *)&rdata[0]; + header->token = XAP_BROADCAST_TOKEN; + header->type = type; + + if (length > (XAP_EPSIZE - sizeof(xap_broadcast_header_t))) return; + + header->length = (uint8_t)length; + if (data != NULL) { + memcpy(&rdata[sizeof(xap_broadcast_header_t)], data, length); + } + xap_send_base(rdata, sizeof(rdata)); +} + void xap_receive_base(const void *data) { const uint8_t * u8data = (const uint8_t *)data; xap_request_header_t *header = (xap_request_header_t *)&u8data[0]; diff --git a/tmk_core/protocol/vusb/vusb.c b/tmk_core/protocol/vusb/vusb.c index 49d59f1574..f8375de369 100644 --- a/tmk_core/protocol/vusb/vusb.c +++ b/tmk_core/protocol/vusb/vusb.c @@ -204,7 +204,7 @@ void xap_send_base(uint8_t *data, uint8_t length) { usbSetInterrupt4(0, 0); } -void xap_send(xap_token_t token, uint8_t response_flags, const void *data, size_t length) { +void xap_send(xap_token_t token, xap_response_flags_t response_flags, const void *data, size_t length) { uint8_t rdata[XAP_BUFFER_SIZE] = {0}; xap_response_header_t *header = (xap_response_header_t *)&rdata[0]; header->token = token; @@ -221,6 +221,21 @@ void xap_send(xap_token_t token, uint8_t response_flags, const void *data, size_ xap_send_base(rdata, sizeof(rdata)); } +void xap_broadcast(uint8_t type, const void *data, size_t length) { + uint8_t rdata[XAP_BUFFER_SIZE] = {0}; + xap_broadcast_header_t *header = (xap_broadcast_header_t *)&rdata[0]; + header->token = XAP_BROADCAST_TOKEN; + header->type = type; + + if (length > (XAP_BUFFER_SIZE - sizeof(xap_broadcast_header_t))) return; + + header->length = (uint8_t)length; + if (data != NULL) { + memcpy(&rdata[sizeof(xap_broadcast_header_t)], data, length); + } + xap_send_base(rdata, sizeof(rdata)); +} + void xap_receive_base(const void *data) { const uint8_t * u8data = (const uint8_t *)data; xap_request_header_t *header = (xap_request_header_t *)&u8data[0]; From 194d6d65befe908baa5642e64361d77b57572bed Mon Sep 17 00:00:00 2001 From: zvecr Date: Tue, 5 Apr 2022 19:17:48 +0100 Subject: [PATCH 46/77] format --- tmk_core/protocol/chibios/usb_main.c | 8 ++++---- tmk_core/protocol/lufa/lufa.c | 8 ++++---- tmk_core/protocol/vusb/vusb.c | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tmk_core/protocol/chibios/usb_main.c b/tmk_core/protocol/chibios/usb_main.c index af4eca4da0..ffe1ba40cb 100644 --- a/tmk_core/protocol/chibios/usb_main.c +++ b/tmk_core/protocol/chibios/usb_main.c @@ -1155,10 +1155,10 @@ void xap_send(xap_token_t token, xap_response_flags_t response_flags, const void } void xap_broadcast(uint8_t type, const void *data, size_t length) { - uint8_t rdata[XAP_EPSIZE] = {0}; - xap_broadcast_header_t *header = (xap_broadcast_header_t *)&rdata[0]; - header->token = XAP_BROADCAST_TOKEN; - header->type = type; + uint8_t rdata[XAP_EPSIZE] = {0}; + xap_broadcast_header_t *header = (xap_broadcast_header_t *)&rdata[0]; + header->token = XAP_BROADCAST_TOKEN; + header->type = type; if (length > (XAP_EPSIZE - sizeof(xap_broadcast_header_t))) return; diff --git a/tmk_core/protocol/lufa/lufa.c b/tmk_core/protocol/lufa/lufa.c index 0353ec7d4d..b9bfef0b97 100644 --- a/tmk_core/protocol/lufa/lufa.c +++ b/tmk_core/protocol/lufa/lufa.c @@ -261,10 +261,10 @@ void xap_send(xap_token_t token, xap_response_flags_t response_flags, const void } void xap_broadcast(uint8_t type, const void *data, size_t length) { - uint8_t rdata[XAP_EPSIZE] = {0}; - xap_broadcast_header_t *header = (xap_broadcast_header_t *)&rdata[0]; - header->token = XAP_BROADCAST_TOKEN; - header->type = type; + uint8_t rdata[XAP_EPSIZE] = {0}; + xap_broadcast_header_t *header = (xap_broadcast_header_t *)&rdata[0]; + header->token = XAP_BROADCAST_TOKEN; + header->type = type; if (length > (XAP_EPSIZE - sizeof(xap_broadcast_header_t))) return; diff --git a/tmk_core/protocol/vusb/vusb.c b/tmk_core/protocol/vusb/vusb.c index f8375de369..245e454cbe 100644 --- a/tmk_core/protocol/vusb/vusb.c +++ b/tmk_core/protocol/vusb/vusb.c @@ -222,10 +222,10 @@ void xap_send(xap_token_t token, xap_response_flags_t response_flags, const void } void xap_broadcast(uint8_t type, const void *data, size_t length) { - uint8_t rdata[XAP_BUFFER_SIZE] = {0}; - xap_broadcast_header_t *header = (xap_broadcast_header_t *)&rdata[0]; - header->token = XAP_BROADCAST_TOKEN; - header->type = type; + uint8_t rdata[XAP_BUFFER_SIZE] = {0}; + xap_broadcast_header_t *header = (xap_broadcast_header_t *)&rdata[0]; + header->token = XAP_BROADCAST_TOKEN; + header->type = type; if (length > (XAP_BUFFER_SIZE - sizeof(xap_broadcast_header_t))) return; From 89fab427c4e1a3de0c8a54eb003d4f27d57c2e37 Mon Sep 17 00:00:00 2001 From: zvecr Date: Mon, 11 Apr 2022 00:43:18 +0100 Subject: [PATCH 47/77] stub out secure as its own feature --- builddefs/common_features.mk | 1 + builddefs/generic_features.mk | 1 + data/xap/xap_0.1.0.hjson | 16 +++++- lib/python/qmk/cli/xap/xap.py | 15 +++++- quantum/keyboard.c | 8 +++ quantum/process_keycode/process_secure.c | 18 +++++++ quantum/process_keycode/process_secure.h | 9 ++++ quantum/quantum.c | 3 ++ quantum/quantum.h | 5 ++ quantum/secure.c | 66 ++++++++++++++++++++++++ quantum/secure.h | 27 ++++++++++ quantum/xap/xap.c | 22 ++++++-- quantum/xap/xap.h | 2 + quantum/xap/xap_handlers.c | 16 ++++++ 14 files changed, 204 insertions(+), 5 deletions(-) create mode 100644 quantum/process_keycode/process_secure.c create mode 100644 quantum/process_keycode/process_secure.h create mode 100644 quantum/secure.c create mode 100644 quantum/secure.h diff --git a/builddefs/common_features.mk b/builddefs/common_features.mk index 77d5aa25e3..52bf40228a 100644 --- a/builddefs/common_features.mk +++ b/builddefs/common_features.mk @@ -802,6 +802,7 @@ ifeq ($(strip $(XAP_ENABLE)), yes) OPT_DEFS += -DXAP_ENABLE DYNAMIC_KEYMAP_ENABLE := yes + SECURE_ENABLE := yes EMBED_INFO_JSON := yes VPATH += $(QUANTUM_DIR)/xap SRC += $(QUANTUM_DIR)/xap/xap.c $(QUANTUM_DIR)/xap/xap_handlers.c diff --git a/builddefs/generic_features.mk b/builddefs/generic_features.mk index 53d4e16fd4..0475a2ff09 100644 --- a/builddefs/generic_features.mk +++ b/builddefs/generic_features.mk @@ -32,6 +32,7 @@ GENERIC_FEATURES = \ KEY_OVERRIDE \ LEADER \ PROGRAMMABLE_BUTTON \ + SECURE \ SPACE_CADET \ SWAP_HANDS \ TAP_DANCE \ diff --git a/data/xap/xap_0.1.0.hjson b/data/xap/xap_0.1.0.hjson index e9506b5c7b..3d094eb375 100755 --- a/data/xap/xap_0.1.0.hjson +++ b/data/xap/xap_0.1.0.hjson @@ -172,7 +172,21 @@ * any other value should be interpreted as disabled ''' return_type: u8 - return_value: secure_status + return_execute: secure_status + } + 0x04: { + type: command + name: Secure Unlock + define: SECURE_UNLOCK + description: Initiate secure route unlock sequence + return_execute: secure_unlock + } + 0x05: { + type: command + name: Secure Lock + define: SECURE_LOCK + description: Disable secure routes + return_execute: secure_lock } } }, diff --git a/lib/python/qmk/cli/xap/xap.py b/lib/python/qmk/cli/xap/xap.py index e61f7fe48b..36979bf47c 100644 --- a/lib/python/qmk/cli/xap/xap.py +++ b/lib/python/qmk/cli/xap/xap.py @@ -185,9 +185,14 @@ def xap_broadcast_listen(device): cli.log.info("Stopping...") +def xap_unlock(device): + _xap_transaction(device, 0x00, 0x04) + + @cli.argument('-d', '--device', help='device to select - uses format :.') @cli.argument('-i', '--index', default=0, help='device index to select.') @cli.argument('-l', '--list', arg_only=True, action='store_true', help='List available devices.') +@cli.argument('action', nargs='?', arg_only=True) @cli.subcommand('Acquire debugging information from usb XAP devices.', hidden=False if cli.config.user.developer else True) def xap(cli): """Acquire debugging information from XAP devices @@ -210,4 +215,12 @@ def xap(cli): cli.log.info("Connected to:%04x:%04x %s %s", dev['vendor_id'], dev['product_id'], dev['manufacturer_string'], dev['product_string']) # xap_doit(device) - xap_broadcast_listen(device) + if cli.args.action == 'unlock': + xap_unlock(device) + cli.log.info("Done") + + elif cli.args.action == 'listen': + xap_broadcast_listen(device) + + elif not cli.args.action: + xap_broadcast_listen(device) \ No newline at end of file diff --git a/quantum/keyboard.c b/quantum/keyboard.c index fc8a2fe8e3..5a80330b55 100644 --- a/quantum/keyboard.c +++ b/quantum/keyboard.c @@ -562,6 +562,14 @@ void quantum_task(void) { #ifdef AUTO_SHIFT_ENABLE autoshift_matrix_scan(); #endif + +#ifdef SECURE_ENABLE + secure_task(); +#endif + +#ifdef XAP_ENABLE + xap_event_task(); +#endif } /** \brief Keyboard task: Do keyboard routine jobs diff --git a/quantum/process_keycode/process_secure.c b/quantum/process_keycode/process_secure.c new file mode 100644 index 0000000000..af06906344 --- /dev/null +++ b/quantum/process_keycode/process_secure.c @@ -0,0 +1,18 @@ +// Copyright 2022 QMK +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "secure.h" +#include "process_secure.h" + +bool process_secure(uint16_t keycode, keyrecord_t *record) { + if (secure_is_unlocking()) { + if (!record->event.pressed) { + secure_keypress_event(record->event.key.row, record->event.key.col); + } + + // Normal keypresses should be disabled until the sequence is completed + return false; + } + + return true; +} diff --git a/quantum/process_keycode/process_secure.h b/quantum/process_keycode/process_secure.h new file mode 100644 index 0000000000..7f6821f0d8 --- /dev/null +++ b/quantum/process_keycode/process_secure.h @@ -0,0 +1,9 @@ +// Copyright 2022 QMK +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include "action.h" + +bool process_secure(uint16_t keycode, keyrecord_t *record); diff --git a/quantum/quantum.c b/quantum/quantum.c index d4e91ddd37..eedc004a30 100644 --- a/quantum/quantum.c +++ b/quantum/quantum.c @@ -245,6 +245,9 @@ bool process_record_quantum(keyrecord_t *record) { #endif #if defined(VIA_ENABLE) process_record_via(keycode, record) && +#endif +#if defined(SECURE_ENABLE) + process_secure(keycode, record) && #endif process_record_kb(keycode, record) && #if defined(SEQUENCER_ENABLE) diff --git a/quantum/quantum.h b/quantum/quantum.h index ff3679bb5e..122e886ad2 100644 --- a/quantum/quantum.h +++ b/quantum/quantum.h @@ -196,6 +196,11 @@ extern layer_state_t layer_state; # include "process_dynamic_macro.h" #endif +#ifdef SECURE_ENABLE +# include "secure.h" +# include "process_secure.h" +#endif + #ifdef DYNAMIC_KEYMAP_ENABLE # include "dynamic_keymap.h" #endif diff --git a/quantum/secure.c b/quantum/secure.c new file mode 100644 index 0000000000..26a3246ccf --- /dev/null +++ b/quantum/secure.c @@ -0,0 +1,66 @@ +// Copyright 2022 QMK +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "secure.h" +#include "timer.h" + +#ifndef SECURE_UNLOCK_TIMEOUT +# define SECURE_UNLOCK_TIMEOUT 5000 +#endif + +#ifndef SECURE_IDLE_TIMEOUT +# define SECURE_IDLE_TIMEOUT 60000 +#endif + +secure_status_t secure_status = SECURE_LOCKED; +static uint32_t unlock_time = 0; +static uint32_t idle_time = 0; + +secure_status_t secure_get_status(void) { + return secure_status; +} + +bool secure_is_unlocking(void) { + return secure_status == SECURE_PENDING; +} + +void secure_lock(void) { + secure_status = SECURE_LOCKED; +} + +void secure_unlock(void) { + secure_status = SECURE_UNLOCKED; + idle_time = timer_read32(); +} + +void secure_request_unlock(void) { + if (secure_status == SECURE_LOCKED) { + secure_status = SECURE_PENDING; + unlock_time = timer_read32(); + } +} + +void secure_keypress_event(uint8_t row, uint8_t col) { + // TODO: check keypress is actually part of unlock sequence + secure_unlock(); +} + +void secure_task(void) { +#if SECURE_UNLOCK_TIMEOUT != 0 + // handle unlock timeout + if (secure_status == SECURE_PENDING) { + if (timer_elapsed32(unlock_time) >= SECURE_UNLOCK_TIMEOUT) { + secure_lock(); + } + } +#endif + +#if SECURE_IDLE_TIMEOUT != 0 + // handle idle timeout + if (secure_status == SECURE_UNLOCKED) { + if (timer_elapsed32(idle_time) >= SECURE_IDLE_TIMEOUT) { + secure_lock(); + } + } +#endif +} diff --git a/quantum/secure.h b/quantum/secure.h new file mode 100644 index 0000000000..ba5fd6cbff --- /dev/null +++ b/quantum/secure.h @@ -0,0 +1,27 @@ +// Copyright 2022 QMK +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +typedef enum { + SECURE_LOCKED, + SECURE_PENDING, + SECURE_UNLOCKED, +} secure_status_t; + +secure_status_t secure_get_status(void); + +bool secure_is_unlocking(void); + +void secure_lock(void); + +void secure_unlock(void); + +void secure_request_unlock(void); + +void secure_keypress_event(uint8_t row, uint8_t col); + +void secure_task(void); diff --git a/quantum/xap/xap.c b/quantum/xap/xap.c index 00901b0423..806ce7f160 100644 --- a/quantum/xap/xap.c +++ b/quantum/xap/xap.c @@ -16,6 +16,7 @@ #include #include +#include "secure.h" #include "info_json_gz.h" bool get_info_json_chunk(uint16_t offset, uint8_t *data, uint8_t data_len) { @@ -27,8 +28,6 @@ bool get_info_json_chunk(uint16_t offset, uint8_t *data, uint8_t data_len) { return true; } -uint8_t secure_status = 2; - #define QSTR2(z) #z #define QSTR(z) QSTR2(z) @@ -86,11 +85,18 @@ void xap_execute_route(xap_token_t token, const xap_route_t *routes, size_t max_ xap_route_t route; memcpy_P(&route, &routes[id], sizeof(xap_route_t)); - if (route.flags.is_secure && secure_status != 2) { + if (route.flags.is_secure && secure_get_status() != SECURE_UNLOCKED) { xap_respond_failure(token, XAP_RESPONSE_FLAG_SECURE_FAILURE); return; } + // TODO: All other subsystems are disabled during unlock. + // how to flag status route as still allowed? + // if (!route.flags.is_secure && secure_get_status() == SECURE_PENDING) { + // xap_respond_failure(token, XAP_RESPONSE_FLAG_UNLOCK_IN_PROGRESS); + // return; + // } + switch (route.flags.type) { case XAP_ROUTE: if (route.child_routes != NULL && route.child_routes_len > 0 && data_len > 0) { @@ -134,3 +140,13 @@ void xap_execute_route(xap_token_t token, const xap_route_t *routes, size_t max_ void xap_receive(xap_token_t token, const uint8_t *data, size_t length) { xap_execute_route(token, xap_route_table, sizeof(xap_route_table) / sizeof(xap_route_t), data, length); } + +void xap_event_task(void) { + static secure_status_t last_status = -1; + + secure_status_t status = secure_get_status(); + if (last_status != status) { + last_status = status; + xap_broadcast_secure_status(status); + } +} diff --git a/quantum/xap/xap.h b/quantum/xap/xap.h index 76e5095492..b1ecd8cb9c 100644 --- a/quantum/xap/xap.h +++ b/quantum/xap/xap.h @@ -36,3 +36,5 @@ bool xap_respond_data_P(xap_token_t token, const void *data, size_t length); void xap_send(xap_token_t token, xap_response_flags_t response_flags, const void *data, size_t length); void xap_broadcast(uint8_t type, const void *data, size_t length); + +void xap_event_task(void); diff --git a/quantum/xap/xap_handlers.c b/quantum/xap/xap_handlers.c index 70cd2fb272..e16a586dbb 100644 --- a/quantum/xap/xap_handlers.c +++ b/quantum/xap/xap_handlers.c @@ -16,6 +16,7 @@ #include #include +#include "secure.h" void xap_respond_success(xap_token_t token) { xap_send(token, XAP_RESPONSE_FLAG_SUCCESS, NULL, 0); @@ -59,6 +60,21 @@ bool xap_respond_get_info_json_chunk(xap_token_t token, const void *data, size_t return xap_respond_data(token, &ret, sizeof(ret)); } +bool xap_respond_secure_status(xap_token_t token, const void *data, size_t length) { + uint8_t ret = secure_get_status(); + return xap_respond_data(token, &ret, sizeof(ret)); +} + +bool xap_respond_secure_unlock(xap_token_t token, const void *data, size_t length) { + secure_request_unlock(); + return xap_respond_data(token, NULL, 0); +} + +bool xap_respond_secure_lock(xap_token_t token, const void *data, size_t length) { + secure_lock(); + return xap_respond_data(token, NULL, 0); +} + // TODO: how to set this if "custom" is just an empty stub #ifndef BOOTLOADER_JUMP_SUPPORTED # define BOOTLOADER_JUMP_SUPPORTED From 3e4de1ebd0287b4ce2f4b184a91a6060d99e10a1 Mon Sep 17 00:00:00 2001 From: zvecr Date: Mon, 11 Apr 2022 00:53:31 +0100 Subject: [PATCH 48/77] format --- lib/python/qmk/cli/xap/xap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/python/qmk/cli/xap/xap.py b/lib/python/qmk/cli/xap/xap.py index 36979bf47c..9ab41e3ee3 100644 --- a/lib/python/qmk/cli/xap/xap.py +++ b/lib/python/qmk/cli/xap/xap.py @@ -223,4 +223,4 @@ def xap(cli): xap_broadcast_listen(device) elif not cli.args.action: - xap_broadcast_listen(device) \ No newline at end of file + xap_broadcast_listen(device) From 79db2df2283b7b2da7be2760337238ab308c1034 Mon Sep 17 00:00:00 2001 From: zvecr Date: Mon, 11 Apr 2022 01:07:22 +0100 Subject: [PATCH 49/77] Allow secure to be slightly more optional --- quantum/xap/xap.c | 6 +++++- quantum/xap/xap_handlers.c | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/quantum/xap/xap.c b/quantum/xap/xap.c index 806ce7f160..15b56297c9 100644 --- a/quantum/xap/xap.c +++ b/quantum/xap/xap.c @@ -69,7 +69,7 @@ struct __attribute__((packed)) xap_route_t { // XAP_VALUE / XAP_CONST_MEM struct { - const void * const_data; + const void *const_data; const uint8_t const_data_len; }; }; @@ -85,6 +85,7 @@ void xap_execute_route(xap_token_t token, const xap_route_t *routes, size_t max_ xap_route_t route; memcpy_P(&route, &routes[id], sizeof(xap_route_t)); +#ifdef SECURE_ENABLE if (route.flags.is_secure && secure_get_status() != SECURE_UNLOCKED) { xap_respond_failure(token, XAP_RESPONSE_FLAG_SECURE_FAILURE); return; @@ -96,6 +97,7 @@ void xap_execute_route(xap_token_t token, const xap_route_t *routes, size_t max_ // xap_respond_failure(token, XAP_RESPONSE_FLAG_UNLOCK_IN_PROGRESS); // return; // } +#endif switch (route.flags.type) { case XAP_ROUTE: @@ -142,6 +144,7 @@ void xap_receive(xap_token_t token, const uint8_t *data, size_t length) { } void xap_event_task(void) { +#ifdef SECURE_ENABLE static secure_status_t last_status = -1; secure_status_t status = secure_get_status(); @@ -149,4 +152,5 @@ void xap_event_task(void) { last_status = status; xap_broadcast_secure_status(status); } +#endif } diff --git a/quantum/xap/xap_handlers.c b/quantum/xap/xap_handlers.c index e16a586dbb..8d0b2e4725 100644 --- a/quantum/xap/xap_handlers.c +++ b/quantum/xap/xap_handlers.c @@ -16,7 +16,13 @@ #include #include + #include "secure.h" +#ifndef SECURE_ENABLE +# define secure_get_status() SECURE_UNLOCKED +# define secure_request_unlock() +# define secure_lock() +#endif void xap_respond_success(xap_token_t token) { xap_send(token, XAP_RESPONSE_FLAG_SUCCESS, NULL, 0); From 05b5a4c23aa8ddf2505f7ca1c3753a902869a228 Mon Sep 17 00:00:00 2001 From: zvecr Date: Mon, 11 Apr 2022 01:08:37 +0100 Subject: [PATCH 50/77] format --- quantum/xap/xap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quantum/xap/xap.c b/quantum/xap/xap.c index 15b56297c9..bda2846a2f 100644 --- a/quantum/xap/xap.c +++ b/quantum/xap/xap.c @@ -69,7 +69,7 @@ struct __attribute__((packed)) xap_route_t { // XAP_VALUE / XAP_CONST_MEM struct { - const void *const_data; + const void * const_data; const uint8_t const_data_len; }; }; From ffb0575eb81f989d0ba034f9ce9d74000bfb7844 Mon Sep 17 00:00:00 2001 From: zvecr Date: Mon, 11 Apr 2022 01:59:48 +0100 Subject: [PATCH 51/77] stub out secure data driven config --- data/mappings/info_config.json | 2 ++ data/mappings/info_rules.json | 1 + data/schemas/keyboard.jsonschema | 24 ++++++++++++++++++++ lib/python/qmk/info.py | 39 ++++++++++++++++++++++++++++++++ quantum/secure.c | 16 ++++++++++++- 5 files changed, 81 insertions(+), 1 deletion(-) diff --git a/data/mappings/info_config.json b/data/mappings/info_config.json index 2121741d19..ccc9cdbb5a 100644 --- a/data/mappings/info_config.json +++ b/data/mappings/info_config.json @@ -78,6 +78,8 @@ "QMK_KEYS_PER_SCAN": {"info_key": "qmk.keys_per_scan", "value_type": "int"}, "QMK_LED": {"info_key": "qmk_lufa_bootloader.led"}, "QMK_SPEAKER": {"info_key": "qmk_lufa_bootloader.speaker"}, + "SECURE_UNLOCK_TIMEOUT": {"info_key": "secure.unlock_timeout", "value_type": "int"}, + "SECURE_IDLE_TIMEOUT": {"info_key": "secure.idle_timeout", "value_type": "int"}, "SENDSTRING_BELL": {"info_key": "audio.macro_beep", "value_type": "bool"}, "SPLIT_MODS_ENABLE": {"info_key": "split.transport.sync_modifiers", "value_type": "bool"}, "SPLIT_TRANSPORT_MIRROR": {"info_key": "split.transport.sync_matrix_state", "value_type": "bool"}, diff --git a/data/mappings/info_rules.json b/data/mappings/info_rules.json index 237e9f1024..4b0fde5629 100644 --- a/data/mappings/info_rules.json +++ b/data/mappings/info_rules.json @@ -20,6 +20,7 @@ "MOUSEKEY_ENABLE": {"info_key": "mouse_key.enabled", "value_type": "bool"}, "NO_USB_STARTUP_CHECK": {"info_key": "usb.no_startup_check", "value_type": "bool"}, "PIN_COMPATIBLE": {"info_key": "pin_compatible"}, + "SECURE_ENABLE": {"info_key": "secure.enabled", "value_type": "bool"}, "SPLIT_KEYBOARD": {"info_key": "split.enabled", "value_type": "bool"}, "SPLIT_TRANSPORT": {"info_key": "split.transport.protocol", "to_c": false}, "WAIT_FOR_USB": {"info_key": "usb.wait_for", "value_type": "bool"} diff --git a/data/schemas/keyboard.jsonschema b/data/schemas/keyboard.jsonschema index dce2e855a1..9cde658128 100644 --- a/data/schemas/keyboard.jsonschema +++ b/data/schemas/keyboard.jsonschema @@ -300,6 +300,30 @@ } } }, + "secure": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": {"type": "boolean"}, + "unlock_timeout": {"$ref": "qmk.definitions.v1#/unsigned_int"}, + "idle_timeout": {"$ref": "qmk.definitions.v1#/unsigned_int"}, + "unlock_sequence": { + "type": "array", + "minLength": 1, + "maxLength": 5, + "items": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": { + "type": "number", + "min": 0, + "multipleOf": 1 + } + } + } + } + }, "split": { "type": "object", "additionalProperties": false, diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index 060404667d..7990504517 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -171,6 +171,29 @@ def _extract_pins(pins): return [_pin_name(pin) for pin in pins.split(',')] +def _parse_2d_array(raw): + """Return a 2d array of ints + """ + out_array = [] + + while raw[-1] != '}': + raw = raw[:-1] + + for row in raw.split('},{'): + if row.startswith('{'): + row = row[1:] + + if row.endswith('}'): + row = row[:-1] + + out_array.append([]) + + for val in row.split(','): + out_array[-1].append(int(val)) + + return out_array + + def _extract_direct_matrix(direct_pins): """ """ @@ -210,6 +233,21 @@ def _extract_audio(info_data, config_c): info_data['audio'] = {'pins': audio_pins} +def _extract_secure_unlock(info_data, config_c): + """Populate data about the secure unlock sequence + """ + unlock = config_c.get('SECURE_UNLOCK_SEQUENCE', '').replace(' ', '')[1:-1] + if unlock: + unlock_array = _parse_2d_array(unlock) + if 'secure' not in info_data: + info_data['secure'] = {} + + if 'unlock_sequence' in info_data['secure']: + _log_warning(info_data, 'Secure unlock sequence is specified in both config.h (SECURE_UNLOCK_SEQUENCE) and info.json (secure.unlock_sequence) (Value: %s), the config.h value wins.' % info_data['secure']['unlock_sequence']) + + info_data['secure']['unlock_sequence'] = unlock_array + + def _extract_split_main(info_data, config_c): """Populate data about the split configuration """ @@ -469,6 +507,7 @@ def _extract_config_h(info_data, config_c): # Pull data that easily can't be mapped in json _extract_matrix_info(info_data, config_c) _extract_audio(info_data, config_c) + _extract_secure_unlock(info_data, config_c) _extract_split_main(info_data, config_c) _extract_split_transport(info_data, config_c) _extract_split_right_pins(info_data, config_c) diff --git a/quantum/secure.c b/quantum/secure.c index 26a3246ccf..751977d351 100644 --- a/quantum/secure.c +++ b/quantum/secure.c @@ -12,6 +12,13 @@ # define SECURE_IDLE_TIMEOUT 60000 #endif +#ifndef SECURE_UNLOCK_SEQUENCE +# define SECURE_UNLOCK_SEQUENCE \ + { \ + { 0, 0 } \ + } +#endif + secure_status_t secure_status = SECURE_LOCKED; static uint32_t unlock_time = 0; static uint32_t idle_time = 0; @@ -41,8 +48,15 @@ void secure_request_unlock(void) { } void secure_keypress_event(uint8_t row, uint8_t col) { + static const uint8_t sequence[][2] = SECURE_UNLOCK_SEQUENCE; + // TODO: check keypress is actually part of unlock sequence - secure_unlock(); + uint8_t offset = 0; + if ((sequence[offset][0] == row) && (sequence[offset][1] == col)) { + secure_unlock(); + } else { + secure_lock(); + } } void secure_task(void) { From 1ea2928d2a9307427afce68c654b3df63c1e205e Mon Sep 17 00:00:00 2001 From: zvecr Date: Mon, 11 Apr 2022 02:14:19 +0100 Subject: [PATCH 52/77] XAP messages extend timeout? --- quantum/secure.c | 6 ++++++ quantum/secure.h | 2 ++ quantum/xap/xap.c | 3 +++ 3 files changed, 11 insertions(+) diff --git a/quantum/secure.c b/quantum/secure.c index 751977d351..375f75fb00 100644 --- a/quantum/secure.c +++ b/quantum/secure.c @@ -47,6 +47,12 @@ void secure_request_unlock(void) { } } +void secure_activity_event(void) { + if (secure_status == SECURE_UNLOCKED) { + idle_time = timer_read32(); + } +} + void secure_keypress_event(uint8_t row, uint8_t col) { static const uint8_t sequence[][2] = SECURE_UNLOCK_SEQUENCE; diff --git a/quantum/secure.h b/quantum/secure.h index ba5fd6cbff..e1e3067c55 100644 --- a/quantum/secure.h +++ b/quantum/secure.h @@ -22,6 +22,8 @@ void secure_unlock(void); void secure_request_unlock(void); +void secure_activity_event(void); + void secure_keypress_event(uint8_t row, uint8_t col); void secure_task(void); diff --git a/quantum/xap/xap.c b/quantum/xap/xap.c index bda2846a2f..61e0d4de30 100644 --- a/quantum/xap/xap.c +++ b/quantum/xap/xap.c @@ -97,6 +97,9 @@ void xap_execute_route(xap_token_t token, const xap_route_t *routes, size_t max_ // xap_respond_failure(token, XAP_RESPONSE_FLAG_UNLOCK_IN_PROGRESS); // return; // } + + // TODO: XAP messages extend timeout? + secure_activity_event(); #endif switch (route.flags.type) { From d19285019d7f9a868f3bd603aa3d7b05c87c9eb3 Mon Sep 17 00:00:00 2001 From: zvecr Date: Tue, 12 Apr 2022 01:37:25 +0100 Subject: [PATCH 53/77] All other subsystems are disabled during unlock --- data/xap/xap_0.1.0.hjson | 4 +- lib/python/qmk/cli/xap/xap.py | 2 +- .../qmk/xap/gen_firmware/inline_generator.py | 11 ++++- quantum/secure.c | 4 -- quantum/secure.h | 40 ++++++++++++++++- quantum/xap/xap.c | 45 ++++++++++++------- 6 files changed, 81 insertions(+), 25 deletions(-) diff --git a/data/xap/xap_0.1.0.hjson b/data/xap/xap_0.1.0.hjson index 3d094eb375..6835ec05bd 100755 --- a/data/xap/xap_0.1.0.hjson +++ b/data/xap/xap_0.1.0.hjson @@ -171,6 +171,7 @@ * 2 means secure routes are allowed * any other value should be interpreted as disabled ''' + permissions: ignore return_type: u8 return_execute: secure_status } @@ -185,6 +186,7 @@ type: command name: Secure Lock define: SECURE_LOCK + permissions: ignore description: Disable secure routes return_execute: secure_lock } @@ -301,7 +303,7 @@ type: command name: Jump to bootloader define: BOOTLOADER_JUMP - secure: true + permissions: secure enable_if_preprocessor: defined(BOOTLOADER_JUMP_SUPPORTED) description: ''' diff --git a/lib/python/qmk/cli/xap/xap.py b/lib/python/qmk/cli/xap/xap.py index 9ab41e3ee3..57f93afaf1 100644 --- a/lib/python/qmk/cli/xap/xap.py +++ b/lib/python/qmk/cli/xap/xap.py @@ -93,7 +93,7 @@ def _xap_transaction(device, sub, route, *args): def _query_device(device): ver_data = _xap_transaction(device, 0x00, 0x00) if not ver_data: - return {'xap': 'UNKNOWN'} + return {'xap': 'UNKNOWN', 'secure': 'UNKNOWN'} # to u32 to BCD string a = (ver_data[3] << 24) + (ver_data[2] << 16) + (ver_data[1] << 8) + (ver_data[0]) diff --git a/lib/python/qmk/xap/gen_firmware/inline_generator.py b/lib/python/qmk/xap/gen_firmware/inline_generator.py index 074a37729d..8aeb356047 100755 --- a/lib/python/qmk/xap/gen_firmware/inline_generator.py +++ b/lib/python/qmk/xap/gen_firmware/inline_generator.py @@ -125,10 +125,17 @@ def _append_routing_table_declaration(lines, container, container_id, route_stac def _append_routing_table_entry_flags(lines, container, container_id, route_stack): - is_secure = 1 if ('secure' in container and container['secure'] is True) else 0 + pem_map = { + None: 'ROUTE_PERMISSIONS_INSECURE', + 'secure': 'ROUTE_PERMISSIONS_SECURE', + 'ignore': 'ROUTE_PERMISSIONS_IGNORE', + } + + is_secure = pem_map[container.get('permissions', None)] + lines.append(' .flags = {') lines.append(f' .type = {_get_route_type(container)},') - lines.append(f' .is_secure = {is_secure},') + lines.append(f' .secure = {is_secure},') lines.append(' },') diff --git a/quantum/secure.c b/quantum/secure.c index 375f75fb00..7eab865b9f 100644 --- a/quantum/secure.c +++ b/quantum/secure.c @@ -27,10 +27,6 @@ secure_status_t secure_get_status(void) { return secure_status; } -bool secure_is_unlocking(void) { - return secure_status == SECURE_PENDING; -} - void secure_lock(void) { secure_status = SECURE_LOCKED; } diff --git a/quantum/secure.h b/quantum/secure.h index e1e3067c55..04507fd5b1 100644 --- a/quantum/secure.h +++ b/quantum/secure.h @@ -3,27 +3,65 @@ #pragma once +/** \file + * + * Exposes a set of functionality to act as a virtual padlock for your device + * ... As long as that padlock is made of paper and its currently raining. + */ + #include #include +/** \brief Available secure states + */ typedef enum { SECURE_LOCKED, SECURE_PENDING, SECURE_UNLOCKED, } secure_status_t; +/** \brief Query current secure state + */ secure_status_t secure_get_status(void); -bool secure_is_unlocking(void); +/** \brief Helper to check if unlocking is currently locked + */ +#define secure_is_locked() (secure_get_status() == SECURE_LOCKED) +/** \brief Helper to check if unlocking is currently in progress + */ +#define secure_is_unlocking() (secure_get_status() == SECURE_PENDING) + +/** \brief Helper to check if unlocking is currently unlocked + */ +#define secure_is_unlocked() (secure_get_status() == SECURE_UNLOCKED) + +/** \brief Lock down the device + */ void secure_lock(void); +/** \brief Force unlock the device + * + * \warning bypasses user unlock sequence + */ void secure_unlock(void); +/** \brief Begin listening for an unlock sequence + */ void secure_request_unlock(void); +/** \brief Flag to the secure subsystem that user activity has happened + * + * Call when some user activity has happened and the device should remain unlocked + */ void secure_activity_event(void); +/** \brief Flag to the secure subsystem that user has triggered a keypress + * + * Call to trigger processing of the unlock sequence + */ void secure_keypress_event(uint8_t row, uint8_t col); +/** \brief Handle various secure subsystem background tasks + */ void secure_task(void); diff --git a/quantum/xap/xap.c b/quantum/xap/xap.c index 61e0d4de30..d4b6eb80fa 100644 --- a/quantum/xap/xap.c +++ b/quantum/xap/xap.c @@ -43,9 +43,17 @@ typedef enum xap_route_type_t { #define XAP_ROUTE_TYPE_BIT_COUNT 3 +typedef enum xap_route_secure_t { + ROUTE_PERMISSIONS_INSECURE, + ROUTE_PERMISSIONS_SECURE, + ROUTE_PERMISSIONS_IGNORE, +} xap_route_secure_t; + +#define XAP_ROUTE_SECURE_BIT_COUNT 2 + typedef struct __attribute__((packed)) xap_route_flags_t { - xap_route_type_t type : XAP_ROUTE_TYPE_BIT_COUNT; - uint8_t is_secure : 1; + xap_route_type_t type : XAP_ROUTE_TYPE_BIT_COUNT; + xap_route_secure_t secure : XAP_ROUTE_SECURE_BIT_COUNT; } xap_route_flags_t; _Static_assert(TOTAL_XAP_ROUTE_TYPES <= (1 << (XAP_ROUTE_TYPE_BIT_COUNT)), "Number of XAP route types is too large for XAP_ROUTE_TYPE_BITS."); @@ -77,6 +85,24 @@ struct __attribute__((packed)) xap_route_t { #include +bool xap_pre_execute_route(xap_token_t token, const xap_route_t *route) { +#ifdef SECURE_ENABLE + if (!secure_is_unlocked() && (route->flags.secure == ROUTE_PERMISSIONS_SECURE)) { + xap_respond_failure(token, XAP_RESPONSE_FLAG_SECURE_FAILURE); + return true; + } + + if (secure_is_unlocking() && (route->flags.type != XAP_ROUTE) && (route->flags.secure != ROUTE_PERMISSIONS_IGNORE)) { + xap_respond_failure(token, XAP_RESPONSE_FLAG_UNLOCK_IN_PROGRESS); + return true; + } + + // TODO: XAP messages extend unlocked timeout? + secure_activity_event(); +#endif + return false; +} + void xap_execute_route(xap_token_t token, const xap_route_t *routes, size_t max_routes, const uint8_t *data, size_t data_len) { if (data_len == 0) return; xap_identifier_t id = data[0]; @@ -85,23 +111,10 @@ void xap_execute_route(xap_token_t token, const xap_route_t *routes, size_t max_ xap_route_t route; memcpy_P(&route, &routes[id], sizeof(xap_route_t)); -#ifdef SECURE_ENABLE - if (route.flags.is_secure && secure_get_status() != SECURE_UNLOCKED) { - xap_respond_failure(token, XAP_RESPONSE_FLAG_SECURE_FAILURE); + if (xap_pre_execute_route(token, &route)) { return; } - // TODO: All other subsystems are disabled during unlock. - // how to flag status route as still allowed? - // if (!route.flags.is_secure && secure_get_status() == SECURE_PENDING) { - // xap_respond_failure(token, XAP_RESPONSE_FLAG_UNLOCK_IN_PROGRESS); - // return; - // } - - // TODO: XAP messages extend timeout? - secure_activity_event(); -#endif - switch (route.flags.type) { case XAP_ROUTE: if (route.child_routes != NULL && route.child_routes_len > 0 && data_len > 0) { From 320f161c7282c4de797bf3765df74571326fe174 Mon Sep 17 00:00:00 2001 From: zvecr Date: Tue, 12 Apr 2022 01:49:30 +0100 Subject: [PATCH 54/77] Process entire unlock sequence --- quantum/secure.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/quantum/secure.c b/quantum/secure.c index 7eab865b9f..4409cb1b2a 100644 --- a/quantum/secure.c +++ b/quantum/secure.c @@ -15,7 +15,9 @@ #ifndef SECURE_UNLOCK_SEQUENCE # define SECURE_UNLOCK_SEQUENCE \ { \ - { 0, 0 } \ + {0, 0}, { \ + 0, 1 \ + } \ } #endif @@ -51,12 +53,17 @@ void secure_activity_event(void) { void secure_keypress_event(uint8_t row, uint8_t col) { static const uint8_t sequence[][2] = SECURE_UNLOCK_SEQUENCE; + static const uint8_t sequence_len = sizeof(sequence) / sizeof(sequence[0]); - // TODO: check keypress is actually part of unlock sequence - uint8_t offset = 0; + static uint8_t offset = 0; if ((sequence[offset][0] == row) && (sequence[offset][1] == col)) { - secure_unlock(); + offset++; + if (offset == sequence_len) { + offset = 0; + secure_unlock(); + } } else { + offset = 0; secure_lock(); } } From 12e8c8d8eee2d8f61e8432d7f2a784c635894be1 Mon Sep 17 00:00:00 2001 From: zvecr Date: Tue, 12 Apr 2022 01:58:02 +0100 Subject: [PATCH 55/77] Process entire unlock sequence - revert changes to SECURE_UNLOCK_SEQUENCE --- quantum/secure.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/quantum/secure.c b/quantum/secure.c index 4409cb1b2a..46b8badacc 100644 --- a/quantum/secure.c +++ b/quantum/secure.c @@ -15,9 +15,7 @@ #ifndef SECURE_UNLOCK_SEQUENCE # define SECURE_UNLOCK_SEQUENCE \ { \ - {0, 0}, { \ - 0, 1 \ - } \ + { 0, 0 } \ } #endif @@ -60,7 +58,7 @@ void secure_keypress_event(uint8_t row, uint8_t col) { offset++; if (offset == sequence_len) { offset = 0; - secure_unlock(); + secure_unlock(); } } else { offset = 0; From f249e33f7015eaba4cc2bae5baab5a5f34b87a07 Mon Sep 17 00:00:00 2001 From: zvecr Date: Tue, 12 Apr 2022 02:08:18 +0100 Subject: [PATCH 56/77] Short term bodge to force dynamic_keymap population --- quantum/eeconfig.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/quantum/eeconfig.c b/quantum/eeconfig.c index 0ff9996ca4..c829e9d263 100644 --- a/quantum/eeconfig.c +++ b/quantum/eeconfig.c @@ -81,6 +81,10 @@ void eeconfig_init_quantum(void) { // properly re-initialized. via_eeprom_set_valid(false); eeconfig_init_via(); +#elif defined(XAP_ENABLE) + // TODO: define XAP reset behaviour + void dynamic_keymap_reset(void); + dynamic_keymap_reset(); #endif eeconfig_init_kb(); From 2563e7b2a0435d56fe7a7e04ab30277ce9df5400 Mon Sep 17 00:00:00 2001 From: zvecr Date: Tue, 12 Apr 2022 13:53:12 +0100 Subject: [PATCH 57/77] format --- quantum/secure.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quantum/secure.c b/quantum/secure.c index 46b8badacc..28ffa6bcde 100644 --- a/quantum/secure.c +++ b/quantum/secure.c @@ -58,7 +58,7 @@ void secure_keypress_event(uint8_t row, uint8_t col) { offset++; if (offset == sequence_len) { offset = 0; - secure_unlock(); + secure_unlock(); } } else { offset = 0; From b0b6594ded3148e9eebd68358ccc5e7ee9d57e86 Mon Sep 17 00:00:00 2001 From: zvecr Date: Wed, 13 Apr 2022 00:37:44 +0100 Subject: [PATCH 58/77] secure keycodes? --- quantum/process_keycode/process_secure.c | 23 ++++++++++++++++++++++- quantum/process_keycode/process_secure.h | 2 ++ quantum/quantum.c | 8 +++++++- quantum/quantum_keycodes.h | 4 ++++ 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/quantum/process_keycode/process_secure.c b/quantum/process_keycode/process_secure.c index af06906344..27f4a439bc 100644 --- a/quantum/process_keycode/process_secure.c +++ b/quantum/process_keycode/process_secure.c @@ -3,8 +3,9 @@ #include "secure.h" #include "process_secure.h" +#include "quantum_keycodes.h" -bool process_secure(uint16_t keycode, keyrecord_t *record) { +bool preprocess_secure(uint16_t keycode, keyrecord_t *record) { if (secure_is_unlocking()) { if (!record->event.pressed) { secure_keypress_event(record->event.key.row, record->event.key.col); @@ -16,3 +17,23 @@ bool process_secure(uint16_t keycode, keyrecord_t *record) { return true; } + +bool process_secure(uint16_t keycode, keyrecord_t *record) { +#ifndef SECURE_DISABLE_KEYCODES + if (!record->event.pressed) { + if (keycode == SECURE_LOCK) { + secure_lock(); + return false; + } + if (keycode == SECURE_UNLOCK) { + secure_lock(); + return false; + } + if (keycode == SECURE_TOGGLE) { + secure_is_locked() ? secure_unlock() : secure_lock(); + return false; + } + } +#endif + return true; +} \ No newline at end of file diff --git a/quantum/process_keycode/process_secure.h b/quantum/process_keycode/process_secure.h index 7f6821f0d8..a39076ebfa 100644 --- a/quantum/process_keycode/process_secure.h +++ b/quantum/process_keycode/process_secure.h @@ -6,4 +6,6 @@ #include #include "action.h" +bool preprocess_secure(uint16_t keycode, keyrecord_t *record); + bool process_secure(uint16_t keycode, keyrecord_t *record); diff --git a/quantum/quantum.c b/quantum/quantum.c index eedc004a30..673ea91b11 100644 --- a/quantum/quantum.c +++ b/quantum/quantum.c @@ -212,6 +212,12 @@ bool process_record_quantum(keyrecord_t *record) { // return false; // } +#if defined(SECURE_ENABLE) + if (!preprocess_secure(keycode, record)) { + return false; + } +#endif + #ifdef VELOCIKEY_ENABLE if (velocikey_enabled() && record->event.pressed) { velocikey_accelerate(); @@ -246,10 +252,10 @@ bool process_record_quantum(keyrecord_t *record) { #if defined(VIA_ENABLE) process_record_via(keycode, record) && #endif + process_record_kb(keycode, record) && #if defined(SECURE_ENABLE) process_secure(keycode, record) && #endif - process_record_kb(keycode, record) && #if defined(SEQUENCER_ENABLE) process_sequencer(keycode, record) && #endif diff --git a/quantum/quantum_keycodes.h b/quantum/quantum_keycodes.h index dacfe5bdcd..c7b4ea593f 100644 --- a/quantum/quantum_keycodes.h +++ b/quantum/quantum_keycodes.h @@ -597,6 +597,10 @@ enum quantum_keycodes { QK_MAKE, + SECURE_LOCK, + SECURE_UNLOCK, + SECURE_TOGGLE, + // Start of custom keycode range for keyboards and keymaps - always leave at the end SAFE_RANGE }; From 1d96fc866d520cf032379c1bb7674fcfd98067d8 Mon Sep 17 00:00:00 2001 From: zvecr Date: Tue, 19 Apr 2022 00:04:22 +0100 Subject: [PATCH 59/77] Add route for hardware_id --- data/xap/xap_0.1.0.hjson | 8 ++++++++ lib/python/qmk/cli/xap/xap.py | 8 ++++++++ lib/python/qmk/xap/gen_firmware/header_generator.py | 6 ++++-- quantum/xap/xap_handlers.c | 6 ++++++ 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/data/xap/xap_0.1.0.hjson b/data/xap/xap_0.1.0.hjson index 6835ec05bd..7ad69aafba 100755 --- a/data/xap/xap_0.1.0.hjson +++ b/data/xap/xap_0.1.0.hjson @@ -317,6 +317,14 @@ return_type: u8 return_execute: request_bootloader_jump } + 0x08: { + type: command + name: info.json + define: HARDWARE_ID + description: Retrieves a unique identifier for the board. + return_type: u32[4] + return_execute: get_hardware_id + } } }, diff --git a/lib/python/qmk/cli/xap/xap.py b/lib/python/qmk/cli/xap/xap.py index 57f93afaf1..71ff23a9a4 100644 --- a/lib/python/qmk/cli/xap/xap.py +++ b/lib/python/qmk/cli/xap/xap.py @@ -35,6 +35,9 @@ def print_dotted_output(kb_info_json, prefix=''): continue elif key == 'layouts' and prefix == '': cli.echo(' {fg_blue}layouts{fg_reset}: %s', ', '.join(sorted(kb_info_json['layouts'].keys()))) + elif isinstance(kb_info_json[key], bytes): + conv = "".join(["{:02X}".format(b) for b in kb_info_json[key]]) + cli.echo(' {fg_blue}%s{fg_reset}: %s', new_prefix, conv) elif isinstance(kb_info_json[key], dict): print_dotted_output(kb_info_json[key], new_prefix) elif isinstance(kb_info_json[key], list): @@ -105,6 +108,10 @@ def _query_device(device): return {'xap': ver, 'secure': secure} +def _query_device_id(device): + return _xap_transaction(device, 0x01, 0x08) + + def _query_device_info_len(device): len_data = _xap_transaction(device, 0x01, 0x05) if not len_data: @@ -146,6 +153,7 @@ def _list_devices(): if cli.config.general.verbose: # TODO: better formatting like "lsusb -v"? data = _query_device_info(device) + data["_id"] = _query_device_id(device) print_dotted_output(data) diff --git a/lib/python/qmk/xap/gen_firmware/header_generator.py b/lib/python/qmk/xap/gen_firmware/header_generator.py index 0af3b6d7c4..292cd4a7b3 100755 --- a/lib/python/qmk/xap/gen_firmware/header_generator.py +++ b/lib/python/qmk/xap/gen_firmware/header_generator.py @@ -150,8 +150,10 @@ def _append_route_types(lines, container, container_id=None, route_stack=None): elif 'return_type' in container: return_type = container['return_type'] - if return_type == 'u8[32]': - lines.append(f'typedef struct {{ uint8_t x[32]; }} {route_name}_t;') + found = re.search(r'(u\d+)\[(\d+)\]', return_type) + if found: + return_type, size = found.groups() + lines.append(f'typedef struct {{ {_get_c_type(return_type)} x[{size}]; }} {route_name}_t;') else: lines.append(f'typedef {_get_c_type(return_type)} {route_name}_t;') diff --git a/quantum/xap/xap_handlers.c b/quantum/xap/xap_handlers.c index 8d0b2e4725..c6350b1038 100644 --- a/quantum/xap/xap_handlers.c +++ b/quantum/xap/xap_handlers.c @@ -17,6 +17,7 @@ #include #include +#include "hardware_id.h" #include "secure.h" #ifndef SECURE_ENABLE # define secure_get_status() SECURE_UNLOCKED @@ -98,6 +99,11 @@ bool xap_respond_request_bootloader_jump(xap_token_t token, const void *data, si } #endif +bool xap_respond_get_hardware_id(xap_token_t token, const void *data, size_t length) { + hardware_id_t ret = get_hardware_id(); + return xap_respond_data(token, &ret, sizeof(ret)); +} + #if ((defined(DYNAMIC_KEYMAP_ENABLE))) bool xap_respond_dynamic_keymap_get_keycode(xap_token_t token, const void *data, size_t length) { if (length != sizeof(xap_route_dynamic_keymap_get_keymap_keycode_arg_t)) { From 3730ddacacc5e12567a5947577b9db8691306d98 Mon Sep 17 00:00:00 2001 From: zvecr Date: Tue, 19 Apr 2022 02:06:01 +0100 Subject: [PATCH 60/77] Fix ARM builds due to packing inconsistencies --- .../qmk/xap/gen_firmware/header_generator.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/python/qmk/xap/gen_firmware/header_generator.py b/lib/python/qmk/xap/gen_firmware/header_generator.py index 292cd4a7b3..52b1bc29ca 100755 --- a/lib/python/qmk/xap/gen_firmware/header_generator.py +++ b/lib/python/qmk/xap/gen_firmware/header_generator.py @@ -131,11 +131,16 @@ def _append_route_types(lines, container, container_id=None, route_stack=None): member_type = _get_c_type(member['type']) member_name = to_snake(member['name']) lines.append(f' {member_type} {member_name};') - lines.append(f'}} {route_name}_arg_t;') + lines.append(f'}} __attribute__((__packed__)) {route_name}_arg_t;') elif 'request_type' in container: request_type = container['request_type'] - lines.append(f'typedef {_get_c_type(request_type)} {route_name}_arg_t;') + found = re.search(r'(u\d+)\[(\d+)\]', request_type) + if found: + request_type, size = found.groups() + lines.append(f'typedef struct __attribute__((__packed__)) {{ {_get_c_type(request_type)} x[{size}]; }} {route_name}_arg_t;') + else: + lines.append(f'typedef {_get_c_type(request_type)} {route_name}_arg_t;') # Outbound qualifier = 'const' if 'return_constant' in container else '' @@ -146,14 +151,14 @@ def _append_route_types(lines, container, container_id=None, route_stack=None): member_type = _get_c_type(member['type']) member_name = f'{qualifier} {to_snake(member["name"])}' lines.append(f' {member_type} {member_name};') - lines.append(f'}} {route_name}_t;') + lines.append(f'}} __attribute__((__packed__)) {route_name}_t;') elif 'return_type' in container: return_type = container['return_type'] found = re.search(r'(u\d+)\[(\d+)\]', return_type) if found: return_type, size = found.groups() - lines.append(f'typedef struct {{ {_get_c_type(return_type)} x[{size}]; }} {route_name}_t;') + lines.append(f'typedef struct __attribute__((__packed__)) {{ {_get_c_type(return_type)} x[{size}]; }} {route_name}_t;') else: lines.append(f'typedef {_get_c_type(return_type)} {route_name}_t;') @@ -207,7 +212,7 @@ def _append_internal_types(lines, container): if member_type == 'unknown': member_type = additional_types[member["type"]] lines.append(f' {member_type} {member_name};') - lines.append(f'}} xap_{key}_t;') + lines.append(f'}} __attribute__((__packed__)) xap_{key}_t;') else: lines.append(f'typedef {data_type} xap_{key}_t;') From d17aed8e8266398113b50e7a7cc6abbd46d205fb Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Tue, 19 Apr 2022 12:33:56 +1000 Subject: [PATCH 61/77] Verify struct sizing at build time. --- data/xap/xap_0.0.1.hjson | 2 ++ data/xap/xap_0.1.0.hjson | 2 ++ data/xap/xap_0.2.0.hjson | 2 ++ lib/python/qmk/xap/gen_firmware/header_generator.py | 9 +++++++++ 4 files changed, 15 insertions(+) diff --git a/data/xap/xap_0.0.1.hjson b/data/xap/xap_0.0.1.hjson index b8a479f01b..382075b941 100755 --- a/data/xap/xap_0.0.1.hjson +++ b/data/xap/xap_0.0.1.hjson @@ -142,6 +142,7 @@ name: Request Header description: Packet format for inbound data. type: struct + struct_length: 3 struct_members: [ { type: token @@ -158,6 +159,7 @@ name: Response Header description: Packet format for inbound data. type: struct + struct_length: 4 struct_members: [ { type: token diff --git a/data/xap/xap_0.1.0.hjson b/data/xap/xap_0.1.0.hjson index 7ad69aafba..9499314d4b 100755 --- a/data/xap/xap_0.1.0.hjson +++ b/data/xap/xap_0.1.0.hjson @@ -83,6 +83,7 @@ name: Broadcast Header description: Packet format for broadcast messages. type: struct + struct_length: 4 struct_members: [ { type: token @@ -240,6 +241,7 @@ Retrieves the set of identifying information for the board. ''' return_type: struct + return_struct_length: 10 return_struct_members: [ { type: u16 diff --git a/data/xap/xap_0.2.0.hjson b/data/xap/xap_0.2.0.hjson index f2fce07b84..2cb7103578 100755 --- a/data/xap/xap_0.2.0.hjson +++ b/data/xap/xap_0.2.0.hjson @@ -38,6 +38,7 @@ define: GET_KEYMAP_KEYCODE description: TODO request_type: struct + request_struct_length: 3 request_struct_members: [ { type: u8 @@ -61,6 +62,7 @@ define: SET_KEYMAP_KEYCODE description: TODO request_type: struct + request_struct_length: 5 request_struct_members: [ { type: u8 diff --git a/lib/python/qmk/xap/gen_firmware/header_generator.py b/lib/python/qmk/xap/gen_firmware/header_generator.py index 52b1bc29ca..7ce1d9d84e 100755 --- a/lib/python/qmk/xap/gen_firmware/header_generator.py +++ b/lib/python/qmk/xap/gen_firmware/header_generator.py @@ -133,6 +133,9 @@ def _append_route_types(lines, container, container_id=None, route_stack=None): lines.append(f' {member_type} {member_name};') lines.append(f'}} __attribute__((__packed__)) {route_name}_arg_t;') + req_len = container['request_struct_length'] + lines.append(f'_Static_assert(sizeof({route_name}_arg_t) == {req_len}, "{route_name}_arg_t needs to be {req_len} bytes in size");') + elif 'request_type' in container: request_type = container['request_type'] found = re.search(r'(u\d+)\[(\d+)\]', request_type) @@ -153,6 +156,9 @@ def _append_route_types(lines, container, container_id=None, route_stack=None): lines.append(f' {member_type} {member_name};') lines.append(f'}} __attribute__((__packed__)) {route_name}_t;') + req_len = container['return_struct_length'] + lines.append(f'_Static_assert(sizeof({route_name}_t) == {req_len}, "{route_name}_t needs to be {req_len} bytes in size");') + elif 'return_type' in container: return_type = container['return_type'] found = re.search(r'(u\d+)\[(\d+)\]', return_type) @@ -213,6 +219,9 @@ def _append_internal_types(lines, container): member_type = additional_types[member["type"]] lines.append(f' {member_type} {member_name};') lines.append(f'}} __attribute__((__packed__)) xap_{key}_t;') + + req_len = value['struct_length'] + lines.append(f'_Static_assert(sizeof(xap_{key}_t) == {req_len}, "xap_{key}_t needs to be {req_len} bytes in size");') else: lines.append(f'typedef {data_type} xap_{key}_t;') From 4d895892e5349285f3eb9c86c35e723e629f048c Mon Sep 17 00:00:00 2001 From: zvecr Date: Wed, 20 Apr 2022 22:38:06 +0100 Subject: [PATCH 62/77] Stubs for ENCODER_MAP --- data/xap/xap_0.2.0.hjson | 52 ++++++++++++++++++++++++++++++++++- lib/python/qmk/cli/xap/xap.py | 37 ++++++++++++++++--------- quantum/xap/xap_handlers.c | 25 +++++++++++++++++ 3 files changed, 100 insertions(+), 14 deletions(-) diff --git a/data/xap/xap_0.2.0.hjson b/data/xap/xap_0.2.0.hjson index 2cb7103578..6fd9853148 100755 --- a/data/xap/xap_0.2.0.hjson +++ b/data/xap/xap_0.2.0.hjson @@ -81,7 +81,6 @@ name: Keycode } ] - return_type: u8 return_execute: dynamic_keymap_set_keycode } } @@ -109,6 +108,57 @@ return_purpose: capabilities return_constant: XAP_ROUTE_DYNAMIC_ENCODER_CAPABILITIES } + 0x02: { + type: command + name: Get Keycode + define: GET_ENCODER_KEYCODE + description: TODO + request_type: struct + request_struct_length: 3 + request_struct_members: [ + { + type: u8 + name: Layer + }, + { + type: u8 + name: Encoder + }, + { + type: u8 + name: Clockwise + } + ] + return_type: u16 + return_execute: dynamic_encoder_get_keycode + } + 0x03: { + type: command + name: Set Keycode + define: SET_ENCODER_KEYCODE + description: TODO + request_type: struct + request_struct_length: 5 + request_struct_members: [ + { + type: u8 + name: Layer + }, + { + type: u8 + name: Encoder + }, + { + type: u8 + name: Clockwise + }, + { + type: u16 + name: Keycode + } + ] + return_execute: dynamic_encoder_set_keycode + } } } diff --git a/lib/python/qmk/cli/xap/xap.py b/lib/python/qmk/cli/xap/xap.py index 71ff23a9a4..8ce77e422a 100644 --- a/lib/python/qmk/cli/xap/xap.py +++ b/lib/python/qmk/cli/xap/xap.py @@ -157,26 +157,34 @@ def _list_devices(): print_dotted_output(data) -def xap_doit(): - print("xap_doit") +def xap_dump_keymap(device): # get layer count - # layers = _xap_transaction(device, 0x04, 0x01) - # layers = int.from_bytes(layers, "little") - # print(f'layers:{layers}') + layers = _xap_transaction(device, 0x04, 0x01) + layers = int.from_bytes(layers, "little") + print(f'layers:{layers}') # get keycode [layer:0, row:0, col:0] # keycode = _xap_transaction(device, 0x04, 0x02, b"\x00\x00\x00") - # keycode = int.from_bytes(keycode, "little") - # keycode_map = { - # # TODO: this should be data driven... - # 0x04: 'KC_A', - # 0x05: 'KC_B', - # 0x29: 'KC_ESCAPE' - # } - # print('keycode:' + keycode_map.get(keycode, 'unknown')) + # get encoder [layer:0, index:0, clockwise:0] + keycode = _xap_transaction(device, 0x05, 0x02, b"\x00\x00\x00") + + keycode = int.from_bytes(keycode, "little") + keycode_map = { + # TODO: this should be data driven... + 0x04: 'KC_A', + 0x05: 'KC_B', + 0x29: 'KC_ESCAPE', + 0xF9: 'KC_MS_WH_UP', + } + print(f'keycode:{keycode_map.get(keycode, "unknown")}') + + +def xap_doit(): + print("xap_doit") # Reboot # _xap_transaction(device, 0x01, 0x07) + exit(1) def xap_broadcast_listen(device): @@ -227,6 +235,9 @@ def xap(cli): xap_unlock(device) cli.log.info("Done") + elif cli.args.action == 'dump': + xap_dump_keymap(device) + elif cli.args.action == 'listen': xap_broadcast_listen(device) diff --git a/quantum/xap/xap_handlers.c b/quantum/xap/xap_handlers.c index c6350b1038..52b74cc2b0 100644 --- a/quantum/xap/xap_handlers.c +++ b/quantum/xap/xap_handlers.c @@ -128,3 +128,28 @@ bool xap_respond_dynamic_keymap_set_keycode(xap_token_t token, const void *data, return true; } #endif + +#if ((defined(ENCODER_MAP_ENABLE))) +bool xap_respond_dynamic_encoder_get_keycode(xap_token_t token, const void *data, size_t length) { + if (length != sizeof(xap_route_dynamic_encoder_get_encoder_keycode_arg_t)) { + return false; + } + + xap_route_dynamic_encoder_get_encoder_keycode_arg_t *arg = (xap_route_dynamic_encoder_get_encoder_keycode_arg_t *)data; + + uint16_t keycode = dynamic_keymap_get_encoder(arg->layer, arg->encoder, arg->clockwise); + return xap_respond_data(token, &keycode, sizeof(keycode)); +} + +bool xap_respond_dynamic_encoder_set_keycode(xap_token_t token, const void *data, size_t length) { + if (length != sizeof(xap_route_dynamic_encoder_set_encoder_keycode_arg_t)) { + return false; + } + + xap_route_dynamic_encoder_set_encoder_keycode_arg_t *arg = (xap_route_dynamic_encoder_set_encoder_keycode_arg_t *)data; + + dynamic_keymap_set_encoder(arg->layer, arg->encoder, arg->clockwise, arg->keycode); + xap_respond_success(token); + return true; +} +#endif \ No newline at end of file From c01e8ed75d8a8624c64fcf283218446ceaa5feb7 Mon Sep 17 00:00:00 2001 From: zvecr Date: Wed, 20 Apr 2022 23:47:08 +0100 Subject: [PATCH 63/77] stash --- lib/python/qmk/cli/xap/xap.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/python/qmk/cli/xap/xap.py b/lib/python/qmk/cli/xap/xap.py index 8ce77e422a..737d9738b1 100644 --- a/lib/python/qmk/cli/xap/xap.py +++ b/lib/python/qmk/cli/xap/xap.py @@ -177,7 +177,10 @@ def xap_dump_keymap(device): 0x29: 'KC_ESCAPE', 0xF9: 'KC_MS_WH_UP', } - print(f'keycode:{keycode_map.get(keycode, "unknown")}') + print(f'keycode:{keycode_map.get(keycode, "unknown")}[{keycode}]') + + # set encoder [layer:0, index:0, clockwise:0, keycode:KC_A] + _xap_transaction(device, 0x05, 0x03, b"\x00\x00\x00\x04\00") def xap_doit(): From 01cd1ac71f6a9b9199e9c9212ae11c03a89826ca Mon Sep 17 00:00:00 2001 From: zvecr Date: Thu, 21 Apr 2022 18:26:27 +0100 Subject: [PATCH 64/77] stash --- keyboards/zvecr/zv48/keymaps/xap/keymap.c | 102 ++++++++++++++++++++++ keyboards/zvecr/zv48/keymaps/xap/rules.mk | 2 + 2 files changed, 104 insertions(+) create mode 100644 keyboards/zvecr/zv48/keymaps/xap/keymap.c create mode 100644 keyboards/zvecr/zv48/keymaps/xap/rules.mk diff --git a/keyboards/zvecr/zv48/keymaps/xap/keymap.c b/keyboards/zvecr/zv48/keymaps/xap/keymap.c new file mode 100644 index 0000000000..c45e573bd9 --- /dev/null +++ b/keyboards/zvecr/zv48/keymaps/xap/keymap.c @@ -0,0 +1,102 @@ +// Copyright 2020 zvecr +// SPDX-License-Identifier: GPL-2.0-or-later +#include QMK_KEYBOARD_H + +// Defines names for use in layer keycodes and the keymap +enum layer_names { + _QWERTY, + _LOWER, + _RAISE, + _ADJUST, +}; + +#define LOWER MO(_LOWER) +#define RAISE MO(_RAISE) + +const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { +/* Qwerty + * ,-----------------------------------------------------------------------------------. + * | Esc | Q | W | E | R | T | Y | U | I | O | P | Bksp | + * |------+------+------+------+------+-------------+------+------+------+------+------| + * | Tab | A | S | D | F | G | H | J | K | L | ; | " | + * |------+------+------+------+------+------|------+------+------+------+------+------| + * | Shift| Z | X | C | V | B | N | M | , | . | / |Enter | + * |------+------+------+------+------+------+------+------+------+------+------+------| + * | Ctrl | GUI | Alt | App |Lower | Space |Raise | Left | Down | Up |Right | + * `-----------------------------------------------------------------------------------' + */ +[_QWERTY] = LAYOUT_ortho_4x12( + KC_ESC, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_BSPC, + KC_TAB, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT, + KC_LSFT, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_ENT , + KC_LCTL, KC_LGUI, KC_LALT, KC_APP, LOWER, KC_SPC, KC_SPC, RAISE, KC_LEFT, KC_DOWN, KC_UP, KC_RGHT +), + +/* Lower + * ,-----------------------------------------------------------------------------------. + * | ~ | ! | @ | # | $ | % | ^ | & | * | ( | ) | Del | + * |------+------+------+------+------+-------------+------+------+------+------+------| + * | Del | F1 | F2 | F3 | F4 | F5 | F6 | _ | + | { | } | | | + * |------+------+------+------+------+------|------+------+------+------+------+------| + * | | F7 | F8 | F9 | F10 | F11 | F12 |ISO ~ |ISO | | | | | + * |------+------+------+------+------+------+------+------+------+------+------+------| + * | | | | | | | | | Next | Vol- | Vol+ | Play | + * `-----------------------------------------------------------------------------------' + */ +[_LOWER] = LAYOUT_ortho_4x12( + KC_TILD, KC_EXLM, KC_AT, KC_HASH, KC_DLR, KC_PERC, KC_CIRC, KC_AMPR, KC_ASTR, KC_LPRN, KC_RPRN, KC_DEL, + KC_DEL, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_UNDS, KC_PLUS, KC_LCBR, KC_RCBR, KC_PIPE, + _______, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12,S(KC_NUHS),S(KC_NUBS),_______, _______, _______, + _______, _______, _______, _______, _______, _______, _______, _______, KC_MNXT, KC_VOLD, KC_VOLU, KC_MPLY +), + +/* Raise + * ,-----------------------------------------------------------------------------------. + * | ` | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | Del | + * |------+------+------+------+------+-------------+------+------+------+------+------| + * | Del | F1 | F2 | F3 | F4 | F5 | F6 | - | = | [ | ] | \ | + * |------+------+------+------+------+------|------+------+------+------+------+------| + * | | F7 | F8 | F9 | F10 | F11 | F12 |ISO # |ISO / | | | | + * |------+------+------+------+------+------+------+------+------+------+------+------| + * | | | | | | | | | Next | Vol- | Vol+ | Play | + * `-----------------------------------------------------------------------------------' + */ +[_RAISE] = LAYOUT_ortho_4x12( + KC_GRV, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_DEL, + KC_DEL, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_MINS, KC_EQL, KC_LBRC, KC_RBRC, KC_BSLS, + _______, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, KC_NUHS, KC_NUBS, _______, _______, _______, + _______, _______, _______, _______, _______, _______, _______, _______, KC_MNXT, KC_VOLD, KC_VOLU, KC_MPLY +), + +/* Adjust (Lower + Raise) + * ,-----------------------------------------------------------------------------------. + * | | Reset| | | | |R Tog |R Mode|R Rev |R Grad| Reset| | + * |------+------+------+------+------+-------------+------+------+------+------+------| + * | | | | | | |R HUI|R SAI|R VAI| | | | + * |------+------+------+------+------+------|------+------+------+------+------+------| + * | | | | | | |R HUD|R SAD|R VAD| | | | + * |------+------+------+------+------+------+------+------+------+------+------+------| + * | | | | | | | | | | | | | + * `-----------------------------------------------------------------------------------' + */ +[_ADJUST] = LAYOUT_ortho_4x12( + _______, RESET, _______, _______, _______, _______, RGB_TOG, RGB_MOD, RGB_RMOD,RGB_M_G, RESET, _______, + _______, _______, _______, _______, _______, _______, RGB_HUI, RGB_SAI, RGB_VAI, _______, _______, _______, + _______, _______, _______, _______, _______, _______, RGB_HUD, RGB_SAD, RGB_VAD, _______, _______, _______, + _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______ +) + +}; + +layer_state_t layer_state_set_user(layer_state_t state) { + return update_tri_layer_state(state, _LOWER, _RAISE, _ADJUST); +} + +#if defined(ENCODER_MAP_ENABLE) +const uint16_t PROGMEM encoder_map[][NUM_ENCODERS][2] = { + [_QWERTY] = { ENCODER_CCW_CW(KC_MS_WH_UP, KC_MS_WH_DOWN), ENCODER_CCW_CW(KC_VOLD, KC_VOLU) }, + [_LOWER] = { ENCODER_CCW_CW(RGB_HUD, RGB_HUI), ENCODER_CCW_CW(RGB_SAD, RGB_SAI) }, + [_RAISE] = { ENCODER_CCW_CW(RGB_VAD, RGB_VAI), ENCODER_CCW_CW(RGB_SPD, RGB_SPI) }, + [_ADJUST] = { ENCODER_CCW_CW(RGB_RMOD, RGB_MOD), ENCODER_CCW_CW(KC_RIGHT, KC_LEFT) }, +}; +#endif diff --git a/keyboards/zvecr/zv48/keymaps/xap/rules.mk b/keyboards/zvecr/zv48/keymaps/xap/rules.mk new file mode 100644 index 0000000000..1eb54897cd --- /dev/null +++ b/keyboards/zvecr/zv48/keymaps/xap/rules.mk @@ -0,0 +1,2 @@ +ENCODER_MAP_ENABLE = yes +XAP_ENABLE = yes From c65ec90484edfbddf4c5ba824066717e5d39fde3 Mon Sep 17 00:00:00 2001 From: zvecr Date: Thu, 5 May 2022 21:05:10 +0100 Subject: [PATCH 65/77] Fix a few mistakes in docs --- data/xap/xap_0.0.1.hjson | 4 ++-- docs/xap_0.0.1.md | 4 ++-- docs/xap_0.1.0.md | 5 +++-- docs/xap_0.2.0.md | 5 +++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/data/xap/xap_0.0.1.hjson b/data/xap/xap_0.0.1.hjson index 382075b941..48325247bf 100755 --- a/data/xap/xap_0.0.1.hjson +++ b/data/xap/xap_0.0.1.hjson @@ -46,7 +46,7 @@ Communication generally follows a request/response pattern. - Each request needs to include a _token_ -- this `u16` value prefixes each outbound request from the host application and its corresponding response, allowing repsonse messages to be correlated with their request, even if multiple host applications are communicating with the firmware simultaneously. Host applications should randomly generate a token ID for **every** outbound request, unless using a reserved token defined below. + Each request needs to include a _token_ -- this `u16` value prefixes each outbound request from the host application and its corresponding response, allowing response messages to be correlated with their request, even if multiple host applications are communicating with the firmware simultaneously. Host applications should randomly generate a token ID for **every** outbound request, unless using a reserved token defined below. This token is followed by a `u8` signifying the length of data in the request. ''' @@ -157,7 +157,7 @@ response_header: { name: Response Header - description: Packet format for inbound data. + description: Packet format for outbound data. type: struct struct_length: 4 struct_members: [ diff --git a/docs/xap_0.0.1.md b/docs/xap_0.0.1.md index afa23c85c3..313ab9e488 100644 --- a/docs/xap_0.0.1.md +++ b/docs/xap_0.0.1.md @@ -27,14 +27,14 @@ This list defines the terms used across the entire set of XAP protocol documenta | _ID_ | A single octet / 8-bit byte, representing Subsystem or Route index. | | _Request Header_ | Packet format for inbound data. Takes the format:
`token` - token
`u8` - length | | _Response Flags_ | An `u8` containing the status of the request. | -| _Response Header_ | Packet format for inbound data. Takes the format:
`token` - token
`response_flags` - flags
`u8` - length | +| _Response Header_ | Packet format for outbound data. Takes the format:
`token` - token
`response_flags` - flags
`u8` - length | | _Token_ | A `u16` associated with a specific request as well as its corresponding response. | ## Requests and Responses Communication generally follows a request/response pattern. -Each request needs to include a _token_ -- this `u16` value prefixes each outbound request from the host application and its corresponding response, allowing repsonse messages to be correlated with their request, even if multiple host applications are communicating with the firmware simultaneously. Host applications should randomly generate a token ID for **every** outbound request, unless using a reserved token defined below. +Each request needs to include a _token_ -- this `u16` value prefixes each outbound request from the host application and its corresponding response, allowing response messages to be correlated with their request, even if multiple host applications are communicating with the firmware simultaneously. Host applications should randomly generate a token ID for **every** outbound request, unless using a reserved token defined below. This token is followed by a `u8` signifying the length of data in the request. diff --git a/docs/xap_0.1.0.md b/docs/xap_0.1.0.md index c61ee1df60..73a76680c5 100644 --- a/docs/xap_0.1.0.md +++ b/docs/xap_0.1.0.md @@ -29,17 +29,18 @@ This list defines the terms used across the entire set of XAP protocol documenta | _Secure Route_ | A _route_ which has potentially destructive consequences, necessitating prior approval by the user before executing. | | _Subsystem_ | A high-level area of functionality within XAP. | | _Unlock sequence_ | A physical sequence initiated by the user to enable execution of _secure routes_. | +| _Broadcast Header_ | Packet format for broadcast messages. Takes the format:
`token` - token
`u8` - type
`u8` - length | | _ID_ | A single octet / 8-bit byte, representing Subsystem or Route index. | | _Request Header_ | Packet format for inbound data. Takes the format:
`token` - token
`u8` - length | | _Response Flags_ | An `u8` containing the status of the request. | -| _Response Header_ | Packet format for inbound data. Takes the format:
`token` - token
`response_flags` - flags
`u8` - length | +| _Response Header_ | Packet format for outbound data. Takes the format:
`token` - token
`response_flags` - flags
`u8` - length | | _Token_ | A `u16` associated with a specific request as well as its corresponding response. | ## Requests and Responses Communication generally follows a request/response pattern. -Each request needs to include a _token_ -- this `u16` value prefixes each outbound request from the host application and its corresponding response, allowing repsonse messages to be correlated with their request, even if multiple host applications are communicating with the firmware simultaneously. Host applications should randomly generate a token ID for **every** outbound request, unless using a reserved token defined below. +Each request needs to include a _token_ -- this `u16` value prefixes each outbound request from the host application and its corresponding response, allowing response messages to be correlated with their request, even if multiple host applications are communicating with the firmware simultaneously. Host applications should randomly generate a token ID for **every** outbound request, unless using a reserved token defined below. This token is followed by a `u8` signifying the length of data in the request. diff --git a/docs/xap_0.2.0.md b/docs/xap_0.2.0.md index c61ee1df60..73a76680c5 100644 --- a/docs/xap_0.2.0.md +++ b/docs/xap_0.2.0.md @@ -29,17 +29,18 @@ This list defines the terms used across the entire set of XAP protocol documenta | _Secure Route_ | A _route_ which has potentially destructive consequences, necessitating prior approval by the user before executing. | | _Subsystem_ | A high-level area of functionality within XAP. | | _Unlock sequence_ | A physical sequence initiated by the user to enable execution of _secure routes_. | +| _Broadcast Header_ | Packet format for broadcast messages. Takes the format:
`token` - token
`u8` - type
`u8` - length | | _ID_ | A single octet / 8-bit byte, representing Subsystem or Route index. | | _Request Header_ | Packet format for inbound data. Takes the format:
`token` - token
`u8` - length | | _Response Flags_ | An `u8` containing the status of the request. | -| _Response Header_ | Packet format for inbound data. Takes the format:
`token` - token
`response_flags` - flags
`u8` - length | +| _Response Header_ | Packet format for outbound data. Takes the format:
`token` - token
`response_flags` - flags
`u8` - length | | _Token_ | A `u16` associated with a specific request as well as its corresponding response. | ## Requests and Responses Communication generally follows a request/response pattern. -Each request needs to include a _token_ -- this `u16` value prefixes each outbound request from the host application and its corresponding response, allowing repsonse messages to be correlated with their request, even if multiple host applications are communicating with the firmware simultaneously. Host applications should randomly generate a token ID for **every** outbound request, unless using a reserved token defined below. +Each request needs to include a _token_ -- this `u16` value prefixes each outbound request from the host application and its corresponding response, allowing response messages to be correlated with their request, even if multiple host applications are communicating with the firmware simultaneously. Host applications should randomly generate a token ID for **every** outbound request, unless using a reserved token defined below. This token is followed by a `u8` signifying the length of data in the request. From cc851142faf60ad0c75f95fd588a68e8159fec47 Mon Sep 17 00:00:00 2001 From: zvecr Date: Thu, 5 May 2022 22:16:38 +0100 Subject: [PATCH 66/77] Add cli interactive shell --- lib/python/qmk/cli/xap/xap.py | 102 ++++++++++++++++++++++++---------- 1 file changed, 74 insertions(+), 28 deletions(-) diff --git a/lib/python/qmk/cli/xap/xap.py b/lib/python/qmk/cli/xap/xap.py index 737d9738b1..c49c6062cc 100644 --- a/lib/python/qmk/cli/xap/xap.py +++ b/lib/python/qmk/cli/xap/xap.py @@ -1,5 +1,6 @@ """Interactions with compatible XAP devices """ +import cmd import json import random import gzip @@ -7,6 +8,14 @@ from platform import platform from milc import cli +KEYCODE_MAP = { + # TODO: this should be data driven... + 0x04: 'KC_A', + 0x05: 'KC_B', + 0x29: 'KC_ESCAPE', + 0xF9: 'KC_MS_WH_UP', +} + def _is_xap_usage(x): return x['usage_page'] == 0xFF51 and x['usage'] == 0x0058 @@ -170,26 +179,12 @@ def xap_dump_keymap(device): keycode = _xap_transaction(device, 0x05, 0x02, b"\x00\x00\x00") keycode = int.from_bytes(keycode, "little") - keycode_map = { - # TODO: this should be data driven... - 0x04: 'KC_A', - 0x05: 'KC_B', - 0x29: 'KC_ESCAPE', - 0xF9: 'KC_MS_WH_UP', - } - print(f'keycode:{keycode_map.get(keycode, "unknown")}[{keycode}]') + print(f'keycode:{KEYCODE_MAP.get(keycode, "unknown")}[{keycode}]') # set encoder [layer:0, index:0, clockwise:0, keycode:KC_A] _xap_transaction(device, 0x05, 0x03, b"\x00\x00\x00\x04\00") -def xap_doit(): - print("xap_doit") - # Reboot - # _xap_transaction(device, 0x01, 0x07) - exit(1) - - def xap_broadcast_listen(device): try: cli.log.info("Listening for XAP broadcasts...") @@ -208,9 +203,67 @@ def xap_unlock(device): _xap_transaction(device, 0x00, 0x04) +class XAPShell(cmd.Cmd): + intro = 'Welcome to the XAP shell. Type help or ? to list commands.\n' + prompt = 'Ψ> ' + + def __init__(self, device): + cmd.Cmd.__init__(self) + self.device = device + + def do_about(self, arg): + """Prints out the current version of QMK with a build date + """ + data = _query_device(self.device) + print(data) + + def do_unlock(self, arg): + """Initiate secure unlock + """ + xap_unlock(self.device) + print("Done") + + def do_listen(self, arg): + """Log out XAP broadcast messages + """ + xap_broadcast_listen(self.device) + + def do_keycode(self, arg): + """Prints out the keycode value of a certain layer, row, and column + """ + data = bytes(map(int, arg.split())) + if len(data) != 3: + cli.log.error("Invalid args") + return + + keycode = _xap_transaction(self.device, 0x04, 0x02, data) + keycode = int.from_bytes(keycode, "little") + print(f'keycode:{KEYCODE_MAP.get(keycode, "unknown")}[{keycode}]') + + def do_exit(self, line): + """Quit shell + """ + return True + + def do_EOF(self, line): # noqa: N802 + """Quit shell (ctrl+D) + """ + return True + + def loop(self): + """Wrapper for cmdloop that handles ctrl+C + """ + try: + self.cmdloop() + print('') + except KeyboardInterrupt: + print('^C') + return False + + @cli.argument('-d', '--device', help='device to select - uses format :.') -@cli.argument('-i', '--index', default=0, help='device index to select.') @cli.argument('-l', '--list', arg_only=True, action='store_true', help='List available devices.') +@cli.argument('-i', '--interactive', arg_only=True, action='store_true', help='Start interactive shell.') @cli.argument('action', nargs='?', arg_only=True) @cli.subcommand('Acquire debugging information from usb XAP devices.', hidden=False if cli.config.user.developer else True) def xap(cli): @@ -233,16 +286,9 @@ def xap(cli): device = hid.Device(path=dev['path']) cli.log.info("Connected to:%04x:%04x %s %s", dev['vendor_id'], dev['product_id'], dev['manufacturer_string'], dev['product_string']) - # xap_doit(device) - if cli.args.action == 'unlock': - xap_unlock(device) - cli.log.info("Done") + # shell? + if cli.args.interactive: + XAPShell(device).loop() + return True - elif cli.args.action == 'dump': - xap_dump_keymap(device) - - elif cli.args.action == 'listen': - xap_broadcast_listen(device) - - elif not cli.args.action: - xap_broadcast_listen(device) + XAPShell(device).onecmd(cli.args.action or 'listen') From 94ec23ea77c1e3a54b8c61b8c85d7561d8416b8b Mon Sep 17 00:00:00 2001 From: zvecr Date: Thu, 5 May 2022 22:35:04 +0100 Subject: [PATCH 67/77] Remove requirement to quote action args --- lib/python/qmk/cli/xap/xap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/python/qmk/cli/xap/xap.py b/lib/python/qmk/cli/xap/xap.py index c49c6062cc..a04970f608 100644 --- a/lib/python/qmk/cli/xap/xap.py +++ b/lib/python/qmk/cli/xap/xap.py @@ -264,7 +264,7 @@ class XAPShell(cmd.Cmd): @cli.argument('-d', '--device', help='device to select - uses format :.') @cli.argument('-l', '--list', arg_only=True, action='store_true', help='List available devices.') @cli.argument('-i', '--interactive', arg_only=True, action='store_true', help='Start interactive shell.') -@cli.argument('action', nargs='?', arg_only=True) +@cli.argument('action', nargs='*', default=['listen'], arg_only=True) @cli.subcommand('Acquire debugging information from usb XAP devices.', hidden=False if cli.config.user.developer else True) def xap(cli): """Acquire debugging information from XAP devices @@ -291,4 +291,4 @@ def xap(cli): XAPShell(device).loop() return True - XAPShell(device).onecmd(cli.args.action or 'listen') + XAPShell(device).onecmd(" ".join(cli.args.action)) From 58642ff40cb8a9956e28da7ab1be9ee5fef26a91 Mon Sep 17 00:00:00 2001 From: zvecr Date: Fri, 6 May 2022 23:11:16 +0100 Subject: [PATCH 68/77] Publish resolved XAP specs? --- lib/python/qmk/cli/generate/api.py | 67 +++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 19 deletions(-) diff --git a/lib/python/qmk/cli/generate/api.py b/lib/python/qmk/cli/generate/api.py index 0596b3f22b..d49e40c591 100755 --- a/lib/python/qmk/cli/generate/api.py +++ b/lib/python/qmk/cli/generate/api.py @@ -2,6 +2,7 @@ """ from pathlib import Path import shutil +import hjson import json from milc import cli @@ -11,30 +12,16 @@ from qmk.info import info_json from qmk.json_encoders import InfoJSONEncoder from qmk.json_schema import json_load from qmk.keyboard import find_readme, list_keyboards +from qmk.xap.common import get_xap_definition_files, update_xap_definitions -TEMPLATE_PATH = Path('data/templates/api/') +DATA_PATH = Path('data') +TEMPLATE_PATH = DATA_PATH / 'templates/api/' BUILD_API_PATH = Path('.build/api_data/') -@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't write the data to disk.") -@cli.argument('-f', '--filter', arg_only=True, action='append', default=[], help="Filter the list of keyboards based on partial name matches the supplied value. May be passed multiple times.") -@cli.subcommand('Creates a new keymap for the keyboard of your choosing', hidden=False if cli.config.user.developer else True) -def generate_api(cli): - """Generates the QMK API data. +def _filtered_keyboard_list(): + """Perform basic filtering of list_keyboards """ - if BUILD_API_PATH.exists(): - shutil.rmtree(BUILD_API_PATH) - - shutil.copytree(TEMPLATE_PATH, BUILD_API_PATH) - - v1_dir = BUILD_API_PATH / 'v1' - keyboard_all_file = v1_dir / 'keyboards.json' # A massive JSON containing everything - keyboard_list_file = v1_dir / 'keyboard_list.json' # A simple list of keyboard targets - keyboard_aliases_file = v1_dir / 'keyboard_aliases.json' # A list of historical keyboard names and their new name - keyboard_metadata_file = v1_dir / 'keyboard_metadata.json' # All the data configurator/via needs for initialization - usb_file = v1_dir / 'usb.json' # A mapping of USB VID/PID -> keyboard target - - # Filter down when required keyboard_list = list_keyboards() if cli.args.filter: kb_list = [] @@ -42,6 +29,45 @@ def generate_api(cli): if any(i in keyboard_name for i in cli.args.filter): kb_list.append(keyboard_name) keyboard_list = kb_list + return keyboard_list + + +def _resolve_xap_specs(output_folder): + """To make it easier for consumers, replace specs with pre-merged versions + """ + overall = None + for file in get_xap_definition_files(): + overall = update_xap_definitions(overall, hjson.load(file.open(encoding='utf-8'))) + + # Inject dummy bits for unspecified response flags + for n in range(0, 8): + if str(n) not in overall['response_flags']['bits']: + overall['response_flags']['bits'][str(n)] = {'name': '', 'description': '', 'define': '-'} + + hjson.dump(overall, (output_folder / file.name).open(mode='w', encoding='utf-8')) + + +@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't write the data to disk.") +@cli.argument('-f', '--filter', arg_only=True, action='append', default=[], help="Filter the list of keyboards based on partial name matches the supplied value. May be passed multiple times.") +@cli.subcommand('Generate QMK API data', hidden=False if cli.config.user.developer else True) +def generate_api(cli): + """Generates the QMK API data. + """ + v1_dir = BUILD_API_PATH / 'v1' + keyboard_all_file = v1_dir / 'keyboards.json' # A massive JSON containing everything + keyboard_list_file = v1_dir / 'keyboard_list.json' # A simple list of keyboard targets + keyboard_aliases_file = v1_dir / 'keyboard_aliases.json' # A list of historical keyboard names and their new name + keyboard_metadata_file = v1_dir / 'keyboard_metadata.json' # All the data configurator/via needs for initialization + usb_file = v1_dir / 'usb.json' # A mapping of USB VID/PID -> keyboard target + + if BUILD_API_PATH.exists(): + shutil.rmtree(BUILD_API_PATH) + + shutil.copytree(TEMPLATE_PATH, BUILD_API_PATH) + shutil.copytree(DATA_PATH, v1_dir) + + # Filter down when required + keyboard_list = _filtered_keyboard_list() kb_all = {} usb_list = {} @@ -86,6 +112,9 @@ def generate_api(cli): 'usb': usb_list, } + # Feature specific handling + _resolve_xap_specs(v1_dir / 'xap') + # Write the global JSON files keyboard_all_json = json.dumps({'last_updated': current_datetime(), 'keyboards': kb_all}, cls=InfoJSONEncoder) usb_json = json.dumps({'last_updated': current_datetime(), 'usb': usb_list}, cls=InfoJSONEncoder) From 7e819d794516495587476f130ee1fe2a331ca232 Mon Sep 17 00:00:00 2001 From: zvecr Date: Fri, 6 May 2022 23:33:51 +0100 Subject: [PATCH 69/77] specs as json? --- lib/python/qmk/cli/generate/api.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/python/qmk/cli/generate/api.py b/lib/python/qmk/cli/generate/api.py index d49e40c591..3d3c8c9d53 100755 --- a/lib/python/qmk/cli/generate/api.py +++ b/lib/python/qmk/cli/generate/api.py @@ -33,7 +33,7 @@ def _filtered_keyboard_list(): def _resolve_xap_specs(output_folder): - """To make it easier for consumers, replace specs with pre-merged versions + """To make it easier for consumers, publish pre-merged spec files """ overall = None for file in get_xap_definition_files(): @@ -44,7 +44,8 @@ def _resolve_xap_specs(output_folder): if str(n) not in overall['response_flags']['bits']: overall['response_flags']['bits'][str(n)] = {'name': '', 'description': '', 'define': '-'} - hjson.dump(overall, (output_folder / file.name).open(mode='w', encoding='utf-8')) + output_file = output_folder / (file.stem + ".json") + output_file.write_text(json.dumps(overall, indent=4), encoding='utf-8') @cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't write the data to disk.") From ea92d5ed7d6641e42698c8c648875c7ed28bcdb6 Mon Sep 17 00:00:00 2001 From: zvecr Date: Mon, 9 May 2022 23:51:43 +0100 Subject: [PATCH 70/77] Block out basic keycodes --- data/constants/keycodes_0.0.1.json | 1298 ++++++++++++++++++++++++++++ data/xap/xap_0.1.0.hjson | 4 + lib/python/qmk/cli/xap/xap.py | 14 +- 3 files changed, 1309 insertions(+), 7 deletions(-) create mode 100644 data/constants/keycodes_0.0.1.json diff --git a/data/constants/keycodes_0.0.1.json b/data/constants/keycodes_0.0.1.json new file mode 100644 index 0000000000..cd3c2876ed --- /dev/null +++ b/data/constants/keycodes_0.0.1.json @@ -0,0 +1,1298 @@ +{ + "groups": { + "basic": {} + }, + "ranges": { + "0x0000/0x00FF": {} + }, + "keycodes": { + "0x0000": { + "group": "basic", + "key": "KC_NO", + "label": "", + "aliases": [ + "XXXXXXX" + ] + }, + "0x0001": { + "group": "basic", + "key": "KC_TRANSPARENT", + "label": "", + "aliases": [ + "_______", + "KC_TRNS" + ] + }, + "0x0004": { + "group": "basic", + "key": "KC_A", + "label": "A" + }, + "0x0005": { + "group": "basic", + "key": "KC_B", + "label": "B" + }, + "0x0006": { + "group": "basic", + "key": "KC_C", + "label": "C" + }, + "0x0007": { + "group": "basic", + "key": "KC_D", + "label": "D" + }, + "0x0008": { + "group": "basic", + "key": "KC_E", + "label": "E" + }, + "0x0009": { + "group": "basic", + "key": "KC_F", + "label": "F" + }, + "0x000a": { + "group": "basic", + "key": "KC_G", + "label": "G" + }, + "0x000b": { + "group": "basic", + "key": "KC_H", + "label": "H" + }, + "0x000c": { + "group": "basic", + "key": "KC_I", + "label": "I" + }, + "0x000d": { + "group": "basic", + "key": "KC_J", + "label": "J" + }, + "0x000e": { + "group": "basic", + "key": "KC_K", + "label": "K" + }, + "0x000f": { + "group": "basic", + "key": "KC_L", + "label": "L" + }, + "0x0010": { + "group": "basic", + "key": "KC_M", + "label": "M" + }, + "0x0011": { + "group": "basic", + "key": "KC_N", + "label": "N" + }, + "0x0012": { + "group": "basic", + "key": "KC_O", + "label": "O" + }, + "0x0013": { + "group": "basic", + "key": "KC_P", + "label": "P" + }, + "0x0014": { + "group": "basic", + "key": "KC_Q", + "label": "Q" + }, + "0x0015": { + "group": "basic", + "key": "KC_R", + "label": "R" + }, + "0x0016": { + "group": "basic", + "key": "KC_S", + "label": "S" + }, + "0x0017": { + "group": "basic", + "key": "KC_T", + "label": "T" + }, + "0x0018": { + "group": "basic", + "key": "KC_U", + "label": "U" + }, + "0x0019": { + "group": "basic", + "key": "KC_V", + "label": "V" + }, + "0x001a": { + "group": "basic", + "key": "KC_W", + "label": "W" + }, + "0x001b": { + "group": "basic", + "key": "KC_X", + "label": "X" + }, + "0x001c": { + "group": "basic", + "key": "KC_Y", + "label": "Y" + }, + "0x001d": { + "group": "basic", + "key": "KC_Z", + "label": "Z" + }, + "0x001e": { + "group": "basic", + "key": "KC_1", + "label": "1" + }, + "0x001f": { + "group": "basic", + "key": "KC_2", + "label": "2" + }, + "0x0020": { + "group": "basic", + "key": "KC_3", + "label": "3" + }, + "0x0021": { + "group": "basic", + "key": "KC_4", + "label": "4" + }, + "0x0022": { + "group": "basic", + "key": "KC_5", + "label": "5" + }, + "0x0023": { + "group": "basic", + "key": "KC_6", + "label": "6" + }, + "0x0024": { + "group": "basic", + "key": "KC_7", + "label": "7" + }, + "0x0025": { + "group": "basic", + "key": "KC_8", + "label": "8" + }, + "0x0026": { + "group": "basic", + "key": "KC_9", + "label": "9" + }, + "0x0027": { + "group": "basic", + "key": "KC_0", + "label": "0" + }, + "0x0028": { + "group": "basic", + "key": "KC_ENTER", + "label": "0", + "aliases": [ + "KC_ENT" + ] + }, + "0x0029": { + "group": "basic", + "key": "KC_ESCAPE", + "label": "0", + "aliases": [ + "KC_ESC" + ] + }, + "0x002a": { + "group": "basic", + "key": "KC_BACKSPACE", + "label": "0", + "aliases": [ + "KC_BSPC" + ] + }, + "0x002b": { + "group": "basic", + "key": "KC_TAB", + "label": "Tab" + }, + "0x002c": { + "group": "basic", + "key": "KC_SPACE", + "label": "Spacebar", + "aliases": [ + "KC_SPC" + ] + }, + "0x002d": { + "group": "basic", + "key": "KC_MINUS", + "label": "-", + "aliases": [ + "KC_MINS" + ] + }, + "0x002e": { + "group": "basic", + "key": "KC_EQUAL", + "label": "=", + "aliases": [ + "KC_EQL" + ] + }, + "0x002f": { + "group": "basic", + "key": "KC_LEFT_BRACKET", + "label": "]", + "aliases": [ + "KC_LBRC" + ] + }, + "0x0030": { + "group": "basic", + "key": "KC_RIGHT_BRACKET", + "label": "[", + "aliases": [ + "KC_RBRC" + ] + }, + "0x0031": { + "group": "basic", + "key": "KC_BACKSLASH", + "label": "\\", + "aliases": [ + "KC_BSLS" + ] + }, + "0x0032": { + "group": "basic", + "key": "KC_NONUS_HASH", + "label": "#", + "aliases": [ + "KC_NUHS" + ] + }, + "0x0033": { + "group": "basic", + "key": "KC_SEMICOLON", + "label": ";", + "aliases": [ + "KC_SCLN" + ] + }, + "0x0034": { + "group": "basic", + "key": "KC_QUOTE", + "label": "'", + "aliases": [ + "KC_QUOT" + ] + }, + "0x0035": { + "group": "basic", + "key": "KC_GRAVE", + "label": "`", + "aliases": [ + "KC_GRV" + ] + }, + "0x0036": { + "group": "basic", + "key": "KC_COMMA", + "label": ",", + "aliases": [ + "KC_COMM" + ] + }, + "0x0037": { + "group": "basic", + "key": "KC_DOT", + "label": "." + }, + "0x0038": { + "group": "basic", + "key": "KC_SLASH", + "label": "/", + "aliases": [ + "KC_SLSH" + ] + }, + "0x0039": { + "group": "basic", + "key": "KC_CAPS_LOCK", + "label": "Caps Lock", + "aliases": [ + "KC_CAPS" + ] + }, + "0x003a": { + "group": "basic", + "key": "KC_F1", + "label": "F1" + }, + "0x003b": { + "group": "basic", + "key": "KC_F2", + "label": "F2" + }, + "0x003c": { + "group": "basic", + "key": "KC_F3", + "label": "F3" + }, + "0x003d": { + "group": "basic", + "key": "KC_F4", + "label": "F4" + }, + "0x003e": { + "group": "basic", + "key": "KC_F5", + "label": "F5" + }, + "0x003f": { + "group": "basic", + "key": "KC_F6", + "label": "F6" + }, + "0x0040": { + "group": "basic", + "key": "KC_F7", + "label": "F7" + }, + "0x0041": { + "group": "basic", + "key": "KC_F8", + "label": "F8" + }, + "0x0042": { + "group": "basic", + "key": "KC_F9", + "label": "F9" + }, + "0x0043": { + "group": "basic", + "key": "KC_F10", + "label": "F10" + }, + "0x0044": { + "group": "basic", + "key": "KC_F11", + "label": "F11" + }, + "0x0045": { + "group": "basic", + "key": "KC_F12", + "label": "F12" + }, + "0x0046": { + "group": "basic", + "key": "KC_PRINT_SCREEN", + "label": "Print Screen", + "aliases": [ + "KC_PSCR" + ] + }, + "0x0047": { + "group": "basic", + "key": "KC_SCROLL_LOCK", + "label": "Scroll Lock", + "aliases": [ + "KC_SCRL" + ] + }, + "0x0048": { + "group": "basic", + "key": "KC_PAUSE", + "label": "Pause", + "aliases": [ + "KC_PAUS", + "KC_BRK" + ] + }, + "0x0049": { + "group": "basic", + "key": "KC_INSERT", + "label": "Insert", + "aliases": [ + "KC_INS" + ] + }, + "0x004a": { + "group": "basic", + "key": "KC_HOME", + "label": "Home" + }, + "0x004b": { + "group": "basic", + "key": "KC_PAGE_UP", + "label": "Page Up", + "aliases": [ + "KC_PGUP" + ] + }, + "0x004c": { + "group": "basic", + "key": "KC_DELETE", + "label": "Delete", + "aliases": [ + "KC_DEL" + ] + }, + "0x004d": { + "group": "basic", + "key": "KC_END", + "label": "End" + }, + "0x004e": { + "group": "basic", + "key": "KC_PAGE_DOWN", + "label": "Page Down", + "aliases": [ + "KC_PGDN" + ] + }, + "0x004f": { + "group": "basic", + "key": "KC_RIGHT", + "label": "Right", + "aliases": [ + "KC_RGHT" + ] + }, + "0x0050": { + "group": "basic", + "key": "KC_LEFT", + "label": "Left" + }, + "0x0051": { + "group": "basic", + "key": "KC_DOWN", + "label": "Down" + }, + "0x0052": { + "group": "basic", + "key": "KC_Up", + "label": "Up" + }, + "0x0053": { + "group": "basic", + "key": "KC_NUM_LOCK", + "label": "Num Lock", + "aliases": [ + "KC_NUM" + ] + }, + "0x0054": { + "group": "basic", + "key": "KC_KP_SLASH", + "label": "/", + "aliases": [ + "KC_PSLS" + ] + }, + "0x0055": { + "group": "basic", + "key": "KC_KP_ASTERISK", + "label": "*", + "aliases": [ + "KC_PAST" + ] + }, + "0x0056": { + "group": "basic", + "key": "KC_KP_MINUS", + "label": "-", + "aliases": [ + "KC_PMNS" + ] + }, + "0x0057": { + "group": "basic", + "key": "KC_KP_PLUS", + "label": "+", + "aliases": [ + "KC_PPLS" + ] + }, + "0x0058": { + "group": "basic", + "key": "KC_KP_ENTER", + "label": "Enter", + "aliases": [ + "KC_PENT" + ] + }, + "0x0059": { + "group": "basic", + "key": "KC_KP_1", + "label": "1", + "aliases": [ + "KC_P1" + ] + }, + "0x005a": { + "group": "basic", + "key": "KC_KP_2", + "label": "2", + "aliases": [ + "KC_P2" + ] + }, + "0x005b": { + "group": "basic", + "key": "KC_KP_3", + "label": "3", + "aliases": [ + "KC_P3" + ] + }, + "0x005c": { + "group": "basic", + "key": "KC_KP_4", + "label": "4", + "aliases": [ + "KC_P4" + ] + }, + "0x005d": { + "group": "basic", + "key": "KC_KP_5", + "label": "5", + "aliases": [ + "KC_P5" + ] + }, + "0x005e": { + "group": "basic", + "key": "KC_KP_6", + "label": "6", + "aliases": [ + "KC_P6" + ] + }, + "0x005f": { + "group": "basic", + "key": "KC_KP_7", + "label": "7", + "aliases": [ + "KC_P7" + ] + }, + "0x0060": { + "group": "basic", + "key": "KC_KP_8", + "label": "8", + "aliases": [ + "KC_P8" + ] + }, + "0x0061": { + "group": "basic", + "key": "KC_KP_9", + "label": "9", + "aliases": [ + "KC_P9" + ] + }, + "0x0062": { + "group": "basic", + "key": "KC_KP_0", + "label": "0", + "aliases": [ + "KC_P0" + ] + }, + "0x0063": { + "group": "basic", + "key": "KC_KP_DOT", + "label": ".", + "aliases": [ + "KC_PDOT" + ] + }, + "0x0064": { + "group": "basic", + "key": "KC_NONUS_BACKSLASH", + "label": "\\", + "aliases": [ + "KC_NUBS" + ] + }, + "0x0065": { + "group": "basic", + "key": "KC_APPLICATION", + "label": "Application", + "aliases": [ + "KC_APP" + ] + }, + "0x0066": { + "group": "basic", + "key": "KC_KB_POWER", + "label": "Application" + }, + "0x0067": { + "group": "basic", + "key": "KC_KP_EQUAL", + "label": "=", + "aliases": [ + "KC_PEQL" + ] + }, + "0x0068": { + "group": "basic", + "key": "KC_F13", + "label": "F13" + }, + "0x0069": { + "group": "basic", + "key": "KC_F14", + "label": "F14" + }, + "0x006a": { + "group": "basic", + "key": "KC_F15", + "label": "F15" + }, + "0x006b": { + "group": "basic", + "key": "KC_F16", + "label": "F16" + }, + "0x006c": { + "group": "basic", + "key": "KC_F17", + "label": "F17" + }, + "0x006d": { + "group": "basic", + "key": "KC_F17", + "label": "F18" + }, + "0x006e": { + "group": "basic", + "key": "KC_F19", + "label": "F19" + }, + "0x006f": { + "group": "basic", + "key": "KC_F20", + "label": "F20" + }, + "0x0070": { + "group": "basic", + "key": "KC_F21", + "label": "F21" + }, + "0x0071": { + "group": "basic", + "key": "KC_F22", + "label": "F22" + }, + "0x0072": { + "group": "basic", + "key": "KC_F23", + "label": "F23" + }, + "0x0073": { + "group": "basic", + "key": "KC_F24", + "label": "F24" + }, + "0x0074": { + "group": "basic", + "key": "KC_EXECUTE", + "label": "Execute", + "aliases": [ + "KC_EXEC" + ] + }, + "0x0075": { + "group": "basic", + "key": "KC_HELP", + "label": "Help" + }, + "0x0076": { + "group": "basic", + "key": "KC_MENU", + "label": "Menu" + }, + "0x0077": { + "group": "basic", + "key": "KC_SELECT", + "label": "Select", + "aliases": [ + "KC_SLCT" + ] + }, + "0x0078": { + "group": "basic", + "key": "KC_STOP", + "label": "Stop" + }, + "0x0079": { + "group": "basic", + "key": "KC_AGAIN", + "label": "Again", + "aliases": [ + "KC_AGIN" + ] + }, + "0x007a": { + "group": "basic", + "key": "KC_UNDO", + "label": "Undo" + }, + "0x007b": { + "group": "basic", + "key": "KC_CUT", + "label": "Cut" + }, + "0x007c": { + "group": "basic", + "key": "KC_COPY", + "label": "Copy" + }, + "0x007d": { + "group": "basic", + "key": "KC_PASTE", + "label": "Paste", + "aliases": [ + "KC_PSTE" + ] + }, + "0x007e": { + "group": "basic", + "key": "KC_FIND", + "label": "Find" + }, + "0x007f": { + "group": "basic", + "key": "KC_KB_MUTE", + "label": "Mute" + }, + "0x0080": { + "group": "basic", + "key": "KC_KB_VOLUME_UP", + "label": "Volume Up" + }, + "0x0081": { + "group": "basic", + "key": "KC_KB_VOLUME_DOWN", + "label": "Volume Down" + }, + "0x0082": { + "group": "basic", + "key": "KC_LOCKING_CAPS_LOCK", + "label": "Caps Lock", + "aliases": [ + "KC_LCAP" + ] + }, + "0x0083": { + "group": "basic", + "key": "KC_LOCKING_NUM_LOCK", + "label": "Num Lock", + "aliases": [ + "KC_LSCR" + ] + }, + "0x0084": { + "group": "basic", + "key": "KC_LOCKING_SCROLL_LOCK", + "label": "Scroll Lock", + "aliases": [ + "KC_LCAP" + ] + }, + "0x0085": { + "group": "basic", + "key": "KC_KP_COMMA", + "label": ",", + "aliases": [ + "KC_PCMM" + ] + }, + "0x0086": { + "group": "basic", + "key": "KC_KP_EQUAL_AS400", + "label": "=" + }, + "0x0087": { + "group": "basic", + "key": "KC_INTERNATIONAL_1", + "label": "INT 1", + "aliases": [ + "KC_INT1" + ] + }, + "0x0088": { + "group": "basic", + "key": "KC_INTERNATIONAL_2", + "label": "INT 2", + "aliases": [ + "KC_INT2" + ] + }, + "0x0089": { + "group": "basic", + "key": "KC_INTERNATIONAL_3", + "label": "INT 3", + "aliases": [ + "KC_INT3" + ] + }, + "0x008a": { + "group": "basic", + "key": "KC_INTERNATIONAL_4", + "label": "INT 4", + "aliases": [ + "KC_INT4" + ] + }, + "0x008b": { + "group": "basic", + "key": "KC_INTERNATIONAL_5", + "label": "INT 5", + "aliases": [ + "KC_INT5" + ] + }, + "0x008c": { + "group": "basic", + "key": "KC_INTERNATIONAL_6", + "label": "INT 6", + "aliases": [ + "KC_INT6" + ] + }, + "0x008d": { + "group": "basic", + "key": "KC_INTERNATIONAL_7", + "label": "INT 7", + "aliases": [ + "KC_INT7" + ] + }, + "0x008e": { + "group": "basic", + "key": "KC_INTERNATIONAL_8", + "label": "INT 8", + "aliases": [ + "KC_INT8" + ] + }, + "0x008f": { + "group": "basic", + "key": "KC_INTERNATIONAL_9", + "label": "INT 9", + "aliases": [ + "KC_INT9" + ] + }, + "0x0090": { + "group": "basic", + "key": "KC_LANGUAGE_1", + "label": "LANG 1", + "aliases": [ + "KC_LNG1" + ] + }, + "0x0091": { + "group": "basic", + "key": "KC_LANGUAGE_2", + "label": "LANG 2", + "aliases": [ + "KC_LNG2" + ] + }, + "0x0092": { + "group": "basic", + "key": "KC_LANGUAGE_3", + "label": "LANG 3", + "aliases": [ + "KC_LNG3" + ] + }, + "0x0093": { + "group": "basic", + "key": "KC_LANGUAGE_4", + "label": "LANG 4", + "aliases": [ + "KC_LNG4" + ] + }, + "0x0094": { + "group": "basic", + "key": "KC_LANGUAGE_5", + "label": "LANG 5", + "aliases": [ + "KC_LNG5" + ] + }, + "0x0095": { + "group": "basic", + "key": "KC_LANGUAGE_6", + "label": "LANG 6", + "aliases": [ + "KC_LNG6" + ] + }, + "0x0096": { + "group": "basic", + "key": "KC_LANGUAGE_7", + "label": "LANG 7", + "aliases": [ + "KC_LNG7" + ] + }, + "0x0097": { + "group": "basic", + "key": "KC_LANGUAGE_8", + "label": "LANG 8", + "aliases": [ + "KC_LNG8" + ] + }, + "0x0098": { + "group": "basic", + "key": "KC_LANGUAGE_9", + "label": "LANG 9", + "aliases": [ + "KC_LNG9" + ] + }, + "0x0099": { + "group": "basic", + "key": "KC_ALTERNATE_ERASE", + "label": "Alternate Erase", + "aliases": [ + "KC_ERAS" + ] + }, + "0x009a": { + "group": "basic", + "key": "KC_SYSTEM_REQUEST", + "label": "SysReq/Attention", + "aliases": [ + "KC_SYRQ" + ] + }, + "0x009b": { + "group": "basic", + "key": "KC_CANCEL", + "label": "Cancel", + "aliases": [ + "KC_CNCL" + ] + }, + "0x009c": { + "group": "basic", + "key": "KC_CLEAR", + "label": "Clear", + "aliases": [ + "KC_CLR" + ] + }, + "0x009d": { + "group": "basic", + "key": "KC_PRIOR", + "label": "Prior", + "aliases": [ + "KC_PRIR" + ] + }, + "0x009e": { + "group": "basic", + "key": "KC_RETURN", + "label": "Return", + "aliases": [ + "KC_RETN" + ] + }, + "0x009f": { + "group": "basic", + "key": "KC_SEPARATOR", + "label": "Separator", + "aliases": [ + "KC_SEPR" + ] + }, + "0x00a0": { + "group": "basic", + "key": "KC_OUT", + "label": "Out" + }, + "0x00a1": { + "group": "basic", + "key": "KC_OPER", + "label": "Oper" + }, + "0x00a2": { + "group": "basic", + "key": "KC_CLEAR_AGAIN", + "label": "Clear/Again", + "aliases": [ + "KC_CLAG" + ] + }, + "0x00a3": { + "group": "basic", + "key": "KC_CRSEL", + "label": "CrSel/Props", + "aliases": [ + "KC_CRSL" + ] + }, + "0x00a4": { + "group": "basic", + "key": "KC_EXSEL", + "label": "ExSel", + "aliases": [ + "KC_EXSL" + ] + }, + + "0x00e0": { + "group": "basic", + "key": "KC_LEFT_CTRL", + "label": "Left Control", + "aliases": [ + "KC_LCTL" + ] + }, + "0x00e1": { + "group": "basic", + "key": "KC_LEFT_SHIFT", + "label": "Left Shift", + "aliases": [ + "KC_LSFT" + ] + }, + "0x00e2": { + "group": "basic", + "key": "KC_LEFT_ALT", + "label": "Left Alt", + "aliases": [ + "KC_LALT", + "KC_LOPT" + ] + }, + "0x00e3": { + "group": "basic", + "key": "KC_LEFT_GUI", + "label": "Left GUI", + "aliases": [ + "KC_LGUI", + "KC_LCMD", + "KC_LWIN" + ] + }, + "0x00e4": { + "group": "basic", + "key": "KC_RIGHT_CTRL", + "label": "Right Control", + "aliases": [ + "KC_RCTL" + ] + }, + "0x00e5": { + "group": "basic", + "key": "KC_RIGHT_SHIFT", + "label": "Right Shift", + "aliases": [ + "KC_RSFT" + ] + }, + "0x00e6": { + "group": "basic", + "key": "KC_RIGHT_ALT", + "label": "Right Alt", + "aliases": [ + "KC_RALT", + "KC_ROPT", + "KC_ALGR" + ] + }, + "0x00e7": { + "group": "basic", + "key": "KC_RIGHT_GUI", + "label": "Right GUI", + "aliases": [ + "KC_RGUI", + "KC_RCMD", + "KC_RWIN" + ] + }, + + "0x00ed": { + "group": "basic", + "key": "KC_MS_UP", + "label": "Move cursor up", + "aliases": [ + "KC_MS_U" + ] + }, + "0x00ee": { + "group": "basic", + "key": "KC_MS_DOWN", + "label": "Move cursor down", + "aliases": [ + "KC_MS_D" + ] + }, + "0x00ef": { + "group": "basic", + "key": "KC_MS_LEFT", + "label": "Move cursor left", + "aliases": [ + "KC_MS_L" + ] + }, + "0x00f0": { + "group": "basic", + "key": "KC_MS_LEFT", + "label": "Move cursor right", + "aliases": [ + "KC_MS_R" + ] + }, + "0x00f1": { + "group": "basic", + "key": "KC_MS_BTN1", + "label": "Press button 1", + "aliases": [ + "KC_BTN1" + ] + }, + "0x00f2": { + "group": "basic", + "key": "KC_MS_BTN2", + "label": "Press button 2", + "aliases": [ + "KC_BTN2" + ] + }, + "0x00f3": { + "group": "basic", + "key": "KC_MS_BTN3", + "label": "Press button 3", + "aliases": [ + "KC_BTN3" + ] + }, + "0x00f4": { + "group": "basic", + "key": "KC_MS_BTN4", + "label": "Press button 4", + "aliases": [ + "KC_BTN4" + ] + }, + "0x00f5": { + "group": "basic", + "key": "KC_MS_BTN5", + "label": "Press button 5", + "aliases": [ + "KC_BTN5" + ] + }, + "0x00f6": { + "group": "basic", + "key": "KC_MS_BTN6", + "label": "Press button 6", + "aliases": [ + "KC_BTN6" + ] + }, + "0x00f7": { + "group": "basic", + "key": "KC_MS_BTN7", + "label": "Press button 7", + "aliases": [ + "KC_BTN7" + ] + }, + "0x00f8": { + "group": "basic", + "key": "KC_MS_BTN8", + "label": "Press button 8", + "aliases": [ + "KC_BTN8" + ] + }, + "0x00f9": { + "group": "basic", + "key": "KC_MS_WH_UP", + "label": "Move wheel up", + "aliases": [ + "KC_WH_U" + ] + }, + "0x00fa": { + "group": "basic", + "key": "KC_MS_WH_DOWN", + "label": "Move wheel down", + "aliases": [ + "KC_WH_D" + ] + }, + "0x00fb": { + "group": "basic", + "key": "KC_MS_WH_LEFT", + "label": "Move wheel left", + "aliases": [ + "KC_WH_L" + ] + }, + "0x00fc": { + "group": "basic", + "key": "KC_MS_WH_RIGHT", + "label": "Move wheel right", + "aliases": [ + "KC_WH_R" + ] + }, + "0x00fd": { + "group": "basic", + "key": "KC_MS_ACCEL0", + "label": "Set speed to 0", + "aliases": [ + "KC_ACL0" + ] + }, + "0x00fe": { + "group": "basic", + "key": "KC_MS_ACCEL1", + "label": "Set speed to 1", + "aliases": [ + "KC_ACL1" + ] + }, + "0x00ff": { + "group": "basic", + "key": "KC_MS_ACCEL2", + "label": "Set speed to 2", + "aliases": [ + "KC_ACL2" + ] + } + } +} \ No newline at end of file diff --git a/data/xap/xap_0.1.0.hjson b/data/xap/xap_0.1.0.hjson index 9499314d4b..078997a0c5 100755 --- a/data/xap/xap_0.1.0.hjson +++ b/data/xap/xap_0.1.0.hjson @@ -1,6 +1,10 @@ { version: 0.1.0 + uses: { + keycodes: 0.0.1 + } + documentation: { order: [ broadcast_messages diff --git a/lib/python/qmk/cli/xap/xap.py b/lib/python/qmk/cli/xap/xap.py index a04970f608..7db2fbcbe3 100644 --- a/lib/python/qmk/cli/xap/xap.py +++ b/lib/python/qmk/cli/xap/xap.py @@ -4,17 +4,17 @@ import cmd import json import random import gzip +from pathlib import Path from platform import platform from milc import cli -KEYCODE_MAP = { - # TODO: this should be data driven... - 0x04: 'KC_A', - 0x05: 'KC_B', - 0x29: 'KC_ESCAPE', - 0xF9: 'KC_MS_WH_UP', -} +from qmk.json_schema import json_load + +# TODO: get from xap "uses" for the current device +keycode_version = '0.0.1' +spec = json_load(Path(f'data/constants/keycodes_{keycode_version}.json')) +KEYCODE_MAP = {int(k, 16): v.get('key') for k, v in spec['keycodes'].items()} def _is_xap_usage(x): From 41a5dcbfa7ee5dd0a5d5318fdbf697aba067e46e Mon Sep 17 00:00:00 2001 From: zvecr Date: Tue, 10 May 2022 01:38:14 +0100 Subject: [PATCH 71/77] Add more DD basic keycodes --- data/constants/keycodes_0.0.1.json | 272 +++++++++++++++++++++++++---- lib/python/qmk/cli/xap/xap.py | 25 ++- 2 files changed, 265 insertions(+), 32 deletions(-) diff --git a/data/constants/keycodes_0.0.1.json b/data/constants/keycodes_0.0.1.json index cd3c2876ed..ea0fd4257d 100644 --- a/data/constants/keycodes_0.0.1.json +++ b/data/constants/keycodes_0.0.1.json @@ -1,6 +1,10 @@ { "groups": { - "basic": {} + "basic": {}, + "media": {}, + "modifiers": {}, + "mouse": {} + }, "ranges": { "0x0000/0x00FF": {} @@ -414,7 +418,8 @@ "key": "KC_SCROLL_LOCK", "label": "Scroll Lock", "aliases": [ - "KC_SCRL" + "KC_SCRL", + "KC_BRMD" ] }, "0x0048": { @@ -423,7 +428,8 @@ "label": "Pause", "aliases": [ "KC_PAUS", - "KC_BRK" + "KC_BRK", + "KC_BRMU" ] }, "0x0049": { @@ -1070,8 +1076,214 @@ ] }, + "0x00a5": { + "group": "media", + "key": "KC_SYSTEM_POWER", + "label": "System Power Down", + "aliases": [ + "KC_PWR" + ] + }, + "0x00a6": { + "group": "media", + "key": "KC_SYSTEM_SLEEP", + "label": "System Sleep", + "aliases": [ + "KC_SLEP" + ] + }, + "0x00a7": { + "group": "media", + "key": "KC_SYSTEM_WAKE", + "label": "System Wake", + "aliases": [ + "KC_WAKE" + ] + }, + "0x00a8": { + "group": "media", + "key": "KC_AUDIO_MUTE", + "label": "Mute", + "aliases": [ + "KC_MUTE" + ] + }, + "0x00a9": { + "group": "media", + "key": "KC_AUDIO_VOL_UP", + "label": "Volume Up", + "aliases": [ + "KC_VOLU" + ] + }, + "0x00aa": { + "group": "media", + "key": "KC_AUDIO_VOL_DOWN", + "label": "Volume Down", + "aliases": [ + "KC_VOLD" + ] + }, + "0x00ab": { + "group": "media", + "key": "KC_MEDIA_NEXT_TRACK", + "label": "Next", + "aliases": [ + "KC_MNXT" + ] + }, + "0x00ac": { + "group": "media", + "key": "KC_MEDIA_PREV_TRACK", + "label": "Previous", + "aliases": [ + "KC_MPRV" + ] + }, + "0x00ad": { + "group": "media", + "key": "KC_MEDIA_STOP", + "label": "Stop", + "aliases": [ + "KC_MSTP" + ] + }, + "0x00ae": { + "group": "media", + "key": "KC_MEDIA_PLAY_PAUSE", + "label": "Mute", + "aliases": [ + "KC_MPLY" + ] + }, + "0x00af": { + "group": "media", + "key": "KC_MEDIA_SELECT", + "label": "Launch Player", + "aliases": [ + "KC_MSEL" + ] + }, + "0x00b0": { + "group": "media", + "key": "KC_MEDIA_EJECT", + "label": "Eject", + "aliases": [ + "KC_EJCT" + ] + }, + "0x00b1": { + "group": "media", + "key": "KC_MAIL", + "label": "Launch Mail" + }, + "0x00b2": { + "group": "media", + "key": "KC_CALCULATOR", + "label": "Launch Calculator", + "aliases": [ + "KC_CALC" + ] + }, + "0x00b3": { + "group": "media", + "key": "KC_MY_COMPUTER", + "label": "Launch My Computer", + "aliases": [ + "KC_MYCM" + ] + }, + "0x00b4": { + "group": "media", + "key": "KC_WWW_SEARCH", + "label": "Browser Search", + "aliases": [ + "KC_WSCH" + ] + }, + "0x00b5": { + "group": "media", + "key": "KC_WWW_HOME", + "label": "Browser Home", + "aliases": [ + "KC_WHOM" + ] + }, + "0x00b6": { + "group": "media", + "key": "KC_WWW_BACK", + "label": "Browser Back", + "aliases": [ + "KC_WBAK" + ] + }, + "0x00b7": { + "group": "media", + "key": "KC_WWW_FORWARD", + "label": "Browser Forward", + "aliases": [ + "KC_WFWD" + ] + }, + "0x00b8": { + "group": "media", + "key": "KC_WWW_STOP", + "label": "Browser Stop", + "aliases": [ + "KC_WSTP" + ] + }, + "0x00b9": { + "group": "media", + "key": "KC_WWW_REFRESH", + "label": "Browser Refresh", + "aliases": [ + "KC_WREF" + ] + }, + "0x00ba": { + "group": "media", + "key": "KC_WWW_FAVORITES", + "label": "Browser Favorites", + "aliases": [ + "KC_WREF" + ] + }, + "0x00bb": { + "group": "media", + "key": "KC_MEDIA_FAST_FORWARD", + "label": "Browser Favorites", + "aliases": [ + "KC_WREF" + ] + }, + "0x00bc": { + "group": "media", + "key": "KC_MEDIA_REWIND", + "label": "Browser Favorites", + "aliases": [ + "KC_WFAV" + ] + }, + "0x00bd": { + "group": "media", + "key": "KC_BRIGHTNESS_UP", + "label": "Brightness Up", + "aliases": [ + "KC_BRIU" + ] + }, + "0x00be": { + "group": "media", + "key": "KC_BRIGHTNESS_DOWN", + "label": "Brightness Down", + "aliases": [ + "KC_BRID" + ] + }, + "0x00e0": { - "group": "basic", + "group": "modifiers", "key": "KC_LEFT_CTRL", "label": "Left Control", "aliases": [ @@ -1079,7 +1291,7 @@ ] }, "0x00e1": { - "group": "basic", + "group": "modifiers", "key": "KC_LEFT_SHIFT", "label": "Left Shift", "aliases": [ @@ -1087,7 +1299,7 @@ ] }, "0x00e2": { - "group": "basic", + "group": "modifiers", "key": "KC_LEFT_ALT", "label": "Left Alt", "aliases": [ @@ -1096,7 +1308,7 @@ ] }, "0x00e3": { - "group": "basic", + "group": "modifiers", "key": "KC_LEFT_GUI", "label": "Left GUI", "aliases": [ @@ -1106,7 +1318,7 @@ ] }, "0x00e4": { - "group": "basic", + "group": "modifiers", "key": "KC_RIGHT_CTRL", "label": "Right Control", "aliases": [ @@ -1114,7 +1326,7 @@ ] }, "0x00e5": { - "group": "basic", + "group": "modifiers", "key": "KC_RIGHT_SHIFT", "label": "Right Shift", "aliases": [ @@ -1122,7 +1334,7 @@ ] }, "0x00e6": { - "group": "basic", + "group": "modifiers", "key": "KC_RIGHT_ALT", "label": "Right Alt", "aliases": [ @@ -1132,7 +1344,7 @@ ] }, "0x00e7": { - "group": "basic", + "group": "modifiers", "key": "KC_RIGHT_GUI", "label": "Right GUI", "aliases": [ @@ -1143,7 +1355,7 @@ }, "0x00ed": { - "group": "basic", + "group": "mouse", "key": "KC_MS_UP", "label": "Move cursor up", "aliases": [ @@ -1151,7 +1363,7 @@ ] }, "0x00ee": { - "group": "basic", + "group": "mouse", "key": "KC_MS_DOWN", "label": "Move cursor down", "aliases": [ @@ -1159,7 +1371,7 @@ ] }, "0x00ef": { - "group": "basic", + "group": "mouse", "key": "KC_MS_LEFT", "label": "Move cursor left", "aliases": [ @@ -1167,7 +1379,7 @@ ] }, "0x00f0": { - "group": "basic", + "group": "mouse", "key": "KC_MS_LEFT", "label": "Move cursor right", "aliases": [ @@ -1175,7 +1387,7 @@ ] }, "0x00f1": { - "group": "basic", + "group": "mouse", "key": "KC_MS_BTN1", "label": "Press button 1", "aliases": [ @@ -1183,7 +1395,7 @@ ] }, "0x00f2": { - "group": "basic", + "group": "mouse", "key": "KC_MS_BTN2", "label": "Press button 2", "aliases": [ @@ -1191,7 +1403,7 @@ ] }, "0x00f3": { - "group": "basic", + "group": "mouse", "key": "KC_MS_BTN3", "label": "Press button 3", "aliases": [ @@ -1199,7 +1411,7 @@ ] }, "0x00f4": { - "group": "basic", + "group": "mouse", "key": "KC_MS_BTN4", "label": "Press button 4", "aliases": [ @@ -1207,7 +1419,7 @@ ] }, "0x00f5": { - "group": "basic", + "group": "mouse", "key": "KC_MS_BTN5", "label": "Press button 5", "aliases": [ @@ -1215,7 +1427,7 @@ ] }, "0x00f6": { - "group": "basic", + "group": "mouse", "key": "KC_MS_BTN6", "label": "Press button 6", "aliases": [ @@ -1223,7 +1435,7 @@ ] }, "0x00f7": { - "group": "basic", + "group": "mouse", "key": "KC_MS_BTN7", "label": "Press button 7", "aliases": [ @@ -1231,7 +1443,7 @@ ] }, "0x00f8": { - "group": "basic", + "group": "mouse", "key": "KC_MS_BTN8", "label": "Press button 8", "aliases": [ @@ -1239,7 +1451,7 @@ ] }, "0x00f9": { - "group": "basic", + "group": "mouse", "key": "KC_MS_WH_UP", "label": "Move wheel up", "aliases": [ @@ -1247,7 +1459,7 @@ ] }, "0x00fa": { - "group": "basic", + "group": "mouse", "key": "KC_MS_WH_DOWN", "label": "Move wheel down", "aliases": [ @@ -1255,7 +1467,7 @@ ] }, "0x00fb": { - "group": "basic", + "group": "mouse", "key": "KC_MS_WH_LEFT", "label": "Move wheel left", "aliases": [ @@ -1263,7 +1475,7 @@ ] }, "0x00fc": { - "group": "basic", + "group": "mouse", "key": "KC_MS_WH_RIGHT", "label": "Move wheel right", "aliases": [ @@ -1271,7 +1483,7 @@ ] }, "0x00fd": { - "group": "basic", + "group": "mouse", "key": "KC_MS_ACCEL0", "label": "Set speed to 0", "aliases": [ @@ -1279,7 +1491,7 @@ ] }, "0x00fe": { - "group": "basic", + "group": "mouse", "key": "KC_MS_ACCEL1", "label": "Set speed to 1", "aliases": [ @@ -1287,7 +1499,7 @@ ] }, "0x00ff": { - "group": "basic", + "group": "mouse", "key": "KC_MS_ACCEL2", "label": "Set speed to 2", "aliases": [ diff --git a/lib/python/qmk/cli/xap/xap.py b/lib/python/qmk/cli/xap/xap.py index 7db2fbcbe3..69ef8e6e7d 100644 --- a/lib/python/qmk/cli/xap/xap.py +++ b/lib/python/qmk/cli/xap/xap.py @@ -14,7 +14,7 @@ from qmk.json_schema import json_load # TODO: get from xap "uses" for the current device keycode_version = '0.0.1' spec = json_load(Path(f'data/constants/keycodes_{keycode_version}.json')) -KEYCODE_MAP = {int(k, 16): v.get('key') for k, v in spec['keycodes'].items()} +KEYCODE_MAP = {int(k, 16): v.get('aliases', [v.get('key')])[0] for k, v in spec['keycodes'].items()} def _is_xap_usage(x): @@ -166,7 +166,7 @@ def _list_devices(): print_dotted_output(data) -def xap_dump_keymap(device): +def xap_dummy(device): # get layer count layers = _xap_transaction(device, 0x04, 0x01) layers = int.from_bytes(layers, "little") @@ -240,6 +240,27 @@ class XAPShell(cmd.Cmd): keycode = int.from_bytes(keycode, "little") print(f'keycode:{KEYCODE_MAP.get(keycode, "unknown")}[{keycode}]') + def do_keymap(self, arg): + """Prints out the keycode values of a certain layer + """ + data = bytes(map(int, arg.split())) + if len(data) != 1: + cli.log.error("Invalid args") + return + + info = _query_device_info(self.device) + rows = info['matrix_size']['rows'] + cols = info['matrix_size']['cols'] + + # TODO: render like qmk info? + for r in range(rows): + for c in range(cols): + q = data + r.to_bytes(1, byteorder='little') + c.to_bytes(1, byteorder='little') + keycode = _xap_transaction(self.device, 0x04, 0x02, q) + keycode = int.from_bytes(keycode, "little") + print(f'| {KEYCODE_MAP.get(keycode, "unknown").ljust(7)} ', end='', flush=True) + print('|') + def do_exit(self, line): """Quit shell """ From 5028d6672af7c43c82e0cecef5cc698b9162a929 Mon Sep 17 00:00:00 2001 From: zvecr Date: Tue, 10 May 2022 02:29:30 +0100 Subject: [PATCH 72/77] Use keycodes for xap version --- lib/python/qmk/cli/xap/xap.py | 14 ++++++-------- lib/python/qmk/xap/common.py | 33 +++++++++++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/lib/python/qmk/cli/xap/xap.py b/lib/python/qmk/cli/xap/xap.py index 69ef8e6e7d..a90625e748 100644 --- a/lib/python/qmk/cli/xap/xap.py +++ b/lib/python/qmk/cli/xap/xap.py @@ -4,17 +4,13 @@ import cmd import json import random import gzip -from pathlib import Path from platform import platform from milc import cli -from qmk.json_schema import json_load +from qmk.xap.common import get_xap_keycodes -# TODO: get from xap "uses" for the current device -keycode_version = '0.0.1' -spec = json_load(Path(f'data/constants/keycodes_{keycode_version}.json')) -KEYCODE_MAP = {int(k, 16): v.get('aliases', [v.get('key')])[0] for k, v in spec['keycodes'].items()} +KEYCODE_MAP = get_xap_keycodes('0.2.0') def _is_xap_usage(x): @@ -210,6 +206,8 @@ class XAPShell(cmd.Cmd): def __init__(self, device): cmd.Cmd.__init__(self) self.device = device + # cache keycodes for this device + self.keycodes = get_xap_keycodes(_query_device(device)['xap']) def do_about(self, arg): """Prints out the current version of QMK with a build date @@ -238,7 +236,7 @@ class XAPShell(cmd.Cmd): keycode = _xap_transaction(self.device, 0x04, 0x02, data) keycode = int.from_bytes(keycode, "little") - print(f'keycode:{KEYCODE_MAP.get(keycode, "unknown")}[{keycode}]') + print(f'keycode:{self.keycodes.get(keycode, "unknown")}[{keycode}]') def do_keymap(self, arg): """Prints out the keycode values of a certain layer @@ -258,7 +256,7 @@ class XAPShell(cmd.Cmd): q = data + r.to_bytes(1, byteorder='little') + c.to_bytes(1, byteorder='little') keycode = _xap_transaction(self.device, 0x04, 0x02, q) keycode = int.from_bytes(keycode, "little") - print(f'| {KEYCODE_MAP.get(keycode, "unknown").ljust(7)} ', end='', flush=True) + print(f'| {self.keycodes.get(keycode, "unknown").ljust(7)} ', end='', flush=True) print('|') def do_exit(self, line): diff --git a/lib/python/qmk/xap/common.py b/lib/python/qmk/xap/common.py index d1e0d5dbb0..1a39682e09 100755 --- a/lib/python/qmk/xap/common.py +++ b/lib/python/qmk/xap/common.py @@ -2,9 +2,12 @@ """ import os import hjson -from jinja2 import Environment, FileSystemLoader, select_autoescape -from qmk.constants import QMK_FIRMWARE +from pathlib import Path from typing import OrderedDict +from jinja2 import Environment, FileSystemLoader, select_autoescape + +from qmk.constants import QMK_FIRMWARE +from qmk.json_schema import json_load def _get_jinja2_env(data_templates_xap_subdir: str): @@ -78,6 +81,32 @@ def latest_xap_defs(): return _merge_ordered_dicts(definitions) +def get_xap_defs(version): + """Gets the required version of the XAP definitions. + """ + files = get_xap_definition_files() + + # Slice off anything newer than specified version + index = [idx for idx, s in enumerate(files) if version in str(s)][0] + files = files[:(index + 1)] + + definitions = [hjson.load(file.open(encoding='utf-8')) for file in files] + return _merge_ordered_dicts(definitions) + + +def get_xap_keycodes(xap_version): + """Gets keycode data for the required version of the XAP definitions. + """ + defs = get_xap_defs(xap_version) + + # Load DD keycodes for the dependency + keycode_version = defs['uses']['keycodes'] + spec = json_load(Path(f'data/constants/keycodes_{keycode_version}.json')) + + # Transform into something more usable - { raw_value : first alias || keycode } + return {int(k, 16): v.get('aliases', [v.get('key')])[0] for k, v in spec['keycodes'].items()} + + def route_conditions(route_stack): """Handles building the C preprocessor conditional based on the current route. """ From f9f0d84eb0eeb1653ef165fcc6b3483b09bfc740 Mon Sep 17 00:00:00 2001 From: zvecr Date: Tue, 10 May 2022 03:48:48 +0100 Subject: [PATCH 73/77] Cache xap specs --- lib/python/qmk/cli/xap/xap.py | 2 +- lib/python/qmk/xap/common.py | 21 ++++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/python/qmk/cli/xap/xap.py b/lib/python/qmk/cli/xap/xap.py index a90625e748..e0af627ef3 100644 --- a/lib/python/qmk/cli/xap/xap.py +++ b/lib/python/qmk/cli/xap/xap.py @@ -10,7 +10,7 @@ from milc import cli from qmk.xap.common import get_xap_keycodes -KEYCODE_MAP = get_xap_keycodes('0.2.0') +KEYCODE_MAP = get_xap_keycodes('latest') def _is_xap_usage(x): diff --git a/lib/python/qmk/xap/common.py b/lib/python/qmk/xap/common.py index 1a39682e09..8d68de3fab 100755 --- a/lib/python/qmk/xap/common.py +++ b/lib/python/qmk/xap/common.py @@ -8,6 +8,7 @@ from jinja2 import Environment, FileSystemLoader, select_autoescape from qmk.constants import QMK_FIRMWARE from qmk.json_schema import json_load +from qmk.decorators import lru_cache def _get_jinja2_env(data_templates_xap_subdir: str): @@ -74,26 +75,28 @@ def update_xap_definitions(original, new): return _merge_ordered_dicts([original, new]) -def latest_xap_defs(): - """Gets the latest version of the XAP definitions. - """ - definitions = [hjson.load(file.open(encoding='utf-8')) for file in get_xap_definition_files()] - return _merge_ordered_dicts(definitions) - - +@lru_cache(timeout=5) def get_xap_defs(version): """Gets the required version of the XAP definitions. """ files = get_xap_definition_files() # Slice off anything newer than specified version - index = [idx for idx, s in enumerate(files) if version in str(s)][0] - files = files[:(index + 1)] + if version != 'latest': + index = [idx for idx, s in enumerate(files) if version in str(s)][0] + files = files[:(index + 1)] definitions = [hjson.load(file.open(encoding='utf-8')) for file in files] return _merge_ordered_dicts(definitions) +def latest_xap_defs(): + """Gets the latest version of the XAP definitions. + """ + return get_xap_defs('latest') + + +@lru_cache(timeout=5) def get_xap_keycodes(xap_version): """Gets keycode data for the required version of the XAP definitions. """ From 3d9c2fd845e33f5a133db295d54f483e77cad40d Mon Sep 17 00:00:00 2001 From: zvecr Date: Tue, 10 May 2022 18:23:38 +0100 Subject: [PATCH 74/77] Fix duplicate keys --- data/constants/keycodes_0.0.1.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/constants/keycodes_0.0.1.json b/data/constants/keycodes_0.0.1.json index ea0fd4257d..a3a1537ab1 100644 --- a/data/constants/keycodes_0.0.1.json +++ b/data/constants/keycodes_0.0.1.json @@ -689,7 +689,7 @@ }, "0x006d": { "group": "basic", - "key": "KC_F17", + "key": "KC_F18", "label": "F18" }, "0x006e": { @@ -1380,7 +1380,7 @@ }, "0x00f0": { "group": "mouse", - "key": "KC_MS_LEFT", + "key": "KC_MS_RIGHT", "label": "Move cursor right", "aliases": [ "KC_MS_R" From 68208278e087dd7c8ce0c4709d79cb6ab5e8941c Mon Sep 17 00:00:00 2001 From: zvecr Date: Wed, 11 May 2022 01:52:48 +0100 Subject: [PATCH 75/77] Render layers with 'qmk info' logic --- data/constants/keycodes_0.0.1.json | 6 +++--- lib/python/qmk/cli/xap/xap.py | 27 +++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/data/constants/keycodes_0.0.1.json b/data/constants/keycodes_0.0.1.json index a3a1537ab1..7644ba223b 100644 --- a/data/constants/keycodes_0.0.1.json +++ b/data/constants/keycodes_0.0.1.json @@ -210,7 +210,7 @@ "0x0028": { "group": "basic", "key": "KC_ENTER", - "label": "0", + "label": "Enter", "aliases": [ "KC_ENT" ] @@ -218,7 +218,7 @@ "0x0029": { "group": "basic", "key": "KC_ESCAPE", - "label": "0", + "label": "Esc", "aliases": [ "KC_ESC" ] @@ -226,7 +226,7 @@ "0x002a": { "group": "basic", "key": "KC_BACKSPACE", - "label": "0", + "label": "Backspace", "aliases": [ "KC_BSPC" ] diff --git a/lib/python/qmk/cli/xap/xap.py b/lib/python/qmk/cli/xap/xap.py index e0af627ef3..8de1b56808 100644 --- a/lib/python/qmk/cli/xap/xap.py +++ b/lib/python/qmk/cli/xap/xap.py @@ -8,6 +8,7 @@ from platform import platform from milc import cli +from qmk.keyboard import render_layout from qmk.xap.common import get_xap_keycodes KEYCODE_MAP = get_xap_keycodes('latest') @@ -86,7 +87,7 @@ def _xap_transaction(device, sub, route, *args): device.write(buffer) # get resp - array_alpha = device.read(64, 100) + array_alpha = device.read(64, 250) # validate tok sent == resp if str(token) != str(array_alpha[:2]): @@ -250,7 +251,6 @@ class XAPShell(cmd.Cmd): rows = info['matrix_size']['rows'] cols = info['matrix_size']['cols'] - # TODO: render like qmk info? for r in range(rows): for c in range(cols): q = data + r.to_bytes(1, byteorder='little') + c.to_bytes(1, byteorder='little') @@ -259,6 +259,29 @@ class XAPShell(cmd.Cmd): print(f'| {self.keycodes.get(keycode, "unknown").ljust(7)} ', end='', flush=True) print('|') + def do_layer(self, arg): + """Renders keycode values of a certain layer + """ + data = bytes(map(int, arg.split())) + if len(data) != 1: + cli.log.error("Invalid args") + return + + info = _query_device_info(self.device) + + # Assumptions on selected layout rather than prompt + first_layout = next(iter(info['layouts'])) + layout = info['layouts'][first_layout]['layout'] + + keycodes = [] + for item in layout: + q = data + bytes(item['matrix']) + keycode = _xap_transaction(self.device, 0x04, 0x02, q) + keycode = int.from_bytes(keycode, "little") + keycodes.append(self.keycodes.get(keycode, "???")) + + print(render_layout(layout, False, keycodes)) + def do_exit(self, line): """Quit shell """ From cb7d103ba8ac5ecfefb907ee71d13cd305bd1e07 Mon Sep 17 00:00:00 2001 From: zvecr Date: Wed, 11 May 2022 01:53:03 +0100 Subject: [PATCH 76/77] wider keys? --- lib/python/qmk/keyboard.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/python/qmk/keyboard.py b/lib/python/qmk/keyboard.py index e69f63aebe..9a3cd4e29f 100644 --- a/lib/python/qmk/keyboard.py +++ b/lib/python/qmk/keyboard.py @@ -4,6 +4,7 @@ from array import array from math import ceil from pathlib import Path import os +import shutil from glob import glob import qmk.path @@ -11,6 +12,7 @@ from qmk.c_parse import parse_config_h_file from qmk.json_schema import json_load from qmk.makefile import parse_rules_mk_file +KEY_WIDTH = 4 if shutil.get_terminal_size().columns < 200 else 6 BOX_DRAWING_CHARACTERS = { "unicode": { "tl": "┌", @@ -205,9 +207,9 @@ def render_layouts(info_json, render_ascii): def render_key_rect(textpad, x, y, w, h, label, style): box_chars = BOX_DRAWING_CHARACTERS[style] - x = ceil(x * 4) + x = ceil(x * KEY_WIDTH) y = ceil(y * 3) - w = ceil(w * 4) + w = ceil(w * KEY_WIDTH) h = ceil(h * 3) label_len = w - 2 @@ -234,9 +236,9 @@ def render_key_rect(textpad, x, y, w, h, label, style): def render_key_isoenter(textpad, x, y, w, h, label, style): box_chars = BOX_DRAWING_CHARACTERS[style] - x = ceil(x * 4) + x = ceil(x * KEY_WIDTH) y = ceil(y * 3) - w = ceil(w * 4) + w = ceil(w * KEY_WIDTH) h = ceil(h * 3) label_len = w - 1 @@ -266,9 +268,9 @@ def render_key_isoenter(textpad, x, y, w, h, label, style): def render_key_baenter(textpad, x, y, w, h, label, style): box_chars = BOX_DRAWING_CHARACTERS[style] - x = ceil(x * 4) + x = ceil(x * KEY_WIDTH) y = ceil(y * 3) - w = ceil(w * 4) + w = ceil(w * KEY_WIDTH) h = ceil(h * 3) label_len = w - 2 From a8cbda7b8ab013c540ca4594c51bcfaed0911897 Mon Sep 17 00:00:00 2001 From: zvecr Date: Wed, 11 May 2022 03:25:53 +0100 Subject: [PATCH 77/77] lower threshold --- lib/python/qmk/keyboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/python/qmk/keyboard.py b/lib/python/qmk/keyboard.py index 9a3cd4e29f..cb201c80e3 100644 --- a/lib/python/qmk/keyboard.py +++ b/lib/python/qmk/keyboard.py @@ -12,7 +12,7 @@ from qmk.c_parse import parse_config_h_file from qmk.json_schema import json_load from qmk.makefile import parse_rules_mk_file -KEY_WIDTH = 4 if shutil.get_terminal_size().columns < 200 else 6 +KEY_WIDTH = 4 if shutil.get_terminal_size().columns < 160 else 6 BOX_DRAWING_CHARACTERS = { "unicode": { "tl": "┌",