From f287597c19dc926c7c28cefa32ffc3f0b4fdba7f Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Wed, 9 Jun 2021 17:19:42 +1000 Subject: Use single memcmp to determine if matrix changed. (#13064) * Use memcmp to determine if matrix changed. * Firmware size issues. * Add documentation for the lack of need of MATRIX_ROW_PINS/MATRIX_COL_PINS, when overriding low-level matrix functions.--- docs/config_options.md | 3 +++ 1 file changed, 3 insertions(+) (limited to 'docs/config_options.md') diff --git a/docs/config_options.md b/docs/config_options.md index d0f0b316e0..26fe8cea55 100644 --- a/docs/config_options.md +++ b/docs/config_options.md @@ -51,8 +51,10 @@ This is a C header file that is one of the first things included, and will persi * the number of columns in your keyboard's matrix * `#define MATRIX_ROW_PINS { D0, D5, B5, B6 }` * pins of the rows, from top to bottom + * may be omitted by the keyboard designer if matrix reads are handled in an alternate manner. See [low-level matrix overrides](custom_quantum_functions.md?id=low-level-matrix-overrides) for more information. * `#define MATRIX_COL_PINS { F1, F0, B0, C7, F4, F5, F6, F7, D4, D6, B4, D7 }` * pins of the columns, from left to right + * may be omitted by the keyboard designer if matrix reads are handled in an alternate manner. See [low-level matrix overrides](custom_quantum_functions.md?id=low-level-matrix-overrides) for more information. * `#define MATRIX_IO_DELAY 30` * the delay in microseconds when between changing matrix pin state and reading values * `#define UNUSED_PINS { D1, D2, D3, B1, B2, B3 }` @@ -280,6 +282,7 @@ There are a few different ways to set handedness for split keyboards (listed in * `#define MATRIX_ROW_PINS_RIGHT { }` * `#define MATRIX_COL_PINS_RIGHT { }` * If you want to specify a different pinout for the right half than the left half, you can define `MATRIX_ROW_PINS_RIGHT`/`MATRIX_COL_PINS_RIGHT`. Currently, the size of `MATRIX_ROW_PINS` must be the same as `MATRIX_ROW_PINS_RIGHT` and likewise for the definition of columns. + * may be omitted by the keyboard designer if matrix reads are handled in an alternate manner. See [low-level matrix overrides](custom_quantum_functions.md?id=low-level-matrix-overrides) for more information. * `#define DIRECT_PINS_RIGHT { { F1, F0, B0, C7 }, { F4, F5, F6, F7 } }` * If you want to specify a different direct pinout for the right half than the left half, you can define `DIRECT_PINS_RIGHT`. Currently, the size of `DIRECT_PINS` must be the same as `DIRECT_PINS_RIGHT`. -- cgit v1.2.3 From 172e6a703041363decd6fc829542f33180c13beb Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Fri, 18 Jun 2021 09:10:06 +1000 Subject: Extensible split data sync (#11930) * Extensible split data sync capability through transactions. - Split common transport has been split up between the transport layer and data layer. - Split "transactions" model used, with convergence between I2C and serial data definitions. - Slave matrix "generation count" is used to determine if the full slave matrix needs to be retrieved. - Encoders get the same "generation count" treatment. - All other blocks of data are synchronised when a change is detected. - All transmissions have a globally-configurable deadline before a transmission is forced (`FORCED_SYNC_THROTTLE_MS`, default 100ms). - Added atomicity for all core-synced data, preventing partial updates - Added retries to AVR i2c_master's i2c_start, to minimise the number of failed transactions when interrupts are disabled on the slave due to atomicity checks. - Some keyboards have had slight modifications made in order to ensure that they still build due to firmware size restrictions. * Fixup LED_MATRIX compile. * Parameterise ERROR_DISCONNECT_COUNT.--- common_features.mk | 6 +- docs/config_options.md | 26 +- docs/feature_split_keyboard.md | 114 ++++- drivers/avr/i2c_master.c | 19 +- drivers/avr/i2c_slave.c | 29 +- drivers/avr/i2c_slave.h | 13 +- drivers/avr/serial.c | 82 +--- drivers/avr/serial.h | 62 --- drivers/chibios/serial.c | 52 +- drivers/chibios/serial.h | 62 --- drivers/chibios/serial_usart.c | 47 +- drivers/serial.h | 46 ++ keyboards/draculad/config.h | 2 + keyboards/handwired/freoduo/rules.mk | 4 +- keyboards/helix/rev2/keymaps/default/rules.mk | 2 +- keyboards/keebio/iris/rev2/rules.mk | 2 + keyboards/keebio/iris/rev3/rules.mk | 1 + keyboards/keebio/iris/rev4/rules.mk | 2 + keyboards/keebio/quefrency/rules.mk | 1 + keyboards/keebio/viterbi/rev2/rules.mk | 2 + keyboards/keebio/viterbi/rules.mk | 8 +- quantum/split_common/matrix.c | 6 +- quantum/split_common/post_config.h | 9 - quantum/split_common/transaction_id_define.h | 94 ++++ quantum/split_common/transactions.c | 670 ++++++++++++++++++++++++++ quantum/split_common/transactions.h | 54 +++ quantum/split_common/transport.c | 484 +++---------------- quantum/split_common/transport.h | 165 +++++++ tmk_core/common/host.c | 14 +- 29 files changed, 1387 insertions(+), 691 deletions(-) delete mode 100644 drivers/avr/serial.h delete mode 100644 drivers/chibios/serial.h create mode 100644 drivers/serial.h create mode 100644 quantum/split_common/transaction_id_define.h create mode 100644 quantum/split_common/transactions.c create mode 100644 quantum/split_common/transactions.h (limited to 'docs/config_options.md') diff --git a/common_features.mk b/common_features.mk index 1a079a8df2..15637d7608 100644 --- a/common_features.mk +++ b/common_features.mk @@ -537,7 +537,11 @@ ifeq ($(strip $(SPLIT_KEYBOARD)), yes) # Determine which (if any) transport files are required ifneq ($(strip $(SPLIT_TRANSPORT)), custom) - QUANTUM_LIB_SRC += $(QUANTUM_DIR)/split_common/transport.c + QUANTUM_SRC += $(QUANTUM_DIR)/split_common/transport.c \ + $(QUANTUM_DIR)/split_common/transactions.c + + OPT_DEFS += -DSPLIT_COMMON_TRANSACTIONS + # Functions added via QUANTUM_LIB_SRC are only included in the final binary if they're called. # Unused functions are pruned away, which is why we can add multiple drivers here without bloat. ifeq ($(PLATFORM),AVR) diff --git a/docs/config_options.md b/docs/config_options.md index 26fe8cea55..980195ac68 100644 --- a/docs/config_options.md +++ b/docs/config_options.md @@ -274,7 +274,7 @@ There are a few different ways to set handedness for split keyboards (listed in ### Other Options * `#define USE_I2C` - * For using I2C instead of Serial (defaults to serial) + * For using I2C instead of Serial (default is serial; serial transport is supported on ARM -- I2C is AVR-only) * `#define SOFT_SERIAL_PIN D0` * When using serial, define this. `D0` or `D1`,`D2`,`D3`,`E6`. @@ -303,7 +303,7 @@ There are a few different ways to set handedness for split keyboards (listed in * `#define SPLIT_USB_DETECT` * Detect (with timeout) USB connection when delegating master/slave * Default behavior for ARM - * Required for AVR Teensy + * Required for AVR Teensy (without hardware mods) * `#define SPLIT_USB_TIMEOUT 2000` * Maximum timeout when detecting master/slave when using `SPLIT_USB_DETECT` @@ -311,6 +311,28 @@ There are a few different ways to set handedness for split keyboards (listed in * `#define SPLIT_USB_TIMEOUT_POLL 10` * Poll frequency when detecting master/slave when using `SPLIT_USB_DETECT` +* `#define FORCED_SYNC_THROTTLE_MS 100` + * Deadline for synchronizing data from master to slave when using the QMK-provided split transport. + +* `#define SPLIT_TRANSPORT_MIRROR` + * Mirrors the master-side matrix on the slave when using the QMK-provided split transport. + +* `#define SPLIT_LAYER_STATE_ENABLE` + * Ensures the current layer state is available on the slave when using the QMK-provided split transport. + +* `#define SPLIT_LED_STATE_ENABLE` + * Ensures the current host indicator state (caps/num/scroll) is available on the slave when using the QMK-provided split transport. + +* `#define SPLIT_MODS_ENABLE` + * Ensures the current modifier state (normal, weak, and oneshot) is available on the slave when using the QMK-provided split transport. + +* `#define SPLIT_WPM_ENABLE` + * Ensures the current WPM is available on the slave when using the QMK-provided split transport. + +* `#define SPLIT_TRANSACTION_IDS_KB .....` +* `#define SPLIT_TRANSACTION_IDS_USER .....` + * Allows for custom data sync with the slave when using the QMK-provided split transport. See [custom data sync between sides](feature_split_keyboard.md#custom-data-sync) for more information. + # The `rules.mk` File This is a [make](https://www.gnu.org/software/make/manual/make.html) file that is included by the top-level `Makefile`. It is used to set some information about the MCU that we will be compiling for as well as enabling and disabling certain features. diff --git a/docs/feature_split_keyboard.md b/docs/feature_split_keyboard.md index 4ebf585f5c..603c387c2d 100644 --- a/docs/feature_split_keyboard.md +++ b/docs/feature_split_keyboard.md @@ -8,8 +8,7 @@ QMK Firmware has a generic implementation that is usable by any board, as well a For this, we will mostly be talking about the generic implementation used by the Let's Split and other keyboards. -!> ARM is not yet fully supported for Split Keyboards and has many limitations. Progress is being made, but we have not yet reached 100% feature parity. - +!> ARM split supports most QMK subsystems when using the 'serial' and 'serial_usart' drivers. I2C slave is currently unsupported. ## Compatibility Overview @@ -169,7 +168,7 @@ Because not every split keyboard is identical, there are a number of additional #define USE_I2C ``` -This enables I2C support for split keyboards. This isn't strictly for communication, but can be used for OLED or other I2C-based devices. +This configures the use of I2C support for split keyboard transport (AVR only). ```c #define SOFT_SERIAL_PIN D0 @@ -193,20 +192,115 @@ If you're having issues with serial communication, you can change this value, as * **`5`**: about 20kbps ```c -#define SPLIT_MODS_ENABLE +#define FORCED_SYNC_THROTTLE_MS 100 ``` -This enables transmitting modifier state (normal, weak and oneshot) to the non -primary side of the split keyboard. This adds a few bytes of data to the split -communication protocol and may impact the matrix scan speed when enabled. -The purpose of this feature is to support cosmetic use of modifer state (e.g. -displaying status on an OLED screen). +This sets the maximum number of milliseconds before forcing a synchronization of data from master to slave. Under normal circumstances this sync occurs whenever the data _changes_, for safety a data transfer occurs after this number of milliseconds if no change has been detected since the last sync. ```c #define SPLIT_TRANSPORT_MIRROR ``` -This mirrors the master side matrix to the slave side for features that react or require knowledge of master side key presses on the slave side. This adds a few bytes of data to the split communication protocol and may impact the matrix scan speed when enabled. The purpose of this feature is to support cosmetic use of key events (e.g. RGB reacting to Keypresses). +This mirrors the master side matrix to the slave side for features that react or require knowledge of master side key presses on the slave side. The purpose of this feature is to support cosmetic use of key events (e.g. RGB reacting to keypresses). This adds overhead to the split communication protocol and may negatively impact the matrix scan speed when enabled. + +```c +#define SPLIT_LAYER_STATE_ENABLE +``` + +This enables syncing of the layer state between both halves of the split keyboard. The main purpose of this feature is to enable support for use of things like OLED display of the currently active layer. This adds overhead to the split communication protocol and may negatively impact the matrix scan speed when enabled. + +```c +#define SPLIT_LED_STATE_ENABLE +``` + +This enables syncing of the Host LED status (caps lock, num lock, etc) between both halves of the split keyboard. The main purpose of this feature is to enable support for use of things like OLED display of the Host LED status. This adds overhead to the split communication protocol and may negatively impact the matrix scan speed when enabled. + +```c +#define SPLIT_MODS_ENABLE +``` + +This enables transmitting modifier state (normal, weak and oneshot) to the non primary side of the split keyboard. The purpose of this feature is to support cosmetic use of modifer state (e.g. displaying status on an OLED screen). This adds overhead to the split communication protocol and may negatively impact the matrix scan speed when enabled. + +```c +#define SPLIT_WPM_ENABLE +``` + +This enables transmitting the current WPM to the slave side of the split keyboard. The purpose of this feature is to support cosmetic use of WPM (e.g. displaying the current value on an OLED screen). This adds overhead to the split communication protocol and may negatively impact the matrix scan speed when enabled. + +### Custom data sync between sides :id=custom-data-sync + +QMK's split transport allows for arbitrary data transactions at both the keyboard and user levels. This is modelled on a remote procedure call, with the master invoking a function on the slave side, with the ability to send data from master to slave, process it slave side, and send data back from slave to master. + +To leverage this, a keyboard or user/keymap can define a comma-separated list of _transaction IDs_: + +```c +// for keyboard-level data sync: +#define SPLIT_TRANSACTION_IDS_KB KEYBOARD_SYNC_A, KEYBOARD_SYNC_B +// or, for user: +#define SPLIT_TRANSACTION_IDS_USER USER_SYNC_A, USER_SYNC_B, USER_SYNC_C +``` + +These _transaction IDs_ then need a slave-side handler function to be registered with the split transport, for example: + +```c +typedef struct _master_to_slave_t { + int m2s_data; +} master_to_slave_t; + +typedef struct _slave_to_master_t { + int s2m_data; +} slave_to_master_t; + +void user_sync_a_slave_handler(uint8_t in_buflen, const void* in_data, uint8_t out_buflen, void* out_data) { + const master_to_slave_t *m2s = (const master_to_slave_t*)in_data; + slave_to_master_t *s2m = (slave_to_master_t*)out_data; + s2m->s2m_data = m2s->m2s_data + 5; // whatever comes in, add 5 so it can be sent back +} + +void keyboard_post_init_user(void) { + transaction_register_rpc(USER_SYNC_A, user_sync_a_slave_handler); +} +``` + +The master side can then invoke the slave-side handler - for normal keyboard functionality to be minimally affected, any keyboard- or user-level code attempting to sync data should be throttled: + +```c +void housekeeping_task_user(void) { + if (is_keyboard_master()) { + // Interact with slave every 500ms + static uint32_t last_sync = 0; + if (timer_elapsed32(last_sync) > 500) { + master_to_slave_t m2s = {6}; + slave_to_master_t s2m = {0}; + if(transaction_rpc_exec(USER_SYNC_A, sizeof(m2s), &m2s, sizeof(s2m), &s2m)) { + last_sync = timer_read32(); + dprintf("Slave value: %d\n", s2m.s2m_data); // this will now be 11, as the slave adds 5 + } else { + dprint("Slave sync failed!\n"); + } + } + } +} +``` + +!> It is recommended that any data sync between halves happens during the master side's _housekeeping task_. This ensures timely retries should failures occur. + +If only one-way data transfer is needed, helper methods are provided: + +```c +bool transaction_rpc_exec(int8_t transaction_id, uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer); +bool transaction_rpc_send(int8_t transaction_id, uint8_t initiator2target_buffer_size, const void *initiator2target_buffer); +bool transaction_rpc_recv(int8_t transaction_id, uint8_t target2initiator_buffer_size, void *target2initiator_buffer); +``` + +By default, the inbound and outbound data is limited to a maximum of 32 bytes each. The sizes can be altered if required: + +```c +// Master to slave: +#define RPC_M2S_BUFFER_SIZE 48 +// Slave to master: +#define RPC_S2M_BUFFER_SIZE 48 +``` ### Hardware Configuration Options diff --git a/drivers/avr/i2c_master.c b/drivers/avr/i2c_master.c index b1e4885298..2773e00778 100644 --- a/drivers/avr/i2c_master.c +++ b/drivers/avr/i2c_master.c @@ -28,8 +28,14 @@ # define F_SCL 400000UL // SCL frequency #endif +#ifndef I2C_START_RETRY_COUNT +# define I2C_START_RETRY_COUNT 20 +#endif // I2C_START_RETRY_COUNT + #define TWBR_val (((F_CPU / F_SCL) - 16) / 2) +#define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) + void i2c_init(void) { TWSR = 0; /* no prescaler */ TWBR = (uint8_t)TWBR_val; @@ -47,7 +53,7 @@ void i2c_init(void) { #endif } -i2c_status_t i2c_start(uint8_t address, uint16_t timeout) { +static i2c_status_t i2c_start_impl(uint8_t address, uint16_t timeout) { // reset TWI control register TWCR = 0; // transmit START condition @@ -86,6 +92,17 @@ i2c_status_t i2c_start(uint8_t address, uint16_t timeout) { return I2C_STATUS_SUCCESS; } +i2c_status_t i2c_start(uint8_t address, uint16_t timeout) { + // Retry i2c_start_impl a bunch times in case the remote side has interrupts disabled. + uint16_t timeout_timer = timer_read(); + uint16_t time_slice = MAX(1, (timeout == (I2C_TIMEOUT_INFINITE)) ? 5 : (timeout / (I2C_START_RETRY_COUNT))); // if it's infinite, wait 1ms between attempts, otherwise split up the entire timeout into the number of retries + i2c_status_t status; + do { + status = i2c_start_impl(address, time_slice); + } while ((status < 0) && ((timeout == I2C_TIMEOUT_INFINITE) || (timer_elapsed(timeout_timer) < timeout))); + return status; +} + i2c_status_t i2c_write(uint8_t data, uint16_t timeout) { // load data into data register TWDR = data; diff --git a/drivers/avr/i2c_slave.c b/drivers/avr/i2c_slave.c index 62a378165a..2907f164c0 100644 --- a/drivers/avr/i2c_slave.c +++ b/drivers/avr/i2c_slave.c @@ -17,6 +17,7 @@ * GitHub repository: https://github.com/g4lvanix/I2C-slave-lib */ +#include #include #include #include @@ -24,6 +25,12 @@ #include "i2c_slave.h" +#if defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS) +# include "transactions.h" + +static volatile bool is_callback_executor = false; +#endif // defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS) + volatile uint8_t i2c_slave_reg[I2C_SLAVE_REG_COUNT]; static volatile uint8_t buffer_address; @@ -48,11 +55,14 @@ ISR(TWI_vect) { case TW_SR_SLA_ACK: // The device is now a slave receiver slave_has_register_set = false; +#if defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS) + is_callback_executor = false; +#endif // defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS) break; case TW_SR_DATA_ACK: // This device is a slave receiver and has received data - // First byte is the location then the bytes will be writen in buffer with auto-incriment + // First byte is the location then the bytes will be writen in buffer with auto-increment if (!slave_has_register_set) { buffer_address = TWDR; @@ -60,10 +70,25 @@ ISR(TWI_vect) { ack = 0; buffer_address = 0; } - slave_has_register_set = true; // address has been receaved now fill in buffer + slave_has_register_set = true; // address has been received now fill in buffer + +#if defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS) + // Work out if we're attempting to execute a callback + is_callback_executor = buffer_address == split_transaction_table[I2C_EXECUTE_CALLBACK].initiator2target_offset; +#endif // defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS) } else { i2c_slave_reg[buffer_address] = TWDR; buffer_address++; + +#if defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS) + // If we're intending to execute a transaction callback, do so, as we've just received the transaction ID + if (is_callback_executor) { + split_transaction_desc_t *trans = &split_transaction_table[split_shmem->transaction_id]; + if (trans->slave_callback) { + trans->slave_callback(trans->initiator2target_buffer_size, split_trans_initiator2target_buffer(trans), trans->target2initiator_buffer_size, split_trans_target2initiator_buffer(trans)); + } + } +#endif // defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS) } break; diff --git a/drivers/avr/i2c_slave.h b/drivers/avr/i2c_slave.h index 1cd0625ef4..a8647c9da3 100644 --- a/drivers/avr/i2c_slave.h +++ b/drivers/avr/i2c_slave.h @@ -22,7 +22,18 @@ #pragma once -#define I2C_SLAVE_REG_COUNT 30 +#ifndef I2C_SLAVE_REG_COUNT + +# if defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS) +# include "transport.h" +# define I2C_SLAVE_REG_COUNT sizeof(split_shared_memory_t) +# else // defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS) +# define I2C_SLAVE_REG_COUNT 30 +# endif // defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS) + +#endif // I2C_SLAVE_REG_COUNT + +_Static_assert(I2C_SLAVE_REG_COUNT < 256, "I2C target registers must be single byte"); extern volatile uint8_t i2c_slave_reg[I2C_SLAVE_REG_COUNT]; diff --git a/drivers/avr/serial.c b/drivers/avr/serial.c index 3647bee0d3..9a7345a53d 100644 --- a/drivers/avr/serial.c +++ b/drivers/avr/serial.c @@ -224,15 +224,8 @@ # define SERIAL_DELAY_HALF2 (SERIAL_DELAY - SERIAL_DELAY / 2) # define SLAVE_INT_WIDTH_US 1 -# ifndef SERIAL_USE_MULTI_TRANSACTION -# define SLAVE_INT_RESPONSE_TIME SERIAL_DELAY -# else -# define SLAVE_INT_ACK_WIDTH_UNIT 2 -# define SLAVE_INT_ACK_WIDTH 4 -# endif - -static SSTD_t *Transaction_table = NULL; -static uint8_t Transaction_table_size = 0; +# define SLAVE_INT_ACK_WIDTH_UNIT 2 +# define SLAVE_INT_ACK_WIDTH 4 inline static void serial_delay(void) ALWAYS_INLINE; inline static void serial_delay(void) { _delay_us(SERIAL_DELAY); } @@ -259,16 +252,12 @@ inline static void serial_low(void) { writePinLow(SOFT_SERIAL_PIN); } inline static void serial_high(void) ALWAYS_INLINE; inline static void serial_high(void) { writePinHigh(SOFT_SERIAL_PIN); } -void soft_serial_initiator_init(SSTD_t *sstd_table, int sstd_table_size) { - Transaction_table = sstd_table; - Transaction_table_size = (uint8_t)sstd_table_size; +void soft_serial_initiator_init(void) { serial_output(); serial_high(); } -void soft_serial_target_init(SSTD_t *sstd_table, int sstd_table_size) { - Transaction_table = sstd_table; - Transaction_table_size = (uint8_t)sstd_table_size; +void soft_serial_target_init(void) { serial_input_with_pullup(); // Enable INT0-INT7 @@ -395,19 +384,14 @@ static inline uint8_t nibble_bits_count(uint8_t bits) { // interrupt handle to be used by the target device ISR(SERIAL_PIN_INTERRUPT) { -# ifndef SERIAL_USE_MULTI_TRANSACTION - serial_low(); - serial_output(); - SSTD_t *trans = Transaction_table; -# else // recive transaction table index uint8_t tid, bits; uint8_t pecount = 0; sync_recv(); - bits = serial_read_chunk(&pecount, 7); + bits = serial_read_chunk(&pecount, 8); tid = bits >> 3; - bits = (bits & 7) != nibble_bits_count(tid); - if (bits || pecount > 0 || tid > Transaction_table_size) { + bits = (bits & 7) != (nibble_bits_count(tid) & 7); + if (bits || pecount > 0 || tid > NUM_TOTAL_TRANSACTIONS) { return; } serial_delay_half1(); @@ -415,18 +399,22 @@ ISR(SERIAL_PIN_INTERRUPT) { serial_high(); // response step1 low->high serial_output(); _delay_sub_us(SLAVE_INT_ACK_WIDTH_UNIT * SLAVE_INT_ACK_WIDTH); - SSTD_t *trans = &Transaction_table[tid]; + split_transaction_desc_t *trans = &split_transaction_table[tid]; serial_low(); // response step2 ack high->low -# endif + + // If the transaction has a callback, we can execute it now + if (trans->slave_callback) { + trans->slave_callback(trans->initiator2target_buffer_size, split_trans_initiator2target_buffer(trans), trans->target2initiator_buffer_size, split_trans_target2initiator_buffer(trans)); + } // target send phase - if (trans->target2initiator_buffer_size > 0) serial_send_packet((uint8_t *)trans->target2initiator_buffer, trans->target2initiator_buffer_size); + if (trans->target2initiator_buffer_size > 0) serial_send_packet((uint8_t *)split_trans_target2initiator_buffer(trans), trans->target2initiator_buffer_size); // target switch to input change_sender2reciver(); // target recive phase if (trans->initiator2target_buffer_size > 0) { - if (serial_recive_packet((uint8_t *)trans->initiator2target_buffer, trans->initiator2target_buffer_size)) { + if (serial_recive_packet((uint8_t *)split_trans_initiator2target_buffer(trans), trans->initiator2target_buffer_size)) { *trans->status = TRANSACTION_ACCEPTED; } else { *trans->status = TRANSACTION_DATA_ERROR; @@ -448,14 +436,12 @@ ISR(SERIAL_PIN_INTERRUPT) { // TRANSACTION_NO_RESPONSE // TRANSACTION_DATA_ERROR // this code is very time dependent, so we need to disable interrupts -# ifndef SERIAL_USE_MULTI_TRANSACTION -int soft_serial_transaction(void) { - SSTD_t *trans = Transaction_table; -# else int soft_serial_transaction(int sstd_index) { - if (sstd_index > Transaction_table_size) return TRANSACTION_TYPE_ERROR; - SSTD_t *trans = &Transaction_table[sstd_index]; -# endif + if (sstd_index > NUM_TOTAL_TRANSACTIONS) return TRANSACTION_TYPE_ERROR; + split_transaction_desc_t *trans = &split_transaction_table[sstd_index]; + + if (!trans->status) return TRANSACTION_TYPE_ERROR; // not registered + cli(); // signal to the target that we want to start a transaction @@ -463,27 +449,11 @@ int soft_serial_transaction(int sstd_index) { serial_low(); _delay_us(SLAVE_INT_WIDTH_US); -# ifndef SERIAL_USE_MULTI_TRANSACTION - // wait for the target response - serial_input_with_pullup(); - _delay_us(SLAVE_INT_RESPONSE_TIME); - - // check if the target is present - if (serial_read_pin()) { - // target failed to pull the line low, assume not present - serial_output(); - serial_high(); - *trans->status = TRANSACTION_NO_RESPONSE; - sei(); - return TRANSACTION_NO_RESPONSE; - } - -# else // send transaction table index int tid = (sstd_index << 3) | (7 & nibble_bits_count(sstd_index)); sync_send(); _delay_sub_us(TID_SEND_ADJUST); - serial_write_chunk(tid, 7); + serial_write_chunk(tid, 8); serial_delay_half1(); // wait for the target response (step1 low->high) @@ -504,12 +474,11 @@ int soft_serial_transaction(int sstd_index) { } _delay_sub_us(SLAVE_INT_ACK_WIDTH_UNIT); } -# endif // initiator recive phase // if the target is present syncronize with it if (trans->target2initiator_buffer_size > 0) { - if (!serial_recive_packet((uint8_t *)trans->target2initiator_buffer, trans->target2initiator_buffer_size)) { + if (!serial_recive_packet((uint8_t *)split_trans_target2initiator_buffer(trans), trans->target2initiator_buffer_size)) { serial_output(); serial_high(); *trans->status = TRANSACTION_DATA_ERROR; @@ -523,7 +492,7 @@ int soft_serial_transaction(int sstd_index) { // initiator send phase if (trans->initiator2target_buffer_size > 0) { - serial_send_packet((uint8_t *)trans->initiator2target_buffer, trans->initiator2target_buffer_size); + serial_send_packet((uint8_t *)split_trans_initiator2target_buffer(trans), trans->initiator2target_buffer_size); } // always, release the line when not in use @@ -534,9 +503,8 @@ int soft_serial_transaction(int sstd_index) { return TRANSACTION_END; } -# ifdef SERIAL_USE_MULTI_TRANSACTION int soft_serial_get_and_clean_status(int sstd_index) { - SSTD_t *trans = &Transaction_table[sstd_index]; + split_transaction_desc_t *trans = &split_transaction_table[sstd_index]; cli(); int retval = *trans->status; *trans->status = 0; @@ -544,8 +512,6 @@ int soft_serial_get_and_clean_status(int sstd_index) { sei(); return retval; } -# endif - #endif // Helix serial.c history diff --git a/drivers/avr/serial.h b/drivers/avr/serial.h deleted file mode 100644 index 53e66cf905..0000000000 --- a/drivers/avr/serial.h +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include - -// ///////////////////////////////////////////////////////////////// -// Need Soft Serial defines in config.h -// ///////////////////////////////////////////////////////////////// -// ex. -// #define SOFT_SERIAL_PIN ?? // ?? = D0,D1,D2,D3,E6 -// OPTIONAL: #define SELECT_SOFT_SERIAL_SPEED ? // ? = 1,2,3,4,5 -// // 1: about 137kbps (default) -// // 2: about 75kbps -// // 3: about 39kbps -// // 4: about 26kbps -// // 5: about 20kbps -// -// //// USE simple API (using signle-type transaction function) -// /* nothing */ -// //// USE flexible API (using multi-type transaction function) -// #define SERIAL_USE_MULTI_TRANSACTION -// -// ///////////////////////////////////////////////////////////////// - -// Soft Serial Transaction Descriptor -typedef struct _SSTD_t { - uint8_t *status; - uint8_t initiator2target_buffer_size; - uint8_t *initiator2target_buffer; - uint8_t target2initiator_buffer_size; - uint8_t *target2initiator_buffer; -} SSTD_t; -#define TID_LIMIT(table) (sizeof(table) / sizeof(SSTD_t)) - -// initiator is transaction start side -void soft_serial_initiator_init(SSTD_t *sstd_table, int sstd_table_size); -// target is interrupt accept side -void soft_serial_target_init(SSTD_t *sstd_table, int sstd_table_size); - -// initiator resullt -#define TRANSACTION_END 0 -#define TRANSACTION_NO_RESPONSE 0x1 -#define TRANSACTION_DATA_ERROR 0x2 -#define TRANSACTION_TYPE_ERROR 0x4 -#ifndef SERIAL_USE_MULTI_TRANSACTION -int soft_serial_transaction(void); -#else -int soft_serial_transaction(int sstd_index); -#endif - -// target status -// *SSTD_t.status has -// initiator: -// TRANSACTION_END -// or TRANSACTION_NO_RESPONSE -// or TRANSACTION_DATA_ERROR -// target: -// TRANSACTION_DATA_ERROR -// or TRANSACTION_ACCEPTED -#define TRANSACTION_ACCEPTED 0x8 -#ifdef SERIAL_USE_MULTI_TRANSACTION -int soft_serial_get_and_clean_status(int sstd_index); -#endif diff --git a/drivers/chibios/serial.c b/drivers/chibios/serial.c index 54f7e1321f..f54fbcee4e 100644 --- a/drivers/chibios/serial.c +++ b/drivers/chibios/serial.c @@ -74,21 +74,12 @@ static THD_FUNCTION(Thread1, arg) { } } -static SSTD_t *Transaction_table = NULL; -static uint8_t Transaction_table_size = 0; - -void soft_serial_initiator_init(SSTD_t *sstd_table, int sstd_table_size) { - Transaction_table = sstd_table; - Transaction_table_size = (uint8_t)sstd_table_size; - +void soft_serial_initiator_init(void) { serial_output(); serial_high(); } -void soft_serial_target_init(SSTD_t *sstd_table, int sstd_table_size) { - Transaction_table = sstd_table; - Transaction_table_size = (uint8_t)sstd_table_size; - +void soft_serial_target_init(void) { serial_input(); palEnablePadEvent(PAL_PORT(SOFT_SERIAL_PIN), PAL_PAD(SOFT_SERIAL_PIN), PAL_EVENT_MODE_FALLING_EDGE); @@ -154,16 +145,14 @@ void interrupt_handler(void *arg) { uint8_t checksum_computed = 0; int sstd_index = 0; -#ifdef SERIAL_USE_MULTI_TRANSACTION sstd_index = serial_read_byte(); sync_send(); -#endif - SSTD_t *trans = &Transaction_table[sstd_index]; + split_transaction_desc_t *trans = &split_transaction_table[sstd_index]; for (int i = 0; i < trans->initiator2target_buffer_size; ++i) { - trans->initiator2target_buffer[i] = serial_read_byte(); + split_trans_initiator2target_buffer(trans)[i] = serial_read_byte(); sync_send(); - checksum_computed += trans->initiator2target_buffer[i]; + checksum_computed += split_trans_initiator2target_buffer(trans)[i]; } checksum_computed ^= 7; uint8_t checksum_received = serial_read_byte(); @@ -172,12 +161,17 @@ void interrupt_handler(void *arg) { // wait for the sync to finish sending serial_delay(); + // Allow any slave processing to occur + if (trans->slave_callback) { + trans->slave_callback(trans->initiator2target_buffer_size, split_trans_initiator2target_buffer(trans), trans->target2initiator_buffer_size, split_trans_target2initiator_buffer(trans)); + } + uint8_t checksum = 0; for (int i = 0; i < trans->target2initiator_buffer_size; ++i) { - serial_write_byte(trans->target2initiator_buffer[i]); + serial_write_byte(split_trans_target2initiator_buffer(trans)[i]); sync_send(); serial_delay_half(); - checksum += trans->target2initiator_buffer[i]; + checksum += split_trans_target2initiator_buffer(trans)[i]; } serial_write_byte(checksum ^ 7); sync_send(); @@ -206,15 +200,10 @@ void interrupt_handler(void *arg) { // TRANSACTION_NO_RESPONSE // TRANSACTION_DATA_ERROR // this code is very time dependent, so we need to disable interrupts -#ifndef SERIAL_USE_MULTI_TRANSACTION -int soft_serial_transaction(void) { - int sstd_index = 0; -#else int soft_serial_transaction(int sstd_index) { -#endif - - if (sstd_index > Transaction_table_size) return TRANSACTION_TYPE_ERROR; - SSTD_t *trans = &Transaction_table[sstd_index]; + if (sstd_index > NUM_TOTAL_TRANSACTIONS) return TRANSACTION_TYPE_ERROR; + split_transaction_desc_t *trans = &split_transaction_table[sstd_index]; + if (!trans->status) return TRANSACTION_TYPE_ERROR; // not registered // TODO: remove extra delay between transactions serial_delay(); @@ -244,14 +233,13 @@ int soft_serial_transaction(int sstd_index) { uint8_t checksum = 0; // send data to the slave -#ifdef SERIAL_USE_MULTI_TRANSACTION serial_write_byte(sstd_index); // first chunk is transaction id sync_recv(); -#endif + for (int i = 0; i < trans->initiator2target_buffer_size; ++i) { - serial_write_byte(trans->initiator2target_buffer[i]); + serial_write_byte(split_trans_initiator2target_buffer(trans)[i]); sync_recv(); - checksum += trans->initiator2target_buffer[i]; + checksum += split_trans_initiator2target_buffer(trans)[i]; } serial_write_byte(checksum ^ 7); sync_recv(); @@ -262,9 +250,9 @@ int soft_serial_transaction(int sstd_index) { // receive data from the slave uint8_t checksum_computed = 0; for (int i = 0; i < trans->target2initiator_buffer_size; ++i) { - trans->target2initiator_buffer[i] = serial_read_byte(); + split_trans_target2initiator_buffer(trans)[i] = serial_read_byte(); sync_recv(); - checksum_computed += trans->target2initiator_buffer[i]; + checksum_computed += split_trans_target2initiator_buffer(trans)[i]; } checksum_computed ^= 7; uint8_t checksum_received = serial_read_byte(); diff --git a/drivers/chibios/serial.h b/drivers/chibios/serial.h deleted file mode 100644 index 0c1857d52e..0000000000 --- a/drivers/chibios/serial.h +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include - -// ///////////////////////////////////////////////////////////////// -// Need Soft Serial defines in config.h -// ///////////////////////////////////////////////////////////////// -// ex. -// #define SOFT_SERIAL_PIN ?? // ?? = D0,D1,D2,D3,E6 -// OPTIONAL: #define SELECT_SOFT_SERIAL_SPEED ? // ? = 1,2,3,4,5 -// // 1: about 137kbps (default) -// // 2: about 75kbps -// // 3: about 39kbps -// // 4: about 26kbps -// // 5: about 20kbps -// -// //// USE simple API (using signle-type transaction function) -// /* nothing */ -// //// USE flexible API (using multi-type transaction function) -// #define SERIAL_USE_MULTI_TRANSACTION -// -// ///////////////////////////////////////////////////////////////// - -// Soft Serial Transaction Descriptor -typedef struct _SSTD_t { - uint8_t *status; - uint8_t initiator2target_buffer_size; - uint8_t *initiator2target_buffer; - uint8_t target2initiator_buffer_size; - uint8_t *target2initiator_buffer; -} SSTD_t; -#define TID_LIMIT(table) (sizeof(table) / sizeof(SSTD_t)) - -// initiator is transaction start side -void soft_serial_initiator_init(SSTD_t *sstd_table, int sstd_table_size); -// target is interrupt accept side -void soft_serial_target_init(SSTD_t *sstd_table, int sstd_table_size); - -// initiator result -#define TRANSACTION_END 0 -#define TRANSACTION_NO_RESPONSE 0x1 -#define TRANSACTION_DATA_ERROR 0x2 -#define TRANSACTION_TYPE_ERROR 0x4 -#ifndef SERIAL_USE_MULTI_TRANSACTION -int soft_serial_transaction(void); -#else -int soft_serial_transaction(int sstd_index); -#endif - -// target status -// *SSTD_t.status has -// initiator: -// TRANSACTION_END -// or TRANSACTION_NO_RESPONSE -// or TRANSACTION_DATA_ERROR -// target: -// TRANSACTION_DATA_ERROR -// or TRANSACTION_ACCEPTED -#define TRANSACTION_ACCEPTED 0x8 -#ifdef SERIAL_USE_MULTI_TRANSACTION -int soft_serial_get_and_clean_status(int sstd_index); -#endif diff --git a/drivers/chibios/serial_usart.c b/drivers/chibios/serial_usart.c index cae29388c3..9f180d2d74 100644 --- a/drivers/chibios/serial_usart.c +++ b/drivers/chibios/serial_usart.c @@ -113,37 +113,29 @@ void usart_slave_init(void) { chThdCreateStatic(waSlaveThread, sizeof(waSlaveThread), HIGHPRIO, SlaveThread, NULL); } -static SSTD_t* Transaction_table = NULL; -static uint8_t Transaction_table_size = 0; +void soft_serial_initiator_init(void) { usart_master_init(); } -void soft_serial_initiator_init(SSTD_t* sstd_table, int sstd_table_size) { - Transaction_table = sstd_table; - Transaction_table_size = (uint8_t)sstd_table_size; - - usart_master_init(); -} - -void soft_serial_target_init(SSTD_t* sstd_table, int sstd_table_size) { - Transaction_table = sstd_table; - Transaction_table_size = (uint8_t)sstd_table_size; - - usart_slave_init(); -} +void soft_serial_target_init(void) { usart_slave_init(); } void handle_soft_serial_slave(void) { - uint8_t sstd_index = sdGet(&SERIAL_USART_DRIVER); // first chunk is always transaction id - SSTD_t* trans = &Transaction_table[sstd_index]; + uint8_t sstd_index = sdGet(&SERIAL_USART_DRIVER); // first chunk is always transaction id + split_transaction_desc_t* trans = &split_transaction_table[sstd_index]; // Always write back the sstd_index as part of a basic handshake sstd_index ^= HANDSHAKE_MAGIC; sdWrite(&SERIAL_USART_DRIVER, &sstd_index, sizeof(sstd_index)); if (trans->initiator2target_buffer_size) { - sdRead(&SERIAL_USART_DRIVER, trans->initiator2target_buffer, trans->initiator2target_buffer_size); + sdRead(&SERIAL_USART_DRIVER, split_trans_initiator2target_buffer(trans), trans->initiator2target_buffer_size); + } + + // Allow any slave processing to occur + if (trans->slave_callback) { + trans->slave_callback(trans->initiator2target_buffer_size, split_trans_initiator2target_buffer(trans), trans->target2initiator_buffer_size, split_trans_target2initiator_buffer(trans)); } if (trans->target2initiator_buffer_size) { - sdWrite(&SERIAL_USART_DRIVER, trans->target2initiator_buffer, trans->target2initiator_buffer_size); + sdWrite(&SERIAL_USART_DRIVER, split_trans_target2initiator_buffer(trans), trans->target2initiator_buffer_size); } if (trans->status) { @@ -160,17 +152,14 @@ void handle_soft_serial_slave(void) { // TRANSACTION_END // TRANSACTION_NO_RESPONSE // TRANSACTION_DATA_ERROR -#ifndef SERIAL_USE_MULTI_TRANSACTION -int soft_serial_transaction(void) { - uint8_t sstd_index = 0; -#else int soft_serial_transaction(int index) { uint8_t sstd_index = index; -#endif - if (sstd_index > Transaction_table_size) return TRANSACTION_TYPE_ERROR; - SSTD_t* trans = &Transaction_table[sstd_index]; - msg_t res = 0; + if (sstd_index > NUM_TOTAL_TRANSACTIONS) return TRANSACTION_TYPE_ERROR; + split_transaction_desc_t* trans = &split_transaction_table[sstd_index]; + msg_t res = 0; + + if (!trans->status) return TRANSACTION_TYPE_ERROR; // not registered sdClear(&SERIAL_USART_DRIVER); @@ -189,7 +178,7 @@ int soft_serial_transaction(int index) { } if (trans->initiator2target_buffer_size) { - res = sdWriteTimeout(&SERIAL_USART_DRIVER, trans->initiator2target_buffer, trans->initiator2target_buffer_size, TIME_MS2I(SERIAL_USART_TIMEOUT)); + res = sdWriteTimeout(&SERIAL_USART_DRIVER, split_trans_initiator2target_buffer(trans), trans->initiator2target_buffer_size, TIME_MS2I(SERIAL_USART_TIMEOUT)); if (res < 0) { dprintf("serial::usart_transmit NO_RESPONSE\n"); return TRANSACTION_NO_RESPONSE; @@ -197,7 +186,7 @@ int soft_serial_transaction(int index) { } if (trans->target2initiator_buffer_size) { - res = sdReadTimeout(&SERIAL_USART_DRIVER, trans->target2initiator_buffer, trans->target2initiator_buffer_size, TIME_MS2I(SERIAL_USART_TIMEOUT)); + res = sdReadTimeout(&SERIAL_USART_DRIVER, split_trans_target2initiator_buffer(trans), trans->target2initiator_buffer_size, TIME_MS2I(SERIAL_USART_TIMEOUT)); if (res < 0) { dprintf("serial::usart_receive NO_RESPONSE\n"); return TRANSACTION_NO_RESPONSE; diff --git a/drivers/serial.h b/drivers/serial.h new file mode 100644 index 0000000000..d9c2a69e96 --- /dev/null +++ b/drivers/serial.h @@ -0,0 +1,46 @@ +/* Copyright 2021 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 . + */ + +#pragma once + +#include +#include + +#include + +// initiator is transaction start side +void soft_serial_initiator_init(void); +// target is interrupt accept side +void soft_serial_target_init(void); + +// initiator result +#define TRANSACTION_END 0 +#define TRANSACTION_NO_RESPONSE 0x1 +#define TRANSACTION_DATA_ERROR 0x2 +#define TRANSACTION_TYPE_ERROR 0x4 +int soft_serial_transaction(int sstd_index); + +// target status +// *SSTD_t.status has +// initiator: +// TRANSACTION_END +// or TRANSACTION_NO_RESPONSE +// or TRANSACTION_DATA_ERROR +// target: +// TRANSACTION_DATA_ERROR +// or TRANSACTION_ACCEPTED +#define TRANSACTION_ACCEPTED 0x8 +int soft_serial_get_and_clean_status(int sstd_index); diff --git a/keyboards/draculad/config.h b/keyboards/draculad/config.h index 8a27fdea4d..d8a9fbd37c 100644 --- a/keyboards/draculad/config.h +++ b/keyboards/draculad/config.h @@ -65,3 +65,5 @@ along with this program. If not, see . #define UNUSED_PINS #define EE_HANDS + +#define LAYER_STATE_8BIT \ No newline at end of file diff --git a/keyboards/handwired/freoduo/rules.mk b/keyboards/handwired/freoduo/rules.mk index e3da3753e4..d8923557d6 100644 --- a/keyboards/handwired/freoduo/rules.mk +++ b/keyboards/handwired/freoduo/rules.mk @@ -11,7 +11,7 @@ BOOTMAGIC_ENABLE = no # Virtual DIP switch configuration MOUSEKEY_ENABLE = yes # Mouse keys EXTRAKEY_ENABLE = yes # Audio control and System control CONSOLE_ENABLE = no # Console for debug -COMMAND_ENABLE = yes # Commands for debug and configuration +COMMAND_ENABLE = no # Commands for debug and configuration # Do not enable SLEEP_LED_ENABLE. it uses the same timer as BACKLIGHT_ENABLE SLEEP_LED_ENABLE = no # Breathing sleep LED during USB suspend # if this doesn't work, see here: https://github.com/tmk/tmk_keyboard/wiki/FAQ#nkro-doesnt-work @@ -21,4 +21,4 @@ RGBLIGHT_ENABLE = yes # Enable keyboard RGB underglow BLUETOOTH_ENABLE = no # Enable Bluetooth AUDIO_ENABLE = no # Audio output VELOCIKEY_ENABLE = yes -SPLIT_KEYBOARD = yes \ No newline at end of file +SPLIT_KEYBOARD = yes diff --git a/keyboards/helix/rev2/keymaps/default/rules.mk b/keyboards/helix/rev2/keymaps/default/rules.mk index 206e836ec0..c16f3e2b87 100644 --- a/keyboards/helix/rev2/keymaps/default/rules.mk +++ b/keyboards/helix/rev2/keymaps/default/rules.mk @@ -5,7 +5,7 @@ # See TOP/keyboards/helix/rules.mk for a list of options that can be set. # See TOP/docs/config_options.md for more information. # -LTO_ENABLE = no # if firmware size over limit, try this option +LTO_ENABLE = yes # if firmware size over limit, try this option # Helix Spacific Build Options # you can uncomment and edit follows 7 Variables diff --git a/keyboards/keebio/iris/rev2/rules.mk b/keyboards/keebio/iris/rev2/rules.mk index 765fa7f07a..197bad4764 100644 --- a/keyboards/keebio/iris/rev2/rules.mk +++ b/keyboards/keebio/iris/rev2/rules.mk @@ -32,3 +32,5 @@ RGBLIGHT_ENABLE = yes # Enable WS2812 RGB underlight. SLEEP_LED_ENABLE = no # Breathing sleep LED during USB suspend SPLIT_KEYBOARD = yes + +LTO_ENABLE = yes \ No newline at end of file diff --git a/keyboards/keebio/iris/rev3/rules.mk b/keyboards/keebio/iris/rev3/rules.mk index ea92bc98f5..b64eab5a4b 100644 --- a/keyboards/keebio/iris/rev3/rules.mk +++ b/keyboards/keebio/iris/rev3/rules.mk @@ -33,3 +33,4 @@ SLEEP_LED_ENABLE = no # Breathing sleep LED during USB suspend SPLIT_KEYBOARD = yes ENCODER_ENABLE = yes +LTO_ENABLE = yes \ No newline at end of file diff --git a/keyboards/keebio/iris/rev4/rules.mk b/keyboards/keebio/iris/rev4/rules.mk index ea92bc98f5..9bf5688da0 100644 --- a/keyboards/keebio/iris/rev4/rules.mk +++ b/keyboards/keebio/iris/rev4/rules.mk @@ -33,3 +33,5 @@ SLEEP_LED_ENABLE = no # Breathing sleep LED during USB suspend SPLIT_KEYBOARD = yes ENCODER_ENABLE = yes + +LTO_ENABLE = yes \ No newline at end of file diff --git a/keyboards/keebio/quefrency/rules.mk b/keyboards/keebio/quefrency/rules.mk index 2c5ad0c36b..c674e10162 100644 --- a/keyboards/keebio/quefrency/rules.mk +++ b/keyboards/keebio/quefrency/rules.mk @@ -19,3 +19,4 @@ SLEEP_LED_ENABLE = no # Breathing sleep LED during USB suspend SPLIT_KEYBOARD = yes DEFAULT_FOLDER = keebio/quefrency/rev1 +LTO_ENABLE = yes diff --git a/keyboards/keebio/viterbi/rev2/rules.mk b/keyboards/keebio/viterbi/rev2/rules.mk index f95e7ae6a4..829d6a56e0 100644 --- a/keyboards/keebio/viterbi/rev2/rules.mk +++ b/keyboards/keebio/viterbi/rev2/rules.mk @@ -1,3 +1,5 @@ BACKLIGHT_ENABLE = yes LAYOUTS = ortho_5x14 + +LTO_ENABLE = yes \ No newline at end of file diff --git a/keyboards/keebio/viterbi/rules.mk b/keyboards/keebio/viterbi/rules.mk index 0b746d1720..92576d33e8 100644 --- a/keyboards/keebio/viterbi/rules.mk +++ b/keyboards/keebio/viterbi/rules.mk @@ -19,16 +19,16 @@ BOOTMAGIC_ENABLE = no # Virtual DIP switch configuration MOUSEKEY_ENABLE = yes # Mouse keys EXTRAKEY_ENABLE = yes # Audio control and System control CONSOLE_ENABLE = no # Console for debug -COMMAND_ENABLE = yes # Commands for debug and configuration +COMMAND_ENABLE = no # Commands for debug and configuration NKRO_ENABLE = no # Nkey Rollover - if this doesn't work, see here: https://github.com/tmk/tmk_keyboard/wiki/FAQ#nkro-doesnt-work -BACKLIGHT_ENABLE = no # Enable keyboard backlight functionality +BACKLIGHT_ENABLE = no # Enable keyboard backlight functionality MIDI_ENABLE = no # MIDI controls AUDIO_ENABLE = no # Audio output on port C6 UNICODE_ENABLE = no # Unicode BLUETOOTH_ENABLE = no # Enable Bluetooth with the Adafruit EZ-Key HID -RGBLIGHT_ENABLE = no # Enable WS2812 RGB underlight. +RGBLIGHT_ENABLE = no # Enable WS2812 RGB underlight. # Do not enable SLEEP_LED_ENABLE. it uses the same timer as BACKLIGHT_ENABLE -SLEEP_LED_ENABLE = no # Breathing sleep LED during USB suspend +SLEEP_LED_ENABLE = no # Breathing sleep LED during USB suspend SPLIT_KEYBOARD = yes diff --git a/quantum/split_common/matrix.c b/quantum/split_common/matrix.c index 2cf7b70582..56d91b07fe 100644 --- a/quantum/split_common/matrix.c +++ b/quantum/split_common/matrix.c @@ -23,9 +23,11 @@ along with this program. If not, see . #include "quantum.h" #include "split_util.h" #include "config.h" -#include "transport.h" +#include "transactions.h" -#define ERROR_DISCONNECT_COUNT 5 +#ifndef ERROR_DISCONNECT_COUNT +# define ERROR_DISCONNECT_COUNT 5 +#endif // ERROR_DISCONNECT_COUNT #define ROWS_PER_HAND (MATRIX_ROWS / 2) diff --git a/quantum/split_common/post_config.h b/quantum/split_common/post_config.h index 4ae1d52732..a4c0a1956b 100644 --- a/quantum/split_common/post_config.h +++ b/quantum/split_common/post_config.h @@ -7,13 +7,4 @@ # ifndef F_SCL # define F_SCL 100000UL // SCL frequency # endif - -#else // use serial -// When using serial, the user must define RGBLIGHT_SPLIT explicitly -// in config.h as needed. -// see quantum/rgblight_post_config.h -# if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) -// When using serial and RGBLIGHT_SPLIT need separate transaction -# define SERIAL_USE_MULTI_TRANSACTION -# endif #endif diff --git a/quantum/split_common/transaction_id_define.h b/quantum/split_common/transaction_id_define.h new file mode 100644 index 0000000000..464c73478a --- /dev/null +++ b/quantum/split_common/transaction_id_define.h @@ -0,0 +1,94 @@ +/* Copyright 2021 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 . + */ + +#pragma once + +enum serial_transaction_id { +#ifdef USE_I2C + I2C_EXECUTE_CALLBACK, +#endif // USE_I2C + + GET_SLAVE_MATRIX_CHECKSUM, + GET_SLAVE_MATRIX_DATA, + +#ifdef SPLIT_TRANSPORT_MIRROR + PUT_MASTER_MATRIX, +#endif // SPLIT_TRANSPORT_MIRROR + +#ifdef ENCODER_ENABLE + GET_ENCODERS_CHECKSUM, + GET_ENCODERS_DATA, +#endif // ENCODER_ENABLE + +#ifndef DISABLE_SYNC_TIMER + PUT_SYNC_TIMER, +#endif // DISABLE_SYNC_TIMER + +#if !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE) + PUT_LAYER_STATE, + PUT_DEFAULT_LAYER_STATE, +#endif // !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE) + +#ifdef SPLIT_LED_STATE_ENABLE + PUT_LED_STATE, +#endif // SPLIT_LED_STATE_ENABLE + +#ifdef SPLIT_MODS_ENABLE + PUT_MODS, +#endif // SPLIT_MODS_ENABLE + +#ifdef BACKLIGHT_ENABLE + PUT_BACKLIGHT, +#endif // BACKLIGHT_ENABLE + +#if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) + PUT_RGBLIGHT, +#endif // defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) + +#if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) + PUT_LED_MATRIX, +#endif // defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) + +#if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT) + PUT_RGB_MATRIX, +#endif // defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) + +#if defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE) + PUT_WPM, +#endif // defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE) + +#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) + PUT_RPC_INFO, + PUT_RPC_REQ_DATA, + EXECUTE_RPC, + GET_RPC_RESP_DATA, +#endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) + +// keyboard-specific +#ifdef SPLIT_TRANSACTION_IDS_KB + SPLIT_TRANSACTION_IDS_KB, +#endif // SPLIT_TRANSACTION_IDS_KB + +// user/keymap-specific +#ifdef SPLIT_TRANSACTION_IDS_USER + SPLIT_TRANSACTION_IDS_USER, +#endif // SPLIT_TRANSACTION_IDS_USER + + NUM_TOTAL_TRANSACTIONS +}; + +// Ensure we only use 5 bits for transaction +_Static_assert(NUM_TOTAL_TRANSACTIONS <= (1 << 5), "Max number of usable transactions exceeded"); diff --git a/quantum/split_common/transactions.c b/quantum/split_common/transactions.c new file mode 100644 index 0000000000..99a8623b3c --- /dev/null +++ b/quantum/split_common/transactions.c @@ -0,0 +1,670 @@ +/* Copyright 2021 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 . + */ + +#include +#include + +#include "debug.h" +#include "matrix.h" +#include "quantum.h" +#include "transactions.h" +#include "transport.h" +#include "transaction_id_define.h" + +#define SYNC_TIMER_OFFSET 2 + +#ifndef FORCED_SYNC_THROTTLE_MS +# define FORCED_SYNC_THROTTLE_MS 100 +#endif // FORCED_SYNC_THROTTLE_MS + +#define sizeof_member(type, member) sizeof(((type *)NULL)->member) + +#define trans_initiator2target_initializer_cb(member, cb) \ + { &dummy, sizeof_member(split_shared_memory_t, member), offsetof(split_shared_memory_t, member), 0, 0, cb } +#define trans_initiator2target_initializer(member) trans_initiator2target_initializer_cb(member, NULL) + +#define trans_target2initiator_initializer_cb(member, cb) \ + { &dummy, 0, 0, sizeof_member(split_shared_memory_t, member), offsetof(split_shared_memory_t, member), cb } +#define trans_target2initiator_initializer(member) trans_target2initiator_initializer_cb(member, NULL) + +#define transport_write(id, data, length) transport_execute_transaction(id, data, length, NULL, 0) +#define transport_read(id, data, length) transport_execute_transaction(id, NULL, 0, data, length) + +static uint8_t crc8(const void *data, size_t len) { + const uint8_t *p = (const uint8_t *)data; + uint8_t crc = 0xff; + size_t i, j; + for (i = 0; i < len; i++) { + crc ^= p[i]; + for (j = 0; j < 8; j++) { + if ((crc & 0x80) != 0) + crc = (uint8_t)((crc << 1) ^ 0x31); + else + crc <<= 1; + } + } + return crc; +} + +#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) +// Forward-declare the RPC callback handlers +void slave_rpc_info_callback(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer); +void slave_rpc_exec_callback(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer); +#endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) + +//////////////////////////////////////////////////// +// Helpers + +bool transaction_handler_master(bool okay, matrix_row_t master_matrix[], matrix_row_t slave_matrix[], const char *prefix, bool (*handler)(matrix_row_t master_matrix[], matrix_row_t slave_matrix[])) { + if (okay) { + bool this_okay = true; + for (int iter = 1; iter <= 10; ++iter) { + if (!this_okay) { + for (int i = 0; i < iter * iter; ++i) { + wait_us(10); + } + } + ATOMIC_BLOCK_FORCEON { this_okay = handler(master_matrix, slave_matrix); }; + if (this_okay) break; + } + okay &= this_okay; + if (!okay) { + dprintf("Failed to execute %s\n", prefix); + } + } + return okay; +} + +#define TRANSACTION_HANDLER_MASTER(prefix) \ + do { \ + okay &= transaction_handler_master(okay, master_matrix, slave_matrix, #prefix, &prefix##_master); \ + } while (0) + +#define TRANSACTION_HANDLER_SLAVE(prefix) \ + do { \ + ATOMIC_BLOCK_FORCEON { prefix##_slave(master_matrix, slave_matrix); }; \ + } while (0) + +inline static bool read_if_checksum_mismatch(int8_t trans_id_checksum, int8_t trans_id_retrieve, uint32_t *last_update, void *destination, const void *equiv_shmem, size_t length) { + uint8_t curr_checksum; + bool okay = transport_read(trans_id_checksum, &curr_checksum, sizeof(curr_checksum)); + if (okay && (timer_elapsed32(*last_update) >= FORCED_SYNC_THROTTLE_MS || curr_checksum != crc8(equiv_shmem, length))) { + okay &= transport_read(trans_id_retrieve, destination, length); + okay &= curr_checksum == crc8(equiv_shmem, length); + if (okay) { + *last_update = timer_read32(); + } + } else { + memcpy(destination, equiv_shmem, length); + } + return okay; +} + +inline static bool send_if_condition(int8_t trans_id, uint32_t *last_update, bool condition, void *source, size_t length) { + bool okay = true; + if (timer_elapsed32(*last_update) >= FORCED_SYNC_THROTTLE_MS || condition) { + okay &= transport_write(trans_id, source, length); + if (okay) { + *last_update = timer_read32(); + } + } + return okay; +} + +inline static bool send_if_data_mismatch(int8_t trans_id, uint32_t *last_update, void *source, const void *equiv_shmem, size_t length) { + // Just run a memcmp to compare the source and equivalent shmem location + return send_if_condition(trans_id, last_update, (memcmp(source, equiv_shmem, length) != 0), source, length); +} + +//////////////////////////////////////////////////// +// Slave matrix + +static bool slave_matrix_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + static uint32_t last_update = 0; + static matrix_row_t last_matrix[(MATRIX_ROWS) / 2] = {0}; // last successfully-read matrix, so we can replicate if there are checksum errors + matrix_row_t temp_matrix[(MATRIX_ROWS) / 2]; // holding area while we test whether or not checksum is correct + + bool okay = read_if_checksum_mismatch(GET_SLAVE_MATRIX_CHECKSUM, GET_SLAVE_MATRIX_DATA, &last_update, temp_matrix, split_shmem->smatrix.matrix, sizeof(split_shmem->smatrix.matrix)); + if (okay) { + // Checksum matches the received data, save as the last matrix state + memcpy(last_matrix, temp_matrix, sizeof(temp_matrix)); + } + // Copy out the last-known-good matrix state to the slave matrix + memcpy(slave_matrix, last_matrix, sizeof(last_matrix)); + return okay; +} + +static void slave_matrix_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + memcpy(split_shmem->smatrix.matrix, slave_matrix, sizeof(split_shmem->smatrix.matrix)); + split_shmem->smatrix.checksum = crc8(split_shmem->smatrix.matrix, sizeof(split_shmem->smatrix.matrix)); +} + +// clang-format off +#define TRANSACTIONS_SLAVE_MATRIX_MASTER() TRANSACTION_HANDLER_MASTER(slave_matrix_handlers) +#define TRANSACTIONS_SLAVE_MATRIX_SLAVE() TRANSACTION_HANDLER_SLAVE(slave_matrix_handlers) +#define TRANSACTIONS_SLAVE_MATRIX_REGISTRATIONS \ + [GET_SLAVE_MATRIX_CHECKSUM] = trans_target2initiator_initializer(smatrix.checksum), \ + [GET_SLAVE_MATRIX_DATA] = trans_target2initiator_initializer(smatrix.matrix), +// clang-format on + +//////////////////////////////////////////////////// +// Master matrix + +#ifdef SPLIT_TRANSPORT_MIRROR + +static bool master_matrix_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + static uint32_t last_update = 0; + return send_if_data_mismatch(PUT_MASTER_MATRIX, &last_update, master_matrix, split_shmem->mmatrix.matrix, sizeof(split_shmem->mmatrix.matrix)); +} + +static void master_matrix_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + // Always copy to the master matrix + memcpy(master_matrix, split_shmem->mmatrix.matrix, sizeof(split_shmem->mmatrix.matrix)); +} + +# define TRANSACTIONS_MASTER_MATRIX_MASTER() TRANSACTION_HANDLER_MASTER(master_matrix_handlers) +# define TRANSACTIONS_MASTER_MATRIX_SLAVE() TRANSACTION_HANDLER_SLAVE(master_matrix_handlers) +# define TRANSACTIONS_MASTER_MATRIX_REGISTRATIONS [PUT_MASTER_MATRIX] = trans_initiator2target_initializer(mmatrix.matrix), + +#else // SPLIT_TRANSPORT_MIRROR + +# define TRANSACTIONS_MASTER_MATRIX_MASTER() +# define TRANSACTIONS_MASTER_MATRIX_SLAVE() +# define TRANSACTIONS_MASTER_MATRIX_REGISTRATIONS + +#endif // SPLIT_TRANSPORT_MIRROR + +//////////////////////////////////////////////////// +// Encoders + +#ifdef ENCODER_ENABLE + +static bool encoder_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + static uint32_t last_update = 0; + uint8_t temp_state[NUMBER_OF_ENCODERS]; + + bool okay = read_if_checksum_mismatch(GET_ENCODERS_CHECKSUM, GET_ENCODERS_DATA, &last_update, temp_state, split_shmem->encoders.state, sizeof(temp_state)); + if (okay) encoder_update_raw(temp_state); + return okay; +} + +static void encoder_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + uint8_t encoder_state[NUMBER_OF_ENCODERS]; + encoder_state_raw(encoder_state); + // Always prepare the encoder state for read. + memcpy(split_shmem->encoders.state, encoder_state, sizeof(encoder_state)); + // Now update the checksum given that the encoders has been written to + split_shmem->encoders.checksum = crc8(encoder_state, sizeof(encoder_state)); +} + +// clang-format off +# define TRANSACTIONS_ENCODERS_MASTER() TRANSACTION_HANDLER_MASTER(encoder_handlers) +# define TRANSACTIONS_ENCODERS_SLAVE() TRANSACTION_HANDLER_SLAVE(encoder_handlers) +# define TRANSACTIONS_ENCODERS_REGISTRATIONS \ + [GET_ENCODERS_CHECKSUM] = trans_target2initiator_initializer(encoders.checksum), \ + [GET_ENCODERS_DATA] = trans_target2initiator_initializer(encoders.state), +// clang-format on + +#else // ENCODER_ENABLE + +# define TRANSACTIONS_ENCODERS_MASTER() +# define TRANSACTIONS_ENCODERS_SLAVE() +# define TRANSACTIONS_ENCODERS_REGISTRATIONS + +#endif // ENCODER_ENABLE + +//////////////////////////////////////////////////// +// Sync timer + +#ifndef DISABLE_SYNC_TIMER + +static bool sync_timer_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + static uint32_t last_update = 0; + + bool okay = true; + if (timer_elapsed32(last_update) >= FORCED_SYNC_THROTTLE_MS) { + uint32_t sync_timer = sync_timer_read32() + SYNC_TIMER_OFFSET; + okay &= transport_write(PUT_SYNC_TIMER, &sync_timer, sizeof(sync_timer)); + if (okay) { + last_update = timer_read32(); + } + } + return okay; +} + +static void sync_timer_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + static uint32_t last_sync_timer = 0; + if (last_sync_timer != split_shmem->sync_timer) { + last_sync_timer = split_shmem->sync_timer; + sync_timer_update(last_sync_timer); + } +} + +# define TRANSACTIONS_SYNC_TIMER_MASTER() TRANSACTION_HANDLER_MASTER(sync_timer_handlers) +# define TRANSACTIONS_SYNC_TIMER_SLAVE() TRANSACTION_HANDLER_SLAVE(sync_timer_handlers) +# define TRANSACTIONS_SYNC_TIMER_REGISTRATIONS [PUT_SYNC_TIMER] = trans_initiator2target_initializer(sync_timer), + +#else // DISABLE_SYNC_TIMER + +# define TRANSACTIONS_SYNC_TIMER_MASTER() +# define TRANSACTIONS_SYNC_TIMER_SLAVE() +# define TRANSACTIONS_SYNC_TIMER_REGISTRATIONS + +#endif // DISABLE_SYNC_TIMER + +//////////////////////////////////////////////////// +// Layer state + +#if !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE) + +static bool layer_state_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + static uint32_t last_layer_state_update = 0; + static uint32_t last_default_layer_state_update = 0; + + bool okay = send_if_condition(PUT_LAYER_STATE, &last_layer_state_update, (layer_state != split_shmem->layers.layer_state), &layer_state, sizeof(layer_state)); + if (okay) { + okay &= send_if_condition(PUT_DEFAULT_LAYER_STATE, &last_default_layer_state_update, (default_layer_state != split_shmem->layers.default_layer_state), &default_layer_state, sizeof(default_layer_state)); + } + return okay; +} + +static void layer_state_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + layer_state = split_shmem->layers.layer_state; + default_layer_state = split_shmem->layers.default_layer_state; +} + +// clang-format off +# define TRANSACTIONS_LAYER_STATE_MASTER() TRANSACTION_HANDLER_MASTER(layer_state_handlers) +# define TRANSACTIONS_LAYER_STATE_SLAVE() TRANSACTION_HANDLER_SLAVE(layer_state_handlers) +# define TRANSACTIONS_LAYER_STATE_REGISTRATIONS \ + [PUT_LAYER_STATE] = trans_initiator2target_initializer(layers.layer_state), \ + [PUT_DEFAULT_LAYER_STATE] = trans_initiator2target_initializer(layers.default_layer_state), +// clang-format on + +#else // !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE) + +# define TRANSACTIONS_LAYER_STATE_MASTER() +# define TRANSACTIONS_LAYER_STATE_SLAVE() +# define TRANSACTIONS_LAYER_STATE_REGISTRATIONS + +#endif // !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE) + +//////////////////////////////////////////////////// +// LED state + +#ifdef SPLIT_LED_STATE_ENABLE + +static bool led_state_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + static uint32_t last_update = 0; + uint8_t led_state = host_keyboard_leds(); + return send_if_data_mismatch(PUT_LED_STATE, &last_update, &led_state, &split_shmem->led_state, sizeof(led_state)); +} + +static void led_state_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + void set_split_host_keyboard_leds(uint8_t led_state); + set_split_host_keyboard_leds(split_shmem->led_state); +} + +# define TRANSACTIONS_LED_STATE_MASTER() TRANSACTION_HANDLER_MASTER(led_state_handlers) +# define TRANSACTIONS_LED_STATE_SLAVE() TRANSACTION_HANDLER_SLAVE(led_state_handlers) +# define TRANSACTIONS_LED_STATE_REGISTRATIONS [PUT_LED_STATE] = trans_initiator2target_initializer(led_state), + +#else // SPLIT_LED_STATE_ENABLE + +# define TRANSACTIONS_LED_STATE_MASTER() +# define TRANSACTIONS_LED_STATE_SLAVE() +# define TRANSACTIONS_LED_STATE_REGISTRATIONS + +#endif // SPLIT_LED_STATE_ENABLE + +//////////////////////////////////////////////////// +// Mods + +#ifdef SPLIT_MODS_ENABLE + +static bool mods_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + static uint32_t last_update = 0; + bool mods_need_sync = timer_elapsed32(last_update) >= FORCED_SYNC_THROTTLE_MS; + split_mods_sync_t new_mods; + new_mods.real_mods = get_mods(); + if (!mods_need_sync && new_mods.real_mods != split_shmem->mods.real_mods) { + mods_need_sync = true; + } + + new_mods.weak_mods = get_weak_mods(); + if (!mods_need_sync && new_mods.weak_mods != split_shmem->mods.weak_mods) { + mods_need_sync = true; + } + +# ifndef NO_ACTION_ONESHOT + new_mods.oneshot_mods = get_oneshot_mods(); + if (!mods_need_sync && new_mods.oneshot_mods != split_shmem->mods.oneshot_mods) { + mods_need_sync = true; + } +# endif // NO_ACTION_ONESHOT + + bool okay = true; + if (mods_need_sync) { + okay &= transport_write(PUT_MODS, &new_mods, sizeof(new_mods)); + if (okay) { + last_update = timer_read32(); + } + } + + return okay; +} + +static void mods_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + set_mods(split_shmem->mods.real_mods); + set_weak_mods(split_shmem->mods.weak_mods); +# ifndef NO_ACTION_ONESHOT + set_oneshot_mods(split_shmem->mods.oneshot_mods); +# endif +} + +# define TRANSACTIONS_MODS_MASTER() TRANSACTION_HANDLER_MASTER(mods_handlers) +# define TRANSACTIONS_MODS_SLAVE() TRANSACTION_HANDLER_SLAVE(mods_handlers) +# define TRANSACTIONS_MODS_REGISTRATIONS [PUT_MODS] = trans_initiator2target_initializer(mods), + +#else // SPLIT_MODS_ENABLE + +# define TRANSACTIONS_MODS_MASTER() +# define TRANSACTIONS_MODS_SLAVE() +# define TRANSACTIONS_MODS_REGISTRATIONS + +#endif // SPLIT_MODS_ENABLE + +//////////////////////////////////////////////////// +// Backlight + +#ifdef BACKLIGHT_ENABLE + +static bool backlight_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + static uint32_t last_update = 0; + uint8_t level = is_backlight_enabled() ? get_backlight_level() : 0; + return send_if_condition(PUT_BACKLIGHT, &last_update, (level != split_shmem->backlight_level), &level, sizeof(level)); +} + +static void backlight_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { backlight_set(split_shmem->backlight_level); } + +# define TRANSACTIONS_BACKLIGHT_MASTER() TRANSACTION_HANDLER_MASTER(backlight_handlers) +# define TRANSACTIONS_BACKLIGHT_SLAVE() TRANSACTION_HANDLER_SLAVE(backlight_handlers) +# define TRANSACTIONS_BACKLIGHT_REGISTRATIONS [PUT_BACKLIGHT] = trans_initiator2target_initializer(backlight_level), + +#else // BACKLIGHT_ENABLE + +# define TRANSACTIONS_BACKLIGHT_MASTER() +# define TRANSACTIONS_BACKLIGHT_SLAVE() +# define TRANSACTIONS_BACKLIGHT_REGISTRATIONS + +#endif // BACKLIGHT_ENABLE + +//////////////////////////////////////////////////// +// RGBLIGHT + +#if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) + +static bool rgblight_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + static uint32_t last_update = 0; + rgblight_syncinfo_t rgblight_sync; + rgblight_get_syncinfo(&rgblight_sync); + if (send_if_condition(PUT_RGBLIGHT, &last_update, (rgblight_sync.status.change_flags != 0), &rgblight_sync, sizeof(rgblight_sync))) { + rgblight_clear_change_flags(); + } else { + return false; + } + return true; +} + +static void rgblight_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + // Update the RGB with the new data + if (split_shmem->rgblight_sync.status.change_flags != 0) { + rgblight_update_sync(&split_shmem->rgblight_sync, false); + split_shmem->rgblight_sync.status.change_flags = 0; + } +} + +# define TRANSACTIONS_RGBLIGHT_MASTER() TRANSACTION_HANDLER_MASTER(rgblight_handlers) +# define TRANSACTIONS_RGBLIGHT_SLAVE() TRANSACTION_HANDLER_SLAVE(rgblight_handlers) +# define TRANSACTIONS_RGBLIGHT_REGISTRATIONS [PUT_RGBLIGHT] = trans_initiator2target_initializer(rgblight_sync), + +#else // defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) + +# define TRANSACTIONS_RGBLIGHT_MASTER() +# define TRANSACTIONS_RGBLIGHT_SLAVE() +# define TRANSACTIONS_RGBLIGHT_REGISTRATIONS + +#endif // defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) + +//////////////////////////////////////////////////// +// LED Matrix + +#if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) + +static bool led_matrix_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + static uint32_t last_update = 0; + led_matrix_sync_t led_matrix_sync; + memcpy(&led_matrix_sync.led_matrix, &led_matrix_eeconfig, sizeof(led_eeconfig_t)); + led_matrix_sync.led_suspend_state = led_matrix_get_suspend_state(); + return send_if_data_mismatch(PUT_LED_MATRIX, &last_update, &led_matrix_sync, &split_shmem->led_matrix_sync, sizeof(led_matrix_sync)); +} + +static void led_matrix_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + memcpy(&led_matrix_eeconfig, &split_shmem->led_matrix_sync.led_matrix, sizeof(led_eeconfig_t)); + led_matrix_set_suspend_state(split_shmem->led_matrix_sync.led_suspend_state); +} + +# define TRANSACTIONS_LED_MATRIX_MASTER() TRANSACTION_HANDLER_MASTER(led_matrix_handlers) +# define TRANSACTIONS_LED_MATRIX_SLAVE() TRANSACTION_HANDLER_SLAVE(led_matrix_handlers) +# define TRANSACTIONS_LED_MATRIX_REGISTRATIONS [PUT_LED_MATRIX] = trans_initiator2target_initializer(led_matrix_sync), + +#else // defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) + +# define TRANSACTIONS_LED_MATRIX_MASTER() +# define TRANSACTIONS_LED_MATRIX_SLAVE() +# define TRANSACTIONS_LED_MATRIX_REGISTRATIONS + +#endif // defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) + +//////////////////////////////////////////////////// +// RGB Matrix + +#if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT) + +static bool rgb_matrix_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + static uint32_t last_update = 0; + rgb_matrix_sync_t rgb_matrix_sync; + memcpy(&rgb_matrix_sync.rgb_matrix, &rgb_matrix_config, sizeof(rgb_config_t)); + rgb_matrix_sync.rgb_suspend_state = rgb_matrix_get_suspend_state(); + return send_if_data_mismatch(PUT_RGB_MATRIX, &last_update, &rgb_matrix_sync, &split_shmem->rgb_matrix_sync, sizeof(rgb_matrix_sync)); +} + +static void rgb_matrix_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + memcpy(&rgb_matrix_config, &split_shmem->rgb_matrix_sync.rgb_matrix, sizeof(rgb_config_t)); + rgb_matrix_set_suspend_state(split_shmem->rgb_matrix_sync.rgb_suspend_state); +} + +# define TRANSACTIONS_RGB_MATRIX_MASTER() TRANSACTION_HANDLER_MASTER(rgb_matrix_handlers) +# define TRANSACTIONS_RGB_MATRIX_SLAVE() TRANSACTION_HANDLER_SLAVE(rgb_matrix_handlers) +# define TRANSACTIONS_RGB_MATRIX_REGISTRATIONS [PUT_RGB_MATRIX] = trans_initiator2target_initializer(rgb_matrix_sync), + +#else // defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT) + +# define TRANSACTIONS_RGB_MATRIX_MASTER() +# define TRANSACTIONS_RGB_MATRIX_SLAVE() +# define TRANSACTIONS_RGB_MATRIX_REGISTRATIONS + +#endif // defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT) + +//////////////////////////////////////////////////// +// WPM + +#if defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE) + +static bool wpm_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + static uint32_t last_update = 0; + uint8_t current_wpm = get_current_wpm(); + return send_if_condition(PUT_WPM, &last_update, (current_wpm != split_shmem->current_wpm), ¤t_wpm, sizeof(current_wpm)); +} + +static void wpm_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { set_current_wpm(split_shmem->current_wpm); } + +# define TRANSACTIONS_WPM_MASTER() TRANSACTION_HANDLER_MASTER(wpm_handlers) +# define TRANSACTIONS_WPM_SLAVE() TRANSACTION_HANDLER_SLAVE(wpm_handlers) +# define TRANSACTIONS_WPM_REGISTRATIONS [PUT_WPM] = trans_initiator2target_initializer(current_wpm), + +#else // defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE) + +# define TRANSACTIONS_WPM_MASTER() +# define TRANSACTIONS_WPM_SLAVE() +# define TRANSACTIONS_WPM_REGISTRATIONS + +#endif // defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE) + +//////////////////////////////////////////////////// + +uint8_t dummy; +split_transaction_desc_t split_transaction_table[NUM_TOTAL_TRANSACTIONS] = { + // Set defaults + [0 ...(NUM_TOTAL_TRANSACTIONS - 1)] = {NULL, 0, 0, 0, 0, 0}, + +#ifdef USE_I2C + [I2C_EXECUTE_CALLBACK] = trans_initiator2target_initializer(transaction_id), +#endif // USE_I2C + + // clang-format off + TRANSACTIONS_SLAVE_MATRIX_REGISTRATIONS + TRANSACTIONS_MASTER_MATRIX_REGISTRATIONS + TRANSACTIONS_ENCODERS_REGISTRATIONS + TRANSACTIONS_SYNC_TIMER_REGISTRATIONS + TRANSACTIONS_LAYER_STATE_REGISTRATIONS + TRANSACTIONS_LED_STATE_REGISTRATIONS + TRANSACTIONS_MODS_REGISTRATIONS + TRANSACTIONS_BACKLIGHT_REGISTRATIONS + TRANSACTIONS_RGBLIGHT_REGISTRATIONS + TRANSACTIONS_LED_MATRIX_REGISTRATIONS + TRANSACTIONS_RGB_MATRIX_REGISTRATIONS + TRANSACTIONS_WPM_REGISTRATIONS +// clang-format on + +#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) + [PUT_RPC_INFO] = trans_initiator2target_initializer_cb(rpc_info, slave_rpc_info_callback), + [PUT_RPC_REQ_DATA] = trans_initiator2target_initializer(rpc_m2s_buffer), + [EXECUTE_RPC] = trans_initiator2target_initializer_cb(rpc_info.transaction_id, slave_rpc_exec_callback), + [GET_RPC_RESP_DATA] = trans_target2initiator_initializer(rpc_s2m_buffer), +#endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) +}; + +bool transactions_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + bool okay = true; + TRANSACTIONS_SLAVE_MATRIX_MASTER(); + TRANSACTIONS_MASTER_MATRIX_MASTER(); + TRANSACTIONS_ENCODERS_MASTER(); + TRANSACTIONS_SYNC_TIMER_MASTER(); + TRANSACTIONS_LAYER_STATE_MASTER(); + TRANSACTIONS_LED_STATE_MASTER(); + TRANSACTIONS_MODS_MASTER(); + TRANSACTIONS_BACKLIGHT_MASTER(); + TRANSACTIONS_RGBLIGHT_MASTER(); + TRANSACTIONS_LED_MATRIX_MASTER(); + TRANSACTIONS_RGB_MATRIX_MASTER(); + TRANSACTIONS_WPM_MASTER(); + return okay; +} + +void transactions_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + TRANSACTIONS_SLAVE_MATRIX_SLAVE(); + TRANSACTIONS_MASTER_MATRIX_SLAVE(); + TRANSACTIONS_ENCODERS_SLAVE(); + TRANSACTIONS_SYNC_TIMER_SLAVE(); + TRANSACTIONS_LAYER_STATE_SLAVE(); + TRANSACTIONS_LED_STATE_SLAVE(); + TRANSACTIONS_MODS_SLAVE(); + TRANSACTIONS_BACKLIGHT_SLAVE(); + TRANSACTIONS_RGBLIGHT_SLAVE(); + TRANSACTIONS_LED_MATRIX_SLAVE(); + TRANSACTIONS_RGB_MATRIX_SLAVE(); + TRANSACTIONS_WPM_SLAVE(); +} + +#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) + +void transaction_register_rpc(int8_t transaction_id, slave_callback_t callback) { + // Prevent invoking RPC on QMK core sync data + if (transaction_id <= GET_RPC_RESP_DATA) return; + + // Set the callback + split_transaction_table[transaction_id].slave_callback = callback; + split_transaction_table[transaction_id].initiator2target_offset = offsetof(split_shared_memory_t, rpc_m2s_buffer); + split_transaction_table[transaction_id].target2initiator_offset = offsetof(split_shared_memory_t, rpc_s2m_buffer); +} + +bool transaction_rpc_exec(int8_t transaction_id, uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer) { + // Prevent invoking RPC on QMK core sync data + if (transaction_id <= GET_RPC_RESP_DATA) return false; + // Prevent sizing issues + if (initiator2target_buffer_size > RPC_M2S_BUFFER_SIZE) return false; + if (target2initiator_buffer_size > RPC_S2M_BUFFER_SIZE) return false; + + // Prepare the metadata block + rpc_sync_info_t info = {.transaction_id = transaction_id, .m2s_length = initiator2target_buffer_size, .s2m_length = target2initiator_buffer_size}; + + // Make sure the local side knows that we're not sending the full block of data + split_transaction_table[PUT_RPC_REQ_DATA].initiator2target_buffer_size = initiator2target_buffer_size; + split_transaction_table[GET_RPC_RESP_DATA].target2initiator_buffer_size = target2initiator_buffer_size; + + // Run through the sequence: + // * set the transaction ID and lengths + // * send the request data + // * execute RPC callback + // * retrieve the response data + if (!transport_write(PUT_RPC_INFO, &info, sizeof(info))) { + return false; + } + if (!transport_write(PUT_RPC_REQ_DATA, initiator2target_buffer, initiator2target_buffer_size)) { + return false; + } + if (!transport_write(EXECUTE_RPC, &transaction_id, sizeof(transaction_id))) { + return false; + } + if (!transport_read(GET_RPC_RESP_DATA, target2initiator_buffer, target2initiator_buffer_size)) { + return false; + } + return true; +} + +void slave_rpc_info_callback(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer) { + // The RPC info block contains the intended transaction ID, as well as the sizes for both inbound and outbound data. + // Ignore the args -- the `split_shmem` already has the info, we just need to act upon it. + // We must keep the `split_transaction_table` non-const, so that it is able to be modified at runtime. + + split_transaction_table[PUT_RPC_REQ_DATA].initiator2target_buffer_size = split_shmem->rpc_info.m2s_length; + split_transaction_table[GET_RPC_RESP_DATA].target2initiator_buffer_size = split_shmem->rpc_info.s2m_length; +} + +void slave_rpc_exec_callback(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer) { + // We can assume that the buffer lengths are correctly set, now, given that sequentially the rpc_info callback was already executed. + // Go through the rpc_info and execute _that_ transaction's callback, with the scratch buffers as inputs. + int8_t transaction_id = split_shmem->rpc_info.transaction_id; + if (transaction_id < NUM_TOTAL_TRANSACTIONS) { + split_transaction_desc_t *trans = &split_transaction_table[transaction_id]; + if (trans->slave_callback) { + trans->slave_callback(split_shmem->rpc_info.m2s_length, split_shmem->rpc_m2s_buffer, split_shmem->rpc_info.s2m_length, split_shmem->rpc_s2m_buffer); + } + } +} + +#endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) diff --git a/quantum/split_common/transactions.h b/quantum/split_common/transactions.h new file mode 100644 index 0000000000..4306ba1d87 --- /dev/null +++ b/quantum/split_common/transactions.h @@ -0,0 +1,54 @@ +/* Copyright 2021 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 . + */ + +#pragma once + +#include "stdint.h" +#include "stdbool.h" + +#include "matrix.h" +#include "transaction_id_define.h" +#include "transport.h" + +typedef void (*slave_callback_t)(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer); + +// Split transaction Descriptor +typedef struct _split_transaction_desc_t { + uint8_t * status; + uint8_t initiator2target_buffer_size; + uint16_t initiator2target_offset; + uint8_t target2initiator_buffer_size; + uint16_t target2initiator_offset; + slave_callback_t slave_callback; +} split_transaction_desc_t; + +// Forward declaration for the split transactions +extern split_transaction_desc_t split_transaction_table[NUM_TOTAL_TRANSACTIONS]; + +#define split_shmem_offset_ptr(offset) ((void *)(((uint8_t *)split_shmem) + (offset))) +#define split_trans_initiator2target_buffer(trans) (split_shmem_offset_ptr((trans)->initiator2target_offset)) +#define split_trans_target2initiator_buffer(trans) (split_shmem_offset_ptr((trans)->target2initiator_offset)) + +// returns false if valid data not received from slave +bool transactions_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]); +void transactions_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]); + +void transaction_register_rpc(int8_t transaction_id, slave_callback_t callback); + +bool transaction_rpc_exec(int8_t transaction_id, uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer); + +#define transaction_rpc_send(transaction_id, initiator2target_buffer_size, initiator2target_buffer) transaction_rpc_exec(transaction_id, initiator2target_buffer_size, initiator2target_buffer, 0, NULL) +#define transaction_rpc_recv(transaction_id, target2initiator_buffer_size, target2initiator_buffer) transaction_rpc_exec(transaction_id, 0, NULL, target2initiator_buffer_size, target2initiator_buffer) diff --git a/quantum/split_common/transport.c b/quantum/split_common/transport.c index 9ed0f7591b..a711ef85f0 100644 --- a/quantum/split_common/transport.c +++ b/quantum/split_common/transport.c @@ -1,452 +1,118 @@ -#include -#include - -#include "config.h" -#include "matrix.h" -#include "quantum.h" - -#define ROWS_PER_HAND (MATRIX_ROWS / 2) -#define SYNC_TIMER_OFFSET 2 - -#ifdef RGBLIGHT_ENABLE -# include "rgblight.h" -#endif - -#ifdef BACKLIGHT_ENABLE -# include "backlight.h" -#endif - -#ifdef ENCODER_ENABLE -# include "encoder.h" -static pin_t encoders_pad[] = ENCODERS_PAD_A; -# define NUMBER_OF_ENCODERS (sizeof(encoders_pad) / sizeof(pin_t)) -#endif - -#if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) -# include "led_matrix.h" -#endif -#if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT) -# include "rgb_matrix.h" -#endif - -#if defined(USE_I2C) +/* Copyright 2021 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 . + */ -# include "i2c_master.h" -# include "i2c_slave.h" - -typedef struct _I2C_slave_buffer_t { -# ifndef DISABLE_SYNC_TIMER - uint32_t sync_timer; -# endif -# ifdef SPLIT_TRANSPORT_MIRROR - matrix_row_t mmatrix[ROWS_PER_HAND]; -# endif - matrix_row_t smatrix[ROWS_PER_HAND]; -# ifdef SPLIT_MODS_ENABLE - uint8_t real_mods; - uint8_t weak_mods; -# ifndef NO_ACTION_ONESHOT - uint8_t oneshot_mods; -# endif -# endif -# ifdef BACKLIGHT_ENABLE - uint8_t backlight_level; -# endif -# if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) - rgblight_syncinfo_t rgblight_sync; -# endif -# ifdef ENCODER_ENABLE - uint8_t encoder_state[NUMBER_OF_ENCODERS]; -# endif -# ifdef WPM_ENABLE - uint8_t current_wpm; -# endif -# if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) - led_eeconfig_t led_matrix; - bool led_suspend_state; -# endif -# if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT) - rgb_config_t rgb_matrix; - bool rgb_suspend_state; -# endif -} I2C_slave_buffer_t; +#include +#include -static I2C_slave_buffer_t *const i2c_buffer = (I2C_slave_buffer_t *)i2c_slave_reg; +#include "transactions.h" +#include "transport.h" +#include "transaction_id_define.h" +#include "atomic_util.h" -# define I2C_SYNC_TIME_START offsetof(I2C_slave_buffer_t, sync_timer) -# define I2C_KEYMAP_MASTER_START offsetof(I2C_slave_buffer_t, mmatrix) -# define I2C_KEYMAP_SLAVE_START offsetof(I2C_slave_buffer_t, smatrix) -# define I2C_REAL_MODS_START offsetof(I2C_slave_buffer_t, real_mods) -# define I2C_WEAK_MODS_START offsetof(I2C_slave_buffer_t, weak_mods) -# define I2C_ONESHOT_MODS_START offsetof(I2C_slave_buffer_t, oneshot_mods) -# define I2C_BACKLIGHT_START offsetof(I2C_slave_buffer_t, backlight_level) -# define I2C_RGB_START offsetof(I2C_slave_buffer_t, rgblight_sync) -# define I2C_ENCODER_START offsetof(I2C_slave_buffer_t, encoder_state) -# define I2C_WPM_START offsetof(I2C_slave_buffer_t, current_wpm) -# define I2C_LED_MATRIX_START offsetof(I2C_slave_buffer_t, led_matrix) -# define I2C_LED_SUSPEND_START offsetof(I2C_slave_buffer_t, led_suspend_state) -# define I2C_RGB_MATRIX_START offsetof(I2C_slave_buffer_t, rgb_matrix) -# define I2C_RGB_SUSPEND_START offsetof(I2C_slave_buffer_t, rgb_suspend_state) +#ifdef USE_I2C -# define TIMEOUT 100 +# ifndef SLAVE_I2C_TIMEOUT +# define SLAVE_I2C_TIMEOUT 100 +# endif // SLAVE_I2C_TIMEOUT # ifndef SLAVE_I2C_ADDRESS # define SLAVE_I2C_ADDRESS 0x32 # endif -// Get rows from other half over i2c -bool transport_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { - i2c_readReg(SLAVE_I2C_ADDRESS, I2C_KEYMAP_SLAVE_START, (void *)slave_matrix, sizeof(i2c_buffer->smatrix), TIMEOUT); -# ifdef SPLIT_TRANSPORT_MIRROR - i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_KEYMAP_MASTER_START, (void *)master_matrix, sizeof(i2c_buffer->mmatrix), TIMEOUT); -# endif +# include "i2c_master.h" +# include "i2c_slave.h" - // write backlight info -# ifdef BACKLIGHT_ENABLE - uint8_t level = is_backlight_enabled() ? get_backlight_level() : 0; - if (level != i2c_buffer->backlight_level) { - if (i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_BACKLIGHT_START, (void *)&level, sizeof(level), TIMEOUT) >= 0) { - i2c_buffer->backlight_level = level; - } - } -# endif +// Ensure the I2C buffer has enough space +_Static_assert(sizeof(split_shared_memory_t) <= I2C_SLAVE_REG_COUNT, "split_shared_memory_t too large for I2C_SLAVE_REG_COUNT"); -# if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) - if (rgblight_get_change_flags()) { - rgblight_syncinfo_t rgblight_sync; - rgblight_get_syncinfo(&rgblight_sync); - if (i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_RGB_START, (void *)&rgblight_sync, sizeof(rgblight_sync), TIMEOUT) >= 0) { - rgblight_clear_change_flags(); - } - } -# endif +split_shared_memory_t *const split_shmem = (split_shared_memory_t *)i2c_slave_reg; -# ifdef ENCODER_ENABLE - i2c_readReg(SLAVE_I2C_ADDRESS, I2C_ENCODER_START, (void *)i2c_buffer->encoder_state, sizeof(i2c_buffer->encoder_state), TIMEOUT); - encoder_update_raw(i2c_buffer->encoder_state); -# endif +void transport_master_init(void) { i2c_init(); } +void transport_slave_init(void) { i2c_slave_init(SLAVE_I2C_ADDRESS); } -# ifdef WPM_ENABLE - uint8_t current_wpm = get_current_wpm(); - if (current_wpm != i2c_buffer->current_wpm) { - if (i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_WPM_START, (void *)¤t_wpm, sizeof(current_wpm), TIMEOUT) >= 0) { - i2c_buffer->current_wpm = current_wpm; - } +i2c_status_t transport_trigger_callback(int8_t id) { + // If there's no callback, indicate that we were successful + if (!split_transaction_table[id].slave_callback) { + return I2C_STATUS_SUCCESS; } -# endif -# ifdef SPLIT_MODS_ENABLE - uint8_t real_mods = get_mods(); - if (real_mods != i2c_buffer->real_mods) { - if (i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_REAL_MODS_START, (void *)&real_mods, sizeof(real_mods), TIMEOUT) >= 0) { - i2c_buffer->real_mods = real_mods; + // Kick off the "callback executor", now that data has been written to the slave + split_shmem->transaction_id = id; + split_transaction_desc_t *trans = &split_transaction_table[I2C_EXECUTE_CALLBACK]; + return i2c_writeReg(SLAVE_I2C_ADDRESS, trans->initiator2target_offset, split_trans_initiator2target_buffer(trans), trans->initiator2target_buffer_size, SLAVE_I2C_TIMEOUT); +} + +bool transport_execute_transaction(int8_t id, const void *initiator2target_buf, uint16_t initiator2target_length, void *target2initiator_buf, uint16_t target2initiator_length) { + i2c_status_t status; + split_transaction_desc_t *trans = &split_transaction_table[id]; + if (initiator2target_length > 0) { + size_t len = trans->initiator2target_buffer_size < initiator2target_length ? trans->initiator2target_buffer_size : initiator2target_length; + memcpy(split_trans_initiator2target_buffer(trans), initiator2target_buf, len); + if ((status = i2c_writeReg(SLAVE_I2C_ADDRESS, trans->initiator2target_offset, split_trans_initiator2target_buffer(trans), len, SLAVE_I2C_TIMEOUT)) < 0) { + return false; } } - uint8_t weak_mods = get_weak_mods(); - if (weak_mods != i2c_buffer->weak_mods) { - if (i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_WEAK_MODS_START, (void *)&weak_mods, sizeof(weak_mods), TIMEOUT) >= 0) { - i2c_buffer->weak_mods = weak_mods; - } + // If we need to execute a callback on the slave, do so + if ((status = transport_trigger_callback(id)) < 0) { + return false; } -# ifndef NO_ACTION_ONESHOT - uint8_t oneshot_mods = get_oneshot_mods(); - if (oneshot_mods != i2c_buffer->oneshot_mods) { - if (i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_ONESHOT_MODS_START, (void *)&oneshot_mods, sizeof(oneshot_mods), TIMEOUT) >= 0) { - i2c_buffer->oneshot_mods = oneshot_mods; + if (target2initiator_length > 0) { + size_t len = trans->target2initiator_buffer_size < target2initiator_length ? trans->target2initiator_buffer_size : target2initiator_length; + if ((status = i2c_readReg(SLAVE_I2C_ADDRESS, trans->target2initiator_offset, split_trans_target2initiator_buffer(trans), len, SLAVE_I2C_TIMEOUT)) < 0) { + return false; } + memcpy(target2initiator_buf, split_trans_target2initiator_buffer(trans), len); } -# endif -# endif -# if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) - i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_LED_MATRIX_START, (void *)led_matrix_eeconfig, sizeof(i2c_buffer->led_matrix), TIMEOUT); - bool suspend_state = led_matrix_get_suspend_state(); - i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_LED_SUSPEND_START, (void *)suspend_state, sizeof(i2c_buffer->led_suspend_state), TIMEOUT); -# endif -# if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT) - i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_RGB_MATRIX_START, (void *)rgb_matrix_config, sizeof(i2c_buffer->rgb_matrix), TIMEOUT); - bool suspend_state = rgb_matrix_get_suspend_state(); - i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_RGB_SUSPEND_START, (void *)suspend_state, sizeof(i2c_buffer->rgb_suspend_state), TIMEOUT); -# endif - -# ifndef DISABLE_SYNC_TIMER - i2c_buffer->sync_timer = sync_timer_read32() + SYNC_TIMER_OFFSET; - i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_SYNC_TIME_START, (void *)&i2c_buffer->sync_timer, sizeof(i2c_buffer->sync_timer), TIMEOUT); -# endif return true; } -void transport_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { -# ifndef DISABLE_SYNC_TIMER - sync_timer_update(i2c_buffer->sync_timer); -# endif - // Copy matrix to I2C buffer - memcpy((void *)i2c_buffer->smatrix, (void *)slave_matrix, sizeof(i2c_buffer->smatrix)); -# ifdef SPLIT_TRANSPORT_MIRROR - memcpy((void *)master_matrix, (void *)i2c_buffer->mmatrix, sizeof(i2c_buffer->mmatrix)); -# endif - -// Read Backlight Info -# ifdef BACKLIGHT_ENABLE - backlight_set(i2c_buffer->backlight_level); -# endif - -# if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) - // Update the RGB with the new data - if (i2c_buffer->rgblight_sync.status.change_flags != 0) { - rgblight_update_sync(&i2c_buffer->rgblight_sync, false); - i2c_buffer->rgblight_sync.status.change_flags = 0; - } -# endif - -# ifdef ENCODER_ENABLE - encoder_state_raw(i2c_buffer->encoder_state); -# endif - -# ifdef WPM_ENABLE - set_current_wpm(i2c_buffer->current_wpm); -# endif - -# ifdef SPLIT_MODS_ENABLE - set_mods(i2c_buffer->real_mods); - set_weak_mods(i2c_buffer->weak_mods); -# ifndef NO_ACTION_ONESHOT - set_oneshot_mods(i2c_buffer->oneshot_mods); -# endif -# endif - -# if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) - memcpy((void *)i2c_buffer->led_matrix, (void *)led_matrix_eeconfig, sizeof(i2c_buffer->led_matrix)); - led_matrix_set_suspend_state(i2c_buffer->led_suspend_state); -# endif -# if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT) - memcpy((void *)i2c_buffer->rgb_matrix, (void *)rgb_matrix_config, sizeof(i2c_buffer->rgb_matrix)); - rgb_matrix_set_suspend_state(i2c_buffer->rgb_suspend_state); -# endif -} - -void transport_master_init(void) { i2c_init(); } - -void transport_slave_init(void) { i2c_slave_init(SLAVE_I2C_ADDRESS); } - -#else // USE_SERIAL +#else // USE_I2C # include "serial.h" -typedef struct _Serial_s2m_buffer_t { - // TODO: if MATRIX_COLS > 8 change to uint8_t packed_matrix[] for pack/unpack - matrix_row_t smatrix[ROWS_PER_HAND]; - -# ifdef ENCODER_ENABLE - uint8_t encoder_state[NUMBER_OF_ENCODERS]; -# endif - -} Serial_s2m_buffer_t; - -typedef struct _Serial_m2s_buffer_t { -# ifdef SPLIT_MODS_ENABLE - uint8_t real_mods; - uint8_t weak_mods; -# ifndef NO_ACTION_ONESHOT - uint8_t oneshot_mods; -# endif -# endif -# ifndef DISABLE_SYNC_TIMER - uint32_t sync_timer; -# endif -# ifdef SPLIT_TRANSPORT_MIRROR - matrix_row_t mmatrix[ROWS_PER_HAND]; -# endif -# ifdef BACKLIGHT_ENABLE - uint8_t backlight_level; -# endif -# ifdef WPM_ENABLE - uint8_t current_wpm; -# endif -# if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) - led_eeconfig_t led_matrix; - bool led_suspend_state; -# endif -# if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT) - rgb_config_t rgb_matrix; - bool rgb_suspend_state; -# endif -} Serial_m2s_buffer_t; - -# if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) -// When MCUs on both sides drive their respective RGB LED chains, -// it is necessary to synchronize, so it is necessary to communicate RGB -// information. In that case, define RGBLIGHT_SPLIT with info on the number -// of LEDs on each half. -// -// Otherwise, if the master side MCU drives both sides RGB LED chains, -// there is no need to communicate. - -typedef struct _Serial_rgblight_t { - rgblight_syncinfo_t rgblight_sync; -} Serial_rgblight_t; +static split_shared_memory_t shared_memory; +split_shared_memory_t *const split_shmem = &shared_memory; -volatile Serial_rgblight_t serial_rgblight = {}; -uint8_t volatile status_rgblight = 0; -# endif - -volatile Serial_s2m_buffer_t serial_s2m_buffer = {}; -volatile Serial_m2s_buffer_t serial_m2s_buffer = {}; -uint8_t volatile status0 = 0; - -enum serial_transaction_id { - GET_SLAVE_MATRIX = 0, -# if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) - PUT_RGBLIGHT, -# endif -}; - -SSTD_t transactions[] = { - [GET_SLAVE_MATRIX] = - { - (uint8_t *)&status0, - sizeof(serial_m2s_buffer), - (uint8_t *)&serial_m2s_buffer, - sizeof(serial_s2m_buffer), - (uint8_t *)&serial_s2m_buffer, - }, -# if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) - [PUT_RGBLIGHT] = - { - (uint8_t *)&status_rgblight, sizeof(serial_rgblight), (uint8_t *)&serial_rgblight, 0, NULL // no slave to master transfer - }, -# endif -}; - -void transport_master_init(void) { soft_serial_initiator_init(transactions, TID_LIMIT(transactions)); } - -void transport_slave_init(void) { soft_serial_target_init(transactions, TID_LIMIT(transactions)); } +void transport_master_init(void) { soft_serial_initiator_init(); } +void transport_slave_init(void) { soft_serial_target_init(); } -# if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) - -// rgblight synchronization information communication. - -void transport_rgblight_master(void) { - if (rgblight_get_change_flags()) { - rgblight_get_syncinfo((rgblight_syncinfo_t *)&serial_rgblight.rgblight_sync); - if (soft_serial_transaction(PUT_RGBLIGHT) == TRANSACTION_END) { - rgblight_clear_change_flags(); - } - } -} - -void transport_rgblight_slave(void) { - if (status_rgblight == TRANSACTION_ACCEPTED) { - rgblight_update_sync((rgblight_syncinfo_t *)&serial_rgblight.rgblight_sync, false); - status_rgblight = TRANSACTION_END; +bool transport_execute_transaction(int8_t id, const void *initiator2target_buf, uint16_t initiator2target_length, void *target2initiator_buf, uint16_t target2initiator_length) { + split_transaction_desc_t *trans = &split_transaction_table[id]; + if (initiator2target_length > 0) { + size_t len = trans->initiator2target_buffer_size < initiator2target_length ? trans->initiator2target_buffer_size : initiator2target_length; + memcpy(split_trans_initiator2target_buffer(trans), initiator2target_buf, len); } -} -# else -# define transport_rgblight_master() -# define transport_rgblight_slave() -# endif - -bool transport_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { -# ifndef SERIAL_USE_MULTI_TRANSACTION - if (soft_serial_transaction() != TRANSACTION_END) { - return false; - } -# else - transport_rgblight_master(); - if (soft_serial_transaction(GET_SLAVE_MATRIX) != TRANSACTION_END) { + if (soft_serial_transaction(id) != TRANSACTION_END) { return false; } -# endif - // TODO: if MATRIX_COLS > 8 change to unpack() - for (int i = 0; i < ROWS_PER_HAND; ++i) { - slave_matrix[i] = serial_s2m_buffer.smatrix[i]; -# ifdef SPLIT_TRANSPORT_MIRROR - serial_m2s_buffer.mmatrix[i] = master_matrix[i]; -# endif + if (target2initiator_length > 0) { + size_t len = trans->target2initiator_buffer_size < target2initiator_length ? trans->target2initiator_buffer_size : target2initiator_length; + memcpy(target2initiator_buf, split_trans_target2initiator_buffer(trans), len); } -# ifdef BACKLIGHT_ENABLE - // Write backlight level for slave to read - serial_m2s_buffer.backlight_level = is_backlight_enabled() ? get_backlight_level() : 0; -# endif - -# ifdef ENCODER_ENABLE - encoder_update_raw((uint8_t *)serial_s2m_buffer.encoder_state); -# endif - -# ifdef WPM_ENABLE - // Write wpm to slave - serial_m2s_buffer.current_wpm = get_current_wpm(); -# endif - -# ifdef SPLIT_MODS_ENABLE - serial_m2s_buffer.real_mods = get_mods(); - serial_m2s_buffer.weak_mods = get_weak_mods(); -# ifndef NO_ACTION_ONESHOT - serial_m2s_buffer.oneshot_mods = get_oneshot_mods(); -# endif -# endif - -# if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) - serial_m2s_buffer.led_matrix = led_matrix_eeconfig; - serial_m2s_buffer.led_suspend_state = led_matrix_get_suspend_state(); -# endif -# if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT) - serial_m2s_buffer.rgb_matrix = rgb_matrix_config; - serial_m2s_buffer.rgb_suspend_state = rgb_matrix_get_suspend_state(); -# endif - -# ifndef DISABLE_SYNC_TIMER - serial_m2s_buffer.sync_timer = sync_timer_read32() + SYNC_TIMER_OFFSET; -# endif return true; } -void transport_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { - transport_rgblight_slave(); -# ifndef DISABLE_SYNC_TIMER - sync_timer_update(serial_m2s_buffer.sync_timer); -# endif - - // TODO: if MATRIX_COLS > 8 change to pack() - for (int i = 0; i < ROWS_PER_HAND; ++i) { - serial_s2m_buffer.smatrix[i] = slave_matrix[i]; -# ifdef SPLIT_TRANSPORT_MIRROR - master_matrix[i] = serial_m2s_buffer.mmatrix[i]; -# endif - } -# ifdef BACKLIGHT_ENABLE - backlight_set(serial_m2s_buffer.backlight_level); -# endif - -# ifdef ENCODER_ENABLE - encoder_state_raw((uint8_t *)serial_s2m_buffer.encoder_state); -# endif +#endif // USE_I2C -# ifdef WPM_ENABLE - set_current_wpm(serial_m2s_buffer.current_wpm); -# endif - -# ifdef SPLIT_MODS_ENABLE - set_mods(serial_m2s_buffer.real_mods); - set_weak_mods(serial_m2s_buffer.weak_mods); -# ifndef NO_ACTION_ONESHOT - set_oneshot_mods(serial_m2s_buffer.oneshot_mods); -# endif -# endif - -# if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) - led_matrix_eeconfig = serial_m2s_buffer.led_matrix; - led_matrix_set_suspend_state(serial_m2s_buffer.led_suspend_state); -# endif -# if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT) - rgb_matrix_config = serial_m2s_buffer.rgb_matrix; - rgb_matrix_set_suspend_state(serial_m2s_buffer.rgb_suspend_state); -# endif -} +bool transport_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { return transactions_master(master_matrix, slave_matrix); } -#endif +void transport_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { transactions_slave(master_matrix, slave_matrix); } \ No newline at end of file diff --git a/quantum/split_common/transport.h b/quantum/split_common/transport.h index a9f66301bf..2e07f6b25c 100644 --- a/quantum/split_common/transport.h +++ b/quantum/split_common/transport.h @@ -1,10 +1,175 @@ +/* Copyright 2021 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 . + */ + #pragma once +#include "stdint.h" +#include "stdbool.h" + +#include "progmem.h" +#include "action_layer.h" #include "matrix.h" +#ifndef RPC_M2S_BUFFER_SIZE +# define RPC_M2S_BUFFER_SIZE 32 +#endif // RPC_M2S_BUFFER_SIZE + +#ifndef RPC_S2M_BUFFER_SIZE +# define RPC_S2M_BUFFER_SIZE 32 +#endif // RPC_S2M_BUFFER_SIZE + void transport_master_init(void); void transport_slave_init(void); // returns false if valid data not received from slave bool transport_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]); void transport_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]); + +bool transport_execute_transaction(int8_t id, const void *initiator2target_buf, uint16_t initiator2target_length, void *target2initiator_buf, uint16_t target2initiator_length); + +#ifdef ENCODER_ENABLE +# include "encoder.h" +# define NUMBER_OF_ENCODERS (sizeof((pin_t[])ENCODERS_PAD_A) / sizeof(pin_t)) +#endif // ENCODER_ENABLE + +#ifdef BACKLIGHT_ENABLE +# include "backlight.h" +#endif // BACKLIGHT_ENABLE + +#ifdef RGBLIGHT_ENABLE +# include "rgblight.h" +#endif // RGBLIGHT_ENABLE + +typedef struct _split_slave_matrix_sync_t { + uint8_t checksum; + matrix_row_t matrix[(MATRIX_ROWS) / 2]; +} split_slave_matrix_sync_t; + +#ifdef SPLIT_TRANSPORT_MIRROR +typedef struct _split_master_matrix_sync_t { + matrix_row_t matrix[(MATRIX_ROWS) / 2]; +} split_master_matrix_sync_t; +#endif // SPLIT_TRANSPORT_MIRROR + +#ifdef ENCODER_ENABLE +typedef struct _split_slave_encoder_sync_t { + uint8_t checksum; + uint8_t state[NUMBER_OF_ENCODERS]; +} split_slave_encoder_sync_t; +#endif // ENCODER_ENABLE + +#if !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE) +typedef struct _split_layers_sync_t { + layer_state_t layer_state; + layer_state_t default_layer_state; +} split_layers_sync_t; +#endif // !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE) + +#if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) +# include "led_matrix.h" + +typedef struct _led_matrix_sync_t { + led_eeconfig_t led_matrix; + bool led_suspend_state; +} led_matrix_sync_t; +#endif // defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) + +#if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT) +# include "rgb_matrix.h" + +typedef struct _rgb_matrix_sync_t { + rgb_config_t rgb_matrix; + bool rgb_suspend_state; +} rgb_matrix_sync_t; +#endif // defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT) + +#ifdef SPLIT_MODS_ENABLE +typedef struct _split_mods_sync_t { + uint8_t real_mods; + uint8_t weak_mods; +# ifndef NO_ACTION_ONESHOT + uint8_t oneshot_mods; +# endif // NO_ACTION_ONESHOT +} split_mods_sync_t; +#endif // SPLIT_MODS_ENABLE + +#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) +typedef struct _rpc_sync_info_t { + int8_t transaction_id; + uint8_t m2s_length; + uint8_t s2m_length; +} rpc_sync_info_t; +#endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) + +typedef struct _split_shared_memory_t { +#ifdef USE_I2C + int8_t transaction_id; +#endif // USE_I2C + + split_slave_matrix_sync_t smatrix; + +#ifdef SPLIT_TRANSPORT_MIRROR + split_master_matrix_sync_t mmatrix; +#endif // SPLIT_TRANSPORT_MIRROR + +#ifdef ENCODER_ENABLE + split_slave_encoder_sync_t encoders; +#endif // ENCODER_ENABLE + +#ifndef DISABLE_SYNC_TIMER + uint32_t sync_timer; +#endif // DISABLE_SYNC_TIMER + +#if !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE) + split_layers_sync_t layers; +#endif // !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE) + +#ifdef SPLIT_LED_STATE_ENABLE + uint8_t led_state; +#endif // SPLIT_LED_STATE_ENABLE + +#ifdef SPLIT_MODS_ENABLE + split_mods_sync_t mods; +#endif // SPLIT_MODS_ENABLE + +#ifdef BACKLIGHT_ENABLE + uint8_t backlight_level; +#endif // BACKLIGHT_ENABLE + +#if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) + rgblight_syncinfo_t rgblight_sync; +#endif // defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) + +#if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) + led_matrix_sync_t led_matrix_sync; +#endif // defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) + +#if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT) + rgb_matrix_sync_t rgb_matrix_sync; +#endif // defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT) + +#if defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE) + uint8_t current_wpm; +#endif // defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE) + +#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) + rpc_sync_info_t rpc_info; + uint8_t rpc_m2s_buffer[RPC_M2S_BUFFER_SIZE]; + uint8_t rpc_s2m_buffer[RPC_S2M_BUFFER_SIZE]; +#endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) +} split_shared_memory_t; + +extern split_shared_memory_t *const split_shmem; \ No newline at end of file diff --git a/tmk_core/common/host.c b/tmk_core/common/host.c index e7d92cfac6..a8b391e893 100644 --- a/tmk_core/common/host.c +++ b/tmk_core/common/host.c @@ -17,6 +17,7 @@ along with this program. If not, see . #include //#include +#include "keyboard.h" #include "keycode.h" #include "host.h" #include "util.h" @@ -35,15 +36,20 @@ void host_set_driver(host_driver_t *d) { driver = d; } host_driver_t *host_get_driver(void) { return driver; } +#ifdef SPLIT_KEYBOARD +uint8_t split_led_state = 0; +void set_split_host_keyboard_leds(uint8_t led_state) { split_led_state = led_state; } +#endif + uint8_t host_keyboard_leds(void) { +#ifdef SPLIT_KEYBOARD + if (!is_keyboard_master()) return split_led_state; +#endif if (!driver) return 0; return (*driver->keyboard_leds)(); } -led_t host_keyboard_led_state(void) { - if (!driver) return (led_t){0}; - return (led_t)((*driver->keyboard_leds)()); -} +led_t host_keyboard_led_state(void) { return (led_t)host_keyboard_leds(); } /* send report */ void host_keyboard_send(report_keyboard_t *report) { -- cgit v1.2.3 From 52cfc9259b58a3a11a244fbe35c49c7dd1a9cae0 Mon Sep 17 00:00:00 2001 From: Jonas Gessner Date: Tue, 13 Jul 2021 19:13:51 +0200 Subject: [Feature] Key Overrides (#11422) --- common_features.mk | 5 + docs/_summary.md | 1 + docs/config_options.md | 4 + docs/feature_key_overrides.md | 229 +++++++++ docs/syllabus.md | 1 + docs/understanding_qmk.md | 1 + quantum/process_keycode/process_key_override.c | 518 +++++++++++++++++++++ quantum/process_keycode/process_key_override.h | 147 ++++++ .../process_keycode/process_key_override_private.h | 24 + quantum/quantum.c | 19 +- quantum/quantum.h | 4 + quantum/quantum_keycodes.h | 5 + show_options.mk | 1 + tmk_core/common/action_util.c | 28 ++ 14 files changed, 984 insertions(+), 3 deletions(-) create mode 100644 docs/feature_key_overrides.md create mode 100644 quantum/process_keycode/process_key_override.c create mode 100644 quantum/process_keycode/process_key_override.h create mode 100644 quantum/process_keycode/process_key_override_private.h (limited to 'docs/config_options.md') diff --git a/common_features.mk b/common_features.mk index 8080113efd..74b94ecd77 100644 --- a/common_features.mk +++ b/common_features.mk @@ -335,6 +335,11 @@ ifeq ($(strip $(PRINTING_ENABLE)), yes) SRC += $(TMK_DIR)/protocol/serial_uart.c endif +ifeq ($(strip $(KEY_OVERRIDE_ENABLE)), yes) + OPT_DEFS += -DKEY_OVERRIDE_ENABLE + SRC += $(QUANTUM_DIR)/process_keycode/process_key_override.c +endif + ifeq ($(strip $(SERIAL_LINK_ENABLE)), yes) SERIAL_SRC := $(wildcard $(SERIAL_PATH)/protocol/*.c) SERIAL_SRC += $(wildcard $(SERIAL_PATH)/system/*.c) diff --git a/docs/_summary.md b/docs/_summary.md index 4141e01e77..6c39aeda09 100644 --- a/docs/_summary.md +++ b/docs/_summary.md @@ -77,6 +77,7 @@ * [Combos](feature_combo.md) * [Debounce API](feature_debounce_type.md) * [Key Lock](feature_key_lock.md) + * [Key Overrides](feature_key_overrides.md) * [Layers](feature_layers.md) * [One Shot Keys](one_shot_keys.md) * [Pointing Device](feature_pointing_device.md) diff --git a/docs/config_options.md b/docs/config_options.md index 980195ac68..0c98b31010 100644 --- a/docs/config_options.md +++ b/docs/config_options.md @@ -195,6 +195,8 @@ If you define these options you will enable the associated feature, which may in * Sets the delay between `register_code` and `unregister_code`, if you're having issues with it registering properly (common on VUSB boards). The value is in milliseconds. * `#define TAP_HOLD_CAPS_DELAY 80` * Sets the delay for Tap Hold keys (`LT`, `MT`) when using `KC_CAPSLOCK` keycode, as this has some special handling on MacOS. The value is in milliseconds, and defaults to 80 ms if not defined. For macOS, you may want to set this to 200 or higher. +* `#define KEY_OVERRIDE_REPEAT_DELAY 500` + * Sets the key repeat interval for [key overrides](feature_key_overrides.md). ## RGB Light Configuration @@ -400,6 +402,8 @@ Use these to enable or disable building certain features. The more you have enab * USB N-Key Rollover - if this doesn't work, see here: https://github.com/tmk/tmk_keyboard/wiki/FAQ#nkro-doesnt-work * `AUDIO_ENABLE` * Enable the audio subsystem. +* `KEY_OVERRIDE_ENABLE` + * Enable the key override feature * `RGBLIGHT_ENABLE` * Enable keyboard underlight functionality * `LEADER_ENABLE` diff --git a/docs/feature_key_overrides.md b/docs/feature_key_overrides.md new file mode 100644 index 0000000000..861c4bef5d --- /dev/null +++ b/docs/feature_key_overrides.md @@ -0,0 +1,229 @@ +# Key Overrides + +Key overrides allow you to override modifier-key combinations to send a different modifier-key combination or perform completely custom actions. Don't want `shift` + `1` to type `!` on your computer? Use a key override to make your keyboard type something different when you press `shift` + `1`. The general behavior is like this: If `modifiers w` + `key x` are pressed, replace these keys with `modifiers y` + `key z` in the keyboard report. + +You can use key overrides in a similar way to momentary layer/fn keys to activate custom keycodes/shortcuts, with a number of benefits: You completely keep the original use of the modifier keys, while being able to save space by removing fn keys from your keyboard. You can also easily configure _combinations of modifiers_ to trigger different actions than individual modifiers, and much more. The possibilities are quite vast and this documentation contains a few examples for inspiration throughout. + +##### A few more examples to get started: You could use key overrides to... +- Send `brightness up/down` when pressing `ctrl` + `volume up/down`. +- Send `delete` when pressing `shift` + `backspace`. +- Create custom shortcuts or change existing ones: E.g. Send `ctrl`+`shift`+`z` when `ctrl`+`y` is pressed. +- Run custom code when `ctrl` + `alt` + `esc` is pressed. + +## Setup + +To enable this feature, you need to add `KEY_OVERRIDE_ENABLE = yes` to your `rules.mk`. + +Then, in your `keymap.c` file, you'll need to define the array `key_overrides`, which defines all key overrides to be used. Each override is a value of type `key_override_t`. The array `key_overrides` is `NULL`-terminated and contains pointers to `key_override_t` values (`const key_override_t **`). + +## Creating Key Overrides + +The `key_override_t` struct has many options that allow you to precisely tune your overrides. The full reference is shown below. Instead of manually creating a `key_override_t` value, it is recommended to use these dedicated initializers: + +#### `ko_make_basic(modifiers, key, replacement)` +Returns a `key_override_t`, which sends `replacement` (can be a key-modifer combination), when `key` and `modifiers` are all pressed down. This override still activates if any additional modifiers not specified in `modifiers` are also pressed down. See `ko_make_with_layers_and_negmods` to customize this behavior. + +#### `ko_make_with_layers(modifiers, key, replacement, layers)` +Additionally takes a bitmask `layers` that defines on which layers the override is used. + +#### `ko_make_with_layers_and_negmods(modifiers, key, replacement, layers, negative_mods)` +Additionally takes a bitmask `negative_mods` that defines which modifiers may not be pressed for this override to activate. + +#### `ko_make_with_layers_negmods_and_options(modifiers, key, replacement, layers, negative_mods, options)` +Additionally takes a bitmask `options` that specifies additional options. See `ko_option_t` for available options. + +For more customization possibilities, you may directly create a `key_override_t`, which allows you to customize even more behavior. Read further below for details and examples. + +## Simple Example + +This shows how the mentioned example of sending `delete` when `shift` + `backspace` are pressed is realized: + +```c +const key_override_t delete_key_override = ko_make_basic(MOD_MASK_SHIFT, KC_BSPACE, KC_DELETE); + +// This globally defines all key overrides to be used +const key_override_t **key_overrides = (const key_override_t *[]){ + &delete_key_override, + NULL // Null terminate the array of overrides! +}; +``` + +## Intermediate Difficulty Examples + +### Media Controls & Screen Brightness + +In this example a single key is configured to control media, volume and screen brightness by using key overrides. + +- The key is set to send `play/pause` in the keymap. + +The following key overrides will be configured: + +- `Ctrl` + `play/pause` will send `next track`. +- `Ctrl` + `Shift` + `play/pause` will send `previous track`. +- `Alt` + `play/pause` will send `volume up`. +- `Alt` + `Shift` + `play/pause` will send `volume down`. +- `Ctrl` + `Alt` + `play/pause` will send `brightness up`. +- `Ctrl` + `Alt` + `Shift` + `play/pause` will send `brightness down`. + + +```c +const key_override_t next_track_override = + ko_make_with_layers_negmods_and_options( + MOD_MASK_CTRL, // Trigger modifiers: ctrl + KC_MPLY, // Trigger key: play/pause + KC_MNXT, // Replacement key + ~0, // Activate on all layers + MOD_MASK_SA, // Do not activate when shift or alt are pressed + ko_option_no_reregister_trigger); // Specifies that the play key is not registered again after lifting ctrl + +const key_override_t prev_track_override = ko_make_with_layers_negmods_and_options(MOD_MASK_CS, KC_MPLY, + KC_MPRV, ~0, MOD_MASK_ALT, ko_option_no_reregister_trigger); + +const key_override_t vol_up_override = ko_make_with_layers_negmods_and_options(MOD_MASK_ALT, KC_MPLY, + KC_VOLU, ~0, MOD_MASK_CS, ko_option_no_reregister_trigger); + +const key_override_t vol_down_override = ko_make_with_layers_negmods_and_options(MOD_MASK_SA, KC_MPLY, + KC_VOLD, ~0, MOD_MASK_CTRL, ko_option_no_reregister_trigger); + +const key_override_t brightness_up_override = ko_make_with_layers_negmods_and_options(MOD_MASK_CA, KC_MPLY, + KC_BRIU, ~0, MOD_MASK_SHIFT, ko_option_no_reregister_trigger); + +const key_override_t brightness_down_override = ko_make_basic(MOD_MASK_CSA, KC_MPLY, KC_BRID); + +// This globally defines all key overrides to be used +const key_override_t **key_overrides = (const key_override_t *[]){ + &next_track_override, + &prev_track_override, + &vol_up_override, + &vol_down_override, + &brightness_up_override, + &brightness_down_override, + NULL +}; +``` + +### Flexible macOS-friendly Grave Escape +The [Grave Escape feature](https://docs.qmk.fm/using-qmk/advanced-keycodes/feature_grave_esc) is limited in its configurability and has [bugs when used on macOS](https://docs.qmk.fm/using-qmk/advanced-keycodes/feature_grave_esc#caveats). Key overrides can be used to achieve a similar functionality as Grave Escape, but with more customization and without bugs on macOS. + +```c +// Shift + esc = ~ +const key_override_t tilde_esc_override = ko_make_basic(MOD_MASK_SHIFT, KC_ESC, S(KC_GRAVE)); + +// GUI + esc = ` +const key_override_t grave_esc_override = ko_make_basic(MOD_MASK_GUI, KC_ESC, KC_GRAVE); + +const key_override_t **key_overrides = (const key_override_t *[]){ + &tilde_esc_override, + &grave_esc_override, + NULL +}; +``` + +In addition to not encountering unexpected bugs on macOS, you can also change the behavior as you wish. Instead setting `GUI` + `ESC` = `` ` `` you may change it to an arbitrary other modifier, for example `Ctrl` + `ESC` = `` ` ``. + +## Advanced Examples +### Modifiers as Layer Keys + +Do you really need a dedicated key to toggle your fn layer? With key overrides, perhaps not. This example shows how you can configure to use `rGUI` + `rAlt` (right GUI and right alt) to access a momentary layer like an fn layer. With this you completely eliminate the need to use a dedicated layer key. Of course the choice of modifier keys can be changed as needed, `rGUI` + `rAlt` is just an example here. + +```c +// This is called when the override activates and deactivates. Enable the fn layer on activation and disable on deactivation +bool momentary_layer(bool key_down, void *layer) { + if (key_down) { + layer_on((uint8_t)(uintptr_t)layer); + } else { + layer_off((uint8_t)(uintptr_t)layer); + } + + return false; +} + +const key_override_t fn_override = {.trigger_mods = MOD_BIT(KC_RGUI) | MOD_BIT(KC_RCTL), // + .layers = ~(1 << LAYER_FN), // + .suppressed_mods = MOD_BIT(KC_RGUI) | MOD_BIT(KC_RCTL), // + .options = ko_option_no_unregister_on_other_key_down, // + .negative_mod_mask = (uint8_t) ~(MOD_BIT(KC_RGUI) | MOD_BIT(KC_RCTL)), // + .custom_action = momentary_layer, // + .context = (void *)LAYER_FN, // + .trigger = KC_NO, // + .replacement = KC_NO, // + .enabled = NULL}; +``` + +## Keycodes + +You can enable, disable and toggle all key overrides on the fly. + +|Keycode |Description |Function Equivalent| +|----------|---------------------------------|--------| +|`KEY_OVERRIDE_ON` |Turns on Key Override feature | `key_override_on(void)`| +|`KEY_OVERRIDE_OFF` |Turns off Key Override feature |`key_override_off(void)`| +|`KEY_OVERRIDE_TOGGLE` |Toggles Key Override feature on and off |`key_override_toggle(void)`| + +## Reference for `key_override_t` + +Advanced users may need more customization than what is offered by the simple `ko_make` initializers. For this, directly create a `key_override_t` value and set all members. Below is a reference for all members of `key_override_t`. + +| Member | Description | +|--------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `uint16_t trigger` | The non-modifier keycode that triggers the override. This keycode, and the necessary modifiers (`trigger_mods`) must be pressed to activate this override. Set this to the keycode of the key that should activate the override. Set to `KC_NO` to require only the necessary modifiers to be pressed and no non-modifier. | +| `uint8_t trigger_mods` | Which mods need to be down for activation. If both sides of a modifier are set (e.g. left ctrl and right ctrl) then only one is required to be pressed (e.g. left ctrl suffices). Use the `MOD_MASK_XXX` and `MOD_BIT()` macros for this. | +| `layer_state_t layers` | This is a BITMASK (!), defining which layers this override applies to. To use this override on layer i set the ith bit `(1 << i)`. | +| `uint8_t negative_mod_mask` | Which modifiers cannot be down. It must hold that `(active_modifiers & negative_mod_mask) == 0`, otherwise the key override will not be activated. An active override will be deactivated once this is no longer true. | +| `uint8_t suppressed_mods` | Modifiers to 'suppress' while the override is active. To suppress a modifier means that even though the modifier key is held down, the host OS sees the modifier as not pressed. Can be used to suppress the trigger modifiers, as a trivial example. | +| `uint16_t replacement` | The complex keycode to send as replacement when this override is triggered. This can be a simple keycode, a key-modifier combination (e.g. `C(KC_A)`), or `KC_NO` (to register no replacement keycode). Use in combination with suppressed_mods to get the correct modifiers to be sent. | +| `ko_option_t options` | Options controlling the behavior of the override, such as what actions are allowed to activate the override. | +| `bool (*custom_action)(bool activated, void *context)` | If not NULL, this function will be called right before the replacement key is registered, along with the provided context and a flag indicating whether the override was activated or deactivated. This function allows you to run some custom actions for specific key overrides. If you return `false`, the replacement key is not registered/unregistered as it would normally. Return `true` to register and unregister the override normally. | +| `void *context` | A context that will be passed to the custom action function. | +| `bool *enabled` | If this points to false this override will not be used. Set to NULL to always have this override enabled. | + +### Reference for `ko_option_t` + +Bitfield with various options controlling the behavior of a key override. + +| Value | Description | +|------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `ko_option_activation_trigger_down` | Allow activating when the trigger key is pressed down. | +| `ko_option_activation_required_mod_down` | Allow activating when a necessary modifier is pressed down. | +| `ko_option_activation_negative_mod_up` | Allow activating when a negative modifier is released. | +| `ko_option_one_mod` | If set, any of the modifiers in `trigger_mods` will be enough to activate the override (logical OR of modifiers). If not set, all the modifiers in `trigger_mods` have to be pressed (logical AND of modifiers). | +| `ko_option_no_unregister_on_other_key_down` | If set, the override will not deactivate when another key is pressed down. Use only if you really know you need this. | +| `ko_option_no_reregister_trigger` | If set, the trigger key will never be registered again after the override is deactivated. | +| `ko_options_default` | The default options used by the `ko_make_xxx` functions | + +## For Advanced Users: Inner Workings + +This section explains how a key override works in detail, explaining where each member of `key_override_t` comes into play. Understanding this is essential to be able to take full advantage of all the options offered by key overrides. + +#### Activation + +When the necessary keys are pressed (`trigger_mods` + `trigger`), the override is 'activated' and the replacement key is registered in the keyboard report (`replacement`), while the `trigger` key is removed from the keyboard report. The trigger modifiers may also be removed from the keyboard report upon activation of an override (`suppressed_mods`). The override will not activate if any of the `negative_modifiers` are pressed. + +Overrides can activate in three different cases: + +1. The trigger key is pressed down and necessary modifiers are already down. +2. A necessary modifier is pressed down, while the trigger key and other necessary modifiers are already down. +3. A negative modifier is released, while all necessary modifiers and the trigger key are already down. + +Use the `option` member to customize which of these events are allowed to activate your overrides (default: all three). + +In any case, a key override can only activate if the `trigger` key is the _last_ non-modifier key that was pressed down. This emulates the behavior of how standard OSes (macOS, Windows, Linux) handle normal key input (to understand: Hold down `a`, then also hold down `b`, then hold down `shift`; `B` will be typed but not `A`). + +#### Deactivation + +An override is 'deactivated' when one of the trigger keys (`trigger_mods`, `trigger`) is lifted, another non-modifier key is pressed down, or one of the `negative_modifiers` is pressed down. When an override deactivates, the `replacement` key is removed from the keyboard report, while the `suppressed_mods` that are still held down are re-added to the keyboard report. By default, the `trigger` key is re-added to the keyboard report if it is still held down and no other non-modifier key has been pressed since. This again emulates the behavior of how standard OSes handle normal key input (To understand: hold down `a`, then also hold down `b`, then also `shift`, then release `b`; `A` will not be typed even though you are holding the `a` and `shift` keys). Use the `option` field `ko_option_no_reregister_trigger` to prevent re-registering the trigger key in all cases. + +#### Key Repeat Delay + +A third way in which standard OS-handling of modifier-key input is emulated in key overrides is with a ['key repeat delay'](https://www.dummies.com/computers/pcs/set-your-keyboards-repeat-delay-and-repeat-rate/). To explain what this is, let's look at how normal keyboard input is handled by mainstream OSes again: If you hold down `a`, followed by `shift`, you will see the letter `a` is first typed, then for a short moment nothing is typed and then repeating `A`s are typed. Take note that, although shift is pressed down just after `a` is pressed, it takes a moment until `A` is typed. This is caused by the aforementioned key repeat delay, and it is a feature that prevents unwanted repeated characters from being typed. + +This applies equally to releasing a modifier: When you hold `shift`, then press `a`, the letter `A` is typed. Now if you release `shift` first, followed by `a` shortly after, you will not see the letter `a` being typed, even though for a short moment of time you were just holding down the key `a`. This is because no modified characters are typed until the key repeat delay has passed. + + This exact behavior is implemented in key overrides as well: If a key override for `shift` + `a` = `b` exists, and `a` is pressed and held, followed by `shift`, you will not immediately see the letter `b` being typed. Instead, this event is deferred for a short moment, until the key repeat delay has passed, measured from the moment when the trigger key (`a`) was pressed down. + +The duration of the key repeat delay is controlled with the `KEY_OVERRIDE_REPEAT_DELAY` macro. Define this value in your `config.h` file to change it. It is 500ms by default. + + +## Difference to Combos + +Note that key overrides are very different from [combos](https://docs.qmk.fm/#/feature_combo). Combos require that you press down several keys almost _at the same time_ and can work with any combination of non-modifier keys. Key overrides work like keyboard shortcuts (e.g. `ctrl` + `z`): They take combinations of _multiple_ modifiers and _one_ non-modifier key to then perform some custom action. Key overrides are implemented with much care to behave just like normal keyboard shortcuts would in regards to the order of pressed keys, timing, and interacton with other pressed keys. There are a number of optional settings that can be used to really fine-tune the behavior of each key override as well. Using key overrides also does not delay key input for regular key presses, which inherently happens in combos and may be undesirable. diff --git a/docs/syllabus.md b/docs/syllabus.md index ec7f66ba78..b33bd9e727 100644 --- a/docs/syllabus.md +++ b/docs/syllabus.md @@ -40,6 +40,7 @@ These topics start to dig into some of the features that QMK supports. You don't * [Tap Dance](feature_tap_dance.md) * [Combos](feature_combo.md) * [Userspace](feature_userspace.md) + * [Key Overrides](feature_key_overrides.md) # Advanced Topics diff --git a/docs/understanding_qmk.md b/docs/understanding_qmk.md index 331b1c893c..e3dd5cb780 100644 --- a/docs/understanding_qmk.md +++ b/docs/understanding_qmk.md @@ -146,6 +146,7 @@ The `process_record()` function itself is deceptively simple, but hidden within * [`bool process_audio(uint16_t keycode, keyrecord_t *record)`](https://github.com/qmk/qmk_firmware/blob/e1203a222bb12ab9733916164a000ef3ac48da93/quantum/process_keycode/process_audio.c#L19) * [`bool process_steno(uint16_t keycode, keyrecord_t *record)`](https://github.com/qmk/qmk_firmware/blob/e1203a222bb12ab9733916164a000ef3ac48da93/quantum/process_keycode/process_steno.c#L160) * [`bool process_music(uint16_t keycode, keyrecord_t *record)`](https://github.com/qmk/qmk_firmware/blob/e1203a222bb12ab9733916164a000ef3ac48da93/quantum/process_keycode/process_music.c#L114) + * [`bool process_key_override(uint16_t keycode, keyrecord_t *record)`](https://github.com/qmk/qmk_firmware/blob/5a1b857dea45a17698f6baa7dd1b7a7ea907fb0a/quantum/process_keycode/process_key_override.c#L397) * [`bool process_tap_dance(uint16_t keycode, keyrecord_t *record)`](https://github.com/qmk/qmk_firmware/blob/e1203a222bb12ab9733916164a000ef3ac48da93/quantum/process_keycode/process_tap_dance.c#L141) * [`bool process_unicode_common(uint16_t keycode, keyrecord_t *record)`](https://github.com/qmk/qmk_firmware/blob/e1203a222bb12ab9733916164a000ef3ac48da93/quantum/process_keycode/process_unicode_common.c#L169) calls one of: diff --git a/quantum/process_keycode/process_key_override.c b/quantum/process_keycode/process_key_override.c new file mode 100644 index 0000000000..fe43eacc40 --- /dev/null +++ b/quantum/process_keycode/process_key_override.c @@ -0,0 +1,518 @@ +/* + * Copyright 2021 Jonas Gessner + * + * 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 "quantum.h" +#include "report.h" +#include "timer.h" +#include "process_key_override_private.h" + +#include + +#ifndef KEY_OVERRIDE_REPEAT_DELAY +# define KEY_OVERRIDE_REPEAT_DELAY 500 +#endif + +// For benchmarking the time it takes to call process_key_override on every key press (needs keyboard debugging enabled as well) +// #define BENCH_KEY_OVERRIDE + +// For debug output (needs keyboard debugging enabled as well) +// #define DEBUG_KEY_OVERRIDE + +#ifdef DEBUG_KEY_OVERRIDE +# define key_override_printf dprintf +#else +# define key_override_printf(str, ...) \ + {} +#endif + +// Helpers + +// Private functions implemented elsewhere in qmk/tmk +extern uint8_t extract_mod_bits(uint16_t code); +extern void set_weak_override_mods(uint8_t mods); +extern void clear_weak_override_mods(void); +extern void set_suppressed_override_mods(uint8_t mods); +extern void clear_suppressed_override_mods(void); + +static uint16_t clear_mods_from(uint16_t keycode) { + switch (keycode) { + case QK_MODS ... QK_MODS_MAX: + break; + default: + return keycode; + } + + static const uint16_t all_mods = QK_LCTL | QK_LSFT | QK_LALT | QK_LGUI | QK_RCTL | QK_RSFT | QK_RALT | QK_RGUI; + + return (keycode & ~(all_mods)); +} + +// Internal variables +static const key_override_t *active_override = NULL; +static bool active_override_trigger_is_down = false; + +// Used to keep track of what non-modifier key was last pressed down. We never want to activate an override for a trigger key that is not the last non-mod key that was pressed down. OSes internally completely unregister a key that is held when a different key is held down after. We want to respect this here. +static uint16_t last_key_down = 0; +// When was the last key pressed down? +static uint32_t last_key_down_time = 0; + +// What timestamp are we comparing to when waiting to register a deferred key? +static uint32_t defer_reference_time = 0; +// What delay should pass until deferred key is registered? +static uint32_t defer_delay = 0; + +// Holds the keycode that should be registered at a later time, in order to not get false key presses +static uint16_t deferred_register = 0; + +// TODO: in future maybe save in EEPROM? +static bool enabled = true; + +// Public variables +__attribute__((weak)) const key_override_t **key_overrides = NULL; + +// Forward decls +static const key_override_t *clear_active_override(const bool allow_reregister); + +void key_override_on(void) { + enabled = true; + key_override_printf("Key override ON\n"); +} + +void key_override_off(void) { + enabled = false; + clear_active_override(false); + key_override_printf("Key override OFF\n"); +} + +void key_override_toggle(void) { + if (key_override_is_enabled()) { + key_override_off(); + } else { + key_override_on(); + } +} + +bool key_override_is_enabled(void) { return enabled; } + +// Returns whether the modifiers that are pressed are such that the override should activate +static bool key_override_matches_active_modifiers(const key_override_t *override, const uint8_t mods) { + // Check that negative keys pass + if ((override->negative_mod_mask & mods) != 0) { + return false; + } + + // Immediately return true if the override requires no mods down + if (override->trigger_mods == 0) { + return true; + } + + if ((override->options & ko_option_one_mod) != 0) { + // At least one of the trigger modifiers must be down + return (override->trigger_mods & mods) != 0; + } else { + // All trigger modifiers must be down, but each mod can be active on either side (if both sides are specified). + + // Which mods, regardless of side, are required? + uint8_t one_sided_required_mods = (override->trigger_mods & 0b1111) | (override->trigger_mods >> 4); + + // Which of the required modifiers are active? + uint8_t active_required_mods = override->trigger_mods & mods; + + // Move the active requird mods to one side + uint8_t one_sided_active_required_mods = (active_required_mods & 0b1111) | (active_required_mods >> 4); + + // Check that there is a full match between the required one-sided mods and active required one sided mods + return one_sided_active_required_mods == one_sided_required_mods; + } + + return false; +} + +static void schedule_deferred_register(const uint16_t keycode) { + if (timer_elapsed32(last_key_down_time) < KEY_OVERRIDE_REPEAT_DELAY) { + // Defer until KEY_OVERRIDE_REPEAT_DELAY has passed since the trigger key was pressed down. This emulates the behavior as holding down a key x, then holding down shift shortly after. Usually the shifted key X is not immediately produced, but rather a 'key repeat delay' passes before any repeated character is output. + defer_reference_time = last_key_down_time; + defer_delay = KEY_OVERRIDE_REPEAT_DELAY; + } else { + // Wait a very short time when a modifier event triggers the override to avoid false activations when e.g. a modifier is pressed just before a key is released (with the intention of pairing the modifier with a different key), or a modifier is lifted shortly before the trigger key is lifted. Operating systems by default reject modifier-events that happen very close to a non-modifier event. + defer_reference_time = timer_read32(); + defer_delay = 50; // 50ms + } + deferred_register = keycode; +} + +const key_override_t *clear_active_override(const bool allow_reregister) { + if (active_override == NULL) { + return NULL; + } + + key_override_printf("Deactivating override\n"); + + deferred_register = 0; + + // Clear the suppressed mods + clear_suppressed_override_mods(); + + // Unregister the replacement. First remove the weak override mods + clear_weak_override_mods(); + + const key_override_t *const old = active_override; + + const uint8_t mod_free_replacement = clear_mods_from(active_override->replacement); + + bool unregister_replacement = mod_free_replacement != KC_NO && // KC_NO is never registered + mod_free_replacement < SAFE_RANGE; // Custom keycodes are never registered + + // Try firing the custom handler + if (active_override->custom_action != NULL) { + unregister_replacement &= active_override->custom_action(false, active_override->context); + } + + // Then unregister the mod-free replacement key if desired + if (unregister_replacement) { + if (IS_KEY(mod_free_replacement)) { + del_key(mod_free_replacement); + } else { + key_override_printf("NOT KEY 1\n"); + send_keyboard_report(); + unregister_code(mod_free_replacement); + } + } + + const uint16_t trigger = active_override->trigger; + + const bool reregister_trigger = allow_reregister && // Check if allowed from caller + (active_override->options & ko_option_no_reregister_trigger) == 0 && // Check if override allows + active_override_trigger_is_down && // Check if trigger is even down + trigger != KC_NO && // KC_NO is never registered + trigger < SAFE_RANGE; // A custom keycode should not be registered + + // Optionally re-register the trigger if it is still down + if (reregister_trigger) { + key_override_printf("Re-registering trigger deferred: %u\n", trigger); + + // This will always be a modifier event, so defer always + schedule_deferred_register(trigger); + } + + send_keyboard_report(); + + active_override = NULL; + active_override_trigger_is_down = false; + + return old; +} + +/** Checks if the key event is an allowed activation event for the provided override. Does not check things like whether the correct mods or correct trigger key is down. */ +static bool check_activation_event(const key_override_t *override, const bool key_down, const bool is_mod) { + ko_option_t options = override->options; + + if ((options & ko_options_all_activations) == 0) { + // No activation option provided at all. This is wrong, but let's assume the default activations (ko_options_all_activations) were meant... + options = ko_options_all_activations; + } + + if (is_mod) { + if (key_down) { + return (options & ko_option_activation_required_mod_down) != 0; + } else { + return (options & ko_option_activation_negative_mod_up) != 0; + } + } else { + if (key_down) { + return (options & ko_option_activation_trigger_down) != 0; + } else { + return false; + } + } +} + +/** Iterates through the list of key overrides and tries activating each, until it finds one that activates or reaches the end of overrides. Returns true if the key action for `keycode` should be sent */ +static bool try_activating_override(const uint16_t keycode, const uint8_t layer, const bool key_down, const bool is_mod, const uint8_t active_mods, bool *activated) { + if (key_overrides == NULL) { + return true; + } + + for (uint8_t i = 0;; i++) { + const key_override_t *const override = key_overrides[i]; + + // End of array + if (override == NULL) { + break; + } + + // Fast, but not full mods check. Most key presses will not have any mods down, and most overrides will require mods. Hence here we filter overrides that require mods to be down while no mods are down + if (active_mods == 0 && override->trigger_mods != 0) { + key_override_printf("Not activating override: Modifiers don't match\n"); + continue; + } + + // Check layer + if ((override->layers & (1 << layer)) == 0) { + key_override_printf("Not activating override: Not set to activate on pressed layer\n"); + continue; + } + + // Check allowed activation events + if (!check_activation_event(override, key_down, is_mod)) { + key_override_printf("Not activating override: Activation event not allowed\n"); + continue; + } + + const bool is_trigger = override->trigger == keycode; + + // Check if trigger lifted. This is a small optimization in order to skip the remaining checks + if (is_trigger && !key_down) { + key_override_printf("Not activating override: Trigger lifted\n"); + continue; + } + + // If the trigger is KC_NO it means 'no key', so only the required modifiers need to be down. + const bool no_trigger = override->trigger == KC_NO; + + // Check if aleady active + if (override == active_override) { + key_override_printf("Not activating override: Alerady actived\n"); + continue; + } + + // Check if enabled + if (override->enabled != NULL && !((*(override->enabled) & 1))) { + key_override_printf("Not activating override: Not enabled\n"); + continue; + } + + // Check mods precisely + if (!key_override_matches_active_modifiers(override, active_mods)) { + key_override_printf("Not activating override: Modifiers don't match\n"); + continue; + } + + // Check if trigger key is down. + const bool trigger_down = is_trigger && key_down; + + // At this point, all requirements for activation are checked, except whether the trigger key is pressed. Now we check if the required trigger is down + // If no trigger key is required, yes. + // If the trigger was just pressed, yes. + // If the last non-mod key that was pressed down is the trigger key, yes. + bool should_activate = no_trigger || trigger_down || last_key_down == override->trigger; + + if (!should_activate) { + key_override_printf("Not activating override. Trigger not down\n"); + continue; + } + + key_override_printf("Activating override\n"); + + clear_active_override(false); + + active_override = override; + active_override_trigger_is_down = true; + + set_suppressed_override_mods(override->suppressed_mods); + + if (!trigger_down && !no_trigger) { + // When activating a key override the trigger is is always unregistered. In the case where the key that newly pressed is not the trigger key, we have to explicitly remove the trigger key from the keyboard report. If the trigger was just pressed down we simply suppress the event which also has the effect of the trigger key not being registered in the keyboard report. + if (IS_KEY(override->trigger)) { + del_key(override->trigger); + } else { + unregister_code(override->trigger); + } + } + + const uint16_t mod_free_replacement = clear_mods_from(override->replacement); + + bool register_replacement = mod_free_replacement != KC_NO && // KC_NO is never registered + mod_free_replacement < SAFE_RANGE; // Custom keycodes are never registered + + // Try firing the custom handler + if (override->custom_action != NULL) { + register_replacement &= override->custom_action(true, override->context); + } + + if (register_replacement) { + const uint8_t override_mods = extract_mod_bits(override->replacement); + set_weak_override_mods(override_mods); + + // If this is a modifier event that activates the key override we _always_ defer the actual full activation of the override + if (is_mod) { + key_override_printf("Deferring register replacement key\n"); + schedule_deferred_register(mod_free_replacement); + send_keyboard_report(); + } else { + if (IS_KEY(mod_free_replacement)) { + add_key(mod_free_replacement); + } else { + key_override_printf("NOT KEY 2\n"); + send_keyboard_report(); + // On macOS there seems to be a race condition when it comes to the keyboard report and consumer keycodes. It seems the OS may recognize a consumer keycode before an updated keyboard report, even if the keyboard report is actually sent before the consumer key. I assume it is some sort of race condition because it happens infrequently and very irregularly. Waiting for about at least 10ms between sending the keyboard report and sending the consumer code has shown to fix this. + wait_ms(10); + register_code(mod_free_replacement); + } + } + } else { + // If not registering the replacement key send keyboard report to update the unregistered keys. + send_keyboard_report(); + } + + *activated = true; + + // If the trigger is down, suppress the event so that it does not get added to the keyboard report. + return !trigger_down; + } + + *activated = false; + + return true; +} + +void matrix_scan_key_override(void) { + if (deferred_register == 0) { + return; + } + + if (timer_elapsed32(defer_reference_time) >= defer_delay) { + key_override_printf("Registering deferred key\n"); + register_code16(deferred_register); + deferred_register = 0; + defer_reference_time = 0; + defer_delay = 0; + } +} + +bool process_key_override(const uint16_t keycode, const keyrecord_t *const record) { +#ifdef BENCH_KEY_OVERRIDE + uint16_t start = timer_read(); +#endif + + const bool key_down = record->event.pressed; + const bool is_mod = IS_MOD(keycode); + + if (key_down) { + switch (keycode) { + case KEY_OVERRIDE_TOGGLE: + key_override_toggle(); + return false; + + case KEY_OVERRIDE_ON: + key_override_on(); + return false; + + case KEY_OVERRIDE_OFF: + key_override_off(); + return false; + + default: + break; + } + } + + if (!enabled) { + return true; + } + + uint8_t effective_mods = get_mods(); + +#ifdef KEY_OVERRIDE_INCLUDE_WEAK_MODS + effective_mods |= get_weak_mods(); +#endif + +#ifndef NO_ACTION_ONESHOT + // Locked one shot mods are added to get_mods(), I think (why??) while oneshot mods are in get_oneshot_mods(). Still OR with get_locked_oneshot_mods because that's where those mods _should_ be saved. + effective_mods |= get_oneshot_locked_mods() | get_oneshot_mods(); +#endif + + if (is_mod) { + // The mods returned from get_mods() will be updated with this new event _after_ this code runs. Hence we manually update the effective mods here to really know the effective mods. + if (key_down) { + effective_mods |= MOD_BIT(keycode); + } else { + effective_mods &= ~MOD_BIT(keycode); + } + } else { + if (key_down) { + last_key_down = keycode; + last_key_down_time = timer_read32(); + deferred_register = 0; + } + + // The last key that was pressed was just released. No more keys are therefore sending input + if (!key_down && keycode == last_key_down) { + last_key_down = 0; + last_key_down_time = 0; + // We also cancel any deferred registers because, again, no keys are sending any input. Only the last key that is pressed creates an input – this key was just lifted. + deferred_register = 0; + } + } + + key_override_printf("key down: %u keycode: %u is mod: %u effective mods: %u\n", key_down, keycode, is_mod, effective_mods); + + bool send_key_action = true; + bool activated = false; + + // Non-mod key up events never activate a key override + if (is_mod || key_down) { + // Get the exact layer that was hit. It will be cached at this point + const uint8_t layer = read_source_layers_cache(record->event.key); + + // Use blocked to ensure the same override is not activated again immediately after it is deactivated + send_key_action = try_activating_override(keycode, layer, key_down, is_mod, effective_mods, &activated); + + if (!send_key_action) { + send_keyboard_report(); + } + } + + if (!activated && active_override != NULL) { + if (is_mod) { + // Check if necessary modifier of current override goes up or a negative mod goes down + if (!key_override_matches_active_modifiers(active_override, effective_mods)) { + key_override_printf("Deactivating override because necessary modifier lifted or negative mod pressed\n"); + clear_active_override(true); + } + } else { + // Check if trigger of current override goes up or if override does not allow additional keys to be down and another key goes down + const bool is_trigger = keycode == active_override->trigger; + bool should_deactivate = false; + + // Check if trigger key lifted + if (is_trigger && !key_down) { + should_deactivate = true; + active_override_trigger_is_down = false; + key_override_printf("Deactivating override because trigger key up\n"); + } + + // Check if another key was pressed + if (key_down && (active_override->options & ko_option_no_unregister_on_other_key_down) == 0) { + should_deactivate = true; + key_override_printf("Deactivating override because another key was pressed\n"); + } + + if (should_deactivate) { + clear_active_override(false); + } + } + } + +#ifdef BENCH_KEY_OVERRIDE + uint16_t elapsed = timer_elapsed(start); + + dprintf("Processing key overrides took: %u ms\n", elapsed); +#endif + + return send_key_action; +} diff --git a/quantum/process_keycode/process_key_override.h b/quantum/process_keycode/process_key_override.h new file mode 100644 index 0000000000..9ba59e4e9b --- /dev/null +++ b/quantum/process_keycode/process_key_override.h @@ -0,0 +1,147 @@ +/* + * Copyright 2021 Jonas Gessner + * + * 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 +#include + +#include "action_layer.h" + +/** + * Key overrides allow you to send a different key-modifier combination or perform a custom action when a certain modifier-key combination is pressed. + * + * For example, you may configure a key override to send the delete key when shift + backspace are pressed together, or that your volume keys become screen brightness keys when holding ctrl. The possibilities are quite vast and the documentation contains a few examples for inspiration. + * + * See the documentation and examples here: https://docs.qmk.fm/#/feature_key_overrides + */ + +/** Bitfield with various options controlling the behavior of a key override. */ +typedef enum { + /** Allow activating when the trigger key is pressed down. */ + ko_option_activation_trigger_down = (1 << 0), + /** Allow activating when a necessary modifier is pressed down. */ + ko_option_activation_required_mod_down = (1 << 1), + /** Allow activating when a negative modifier is released. */ + ko_option_activation_negative_mod_up = (1 << 2), + + ko_options_all_activations = ko_option_activation_negative_mod_up | ko_option_activation_required_mod_down | ko_option_activation_trigger_down, + + /** If set, any of the modifiers in trigger_mods will be enough to activate the override (logical OR of modifiers). If not set, all the modifiers in trigger_mods have to be pressed (logical AND of modifiers). */ + ko_option_one_mod = (1 << 3), + + /** If set, the trigger key will never be registered again after the override is deactivated. */ + ko_option_no_reregister_trigger = (1 << 4), + + /** If set, the override will not deactivate when another key is pressed down. Use only if you really know you need this. */ + ko_option_no_unregister_on_other_key_down = (1 << 5), + + /** The default options used by the ko_make_xxx functions. */ + ko_options_default = ko_options_all_activations, +} ko_option_t; + +/** Defines a single key override */ +typedef struct { + // The non-modifier keycode that triggers the override. This keycode, and the necessary modifiers (trigger_mods) must be pressed to activate this override. Set this to the keycode of the key that should activate the override. Set to KC_NO to require only the necessary modifiers to be pressed and no non-modifier. + uint16_t trigger; + + // Which mods need to be down for activation. If both sides of a modifier are set (e.g. left ctrl and right ctrl) then only one is required to be pressed (e.g. left ctrl suffices). Use the MOD_MASK_XXX and MOD_BIT() macros for this. + uint8_t trigger_mods; + + // This is a BITMASK (!), defining which layers this override applies to. To use this override on layer i set the ith bit (1 << i). + layer_state_t layers; + + // Which modifiers cannot be down. It must hold that (active_mods & negative_mod_mask) == 0, otherwise the key override will not be activated. An active override will be deactivated once this is no longer true. + uint8_t negative_mod_mask; + + // Modifiers to 'suppress' while the override is active. To suppress a modifier means that even though the modifier key is held down, the host OS sees the modifier as not pressed. Can be used to suppress the trigger modifiers, as a trivial example. + uint8_t suppressed_mods; + + // The complex keycode to send as replacement when this override is triggered. This can be a simple keycode, a key-modifier combination (e.g. C(KC_A)), or KC_NO (to register no replacement keycode). Use in combination with suppressed_mods to get the correct modifiers to be sent. + uint16_t replacement; + + // Options controlling the behavior of the override, such as what actions are allowed to activate the override. + ko_option_t options; + + // If not NULL, this function will be called right before the replacement key is registered, along with the provided context and a flag indicating whether the override was activated or deactivated. This function allows you to run some custom actions for specific key overrides. If you return `false`, the replacement key is not registered/unregistered as it would normally. Return `true` to register and unregister the override normally. + bool (*custom_action)(bool activated, void *context); + + // A context that will be passed to the custom action function. + void *context; + + // If this points to false this override will not be used. Set to NULL to always have this override enabled. + bool *enabled; +} key_override_t; + +/** Define this as a null-terminated array of pointers to key overrides. These key overrides will be used by qmk. */ +extern const key_override_t **key_overrides; + +/** Turns key overrides on */ +extern void key_override_on(void); + +/** Turns key overrides off */ +extern void key_override_off(void); + +/** Toggles key overrides on */ +extern void key_override_toggle(void); + +/** Returns whether key overrides are enabled */ +extern bool key_override_is_enabled(void); + +/** + * Preferrably use these macros to create key overrides. They fix many of the options to a standard setting that should satisfy most basic use-cases. Only directly create a key_override_t struct when you really need to. + */ + +// clang-format off + +/** + * Convenience initializer to create a basic key override. Activates the override on all layers. + */ +#define ko_make_basic(trigger_mods, trigger_key, replacement_key) \ + ko_make_with_layers(trigger_mods, trigger_key, replacement_key, ~0) + +/** + * Convenience initializer to create a basic key override. Provide a bitmap (of type layer_state_t) with the bits set for each layer on which the override should activate. + */ +#define ko_make_with_layers(trigger_mods, trigger_key, replacement_key, layers) \ + ko_make_with_layers_and_negmods(trigger_mods, trigger_key, replacement_key, layers, 0) + +/** + * Convenience initializer to create a basic key override. Provide a bitmap with the bits set for each layer on which the override should activate. Also provide a negative modifier mask, that is used to define which modifiers may not be pressed. + */ +#define ko_make_with_layers_and_negmods(trigger_mods, trigger_key, replacement_key, layers, negative_mask) \ + ko_make_with_layers_negmods_and_options(trigger_mods, trigger_key, replacement_key, layers, negative_mask, ko_options_default) + + /** + * Convenience initializer to create a basic key override. Provide a bitmap with the bits set for each layer on which the override should activate. Also provide a negative modifier mask, that is used to define which modifiers may not be pressed. Provide options for additional control of the behavior of the override. + */ +#define ko_make_with_layers_negmods_and_options(trigger_mods_, trigger_key, replacement_key, layer_mask, negative_mask, options_) \ + ((const key_override_t){ \ + .trigger_mods = (trigger_mods_), \ + .layers = (layer_mask), \ + .suppressed_mods = (trigger_mods_), \ + .options = (options_), \ + .negative_mod_mask = (negative_mask), \ + .custom_action = NULL, \ + .context = NULL, \ + .trigger = (trigger_key), \ + .replacement = (replacement_key), \ + .enabled = NULL \ + }) + +// clang-format on diff --git a/quantum/process_keycode/process_key_override_private.h b/quantum/process_keycode/process_key_override_private.h new file mode 100644 index 0000000000..1d0e134a19 --- /dev/null +++ b/quantum/process_keycode/process_key_override_private.h @@ -0,0 +1,24 @@ +/* + * Copyright 2021 Jonas Gessner + * + * 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 "action.h" + +bool process_key_override(const uint16_t keycode, const keyrecord_t *const record); +void matrix_scan_key_override(void); diff --git a/quantum/quantum.c b/quantum/quantum.c index b4cfa28d7d..9cfffd9317 100644 --- a/quantum/quantum.c +++ b/quantum/quantum.c @@ -58,12 +58,16 @@ float bell_song[][2] = SONG(TERMINAL_SOUND); # include "process_auto_shift.h" #endif -static void do_code16(uint16_t code, void (*f)(uint8_t)) { +#ifdef KEY_OVERRIDE_ENABLE +# include "process_key_override_private.h" +#endif + +uint8_t extract_mod_bits(uint16_t code) { switch (code) { case QK_MODS ... QK_MODS_MAX: break; default: - return; + return 0; } uint8_t mods_to_send = 0; @@ -80,9 +84,11 @@ static void do_code16(uint16_t code, void (*f)(uint8_t)) { if (code & QK_LGUI) mods_to_send |= MOD_BIT(KC_LGUI); } - f(mods_to_send); + return mods_to_send; } +static void do_code16(uint16_t code, void (*f)(uint8_t)) { f(extract_mod_bits(code)); } + void register_code16(uint16_t code) { if (IS_MOD(code) || code == KC_NO) { do_code16(code, register_mods); @@ -243,6 +249,9 @@ bool process_record_quantum(keyrecord_t *record) { #if (defined(AUDIO_ENABLE) || (defined(MIDI_ENABLE) && defined(MIDI_BASIC))) && !defined(NO_MUSIC_MODE) process_music(keycode, record) && #endif +#ifdef KEY_OVERRIDE_ENABLE + process_key_override(keycode, record) && +#endif #ifdef TAP_DANCE_ENABLE process_tap_dance(keycode, record) && #endif @@ -408,6 +417,10 @@ void matrix_scan_quantum() { matrix_scan_music(); #endif +#ifdef KEY_OVERRIDE_ENABLE + matrix_scan_key_override(); +#endif + #ifdef SEQUENCER_ENABLE matrix_scan_sequencer(); #endif diff --git a/quantum/quantum.h b/quantum/quantum.h index 66ba96fde8..673796e6b1 100644 --- a/quantum/quantum.h +++ b/quantum/quantum.h @@ -118,6 +118,10 @@ extern layer_state_t layer_state; # include "process_unicodemap.h" #endif +#ifdef KEY_OVERRIDE_ENABLE +# include "process_key_override.h" +#endif + #ifdef TAP_DANCE_ENABLE # include "process_tap_dance.h" #endif diff --git a/quantum/quantum_keycodes.h b/quantum/quantum_keycodes.h index c361dd670e..3d2dbde922 100644 --- a/quantum/quantum_keycodes.h +++ b/quantum/quantum_keycodes.h @@ -514,6 +514,11 @@ enum quantum_keycodes { // RGB underglow/matrix (continued) RGB_MODE_TWINKLE, + // Key Overrides + KEY_OVERRIDE_TOGGLE, + KEY_OVERRIDE_ON, + KEY_OVERRIDE_OFF, + // Start of custom keycode range for keyboards and keymaps - always leave at the end SAFE_RANGE }; diff --git a/show_options.mk b/show_options.mk index af38bcb24b..dd1ad5171f 100644 --- a/show_options.mk +++ b/show_options.mk @@ -44,6 +44,7 @@ OTHER_OPTION_NAMES = \ AUTO_SHIFT_MODIFIERS \ COMBO_ENABLE \ KEY_LOCK_ENABLE \ + KEY_OVERRIDE_ENABLE \ LEADER_ENABLE \ PRINTING_ENABLE \ STENO_ENABLE \ diff --git a/tmk_core/common/action_util.c b/tmk_core/common/action_util.c index a57c8bf66a..2b3c00cba0 100644 --- a/tmk_core/common/action_util.c +++ b/tmk_core/common/action_util.c @@ -27,6 +27,10 @@ extern keymap_config_t keymap_config; static uint8_t real_mods = 0; static uint8_t weak_mods = 0; static uint8_t macro_mods = 0; +#ifdef KEY_OVERRIDE_ENABLE +static uint8_t weak_override_mods = 0; +static uint8_t suppressed_mods = 0; +#endif #ifdef USB_6KRO_ENABLE # define RO_ADD(a, b) ((a + b) % KEYBOARD_REPORT_KEYS) @@ -229,6 +233,7 @@ void send_keyboard_report(void) { keyboard_report->mods = real_mods; keyboard_report->mods |= weak_mods; keyboard_report->mods |= macro_mods; + #ifndef NO_ACTION_ONESHOT if (oneshot_mods) { # if (defined(ONESHOT_TIMEOUT) && (ONESHOT_TIMEOUT > 0)) @@ -244,6 +249,13 @@ void send_keyboard_report(void) { } #endif + +#ifdef KEY_OVERRIDE_ENABLE + // These need to be last to be able to properly control key overrides + keyboard_report->mods &= ~suppressed_mods; + keyboard_report->mods |= weak_override_mods; +#endif + host_keyboard_send(keyboard_report); } @@ -299,6 +311,22 @@ void set_weak_mods(uint8_t mods) { weak_mods = mods; } */ void clear_weak_mods(void) { weak_mods = 0; } +#ifdef KEY_OVERRIDE_ENABLE +/** \brief set weak mods used by key overrides. DO not call this manually + */ +void set_weak_override_mods(uint8_t mods) { weak_override_mods = mods; } +/** \brief clear weak mods used by key overrides. DO not call this manually + */ +void clear_weak_override_mods(void) { weak_override_mods = 0; } + +/** \brief set suppressed mods used by key overrides. DO not call this manually + */ +void set_suppressed_override_mods(uint8_t mods) { suppressed_mods = mods; } +/** \brief clear suppressed mods used by key overrides. DO not call this manually + */ +void clear_suppressed_override_mods(void) { suppressed_mods = 0; } +#endif + /* macro modifier */ /** \brief get macro mods * -- cgit v1.2.3 From 7e983796e18e7401c062c158f23966aeb7b1405b Mon Sep 17 00:00:00 2001 From: Pete Sevander Date: Fri, 6 Aug 2021 02:44:57 +0300 Subject: Process combos earlier & overlapping combos (#8591) * Combo processing improvements. Now it is possible to use ModTap and LayerTap keys as part of combos. Overlapping combos also don't trigger all the combos, just exactly the one that you press. New settings: - COMBO_MUST_HOLD_MODS - COMBO_MOD_TERM - COMBO_TERM_PER_COMBO - COMBO_MUST_HOLD_PER_COMBO - COMBO_STRICT_TIMER - COMBO_NO_TIMER * Remove the size flags from combo_t struct boolean members. This in the end actually saves space as the members are accessed so many times. The amount of operations needed to access the bits uses more memory than setting the size saves. * Fix `process_combo_key_release` not called correctly with tap-only combos * Fix not passing a pointer when NO_ACTION_TAPPING is defined. * Docs for `COMBO_ONLY_FROM_LAYER` * Update docs/feature_combo.md Co-authored-by: precondition <57645186+precondition@users.noreply.github.com> * Update quantum/process_keycode/process_combo.c Co-authored-by: precondition <57645186+precondition@users.noreply.github.com> * Add `EXTRA_SHORT_COMBOS` option. Stuff combo's `disabled` and `active` flags into `state`. Possibly can save some space. * Add more examples and clarify things with dict management system. - Simple examples now has a combo that has modifiers included. - The slightly more advanced examples now are actually more advanced instead of just `tap_code16()`. - Added a note that `COMBO_ACTION`s are not needed anymore as you can just use custom keycodes. - Added a note that the `g/keymap_combo.h` macros use the `process_combo_event` function and that it is not usable in one's keymap afterwards. * Update docs/feature_combo.md Co-authored-by: precondition <57645186+precondition@users.noreply.github.com> * Update docs/feature_combo.md Co-authored-by: precondition <57645186+precondition@users.noreply.github.com> * Update docs/feature_combo.md Co-authored-by: precondition <57645186+precondition@users.noreply.github.com> * Update docs/feature_combo.md Co-authored-by: precondition <57645186+precondition@users.noreply.github.com> * Update docs/feature_combo.md Co-authored-by: precondition <57645186+precondition@users.noreply.github.com> * Change "the" combo action example to "email" example. * Update docs/feature_combo.md Co-authored-by: precondition <57645186+precondition@users.noreply.github.com> * Fix sneaky infinite loop with `combo_disable()` No need to call `dump_key_buffer` when disabling combos because the buffer is either being dumped if a combo-key was pressed, or the buffer is empty if a non-combo-key is pressed. * Update docs/feature_combo.md Co-authored-by: precondition <57645186+precondition@users.noreply.github.com> * Update docs/feature_combo.md Co-authored-by: precondition <57645186+precondition@users.noreply.github.com> Co-authored-by: precondition <57645186+precondition@users.noreply.github.com> Co-authored-by: Drashna Jaelre --- docs/config_options.md | 14 +- docs/feature_combo.md | 292 +++++++++++++++-- keyboards/gboards/g/keymap_combo.h | 6 +- quantum/keymap_common.c | 3 + quantum/process_keycode/process_combo.c | 548 ++++++++++++++++++++++++++------ quantum/process_keycode/process_combo.h | 36 ++- quantum/quantum.c | 23 +- tmk_core/common/action.c | 37 ++- tmk_core/common/action.h | 5 + tmk_core/common/action_tapping.c | 41 ++- tmk_core/common/action_tapping.h | 1 + 11 files changed, 841 insertions(+), 165 deletions(-) (limited to 'docs/config_options.md') diff --git a/docs/config_options.md b/docs/config_options.md index 0c98b31010..78c1f70fd7 100644 --- a/docs/config_options.md +++ b/docs/config_options.md @@ -188,9 +188,21 @@ If you define these options you will enable the associated feature, which may in few ms of delay from this. But if you're doing chording on something with 3-4ms scan times? You probably want this. * `#define COMBO_COUNT 2` - * Set this to the number of combos that you're using in the [Combo](feature_combo.md) feature. + * Set this to the number of combos that you're using in the [Combo](feature_combo.md) feature. Or leave it undefined and programmatically set the count. * `#define COMBO_TERM 200` * how long for the Combo keys to be detected. Defaults to `TAPPING_TERM` if not defined. +* `#define COMBO_MUST_HOLD_MODS` + * Flag for enabling extending timeout on Combos containing modifers +* `#define COMBO_MOD_TERM 200` + * Allows for extending COMBO_TERM for mod keys while mid-combo. +* `#define COMBO_MUST_HOLD_PER_COMBO` + * Flag to enable per-combo COMBO_TERM extension and `get_combo_must_hold()` function +* `#define COMBO_TERM_PER_COMBO` + * Flag to enable per-combo COMBO_TERM extension and `get_combo_term()` function +* `#define COMBO_STRICT_TIMER` + * Only start the combo timer on the first key press instead of on all key presses. +* `#define COMBO_NO_TIMER` + * Disable the combo timer completely for relaxed combos. * `#define TAP_CODE_DELAY 100` * Sets the delay between `register_code` and `unregister_code`, if you're having issues with it registering properly (common on VUSB boards). The value is in milliseconds. * `#define TAP_HOLD_CAPS_DELAY 80` diff --git a/docs/feature_combo.md b/docs/feature_combo.md index d831328f69..d98e6f2ac7 100644 --- a/docs/feature_combo.md +++ b/docs/feature_combo.md @@ -1,24 +1,39 @@ # Combos -The Combo feature is a chording type solution for adding custom actions. It lets you hit multiple keys at once and produce a different effect. For instance, hitting `A` and `S` within the tapping term would hit `ESC` instead, or have it perform even more complex tasks. +The Combo feature is a chording type solution for adding custom actions. It lets you hit multiple keys at once and produce a different effect. For instance, hitting `A` and `S` within the combo term would hit `ESC` instead, or have it perform even more complex tasks. To enable this feature, you need to add `COMBO_ENABLE = yes` to your `rules.mk`. -Additionally, in your `config.h`, you'll need to specify the number of combos that you'll be using, by adding `#define COMBO_COUNT 1` (replacing 1 with the number that you're using). - +Additionally, in your `config.h`, you'll need to specify the number of combos that you'll be using, by adding `#define COMBO_COUNT 1` (replacing 1 with the number that you're using). It is also possible to not define this and instead set the variable `COMBO_LEN` yourself. There's a trick where we don't need to think about this variable at all. More on this later. -Also, by default, the tapping term for the Combos is set to the same value as `TAPPING_TERM` (200 by default on most boards). But you can specify a different value by defining it in your `config.h`. For instance: `#define COMBO_TERM 300` would set the time out period for combos to 300ms. -Then, your `keymap.c` file, you'll need to define a sequence of keys, terminated with `COMBO_END`, and a structure to list the combination of keys, and it's resulting action. +Then, in your `keymap.c` file, you'll need to define a sequence of keys, terminated with `COMBO_END`, and a structure to list the combination of keys, and its resulting action. ```c -const uint16_t PROGMEM test_combo[] = {KC_A, KC_B, COMBO_END}; -combo_t key_combos[COMBO_COUNT] = {COMBO(test_combo, KC_ESC)}; +const uint16_t PROGMEM test_combo1[] = {KC_A, KC_B, COMBO_END}; +const uint16_t PROGMEM test_combo2[] = {KC_C, KC_D, COMBO_END}; +combo_t key_combos[COMBO_COUNT] = { + COMBO(test_combo1, KC_ESC), + COMBO(test_combo2, LCTL(KC_Z)), // keycodes with modifiers are possible too! +}; ``` -This will send "Escape" if you hit the A and B keys. +This will send "Escape" if you hit the A and B keys, and Ctrl+Z when you hit the C and D keys. + +As of [PR#8591](https://github.com/qmk/qmk_firmware/pull/8591/), it is possible to fire combos from ModTap keys and LayerTap keys. So in the above example you could have keys `LSFT_T(KC_A)` and `LT(_LAYER, KC_B)` and it would work. So Home Row Mods and Home Row Combos at same time is now a thing! -!> This method only supports [basic keycodes](keycodes_basic.md). See the examples for more control. +It is also now possible to overlap combos. Before, with the example below both combos would activate when all three keys were pressed. Now only the three key combo will activate. + +```c +const uint16_t PROGMEM test_combo1[] = {LSFT_T(KC_A), LT(_LAYER, KC_B), COMBO_END}; +const uint16_t PROGMEM test_combo2[] = {LSFT_T(KC_A), LT(_LAYER, KC_B), KC_C, COMBO_END}; +combo_t key_combos[COMBO_COUNT] = { + COMBO(test_combo1, KC_ESC) + COMBO(test_combo2, KC_TAB) +}; +``` + +Executing more complex keycodes like ModTaps and LayerTaps is now also possible. ## Examples @@ -27,63 +42,68 @@ If you want to add a list, then you'd use something like this: ```c enum combos { AB_ESC, - JK_TAB + JK_TAB, + QW_SFT, + SD_LAYER, }; const uint16_t PROGMEM ab_combo[] = {KC_A, KC_B, COMBO_END}; const uint16_t PROGMEM jk_combo[] = {KC_J, KC_K, COMBO_END}; +const uint16_t PROGMEM qw_combo[] = {KC_Q, KC_W, COMBO_END}; +const uint16_t PROGMEM sd_combo[] = {KC_S, KC_D, COMBO_END}; combo_t key_combos[COMBO_COUNT] = { [AB_ESC] = COMBO(ab_combo, KC_ESC), - [JK_TAB] = COMBO(jk_combo, KC_TAB) + [JK_TAB] = COMBO(jk_combo, KC_TAB), + [QW_SFT] = COMBO(qw_combo, KC_LSFT) + [SD_LAYER] = COMBO(layer_combo, MO(_LAYER)), }; ``` For a more complicated implementation, you can use the `process_combo_event` function to add custom handling. +Additionally, this example shows how you can leave `COMBO_COUNT` undefined. ```c enum combo_events { - ZC_COPY, - XV_PASTE + EM_EMAIL, + BSPC_LSFT_CLEAR, + COMBO_LENGTH }; +uint16_t COMBO_LEN = COMBO_LENGTH; // remove the COMBO_COUNT define and use this instead! -const uint16_t PROGMEM copy_combo[] = {KC_Z, KC_C, COMBO_END}; -const uint16_t PROGMEM paste_combo[] = {KC_X, KC_V, COMBO_END}; +const uint16_t PROGMEM email_combo[] = {KC_E, KC_M, COMBO_END}; +const uint16_t PROGMEM clear_line_combo[] = {KC_BSPC, KC_LSFT, COMBO_END}; -combo_t key_combos[COMBO_COUNT] = { - [ZC_COPY] = COMBO_ACTION(copy_combo), - [XV_PASTE] = COMBO_ACTION(paste_combo), +combo_t key_combos[] = { + [EM_EMAIL] = COMBO_ACTION(email_combo), + [BSPC_LSFT_CLEAR] = COMBO_ACTION(clear_line_combo), }; +/* COMBO_ACTION(x) is same as COMBO(x, KC_NO) */ void process_combo_event(uint16_t combo_index, bool pressed) { switch(combo_index) { - case ZC_COPY: + case EM_EMAIL: if (pressed) { - tap_code16(LCTL(KC_C)); + SEND_STRING("john.doe@example.com"); } break; - case XV_PASTE: + case BSPC_LSFT_CLEAR: if (pressed) { - tap_code16(LCTL(KC_V)); + tap_code16(KC_END); + tap_code16(S(KC_HOME)); + tap_code16(KC_BSPC); } break; } } ``` -This will send Ctrl+C if you hit Z and C, and Ctrl+V if you hit X and V. But you could change this to do stuff like change layers, play sounds, or change settings. - -## Additional Configuration +This will send "john.doe@example.com" if you chord E and M together, and clear the current line with Backspace and Left-Shift. You could change this to do stuff like play sounds or change settings. -If you're using long combos, or even longer combos, you may run into issues with this, as the structure may not be large enough to accommodate what you're doing. +It is worth noting that `COMBO_ACTION`s are not needed anymore. As of [PR#8591](https://github.com/qmk/qmk_firmware/pull/8591/), it is possible to run your own custom keycodes from combos. Just define the custom keycode, program its functionality in `process_record_user`, and define a combo with `COMBO(, )`. -In this case, you can add either `#define EXTRA_LONG_COMBOS` or `#define EXTRA_EXTRA_LONG_COMBOS` in your `config.h` file. - -You may also be able to enable action keys by defining `COMBO_ALLOW_ACTION_KEYS`. - -## Keycodes - -You can enable, disable and toggle the Combo feature on the fly. This is useful if you need to disable them temporarily, such as for a game. +## Keycodes +You can enable, disable and toggle the Combo feature on the fly. This is useful if you need to disable them temporarily, such as for a game. The following keycodes are available for use in your `keymap.c` |Keycode |Description | |----------|---------------------------------| @@ -91,6 +111,187 @@ You can enable, disable and toggle the Combo feature on the fly. This is useful |`CMB_OFF` |Turns off Combo feature | |`CMB_TOG` |Toggles Combo feature on and off | +# Advanced Configuration +These configuration settings can be set in your `config.h` file. + +## Combo Term +By default, the timeout for the Combos to be recognized is set to 50ms. This can be changed if accidental combo misfires are happening or if you're having difficulties pressing keys at the same time. For instance, `#define COMBO_TERM 40` would set the timeout period for combos to 40ms. + +## Buffer and state sizes +If you're using long combos, or you have a lot of overlapping combos, you may run into issues with this, as the buffers may not be large enough to accommodate what you're doing. In this case, you can configure the sizes of the buffers used. Be aware, larger combo sizes and larger buffers will increase memory usage! + +To configure the amount of keys a combo can be composed of, change the following: + +| Keys | Define to be set | +|------|-----------------------------------| +| 6 | `#define EXTRA_SHORT_COMBOS` | +| 8 | QMK Default | +| 16 | `#define EXTRA_LONG_COMBOS` | +| 32 | `#define EXTRA_EXTRA_LONG_COMBOS` | + +Defining `EXTRA_SHORT_COMBOS` combines a combo's internal state into just one byte. This can, in some cases, save some memory. If it doesn't, no point using it. If you do, you also have to make sure you don't define combos with more than 6 keys. + +Processing combos has two buffers, one for the key presses, another for the combos being activated. Use the following options to configure the sizes of these buffers: + +| Define | Default | +|-------------------------------------|------------------------------------------------------| +| `#define COMBO_KEY_BUFFER_LENGTH 8` | 8 (the key amount `(EXTRA_)EXTRA_LONG_COMBOS` gives) | +| `#define COMBO_BUFFER_LENGTH 4` | 4 | + +## Modifier Combos +If a combo resolves to a Modifier, the window for processing the combo can be extended independently from normal combos. By default, this is disabled but can be enabled with `#define COMBO_MUST_HOLD_MODS`, and the time window can be configured with `#define COMBO_HOLD_TERM 150` (default: `TAPPING_TERM`). With `COMBO_MUST_HOLD_MODS`, you cannot tap the combo any more which makes the combo less prone to misfires. + +## Per Combo Timing, Holding and Tapping +For each combo, it is possible to configure the time window it has to pressed in, if it needs to be held down, or if it needs to be tapped. + +For example, tap-only combos are useful if any (or all) of the underlying keys is a Mod-Tap or a Layer-Tap key. When you tap the combo, you get the combo result. When you press the combo and hold it down, the combo doesn't actually activate. Instead the keys are processed separately as if the combo wasn't even there. + +In order to use these features, the following configuration options and functions need to be defined. Coming up with useful timings and configuration is left as an exercise for the reader. + +| Config Flag | Function | Description | +|-----------------------------|-----------------------------------------------------------|--------------------------------------------------------------------------------------------------------| +| `COMBO_TERM_PER_COMBO` | uint16_t get_combo_term(uint16_t index, combo_t \*combo) | Optional per-combo timeout window. (default: `COMBO_TERM`) | +| `COMBO_MUST_HOLD_PER_COMBO` | bool get_combo_must_hold(uint16_t index, combo_t \*combo) | Controls if a given combo should fire immediately on tap or if it needs to be held. (default: `false`) | +| `COMBO_MUST_TAP_PER_COMBO` | bool get_combo_must_tap(uint16_t index, combo_t \*combo) | Controls if a given combo should fire only if tapped within `COMBO_HOLD_TERM`. (default: `false`) | + +Examples: +```c +uint16_t get_combo_term(uint16_t index, combo_t *combo) { + // decide by combo->keycode + switch (combo->keycode) { + case KC_X: + return 50; + } + + // or with combo index, i.e. its name from enum. + switch (index) { + case COMBO_NAME_HERE: + return 9001; + } + + // And if you're feeling adventurous, you can even decide by the keys in the chord, + // i.e. the exact array of keys you defined for the combo. + // This can be useful if your combos have a common key and you want to apply the + // same combo term for all of them. + if (combo->keys[0] == KC_ENTER) { // if first key in the array is KC_ENTER + return 150; + } + + return COMBO_TERM; +} + +bool get_combo_must_hold(uint16_t index, combo_t *combo) { + // Same as above, decide by keycode, the combo index, or by the keys in the chord. + + if (KEYCODE_IS_MOD(combo->keycode) || + (combo->keycode >= QK_MOMENTARY && combo->keycode <= QK_MOMENTARY_MAX) // MO(kc) keycodes + ) { + return true; + } + + switch (index) { + case COMBO_NAME_HERE: + return true; + } + + return false; +} + +bool get_combo_must_tap(uint16_t index, combo_t *combo) { + // If you want all combos to be tap-only, just uncomment the next line + // return true + + // If you want *all* combos, that have Mod-Tap/Layer-Tap/Momentary keys in its chord, to be tap-only, this is for you: + uint16_t key; + uint8_t idx = 0; + while ((key = pgm_read_word(&combo->keys[idx])) != COMBO_END) { + switch (key) { + case QK_MOD_TAP...QK_MOD_TAP_MAX: + case QK_LAYER_TAP...QK_LAYER_TAP_MAX: + case QK_MOMENTARY...QK_MOMENTARY_MAX: + return true; + } + idx += 1; + } + return false; + +} +``` + +## Variable Length Combos +If you leave `COMBO_COUNT` undefined in `config.h`, it allows you to programmatically declare the size of the Combo data structure and avoid updating `COMBO_COUNT`. Instead a variable called `COMBO_LEN` has to be set. It can be set with something similar to the following in `keymap.c`: `uint16_t COMBO_LEN = sizeof(key_combos) / sizeof(key_combos[0]);` or by adding `COMBO_LENGTH` as the *last* entry in the combo enum and then `uint16_t COMBO_LEN = COMBO_LENGTH;` as such: +```c +enum myCombos { + ..., + COMBO_LENGTH +}; +uint16_t COMBO_LEN = COMBO_LENGTH; +``` +Regardless of the method used to declare `COMBO_LEN`, this also requires to convert the `combo_t key_combos[COMBO_COUNT] = {...};` line to `combo_t key_combos[] = {...};`. + + +## Combo timer + +Normally, the timer is started on the first key press and then reset on every subsequent key press within the `COMBO_TERM`. +Inputting combos is relaxed like this, but also slightly more prone to accidental misfires. + +The next two options alter the behaviour of the timer. + +### `#define COMBO_STRICT_TIMER` + +With `COMBO_STRICT_TIMER`, the timer is started only on the first key press. +Inputting combos is now less relaxed; you need to make sure the full chord is pressed within the `COMBO_TERM`. +Misfires are less common but if you type multiple combos fast, there is a +chance that the latter ones might not activate properly. + +### `#define COMBO_NO_TIMER` + +By defining `COMBO_NO_TIMER`, the timer is disabled completely and combos are activated on the first key release. +This also disables the "must hold" functionalities as they just wouldn't work at all. + +## Customizable key releases + +By defining `COMBO_PROCESS_KEY_RELEASE` and implementing the function `bool process_combo_key_release(uint16_t combo_index, combo_t *combo, uint8_t key_index, uint16_t keycode)`, you can run your custom code on each key release after a combo was activated. For example you could change the RGB colors, activate haptics, or alter the modifiers. + +You can also release a combo early by returning `true` from the function. + +Here's an example where a combo resolves to two modifiers, and on key releases the modifiers are unregistered one by one, depending on which key was released. + +```c +enum combos { + AB_MODS, + COMBO_LENGTH +}; +uint16_t COMBO_LEN = COMBO_LENGTH; + +const uint16_t PROGMEM ab_combo[] = {KC_A, KC_B, COMBO_END}; + +combo_t key_combos[] = { + [AB_MODS] = COMBO(ab_combo, LCTL(KC_LSFT)), +}; + +bool process_combo_key_release(uint16_t combo_index, combo_t *combo, uint8_t key_index, uint16_t keycode) { + switch (combo_index) { + case AB_MODS: + switch(keycode) { + case KC_A: + unregister_mods(MOD_MASK_CTRL); + break; + case KC_B: + unregister_mods(MOD_MASK_SHIFT); + break; + } + return false; // do not release combo + } + return false; +} +``` +## Layer independent combos + +If you, for example, use multiple base layers for different key layouts, one for QWERTY, and another one for Colemak, you might want your combos to work from the same key positions on all layers. Defining the same combos again for another layout is redundant and takes more memory. The solution is to just check the keycodes from one layer. + +With `#define COMBO_ONLY_FROM_LAYER _LAYER_A` the combos' keys are always checked from layer `_LAYER_A` even though the active layer would be `_LAYER_B`. + ## User callbacks In addition to the keycodes, there are a few functions that you can use to set the status, or check it: @@ -101,3 +302,28 @@ In addition to the keycodes, there are a few functions that you can use to set t | `combo_disable()` | Disables the combo feature, and clears the combo buffer | | `combo_toggle()` | Toggles the state of the combo feature | | `is_combo_enabled()` | Returns the status of the combo feature state (true or false) | + + +# Dictionary Management + +Having 3 places to update when adding new combos or altering old ones does become cumbersome when you have a lot of combos. We can alleviate this with some magic! ... If you consider C macros magic. +First, you need to add `VPATH += keyboards/gboards` to your `rules.mk`. Next, include the file `g/keymap_combo.h` in your `keymap.c`. + +!> This functionality uses the same `process_combo_event` function as `COMBO_ACTION` macros do, so you cannot use the function yourself in your keymap. Instead, you have to define the `case`s of the `switch` statement by themselves within `inject.h`, which `g/keymap_combo.h` will then include into the function. + +Then, write your combos in `combos.def` file in the following manner: + +```c +// name result chord keys +COMB(AB_ESC, KC_ESC, KC_A, KC_B) +COMB(JK_TAB, KC_TAB, KC_J, KC_K) +COMB(JKL_SPC, KC_SPC, KC_J, KC_K, KC_L) +COMB(BSSL_CLR, KC_NO, KC_BSPC, KC_LSFT) // using KC_NO as the resulting keycode is the same as COMBO_ACTION before. +COMB(QW_UNDO, C(KC_Z), KC_Q, KC_W) +SUBS(TH_THE, "the", KC_T, KC_H) // SUBS uses SEND_STRING to output the given string. +... +``` + +Now, you can update only one place to add or alter combos. You don't even need to remember to update the `COMBO_COUNT` or the `COMBO_LEN` variables at all. Everything is taken care of. Magic! + +For small to huge ready made dictionaries of combos, you can check out http://combos.gboards.ca/. diff --git a/keyboards/gboards/g/keymap_combo.h b/keyboards/gboards/g/keymap_combo.h index 58e99863ea..b92b6a4bc4 100644 --- a/keyboards/gboards/g/keymap_combo.h +++ b/keyboards/gboards/g/keymap_combo.h @@ -28,7 +28,10 @@ #define TOGG A_ENUM enum combos { #include "combos.def" + COMBO_LENGTH }; +// Export length to combo module +uint16_t COMBO_LEN = COMBO_LENGTH; // Bake combos into mem #undef COMB @@ -53,9 +56,6 @@ combo_t key_combos[] = { #undef SUBS #undef TOGG -// Export length to combo module -int COMBO_LEN = sizeof(key_combos) / sizeof(key_combos[0]); - // Fill QMK hook #define COMB BLANK #define SUBS A_ACTI diff --git a/quantum/keymap_common.c b/quantum/keymap_common.c index e0fd6d4793..780c71ab9b 100644 --- a/quantum/keymap_common.c +++ b/quantum/keymap_common.c @@ -40,7 +40,10 @@ extern keymap_config_t keymap_config; action_t action_for_key(uint8_t layer, keypos_t key) { // 16bit keycodes - important uint16_t keycode = keymap_key_to_keycode(layer, key); + return action_for_keycode(keycode); +}; +action_t action_for_keycode(uint16_t keycode) { // keycode remapping keycode = keycode_config(keycode); diff --git a/quantum/process_keycode/process_combo.c b/quantum/process_keycode/process_combo.c index 9e16292486..b4e698decd 100644 --- a/quantum/process_keycode/process_combo.c +++ b/quantum/process_keycode/process_combo.c @@ -16,114 +16,445 @@ #include "print.h" #include "process_combo.h" +#include "action_tapping.h" -#ifndef COMBO_VARIABLE_LEN -__attribute__((weak)) combo_t key_combos[COMBO_COUNT] = {}; + +#ifdef COMBO_COUNT +__attribute__((weak)) combo_t key_combos[COMBO_COUNT]; +uint16_t COMBO_LEN = COMBO_COUNT; #else extern combo_t key_combos[]; -extern int COMBO_LEN; +extern uint16_t COMBO_LEN; #endif __attribute__((weak)) void process_combo_event(uint16_t combo_index, bool pressed) {} -static uint16_t timer = 0; -static uint16_t current_combo_index = 0; -static bool drop_buffer = false; -static bool is_active = false; -static bool b_combo_enable = true; // defaults to enabled +#ifdef COMBO_MUST_HOLD_PER_COMBO +__attribute__((weak)) bool get_combo_must_hold(uint16_t index, combo_t *combo) { return false; } +#endif + +#ifdef COMBO_MUST_TAP_PER_COMBO +__attribute__((weak)) bool get_combo_must_tap(uint16_t index, combo_t *combo) { return false; } +#endif -static uint8_t buffer_size = 0; -#ifdef COMBO_ALLOW_ACTION_KEYS -static keyrecord_t key_buffer[MAX_COMBO_LENGTH]; +#ifdef COMBO_TERM_PER_COMBO +__attribute__((weak)) uint16_t get_combo_term(uint16_t index, combo_t *combo) { return COMBO_TERM; } +#endif + +#ifdef COMBO_PROCESS_KEY_RELEASE +__attribute__((weak)) bool process_combo_key_release(uint16_t combo_index, combo_t *combo, uint8_t key_index, uint16_t keycode) { return false; } +#endif + +#ifndef COMBO_NO_TIMER +static uint16_t timer = 0; +#endif +static bool b_combo_enable = true; // defaults to enabled +static uint16_t longest_term = 0; + +typedef struct { + keyrecord_t record; + uint16_t combo_index; + uint16_t keycode; +} queued_record_t; +static uint8_t key_buffer_size = 0; +static queued_record_t key_buffer[COMBO_KEY_BUFFER_LENGTH]; + +typedef struct { + uint16_t combo_index; +} queued_combo_t; +static uint8_t combo_buffer_write= 0; +static uint8_t combo_buffer_read = 0; +static queued_combo_t combo_buffer[COMBO_BUFFER_LENGTH]; + +#define INCREMENT_MOD(i) i = (i + 1) % COMBO_BUFFER_LENGTH + +#define COMBO_KEY_POS ((keypos_t){.col=254, .row=254}) + + +#ifndef EXTRA_SHORT_COMBOS +/* flags are their own elements in combo_t struct. */ +# define COMBO_ACTIVE(combo) (combo->active) +# define COMBO_DISABLED(combo) (combo->disabled) +# define COMBO_STATE(combo) (combo->state) + +# define ACTIVATE_COMBO(combo) do {combo->active = true;}while(0) +# define DEACTIVATE_COMBO(combo) do {combo->active = false;}while(0) +# define DISABLE_COMBO(combo) do {combo->disabled = true;}while(0) +# define RESET_COMBO_STATE(combo) do { \ + combo->disabled = false; \ + combo->state = 0; \ +}while(0) #else -static uint16_t key_buffer[MAX_COMBO_LENGTH]; +/* flags are at the two high bits of state. */ +# define COMBO_ACTIVE(combo) (combo->state & 0x80) +# define COMBO_DISABLED(combo) (combo->state & 0x40) +# define COMBO_STATE(combo) (combo->state & 0x3F) + +# define ACTIVATE_COMBO(combo) do {combo->state |= 0x80;}while(0) +# define DEACTIVATE_COMBO(combo) do {combo->state &= ~0x80;}while(0) +# define DISABLE_COMBO(combo) do {combo->state |= 0x40;}while(0) +# define RESET_COMBO_STATE(combo) do {combo->state &= ~0x7F;}while(0) #endif -static inline void send_combo(uint16_t action, bool pressed) { - if (action) { - if (pressed) { - register_code16(action); - } else { - unregister_code16(action); - } +static inline void release_combo(uint16_t combo_index, combo_t *combo) { + if (combo->keycode) { + keyrecord_t record = { + .event = { + .key = COMBO_KEY_POS, + .time = timer_read()|1, + .pressed = false, + }, + .keycode = combo->keycode, + }; +#ifndef NO_ACTION_TAPPING + action_tapping_process(record); +#else + process_record(&record); +#endif } else { - process_combo_event(current_combo_index, pressed); + process_combo_event(combo_index, false); + } + DEACTIVATE_COMBO(combo); +} + +static inline bool _get_combo_must_hold(uint16_t combo_index, combo_t *combo) { +#ifdef COMBO_NO_TIMER + return false; +#elif defined(COMBO_MUST_HOLD_PER_COMBO) + return get_combo_must_hold(combo_index, combo); +#elif defined(COMBO_MUST_HOLD_MODS) + return (KEYCODE_IS_MOD(combo->keycode) || + (combo->keycode >= QK_MOMENTARY && combo->keycode <= QK_MOMENTARY_MAX)); +#endif + return false; +} + +static inline uint16_t _get_wait_time(uint16_t combo_index, combo_t *combo ) { + if (_get_combo_must_hold(combo_index, combo) +#ifdef COMBO_MUST_TAP_PER_COMBO + || get_combo_must_tap(combo_index, combo) +#endif + ) { + if (longest_term < COMBO_HOLD_TERM) { + return COMBO_HOLD_TERM; + } + } + + return longest_term; +} + +static inline uint16_t _get_combo_term(uint16_t combo_index, combo_t *combo) { + +#if defined(COMBO_TERM_PER_COMBO) + return get_combo_term(combo_index, combo); +#endif + + return COMBO_TERM; +} + +void clear_combos(void) { + uint16_t index = 0; + longest_term = 0; + for (index = 0; index < COMBO_LEN; ++index) { + combo_t *combo = &key_combos[index]; + if (!COMBO_ACTIVE(combo)) { + RESET_COMBO_STATE(combo); + } } } -static inline void dump_key_buffer(bool emit) { - if (buffer_size == 0) { +static inline void dump_key_buffer(void) { + if (key_buffer_size == 0) { return; } - if (emit) { - for (uint8_t i = 0; i < buffer_size; i++) { -#ifdef COMBO_ALLOW_ACTION_KEYS - const action_t action = store_or_get_action(key_buffer[i].event.pressed, key_buffer[i].event.key); - process_action(&(key_buffer[i]), action); + for (uint8_t key_buffer_i = 0; key_buffer_i < key_buffer_size; key_buffer_i++) { + + queued_record_t *qrecord = &key_buffer[key_buffer_i]; + keyrecord_t *record = &qrecord->record; + + if (IS_NOEVENT(record->event)) { + continue; + } + + if (!record->keycode && qrecord->combo_index != (uint16_t)-1) { + process_combo_event(qrecord->combo_index, true); + } else { +#ifndef NO_ACTION_TAPPING + action_tapping_process(*record); #else - register_code16(key_buffer[i]); - send_keyboard_report(); + process_record(record); #endif } + record->event.time = 0; } - buffer_size = 0; + key_buffer_size = 0; } -#define ALL_COMBO_KEYS_ARE_DOWN (((1 << count) - 1) == combo->state) -#define KEY_STATE_DOWN(key) \ - do { \ - combo->state |= (1 << key); \ +#define NO_COMBO_KEYS_ARE_DOWN (0 == COMBO_STATE(combo)) +#define ALL_COMBO_KEYS_ARE_DOWN(state, key_count) (((1 << key_count) - 1) == state) +#define ONLY_ONE_KEY_IS_DOWN(state) !(state & (state - 1)) +#define KEY_NOT_YET_RELEASED(state, key_index) ((1 << key_index) & state) +#define KEY_STATE_DOWN(state, key_index) \ + do { \ + state |= (1 << key_index); \ } while (0) -#define KEY_STATE_UP(key) \ - do { \ - combo->state &= ~(1 << key); \ +#define KEY_STATE_UP(state, key_index) \ + do { \ + state &= ~(1 << key_index); \ } while (0) -static bool process_single_combo(combo_t *combo, uint16_t keycode, keyrecord_t *record) { - uint8_t count = 0; - uint16_t index = -1; - /* Find index of keycode and number of combo keys */ - for (const uint16_t *keys = combo->keys;; ++count) { - uint16_t key = pgm_read_word(&keys[count]); - if (keycode == key) index = count; +static inline void _find_key_index_and_count(const uint16_t *keys, uint16_t keycode, uint16_t *key_index, uint8_t *key_count) { + while (true) { + uint16_t key = pgm_read_word(&keys[*key_count]); + if (keycode == key) *key_index = *key_count; if (COMBO_END == key) break; + (*key_count)++; + } +} + +void drop_combo_from_buffer(uint16_t combo_index) { + /* Mark a combo as processed from the buffer. If the buffer is in the + * beginning of the buffer, drop it. */ + uint8_t i = combo_buffer_read; + while (i != combo_buffer_write) { + queued_combo_t *qcombo = &combo_buffer[i]; + + if (qcombo->combo_index == combo_index) { + combo_t *combo = &key_combos[combo_index]; + DISABLE_COMBO(combo); + + if (i == combo_buffer_read) { + INCREMENT_MOD(combo_buffer_read); + } + break; + } + INCREMENT_MOD(i); } +} + +void apply_combo(uint16_t combo_index, combo_t *combo) { + /* Apply combo's result keycode to the last chord key of the combo and + * disable the other keys. */ - /* Continue processing if not a combo key */ - if (-1 == (int8_t)index) return false; + if (COMBO_DISABLED(combo)) { return; } - bool is_combo_active = is_active; + // state to check against so we find the last key of the combo from the buffer +#if defined(EXTRA_EXTRA_LONG_COMBOS) + uint32_t state = 0; +#elif defined(EXTRA_LONG_COMBOS) + uint16_t state = 0; +#else + uint8_t state = 0; +#endif + + for (uint8_t key_buffer_i = 0; key_buffer_i < key_buffer_size; key_buffer_i++) { + + queued_record_t *qrecord = &key_buffer[key_buffer_i]; + keyrecord_t *record = &qrecord->record; + uint16_t keycode = qrecord->keycode; + + uint8_t key_count = 0; + uint16_t key_index = -1; + _find_key_index_and_count(combo->keys, keycode, &key_index, &key_count); + + if (-1 == (int16_t)key_index) { + // key not part of this combo + continue; + } - if (record->event.pressed) { - KEY_STATE_DOWN(index); + KEY_STATE_DOWN(state, key_index); + if (ALL_COMBO_KEYS_ARE_DOWN(state, key_count)) { + // this in the end executes the combo when the key_buffer is dumped. + record->keycode = combo->keycode; + record->event.key = COMBO_KEY_POS; - if (is_combo_active) { - if (ALL_COMBO_KEYS_ARE_DOWN) { /* Combo was pressed */ - send_combo(combo->keycode, true); - drop_buffer = true; + qrecord->combo_index = combo_index; + ACTIVATE_COMBO(combo); + + break; + } else { + // key was part of the combo but not the last one, "disable" it + // by making it a TICK event. + record->event.time = 0; + } + + } + drop_combo_from_buffer(combo_index); +} + +static inline void apply_combos(void) { + // Apply all buffered normal combos. + for (uint8_t i = combo_buffer_read; + i != combo_buffer_write; + INCREMENT_MOD(i)) { + + queued_combo_t *buffered_combo = &combo_buffer[i]; + combo_t *combo = &key_combos[buffered_combo->combo_index]; + +#ifdef COMBO_MUST_TAP_PER_COMBO + if (get_combo_must_tap(buffered_combo->combo_index, combo)) { + // Tap-only combos are applied on key release only, so let's drop 'em here. + drop_combo_from_buffer(buffered_combo->combo_index); + continue; + } +#endif + apply_combo(buffered_combo->combo_index, combo); + } + dump_key_buffer(); + clear_combos(); +} + +combo_t* overlaps(combo_t *combo1, combo_t *combo2) { + /* Checks if the combos overlap and returns the combo that should be + * dropped from the combo buffer. + * The combo that has less keys will be dropped. If they have the same + * amount of keys, drop combo1. */ + + uint8_t idx1 = 0, idx2 = 0; + uint16_t key1, key2; + bool overlaps = false; + + while ((key1 = pgm_read_word(&combo1->keys[idx1])) != COMBO_END) { + idx2 = 0; + while ((key2 = pgm_read_word(&combo2->keys[idx2])) != COMBO_END) { + if (key1 == key2) overlaps = true; + idx2 += 1; + } + idx1 += 1; + } + + if (!overlaps) return NULL; + if (idx2 < idx1) return combo2; + return combo1; +} + +static bool process_single_combo(combo_t *combo, uint16_t keycode, keyrecord_t *record, uint16_t combo_index) { + uint8_t key_count = 0; + uint16_t key_index = -1; + _find_key_index_and_count(combo->keys, keycode, &key_index, &key_count); + + /* Continue processing if key isn't part of current combo. */ + if (-1 == (int16_t)key_index) { + return false; + } + + bool key_is_part_of_combo = !COMBO_DISABLED(combo); + + if (record->event.pressed && !COMBO_DISABLED(combo)) { + uint16_t time = _get_combo_term(combo_index, combo); + if (!COMBO_ACTIVE(combo)) { + KEY_STATE_DOWN(combo->state, key_index); + if (longest_term < time) { + longest_term = time; } } + if (ALL_COMBO_KEYS_ARE_DOWN(COMBO_STATE(combo), key_count)) { + /* Combo was fully pressed */ + /* Buffer the combo so we can fire it after COMBO_TERM */ + +#ifndef COMBO_NO_TIMER + /* Don't buffer this combo if its combo term has passed. */ + if (timer && timer_elapsed(timer) > time) { + DISABLE_COMBO(combo); + return true; + } else +#endif + { + + // disable readied combos that overlap with this combo + combo_t *drop = NULL; + for (uint8_t combo_buffer_i = combo_buffer_read; + combo_buffer_i != combo_buffer_write; + INCREMENT_MOD(combo_buffer_i)) { + + queued_combo_t *qcombo = &combo_buffer[combo_buffer_i]; + combo_t *buffered_combo = &key_combos[qcombo->combo_index]; + + if ((drop = overlaps(buffered_combo, combo))) { + DISABLE_COMBO(drop); + if (drop == combo) { + // stop checking for overlaps if dropped combo was current combo. + break; + } else if (combo_buffer_i == combo_buffer_read && drop == buffered_combo) { + /* Drop the disabled buffered combo from the buffer if + * it is in the beginning of the buffer. */ + INCREMENT_MOD(combo_buffer_read); + } + } + + } + + if (drop != combo) { + // save this combo to buffer + combo_buffer[combo_buffer_write] = (queued_combo_t){ + .combo_index=combo_index, + }; + INCREMENT_MOD(combo_buffer_write); + + // get possible longer waiting time for tap-/hold-only combos. + longest_term = _get_wait_time(combo_index, combo); + } + } // if timer elapsed end + + } } else { - if (ALL_COMBO_KEYS_ARE_DOWN) { /* Combo was released */ - send_combo(combo->keycode, false); + // chord releases + if (!COMBO_ACTIVE(combo) && ALL_COMBO_KEYS_ARE_DOWN(COMBO_STATE(combo), key_count)) { + /* First key quickly released */ + if (COMBO_DISABLED(combo) || _get_combo_must_hold(combo_index, combo)) { + // combo wasn't tappable, disable it and drop it from buffer. + drop_combo_from_buffer(combo_index); + key_is_part_of_combo = false; + } +#ifdef COMBO_MUST_TAP_PER_COMBO + else if (get_combo_must_tap(combo_index, combo)) { + // immediately apply tap-only combo + apply_combo(combo_index, combo); + apply_combos(); // also apply other prepared combos and dump key buffer +# ifdef COMBO_PROCESS_KEY_RELEASE + if (process_combo_key_release(combo_index, combo, key_index, keycode)) { + release_combo(combo_index, combo); + } +# endif + } +#endif + } else if (COMBO_ACTIVE(combo) + && ONLY_ONE_KEY_IS_DOWN(COMBO_STATE(combo)) + && KEY_NOT_YET_RELEASED(COMBO_STATE(combo), key_index) + ) { + /* last key released */ + release_combo(combo_index, combo); + key_is_part_of_combo = true; + +#ifdef COMBO_PROCESS_KEY_RELEASE + process_combo_key_release(combo_index, combo, key_index, keycode); +#endif + } else if (COMBO_ACTIVE(combo) + && KEY_NOT_YET_RELEASED(COMBO_STATE(combo), key_index) + ) { + /* first or middle key released */ + key_is_part_of_combo = true; + +#ifdef COMBO_PROCESS_KEY_RELEASE + if (process_combo_key_release(combo_index, combo, key_index, keycode)) { + release_combo(combo_index, combo); + } +#endif } else { - /* continue processing without immediately returning */ - is_combo_active = false; + /* The released key was part of an incomplete combo */ + key_is_part_of_combo = false; } - KEY_STATE_UP(index); + KEY_STATE_UP(combo->state, key_index); } - return is_combo_active; + return key_is_part_of_combo; } -#define NO_COMBO_KEYS_ARE_DOWN (0 == combo->state) - bool process_combo(uint16_t keycode, keyrecord_t *record) { bool is_combo_key = false; - drop_buffer = false; bool no_combo_keys_pressed = true; if (keycode == CMB_ON && record->event.pressed) { @@ -144,62 +475,81 @@ bool process_combo(uint16_t keycode, keyrecord_t *record) { if (!is_combo_enabled()) { return true; } -#ifndef COMBO_VARIABLE_LEN - for (current_combo_index = 0; current_combo_index < COMBO_COUNT; ++current_combo_index) { -#else - for (current_combo_index = 0; current_combo_index < COMBO_LEN; ++current_combo_index) { + +#ifdef COMBO_ONLY_FROM_LAYER + /* Only check keycodes from one layer. */ + keycode = keymap_key_to_keycode(COMBO_ONLY_FROM_LAYER, record->event.key); #endif - combo_t *combo = &key_combos[current_combo_index]; - is_combo_key |= process_single_combo(combo, keycode, record); - no_combo_keys_pressed = no_combo_keys_pressed && NO_COMBO_KEYS_ARE_DOWN; + + for (uint16_t idx = 0; idx < COMBO_LEN; ++idx) { + combo_t *combo = &key_combos[idx]; + is_combo_key |= process_single_combo(combo, keycode, record, idx); + no_combo_keys_pressed = no_combo_keys_pressed && (NO_COMBO_KEYS_ARE_DOWN || COMBO_ACTIVE(combo) || COMBO_DISABLED(combo)); } - if (drop_buffer) { - /* buffer is only dropped when we complete a combo, so we refresh the timer - * here */ - timer = timer_read(); - dump_key_buffer(false); - } else if (!is_combo_key) { - /* if no combos claim the key we need to emit the keybuffer */ - dump_key_buffer(true); - - // reset state if there are no combo keys pressed at all - if (no_combo_keys_pressed) { - timer = 0; - is_active = true; - } - } else if (record->event.pressed && is_active) { - /* otherwise the key is consumed and placed in the buffer */ + if (record->event.pressed && is_combo_key) { +#ifndef COMBO_NO_TIMER +# ifdef COMBO_STRICT_TIMER + if (!timer) { + // timer is set only on the first key + timer = timer_read(); + } +# else timer = timer_read(); +# endif +#endif - if (buffer_size < MAX_COMBO_LENGTH) { -#ifdef COMBO_ALLOW_ACTION_KEYS - key_buffer[buffer_size++] = *record; -#else - key_buffer[buffer_size++] = keycode; + if (key_buffer_size < COMBO_KEY_BUFFER_LENGTH) { + key_buffer[key_buffer_size++] = (queued_record_t){ + .record = *record, + .keycode = keycode, + .combo_index = -1, // this will be set when applying combos + }; + } + } else { + if (combo_buffer_read != combo_buffer_write) { + // some combo is prepared + apply_combos(); + } else { + // reset state if there are no combo keys pressed at all + dump_key_buffer(); +#ifndef COMBO_NO_TIMER + timer = 0; #endif + clear_combos(); } } - return !is_combo_key; } void combo_task(void) { - if (b_combo_enable && is_active && timer && timer_elapsed(timer) > COMBO_TERM) { - /* This disables the combo, meaning key events for this - * combo will be handled by the next processors in the chain - */ - is_active = false; - dump_key_buffer(true); + if (!b_combo_enable) { + return; } + +#ifndef COMBO_NO_TIMER + if (timer && timer_elapsed(timer) > longest_term) { + if (combo_buffer_read != combo_buffer_write) { + apply_combos(); + longest_term = 0; + timer = 0; + } else { + dump_key_buffer(); + timer = 0; + clear_combos(); + } + } +#endif } void combo_enable(void) { b_combo_enable = true; } void combo_disable(void) { - b_combo_enable = is_active = false; +#ifndef COMBO_NO_TIMER timer = 0; - dump_key_buffer(true); +#endif + b_combo_enable = false; + combo_buffer_read = combo_buffer_write; } void combo_toggle(void) { diff --git a/quantum/process_keycode/process_combo.h b/quantum/process_keycode/process_combo.h index 9af97588b2..43c36d79e6 100644 --- a/quantum/process_keycode/process_combo.h +++ b/quantum/process_keycode/process_combo.h @@ -20,23 +20,38 @@ #include "quantum.h" #include -#ifdef EXTRA_EXTRA_LONG_COMBOS +#ifdef EXTRA_SHORT_COMBOS +# define MAX_COMBO_LENGTH 6 +#elif defined(EXTRA_EXTRA_LONG_COMBOS) # define MAX_COMBO_LENGTH 32 -#elif EXTRA_LONG_COMBOS +#elif defined(EXTRA_LONG_COMBOS) # define MAX_COMBO_LENGTH 16 #else # define MAX_COMBO_LENGTH 8 #endif +#ifndef COMBO_KEY_BUFFER_LENGTH +# define COMBO_KEY_BUFFER_LENGTH MAX_COMBO_LENGTH +#endif +#ifndef COMBO_BUFFER_LENGTH +# define COMBO_BUFFER_LENGTH 4 +#endif + typedef struct { const uint16_t *keys; uint16_t keycode; -#ifdef EXTRA_EXTRA_LONG_COMBOS +#ifdef EXTRA_SHORT_COMBOS + uint8_t state; +#else + bool disabled; + bool active; +# if defined(EXTRA_EXTRA_LONG_COMBOS) uint32_t state; -#elif EXTRA_LONG_COMBOS +# elif defined(EXTRA_LONG_COMBOS) uint16_t state; -#else +# else uint8_t state; +# endif #endif } combo_t; @@ -46,12 +61,15 @@ typedef struct { { .keys = &(ck)[0] } #define COMBO_END 0 -#ifndef COMBO_COUNT -# define COMBO_COUNT 0 -#endif #ifndef COMBO_TERM -# define COMBO_TERM TAPPING_TERM +# define COMBO_TERM 50 #endif +#ifndef COMBO_HOLD_TERM +# define COMBO_HOLD_TERM TAPPING_TERM +#endif + +/* check if keycode is only modifiers */ +#define KEYCODE_IS_MOD(code) (IS_MOD(code) || (code >= QK_MODS && code <= QK_MODS_MAX && !(code & QK_BASIC_MAX))) bool process_combo(uint16_t keycode, keyrecord_t *record); void combo_task(void); diff --git a/quantum/quantum.c b/quantum/quantum.c index 3329c1146b..d173388714 100644 --- a/quantum/quantum.c +++ b/quantum/quantum.c @@ -143,7 +143,13 @@ void reset_keyboard(void) { } /* Convert record into usable keycode via the contained event. */ -uint16_t get_record_keycode(keyrecord_t *record, bool update_layer_cache) { return get_event_keycode(record->event, update_layer_cache); } +uint16_t get_record_keycode(keyrecord_t *record, bool update_layer_cache) { +#ifdef COMBO_ENABLE + if (record->keycode) { return record->keycode; } +#endif + return get_event_keycode(record->event, update_layer_cache); +} + /* Convert event into usable keycode. Checks the layer cache to ensure that it * retains the correct keycode after a layer change, if the key is still pressed. @@ -169,6 +175,18 @@ uint16_t get_event_keycode(keyevent_t event, bool update_layer_cache) { return keymap_key_to_keycode(layer_switch_get_layer(event.key), event.key); } +/* Get keycode, and then process pre tapping functionality */ +bool pre_process_record_quantum(keyrecord_t *record) { + if (!( +#ifdef COMBO_ENABLE + process_combo(get_record_keycode(record, true), record) && +#endif + true)) { + return false; + } + return true; // continue processing +} + /* Get keycode, and then call keyboard function */ void post_process_record_quantum(keyrecord_t *record) { uint16_t keycode = get_record_keycode(record, false); @@ -254,9 +272,6 @@ bool process_record_quantum(keyrecord_t *record) { #ifdef LEADER_ENABLE process_leader(keycode, record) && #endif -#ifdef COMBO_ENABLE - process_combo(keycode, record) && -#endif #ifdef PRINTING_ENABLE process_printer(keycode, record) && #endif diff --git a/tmk_core/common/action.c b/tmk_core/common/action.c index bd41d28b66..d19fd2a045 100644 --- a/tmk_core/common/action.c +++ b/tmk_core/common/action.c @@ -55,6 +55,8 @@ __attribute__((weak)) bool get_ignore_mod_tap_interrupt(uint16_t keycode, keyrec __attribute__((weak)) bool get_retro_tapping(uint16_t keycode, keyrecord_t *record) { return false; } #endif +__attribute__((weak)) bool pre_process_record_quantum(keyrecord_t *record) { return true; } + #ifndef TAP_CODE_DELAY # define TAP_CODE_DELAY 0 #endif @@ -106,9 +108,13 @@ void action_exec(keyevent_t event) { #endif #ifndef NO_ACTION_TAPPING - action_tapping_process(record); + if (IS_NOEVENT(record.event) || pre_process_record_quantum(&record)) { + action_tapping_process(record); + } #else - process_record(&record); + if (IS_NOEVENT(record.event) || pre_process_record_quantum(&record)) { + process_record(&record); + } if (!IS_NOEVENT(record.event)) { dprint("processed: "); debug_record(record); @@ -206,7 +212,16 @@ void process_record(keyrecord_t *record) { } void process_record_handler(keyrecord_t *record) { +#ifdef COMBO_ENABLE + action_t action; + if (record->keycode) { + action = action_for_keycode(record->keycode); + } else { + action = store_or_get_action(record->event.pressed, record->event.key); + } +#else action_t action = store_or_get_action(record->event.pressed, record->event.key); +#endif dprint("ACTION: "); debug_action(action); #ifndef NO_ACTION_LAYER @@ -990,6 +1005,24 @@ bool is_tap_key(keypos_t key) { return is_tap_action(action); } +/** \brief Utilities for actions. (FIXME: Needs better description) + * + * FIXME: Needs documentation. + */ +bool is_tap_record(keyrecord_t *record) { +#ifdef COMBO_ENABLE + action_t action; + if (record->keycode) { + action = action_for_keycode(record->keycode); + } else { + action = layer_switch_get_action(record->event.key); + } +#else + action_t action = layer_switch_get_action(record->event.key); +#endif + return is_tap_action(action); +} + /** \brief Utilities for actions. (FIXME: Needs better description) * * FIXME: Needs documentation. diff --git a/tmk_core/common/action.h b/tmk_core/common/action.h index 8cb4722c6e..3d357b33b8 100644 --- a/tmk_core/common/action.h +++ b/tmk_core/common/action.h @@ -53,6 +53,9 @@ typedef struct { #ifndef NO_ACTION_TAPPING tap_t tap; #endif +#ifdef COMBO_ENABLE + uint16_t keycode; +#endif } keyrecord_t; /* Execute action per keyevent */ @@ -60,6 +63,7 @@ void action_exec(keyevent_t event); /* action for key */ action_t action_for_key(uint8_t layer, keypos_t key); +action_t action_for_keycode(uint16_t keycode); /* macro */ const macro_t *action_get_macro(keyrecord_t *record, uint8_t id, uint8_t opt); @@ -111,6 +115,7 @@ void clear_keyboard_but_mods(void); void clear_keyboard_but_mods_and_keys(void); void layer_switch(uint8_t new_layer); bool is_tap_key(keypos_t key); +bool is_tap_record(keyrecord_t *record); bool is_tap_action(action_t action); #ifndef NO_ACTION_TAPPING diff --git a/tmk_core/common/action_tapping.c b/tmk_core/common/action_tapping.c index 56044e096d..1701ae4717 100644 --- a/tmk_core/common/action_tapping.c +++ b/tmk_core/common/action_tapping.c @@ -18,11 +18,16 @@ # define IS_TAPPING_PRESSED() (IS_TAPPING() && tapping_key.event.pressed) # define IS_TAPPING_RELEASED() (IS_TAPPING() && !tapping_key.event.pressed) # define IS_TAPPING_KEY(k) (IS_TAPPING() && KEYEQ(tapping_key.event.key, (k))) +#ifndef COMBO_ENABLE +# define IS_TAPPING_RECORD(r) (IS_TAPPING() && KEYEQ(tapping_key.event.key, (r->event.key))) +#else +# define IS_TAPPING_RECORD(r) (IS_TAPPING() && KEYEQ(tapping_key.event.key, (r->event.key)) && tapping_key.keycode == r->keycode) +#endif __attribute__((weak)) uint16_t get_tapping_term(uint16_t keycode, keyrecord_t *record) { return TAPPING_TERM; } # ifdef TAPPING_TERM_PER_KEY -# define WITHIN_TAPPING_TERM(e) (TIMER_DIFF_16(e.time, tapping_key.event.time) < get_tapping_term(get_event_keycode(tapping_key.event, false), &tapping_key)) +# define WITHIN_TAPPING_TERM(e) (TIMER_DIFF_16(e.time, tapping_key.event.time) < get_tapping_term(get_record_keycode(&tapping_key, false), &tapping_key)) # else # define WITHIN_TAPPING_TERM(e) (TIMER_DIFF_16(e.time, tapping_key.event.time) < TAPPING_TERM) # endif @@ -103,7 +108,7 @@ bool process_tapping(keyrecord_t *keyp) { if (IS_TAPPING_PRESSED()) { if (WITHIN_TAPPING_TERM(event)) { if (tapping_key.tap.count == 0) { - if (IS_TAPPING_KEY(event.key) && !event.pressed) { + if (IS_TAPPING_RECORD(keyp) && !event.pressed) { // first tap! debug("Tapping: First tap(0->1).\n"); tapping_key.tap.count = 1; @@ -122,14 +127,14 @@ bool process_tapping(keyrecord_t *keyp) { # if defined(TAPPING_TERM_PER_KEY) || (TAPPING_TERM >= 500) || defined(PERMISSIVE_HOLD) || defined(PERMISSIVE_HOLD_PER_KEY) else if ((( # ifdef TAPPING_TERM_PER_KEY - get_tapping_term(get_event_keycode(tapping_key.event, false), keyp) + get_tapping_term(get_record_keycode(&tapping_key, false), keyp) # else TAPPING_TERM # endif >= 500) # ifdef PERMISSIVE_HOLD_PER_KEY - || get_permissive_hold(get_event_keycode(tapping_key.event, false), keyp) + || get_permissive_hold(get_record_keycode(&tapping_key, false), keyp) # elif defined(PERMISSIVE_HOLD) || true # endif @@ -177,7 +182,7 @@ bool process_tapping(keyrecord_t *keyp) { } // tap_count > 0 else { - if (IS_TAPPING_KEY(event.key) && !event.pressed) { + if (IS_TAPPING_RECORD(keyp) && !event.pressed) { debug("Tapping: Tap release("); debug_dec(tapping_key.tap.count); debug(")\n"); @@ -186,11 +191,15 @@ bool process_tapping(keyrecord_t *keyp) { tapping_key = *keyp; debug_tapping_key(); return true; - } else if (is_tap_key(event.key) && event.pressed) { + } else if (is_tap_record(keyp) && event.pressed) { if (tapping_key.tap.count > 1) { debug("Tapping: Start new tap with releasing last tap(>1).\n"); // unregister key - process_record(&(keyrecord_t){.tap = tapping_key.tap, .event.key = tapping_key.event.key, .event.time = event.time, .event.pressed = false}); + process_record(&(keyrecord_t){.tap = tapping_key.tap, .event.key = tapping_key.event.key, .event.time = event.time, .event.pressed = false, +#ifdef COMBO_ENABLE + .keycode = tapping_key.keycode, +#endif + }); } else { debug("Tapping: Start while last tap(1).\n"); } @@ -218,17 +227,21 @@ bool process_tapping(keyrecord_t *keyp) { debug_tapping_key(); return false; } else { - if (IS_TAPPING_KEY(event.key) && !event.pressed) { + if (IS_TAPPING_RECORD(keyp) && !event.pressed) { debug("Tapping: End. last timeout tap release(>0)."); keyp->tap = tapping_key.tap; process_record(keyp); tapping_key = (keyrecord_t){}; return true; - } else if (is_tap_key(event.key) && event.pressed) { + } else if (is_tap_record(keyp) && event.pressed) { if (tapping_key.tap.count > 1) { debug("Tapping: Start new tap with releasing last timeout tap(>1).\n"); // unregister key - process_record(&(keyrecord_t){.tap = tapping_key.tap, .event.key = tapping_key.event.key, .event.time = event.time, .event.pressed = false}); + process_record(&(keyrecord_t){.tap = tapping_key.tap, .event.key = tapping_key.event.key, .event.time = event.time, .event.pressed = false, +#ifdef COMBO_ENABLE + .keycode = tapping_key.keycode, +#endif + }); } else { debug("Tapping: Start while last timeout tap(1).\n"); } @@ -248,12 +261,12 @@ bool process_tapping(keyrecord_t *keyp) { } else if (IS_TAPPING_RELEASED()) { if (WITHIN_TAPPING_TERM(event)) { if (event.pressed) { - if (IS_TAPPING_KEY(event.key)) { + if (IS_TAPPING_RECORD(keyp)) { //# ifndef TAPPING_FORCE_HOLD # if !defined(TAPPING_FORCE_HOLD) || defined(TAPPING_FORCE_HOLD_PER_KEY) if ( # ifdef TAPPING_FORCE_HOLD_PER_KEY - !get_tapping_force_hold(get_event_keycode(tapping_key.event, false), keyp) && + !get_tapping_force_hold(get_record_keycode(&tapping_key, false), keyp) && # endif !tapping_key.tap.interrupted && tapping_key.tap.count > 0) { // sequential tap. @@ -271,7 +284,7 @@ bool process_tapping(keyrecord_t *keyp) { // FIX: start new tap again tapping_key = *keyp; return true; - } else if (is_tap_key(event.key)) { + } else if (is_tap_record(keyp)) { // Sequential tap can be interfered with other tap key. debug("Tapping: Start with interfering other tap.\n"); tapping_key = *keyp; @@ -303,7 +316,7 @@ bool process_tapping(keyrecord_t *keyp) { } // not tapping state else { - if (event.pressed && is_tap_key(event.key)) { + if (event.pressed && is_tap_record(keyp)) { debug("Tapping: Start(Press tap key).\n"); tapping_key = *keyp; process_record_tap_hint(&tapping_key); diff --git a/tmk_core/common/action_tapping.h b/tmk_core/common/action_tapping.h index 893ccb1ce1..7de8049c7f 100644 --- a/tmk_core/common/action_tapping.h +++ b/tmk_core/common/action_tapping.h @@ -30,6 +30,7 @@ along with this program. If not, see . #define WAITING_BUFFER_SIZE 8 #ifndef NO_ACTION_TAPPING +uint16_t get_record_keycode(keyrecord_t *record, bool update_layer_cache); uint16_t get_event_keycode(keyevent_t event, bool update_layer_cache); void action_tapping_process(keyrecord_t record); -- cgit v1.2.3 From 488aaa0980b113240a461d6d7878c62f5663f5c6 Mon Sep 17 00:00:00 2001 From: Drashna Jaelre Date: Sat, 14 Aug 2021 22:39:08 -0700 Subject: Enable sync of OLED/ST7565 display on/off state on Splits (#13542) * Enable sync of OLED/ST7565 display on/off state on Splits * Only send if states are not matched Co-authored-by: Nick Brassel Co-authored-by: Nick Brassel --- docs/config_options.md | 6 ++ docs/feature_split_keyboard.md | 12 +++ quantum/split_common/transaction_id_define.h | 8 ++ quantum/split_common/transactions.c | 108 ++++++++++++++++++++++----- quantum/split_common/transport.h | 10 ++- 5 files changed, 123 insertions(+), 21 deletions(-) (limited to 'docs/config_options.md') diff --git a/docs/config_options.md b/docs/config_options.md index 78c1f70fd7..94f99e74c0 100644 --- a/docs/config_options.md +++ b/docs/config_options.md @@ -343,6 +343,12 @@ There are a few different ways to set handedness for split keyboards (listed in * `#define SPLIT_WPM_ENABLE` * Ensures the current WPM is available on the slave when using the QMK-provided split transport. +* `#define SPLIT_OLED_ENABLE` + * Syncs the on/off state of the OLED between the halves. + +* `#define SPLIT_ST7565_ENABLE` + * Syncs the on/off state of the ST7565 screen between the halves. + * `#define SPLIT_TRANSACTION_IDS_KB .....` * `#define SPLIT_TRANSACTION_IDS_USER .....` * Allows for custom data sync with the slave when using the QMK-provided split transport. See [custom data sync between sides](feature_split_keyboard.md#custom-data-sync) for more information. diff --git a/docs/feature_split_keyboard.md b/docs/feature_split_keyboard.md index 428d581cab..a84d384601 100644 --- a/docs/feature_split_keyboard.md +++ b/docs/feature_split_keyboard.md @@ -233,6 +233,18 @@ This enables transmitting modifier state (normal, weak and oneshot) to the non p This enables transmitting the current WPM to the slave side of the split keyboard. The purpose of this feature is to support cosmetic use of WPM (e.g. displaying the current value on an OLED screen). This adds overhead to the split communication protocol and may negatively impact the matrix scan speed when enabled. +```c +#define SPLIT_OLED_ENABLE +``` + +This enables transmitting the current OLED on/off status to the slave side of the split keyboard. The purpose of this feature is to support state (on/off state only) syncing. This adds overhead to the split communication protocol and may negatively impact the matrix scan speed when enabled. + +```c +#define SPLIT_ST7565_ENABLE +``` + +This enables transmitting the current ST7565 on/off status to the slave side of the split keyboard. The purpose of this feature is to support state (on/off state only) syncing. This adds overhead to the split communication protocol and may negatively impact the matrix scan speed when enabled. + ### Custom data sync between sides :id=custom-data-sync QMK's split transport allows for arbitrary data transactions at both the keyboard and user levels. This is modelled on a remote procedure call, with the master invoking a function on the slave side, with the ability to send data from master to slave, process it slave side, and send data back from slave to master. diff --git a/quantum/split_common/transaction_id_define.h b/quantum/split_common/transaction_id_define.h index 464c73478a..3b78402d4c 100644 --- a/quantum/split_common/transaction_id_define.h +++ b/quantum/split_common/transaction_id_define.h @@ -70,6 +70,14 @@ enum serial_transaction_id { PUT_WPM, #endif // defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE) +#if defined(OLED_ENABLE) && defined(SPLIT_OLED_ENABLE) + PUT_OLED, +#endif // defined(WPM_ENABLE) && defined(SPLIT_OLED_ENABLE) + +#if defined(ST7565_ENABLE) && defined(SPLIT_ST7565_ENABLE) + PUT_ST7565, +#endif // defined(ST7565_ENABLE) && defined(SPLIT_ST7565_ENABLE) + #if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) PUT_RPC_INFO, PUT_RPC_REQ_DATA, diff --git a/quantum/split_common/transactions.c b/quantum/split_common/transactions.c index abad626e00..de42882df3 100644 --- a/quantum/split_common/transactions.c +++ b/quantum/split_common/transactions.c @@ -41,8 +41,8 @@ { &dummy, 0, 0, sizeof_member(split_shared_memory_t, member), offsetof(split_shared_memory_t, member), cb } #define trans_target2initiator_initializer(member) trans_target2initiator_initializer_cb(member, NULL) -#define transport_write(id, data, length) transport_execute_transaction(id, data, length, NULL, 0) -#define transport_read(id, data, length) transport_execute_transaction(id, NULL, 0, data, length) +#define transport_write(id, data, length) transport_execute_transaction(id, data, length, NULL, 0) +#define transport_read(id, data, length) transport_execute_transaction(id, NULL, 0, data, length) #if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) // Forward-declare the RPC callback handlers @@ -160,8 +160,8 @@ static void master_matrix_handlers_slave(matrix_row_t master_matrix[], matrix_ro memcpy(master_matrix, split_shmem->mmatrix.matrix, sizeof(split_shmem->mmatrix.matrix)); } -# define TRANSACTIONS_MASTER_MATRIX_MASTER() TRANSACTION_HANDLER_MASTER(master_matrix_handlers) -# define TRANSACTIONS_MASTER_MATRIX_SLAVE() TRANSACTION_HANDLER_SLAVE(master_matrix_handlers) +# define TRANSACTIONS_MASTER_MATRIX_MASTER() TRANSACTION_HANDLER_MASTER(master_matrix_handlers) +# define TRANSACTIONS_MASTER_MATRIX_SLAVE() TRANSACTION_HANDLER_SLAVE(master_matrix_handlers) # define TRANSACTIONS_MASTER_MATRIX_REGISTRATIONS [PUT_MASTER_MATRIX] = trans_initiator2target_initializer(mmatrix.matrix), #else // SPLIT_TRANSPORT_MIRROR @@ -238,8 +238,8 @@ static void sync_timer_handlers_slave(matrix_row_t master_matrix[], matrix_row_t } } -# define TRANSACTIONS_SYNC_TIMER_MASTER() TRANSACTION_HANDLER_MASTER(sync_timer_handlers) -# define TRANSACTIONS_SYNC_TIMER_SLAVE() TRANSACTION_HANDLER_SLAVE(sync_timer_handlers) +# define TRANSACTIONS_SYNC_TIMER_MASTER() TRANSACTION_HANDLER_MASTER(sync_timer_handlers) +# define TRANSACTIONS_SYNC_TIMER_SLAVE() TRANSACTION_HANDLER_SLAVE(sync_timer_handlers) # define TRANSACTIONS_SYNC_TIMER_REGISTRATIONS [PUT_SYNC_TIMER] = trans_initiator2target_initializer(sync_timer), #else // DISABLE_SYNC_TIMER @@ -303,8 +303,8 @@ static void led_state_handlers_slave(matrix_row_t master_matrix[], matrix_row_t set_split_host_keyboard_leds(split_shmem->led_state); } -# define TRANSACTIONS_LED_STATE_MASTER() TRANSACTION_HANDLER_MASTER(led_state_handlers) -# define TRANSACTIONS_LED_STATE_SLAVE() TRANSACTION_HANDLER_SLAVE(led_state_handlers) +# define TRANSACTIONS_LED_STATE_MASTER() TRANSACTION_HANDLER_MASTER(led_state_handlers) +# define TRANSACTIONS_LED_STATE_SLAVE() TRANSACTION_HANDLER_SLAVE(led_state_handlers) # define TRANSACTIONS_LED_STATE_REGISTRATIONS [PUT_LED_STATE] = trans_initiator2target_initializer(led_state), #else // SPLIT_LED_STATE_ENABLE @@ -360,8 +360,8 @@ static void mods_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave # endif } -# define TRANSACTIONS_MODS_MASTER() TRANSACTION_HANDLER_MASTER(mods_handlers) -# define TRANSACTIONS_MODS_SLAVE() TRANSACTION_HANDLER_SLAVE(mods_handlers) +# define TRANSACTIONS_MODS_MASTER() TRANSACTION_HANDLER_MASTER(mods_handlers) +# define TRANSACTIONS_MODS_SLAVE() TRANSACTION_HANDLER_SLAVE(mods_handlers) # define TRANSACTIONS_MODS_REGISTRATIONS [PUT_MODS] = trans_initiator2target_initializer(mods), #else // SPLIT_MODS_ENABLE @@ -385,8 +385,8 @@ static bool backlight_handlers_master(matrix_row_t master_matrix[], matrix_row_t static void backlight_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { backlight_set(split_shmem->backlight_level); } -# define TRANSACTIONS_BACKLIGHT_MASTER() TRANSACTION_HANDLER_MASTER(backlight_handlers) -# define TRANSACTIONS_BACKLIGHT_SLAVE() TRANSACTION_HANDLER_SLAVE(backlight_handlers) +# define TRANSACTIONS_BACKLIGHT_MASTER() TRANSACTION_HANDLER_MASTER(backlight_handlers) +# define TRANSACTIONS_BACKLIGHT_SLAVE() TRANSACTION_HANDLER_SLAVE(backlight_handlers) # define TRANSACTIONS_BACKLIGHT_REGISTRATIONS [PUT_BACKLIGHT] = trans_initiator2target_initializer(backlight_level), #else // BACKLIGHT_ENABLE @@ -422,8 +422,8 @@ static void rgblight_handlers_slave(matrix_row_t master_matrix[], matrix_row_t s } } -# define TRANSACTIONS_RGBLIGHT_MASTER() TRANSACTION_HANDLER_MASTER(rgblight_handlers) -# define TRANSACTIONS_RGBLIGHT_SLAVE() TRANSACTION_HANDLER_SLAVE(rgblight_handlers) +# define TRANSACTIONS_RGBLIGHT_MASTER() TRANSACTION_HANDLER_MASTER(rgblight_handlers) +# define TRANSACTIONS_RGBLIGHT_SLAVE() TRANSACTION_HANDLER_SLAVE(rgblight_handlers) # define TRANSACTIONS_RGBLIGHT_REGISTRATIONS [PUT_RGBLIGHT] = trans_initiator2target_initializer(rgblight_sync), #else // defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) @@ -452,8 +452,8 @@ static void led_matrix_handlers_slave(matrix_row_t master_matrix[], matrix_row_t led_matrix_set_suspend_state(split_shmem->led_matrix_sync.led_suspend_state); } -# define TRANSACTIONS_LED_MATRIX_MASTER() TRANSACTION_HANDLER_MASTER(led_matrix_handlers) -# define TRANSACTIONS_LED_MATRIX_SLAVE() TRANSACTION_HANDLER_SLAVE(led_matrix_handlers) +# define TRANSACTIONS_LED_MATRIX_MASTER() TRANSACTION_HANDLER_MASTER(led_matrix_handlers) +# define TRANSACTIONS_LED_MATRIX_SLAVE() TRANSACTION_HANDLER_SLAVE(led_matrix_handlers) # define TRANSACTIONS_LED_MATRIX_REGISTRATIONS [PUT_LED_MATRIX] = trans_initiator2target_initializer(led_matrix_sync), #else // defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) @@ -482,8 +482,8 @@ static void rgb_matrix_handlers_slave(matrix_row_t master_matrix[], matrix_row_t rgb_matrix_set_suspend_state(split_shmem->rgb_matrix_sync.rgb_suspend_state); } -# define TRANSACTIONS_RGB_MATRIX_MASTER() TRANSACTION_HANDLER_MASTER(rgb_matrix_handlers) -# define TRANSACTIONS_RGB_MATRIX_SLAVE() TRANSACTION_HANDLER_SLAVE(rgb_matrix_handlers) +# define TRANSACTIONS_RGB_MATRIX_MASTER() TRANSACTION_HANDLER_MASTER(rgb_matrix_handlers) +# define TRANSACTIONS_RGB_MATRIX_SLAVE() TRANSACTION_HANDLER_SLAVE(rgb_matrix_handlers) # define TRANSACTIONS_RGB_MATRIX_REGISTRATIONS [PUT_RGB_MATRIX] = trans_initiator2target_initializer(rgb_matrix_sync), #else // defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT) @@ -507,8 +507,8 @@ static bool wpm_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave static void wpm_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { set_current_wpm(split_shmem->current_wpm); } -# define TRANSACTIONS_WPM_MASTER() TRANSACTION_HANDLER_MASTER(wpm_handlers) -# define TRANSACTIONS_WPM_SLAVE() TRANSACTION_HANDLER_SLAVE(wpm_handlers) +# define TRANSACTIONS_WPM_MASTER() TRANSACTION_HANDLER_MASTER(wpm_handlers) +# define TRANSACTIONS_WPM_SLAVE() TRANSACTION_HANDLER_SLAVE(wpm_handlers) # define TRANSACTIONS_WPM_REGISTRATIONS [PUT_WPM] = trans_initiator2target_initializer(current_wpm), #else // defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE) @@ -519,6 +519,68 @@ static void wpm_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_ #endif // defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE) +//////////////////////////////////////////////////// +// OLED + +#if defined(OLED_ENABLE) && defined(SPLIT_OLED_ENABLE) + +static bool oled_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + static uint32_t last_update = 0; + bool current_oled_state = is_oled_on(); + return send_if_condition(PUT_OLED, &last_update, (current_oled_state != split_shmem->current_oled_state), ¤t_oled_state, sizeof(current_oled_state)); +} + +static void oled_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + if (split_shmem->current_oled_state) { + oled_on(); + } else { + oled_off(); + } +} + +# define TRANSACTIONS_OLED_MASTER() TRANSACTION_HANDLER_MASTER(oled_handlers) +# define TRANSACTIONS_OLED_SLAVE() TRANSACTION_HANDLER_SLAVE(oled_handlers) +# define TRANSACTIONS_OLED_REGISTRATIONS [PUT_OLED] = trans_initiator2target_initializer(current_oled_state), + +#else // defined(OLED_ENABLE) && defined(SPLIT_OLED_ENABLE) + +# define TRANSACTIONS_OLED_MASTER() +# define TRANSACTIONS_OLED_SLAVE() +# define TRANSACTIONS_OLED_REGISTRATIONS + +#endif // defined(OLED_ENABLE) && defined(SPLIT_OLED_ENABLE) + +//////////////////////////////////////////////////// +// ST7565 + +#if defined(ST7565_ENABLE) && defined(SPLIT_ST7565_ENABLE) + +static bool st7565_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + static uint32_t last_update = 0; + bool current_st7565_state = st7565_is_on(); + return send_if_condition(PUT_ST7565, &last_update, (current_st7565_state != split_shmem->current_st7565_state), ¤t_st7565_state, sizeof(current_st7565_state)); +} + +static void st7565_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + if (split_shmem->current_st7565_state) { + st7565_on(); + } else { + st7565_off(); + } +} + +# define TRANSACTIONS_ST7565_MASTER() TRANSACTION_HANDLER_MASTER(st7565_handlers) +# define TRANSACTIONS_ST7565_SLAVE() TRANSACTION_HANDLER_SLAVE(st7565_handlers) +# define TRANSACTIONS_ST7565_REGISTRATIONS [PUT_ST7565] = trans_initiator2target_initializer(current_st7565_state), + +#else // defined(ST7565_ENABLE) && defined(SPLIT_ST7565_ENABLE) + +# define TRANSACTIONS_ST7565_MASTER() +# define TRANSACTIONS_ST7565_SLAVE() +# define TRANSACTIONS_ST7565_REGISTRATIONS + +#endif // defined(ST7565_ENABLE) && defined(SPLIT_ST7565_ENABLE) + //////////////////////////////////////////////////// uint8_t dummy; @@ -543,6 +605,8 @@ split_transaction_desc_t split_transaction_table[NUM_TOTAL_TRANSACTIONS] = { TRANSACTIONS_LED_MATRIX_REGISTRATIONS TRANSACTIONS_RGB_MATRIX_REGISTRATIONS TRANSACTIONS_WPM_REGISTRATIONS + TRANSACTIONS_OLED_REGISTRATIONS + TRANSACTIONS_ST7565_REGISTRATIONS // clang-format on #if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) @@ -567,6 +631,8 @@ bool transactions_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix TRANSACTIONS_LED_MATRIX_MASTER(); TRANSACTIONS_RGB_MATRIX_MASTER(); TRANSACTIONS_WPM_MASTER(); + TRANSACTIONS_OLED_MASTER(); + TRANSACTIONS_ST7565_MASTER(); return okay; } @@ -583,6 +649,8 @@ void transactions_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[ TRANSACTIONS_LED_MATRIX_SLAVE(); TRANSACTIONS_RGB_MATRIX_SLAVE(); TRANSACTIONS_WPM_SLAVE(); + TRANSACTIONS_OLED_SLAVE(); + TRANSACTIONS_ST7565_SLAVE(); } #if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) diff --git a/quantum/split_common/transport.h b/quantum/split_common/transport.h index 2e07f6b25c..1d4f6ed0cd 100644 --- a/quantum/split_common/transport.h +++ b/quantum/split_common/transport.h @@ -165,6 +165,14 @@ typedef struct _split_shared_memory_t { uint8_t current_wpm; #endif // defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE) +#if defined(OLED_ENABLE) && defined(SPLIT_OLED_ENABLE) + uint8_t current_oled_state; +#endif // defined(OLED_ENABLE) && defined(SPLIT_OLED_ENABLE) + +#if defined(ST7565_ENABLE) && defined(SPLIT_ST7565_ENABLE) + uint8_t current_st7565_state; +#endif // ST7565_ENABLE(OLED_ENABLE) && defined(SPLIT_ST7565_ENABLE) + #if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) rpc_sync_info_t rpc_info; uint8_t rpc_m2s_buffer[RPC_M2S_BUFFER_SIZE]; @@ -172,4 +180,4 @@ typedef struct _split_shared_memory_t { #endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) } split_shared_memory_t; -extern split_shared_memory_t *const split_shmem; \ No newline at end of file +extern split_shared_memory_t *const split_shmem; -- cgit v1.2.3