From c7cb7ba9765b35930a26ec247e362615ffd10ed2 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Mon, 21 Apr 2025 22:27:56 +0100 Subject: [PATCH] Implement connection keycode logic (#25176) --- builddefs/common_features.mk | 2 +- builddefs/generic_features.mk | 1 + drivers/bluetooth/outputselect.c | 70 --------- drivers/bluetooth/outputselect.h | 24 ++- quantum/connection/connection.c | 147 ++++++++++++++++++ quantum/connection/connection.h | 110 +++++++++++++ quantum/eeconfig.c | 18 +++ quantum/eeconfig.h | 6 + quantum/keyboard.c | 6 + quantum/nvm/eeprom/nvm_eeconfig.c | 13 ++ .../nvm/eeprom/nvm_eeprom_eeconfig_internal.h | 2 + quantum/nvm/nvm_eeconfig.h | 6 + quantum/process_keycode/process_connection.c | 26 +++- quantum/quantum.c | 4 +- tmk_core/protocol/host.c | 11 +- 15 files changed, 347 insertions(+), 99 deletions(-) delete mode 100644 drivers/bluetooth/outputselect.c create mode 100644 quantum/connection/connection.c create mode 100644 quantum/connection/connection.h diff --git a/builddefs/common_features.mk b/builddefs/common_features.mk index ec856715b08..0b9840a14e6 100644 --- a/builddefs/common_features.mk +++ b/builddefs/common_features.mk @@ -892,8 +892,8 @@ ifeq ($(strip $(BLUETOOTH_ENABLE)), yes) OPT_DEFS += -DBLUETOOTH_ENABLE OPT_DEFS += -DBLUETOOTH_$(strip $(shell echo $(BLUETOOTH_DRIVER) | tr '[:lower:]' '[:upper:]')) NO_USB_STARTUP_CHECK := yes + CONNECTION_ENABLE := yes COMMON_VPATH += $(DRIVER_PATH)/bluetooth - SRC += outputselect.c process_connection.c ifeq ($(strip $(BLUETOOTH_DRIVER)), bluefruit_le) SPI_DRIVER_REQUIRED = yes diff --git a/builddefs/generic_features.mk b/builddefs/generic_features.mk index d39727f23bf..c8265144314 100644 --- a/builddefs/generic_features.mk +++ b/builddefs/generic_features.mk @@ -25,6 +25,7 @@ GENERIC_FEATURES = \ CAPS_WORD \ COMBO \ COMMAND \ + CONNECTION \ CRC \ DEFERRED_EXEC \ DIGITIZER \ diff --git a/drivers/bluetooth/outputselect.c b/drivers/bluetooth/outputselect.c deleted file mode 100644 index b986ba274e9..00000000000 --- a/drivers/bluetooth/outputselect.c +++ /dev/null @@ -1,70 +0,0 @@ -/* -Copyright 2017 Priyadi Iman Nurcahyo -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 "outputselect.h" -#include "usb_util.h" - -#ifdef BLUETOOTH_BLUEFRUIT_LE -# include "bluefruit_le.h" -#endif - -uint8_t desired_output = OUTPUT_DEFAULT; - -/** \brief Set Output - * - * FIXME: Needs doc - */ -void set_output(uint8_t output) { - set_output_user(output); - desired_output = output; -} - -/** \brief Set Output User - * - * FIXME: Needs doc - */ -__attribute__((weak)) void set_output_user(uint8_t output) {} - -/** \brief Auto Detect Output - * - * FIXME: Needs doc - */ -uint8_t auto_detect_output(void) { - if (usb_connected_state()) { - return OUTPUT_USB; - } - -#ifdef BLUETOOTH_BLUEFRUIT_LE - if (bluefruit_le_is_connected()) { - return OUTPUT_BLUETOOTH; - } -#endif - -#ifdef BLUETOOTH_ENABLE - return OUTPUT_BLUETOOTH; // should check if BT is connected here -#endif - - return OUTPUT_NONE; -} - -/** \brief Where To Send - * - * FIXME: Needs doc - */ -uint8_t where_to_send(void) { - if (desired_output == OUTPUT_AUTO) { - return auto_detect_output(); - } - return desired_output; -} diff --git a/drivers/bluetooth/outputselect.h b/drivers/bluetooth/outputselect.h index c4548e1122a..25f063bbff0 100644 --- a/drivers/bluetooth/outputselect.h +++ b/drivers/bluetooth/outputselect.h @@ -14,21 +14,17 @@ along with this program. If not, see . #pragma once -#include +#include "connection.h" -enum outputs { - OUTPUT_AUTO, +// DEPRECATED - DO NOT USE - OUTPUT_NONE, - OUTPUT_USB, - OUTPUT_BLUETOOTH -}; +#define OUTPUT_AUTO CONNECTION_HOST_AUTO +#define OUTPUT_NONE CONNECTION_HOST_NONE +#define OUTPUT_USB CONNECTION_HOST_USB +#define OUTPUT_BLUETOOTH CONNECTION_HOST_BLUETOOTH -#ifndef OUTPUT_DEFAULT -# define OUTPUT_DEFAULT OUTPUT_AUTO -#endif +#define set_output connection_set_host_noeeprom +#define where_to_send connection_get_host +#define auto_detect_output connection_auto_detect_host -void set_output(uint8_t output); -void set_output_user(uint8_t output); -uint8_t auto_detect_output(void); -uint8_t where_to_send(void); +void set_output_user(uint8_t output); diff --git a/quantum/connection/connection.c b/quantum/connection/connection.c new file mode 100644 index 00000000000..c7f3c4b4246 --- /dev/null +++ b/quantum/connection/connection.c @@ -0,0 +1,147 @@ +// Copyright 2025 QMK +// SPDX-License-Identifier: GPL-2.0-or-later +#include "connection.h" +#include "eeconfig.h" +#include "usb_util.h" +#include "util.h" + +// ======== DEPRECATED DEFINES - DO NOT USE ======== +#ifdef OUTPUT_DEFAULT +# undef CONNECTION_HOST_DEFAULT +# define CONNECTION_HOST_DEFAULT OUTPUT_DEFAULT +#endif + +__attribute__((weak)) void set_output_user(uint8_t output) {} +// ======== + +#ifdef BLUETOOTH_ENABLE +# ifdef BLUETOOTH_BLUEFRUIT_LE +# include "bluefruit_le.h" +# define bluetooth_is_connected() bluefruit_le_is_connected() +# else +// TODO: drivers should check if BT is connected here +# define bluetooth_is_connected() true +# endif +#endif + +#define CONNECTION_HOST_INVALID 0xFF + +#ifndef CONNECTION_HOST_DEFAULT +# define CONNECTION_HOST_DEFAULT CONNECTION_HOST_AUTO +#endif + +static const connection_host_t host_candidates[] = { + CONNECTION_HOST_AUTO, + CONNECTION_HOST_USB, +#ifdef BLUETOOTH_ENABLE + CONNECTION_HOST_BLUETOOTH, +#endif +#if 0 + CONNECTION_HOST_2P4GHZ, +#endif +}; + +#define HOST_CANDIDATES_COUNT ARRAY_SIZE(host_candidates) + +static connection_config_t config = {.desired_host = CONNECTION_HOST_INVALID}; + +void eeconfig_update_connection_default(void) { + config.desired_host = CONNECTION_HOST_DEFAULT; + + eeconfig_update_connection(&config); +} + +void connection_init(void) { + eeconfig_read_connection(&config); + if (config.desired_host == CONNECTION_HOST_INVALID) { + eeconfig_update_connection_default(); + } +} + +__attribute__((weak)) void connection_host_changed_user(connection_host_t host) {} +__attribute__((weak)) void connection_host_changed_kb(connection_host_t host) {} + +static void handle_host_changed(void) { + connection_host_changed_user(config.desired_host); + connection_host_changed_kb(config.desired_host); + + // TODO: Remove deprecated callback + set_output_user(config.desired_host); +} + +void connection_set_host_noeeprom(connection_host_t host) { + if (config.desired_host == host) { + return; + } + + config.desired_host = host; + + handle_host_changed(); +} + +void connection_set_host(connection_host_t host) { + connection_set_host_noeeprom(host); + + eeconfig_update_connection(&config); +} + +void connection_next_host_noeeprom(void) { + uint8_t next = 0; + for (uint8_t i = 0; i < HOST_CANDIDATES_COUNT; i++) { + if (host_candidates[i] == config.desired_host) { + next = i == HOST_CANDIDATES_COUNT - 1 ? 0 : i + 1; + break; + } + } + + connection_set_host_noeeprom(host_candidates[next]); +} + +void connection_next_host(void) { + connection_next_host_noeeprom(); + + eeconfig_update_connection(&config); +} + +void connection_prev_host_noeeprom(void) { + uint8_t next = 0; + for (uint8_t i = 0; i < HOST_CANDIDATES_COUNT; i++) { + if (host_candidates[i] == config.desired_host) { + next = i == 0 ? HOST_CANDIDATES_COUNT - 1 : i - 1; + break; + } + } + + connection_set_host_noeeprom(host_candidates[next]); +} + +void connection_prev_host(void) { + connection_prev_host_noeeprom(); + + eeconfig_update_connection(&config); +} + +connection_host_t connection_get_host_raw(void) { + return config.desired_host; +} + +connection_host_t connection_auto_detect_host(void) { + if (usb_connected_state()) { + return CONNECTION_HOST_USB; + } + +#ifdef BLUETOOTH_ENABLE + if (bluetooth_is_connected()) { + return CONNECTION_HOST_BLUETOOTH; + } +#endif + + return CONNECTION_HOST_NONE; +} + +connection_host_t connection_get_host(void) { + if (config.desired_host == CONNECTION_HOST_AUTO) { + return connection_auto_detect_host(); + } + return config.desired_host; +} diff --git a/quantum/connection/connection.h b/quantum/connection/connection.h new file mode 100644 index 00000000000..e403141faed --- /dev/null +++ b/quantum/connection/connection.h @@ -0,0 +1,110 @@ +// Copyright 2025 QMK +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include +#include "util.h" + +/** + * \enum connection_host_t + * + * An enumeration of the possible hosts. + */ +typedef enum connection_host_t { + CONNECTION_HOST_AUTO, + + CONNECTION_HOST_NONE, + CONNECTION_HOST_USB, + CONNECTION_HOST_BLUETOOTH, + CONNECTION_HOST_2P4GHZ +} connection_host_t; + +/** + * \union connection_config_t + * + * Configuration structure for the connection subsystem. + */ +typedef union connection_config_t { + uint8_t raw; + connection_host_t desired_host : 8; +} PACKED connection_config_t; + +_Static_assert(sizeof(connection_config_t) == sizeof(uint8_t), "Connection EECONFIG out of spec."); + +/** + * \brief Initialize the subsystem. + * + * This function must be called only once, before any of the below functions can be called. + */ +void connection_init(void); + +/** + * \brief Get currently configured host. Does not resolve 'CONNECTION_HOST_AUTO'. + * + * \return 'connection_host_t' of the configured host. + */ +connection_host_t connection_get_host_raw(void); + +/** + * \brief Get current active host. + * + * \return 'connection_host_t' of the configured host. + */ +connection_host_t connection_auto_detect_host(void); + +/** + * \brief Get currently configured host. Resolves 'CONNECTION_HOST_AUTO' using 'connection_auto_detect_host()'. + * + * \return 'connection_host_t' of the configured host. + */ +connection_host_t connection_get_host(void); + +/** + * \brief Get current host. New state is not written to EEPROM. + * + * \param host The host to configure. + */ +void connection_set_host_noeeprom(connection_host_t host); + +/** + * \brief Get current host. + * + * \param host The host to configure. + */ +void connection_set_host(connection_host_t host); + +/** + * \brief Move to the next potential host. New state is not written to EEPROM. + * + */ +void connection_next_host_noeeprom(void); + +/** + * \brief Move to the next potential host. + * + */ +void connection_next_host(void); + +/** + * \brief Move to the previous potential host. New state is not written to EEPROM. + * + */ +void connection_prev_host_noeeprom(void); + +/** + * \brief Move to the previous potential host. + * + */ +void connection_prev_host(void); + +/** + * \brief user hook called when changing configured host + * + */ +void connection_host_changed_user(connection_host_t host); + +/** + * \brief keyboard hook called when changing configured host + * + */ +void connection_host_changed_kb(connection_host_t host); diff --git a/quantum/eeconfig.c b/quantum/eeconfig.c index addc07ae535..1e8cfd758a5 100644 --- a/quantum/eeconfig.c +++ b/quantum/eeconfig.c @@ -35,6 +35,10 @@ # include "haptic.h" #endif // HAPTIC_ENABLE +#ifdef CONNECTION_ENABLE +# include "connection.h" +#endif // CONNECTION_ENABLE + #ifdef VIA_ENABLE bool via_eeprom_is_valid(void); void via_eeprom_set_valid(bool valid); @@ -127,6 +131,11 @@ void eeconfig_init_quantum(void) { haptic_reset(); #endif // HAPTIC_ENABLE +#ifdef CONNECTION_ENABLE + extern void eeconfig_update_connection_default(void); + eeconfig_update_connection_default(); +#endif // CONNECTION_ENABLE + #if (EECONFIG_KB_DATA_SIZE) > 0 eeconfig_init_kb_datablock(); #endif // (EECONFIG_KB_DATA_SIZE) > 0 @@ -299,6 +308,15 @@ void eeconfig_update_haptic(const haptic_config_t *haptic_config) { } #endif // HAPTIC_ENABLE +#ifdef CONNECTION_ENABLE +void eeconfig_read_connection(connection_config_t *config) { + nvm_eeconfig_read_connection(config); +} +void eeconfig_update_connection(const connection_config_t *config) { + nvm_eeconfig_update_connection(config); +} +#endif // CONNECTION_ENABLE + bool eeconfig_read_handedness(void) { return nvm_eeconfig_read_handedness(); } diff --git a/quantum/eeconfig.h b/quantum/eeconfig.h index 4044f1c2947..d4d8d957bed 100644 --- a/quantum/eeconfig.h +++ b/quantum/eeconfig.h @@ -131,6 +131,12 @@ void eeconfig_read_haptic(haptic_config_t *haptic_confi void eeconfig_update_haptic(const haptic_config_t *haptic_config) __attribute__((nonnull)); #endif +#ifdef CONNECTION_ENABLE +typedef union connection_config_t connection_config_t; +void eeconfig_read_connection(connection_config_t *config); +void eeconfig_update_connection(const connection_config_t *config); +#endif + bool eeconfig_read_handedness(void); void eeconfig_update_handedness(bool val); diff --git a/quantum/keyboard.c b/quantum/keyboard.c index 0671b0461f8..be51190a87d 100644 --- a/quantum/keyboard.c +++ b/quantum/keyboard.c @@ -146,6 +146,9 @@ along with this program. If not, see . #ifdef LAYER_LOCK_ENABLE # include "layer_lock.h" #endif +#ifdef CONNECTION_ENABLE +# include "connection.h" +#endif static uint32_t last_input_modification_time = 0; uint32_t last_input_activity_time(void) { @@ -465,6 +468,9 @@ void keyboard_init(void) { #endif matrix_init(); quantum_init(); +#ifdef CONNECTION_ENABLE + connection_init(); +#endif led_init_ports(); #ifdef BACKLIGHT_ENABLE backlight_init_ports(); diff --git a/quantum/nvm/eeprom/nvm_eeconfig.c b/quantum/nvm/eeprom/nvm_eeconfig.c index d6c388f3bc0..d9495d27534 100644 --- a/quantum/nvm/eeprom/nvm_eeconfig.c +++ b/quantum/nvm/eeprom/nvm_eeconfig.c @@ -41,6 +41,10 @@ # include "haptic.h" #endif +#ifdef CONNECTION_ENABLE +# include "connection.h" +#endif + void nvm_eeconfig_erase(void) { #ifdef EEPROM_DRIVER eeprom_driver_format(false); @@ -196,6 +200,15 @@ void nvm_eeconfig_update_haptic(const haptic_config_t *haptic_config) { } #endif // HAPTIC_ENABLE +#ifdef CONNECTION_ENABLE +void nvm_eeconfig_read_connection(connection_config_t *config) { + config->raw = eeprom_read_byte(EECONFIG_CONNECTION); +} +void nvm_eeconfig_update_connection(const connection_config_t *config) { + eeprom_update_byte(EECONFIG_CONNECTION, config->raw); +} +#endif // CONNECTION_ENABLE + bool nvm_eeconfig_read_handedness(void) { return !!eeprom_read_byte(EECONFIG_HANDEDNESS); } diff --git a/quantum/nvm/eeprom/nvm_eeprom_eeconfig_internal.h b/quantum/nvm/eeprom/nvm_eeprom_eeconfig_internal.h index 6efbf9480b9..41b76f1f650 100644 --- a/quantum/nvm/eeprom/nvm_eeprom_eeconfig_internal.h +++ b/quantum/nvm/eeprom/nvm_eeprom_eeconfig_internal.h @@ -27,6 +27,7 @@ typedef struct PACKED { }; uint32_t haptic; uint8_t rgblight_ext; + uint8_t connection; } eeprom_core_t; /* EEPROM parameter address */ @@ -46,6 +47,7 @@ typedef struct PACKED { #define EECONFIG_RGB_MATRIX (uint64_t *)(offsetof(eeprom_core_t, rgb_matrix)) #define EECONFIG_HAPTIC (uint32_t *)(offsetof(eeprom_core_t, haptic)) #define EECONFIG_RGBLIGHT_EXTENDED (uint8_t *)(offsetof(eeprom_core_t, rgblight_ext)) +#define EECONFIG_CONNECTION (uint8_t *)(offsetof(eeprom_core_t, connection)) // Size of EEPROM being used for core data storage #define EECONFIG_BASE_SIZE ((uint8_t)sizeof(eeprom_core_t)) diff --git a/quantum/nvm/nvm_eeconfig.h b/quantum/nvm/nvm_eeconfig.h index 131f61d5347..40827361ca0 100644 --- a/quantum/nvm/nvm_eeconfig.h +++ b/quantum/nvm/nvm_eeconfig.h @@ -87,6 +87,12 @@ void nvm_eeconfig_read_haptic(haptic_config_t *haptic_c void nvm_eeconfig_update_haptic(const haptic_config_t *haptic_config); #endif // HAPTIC_ENABLE +#ifdef CONNECTION_ENABLE +typedef union connection_config_t connection_config_t; +void nvm_eeconfig_read_connection(connection_config_t *config); +void nvm_eeconfig_update_connection(const connection_config_t *config); +#endif // CONNECTION_ENABLE + bool nvm_eeconfig_read_handedness(void); void nvm_eeconfig_update_handedness(bool val); diff --git a/quantum/process_keycode/process_connection.c b/quantum/process_keycode/process_connection.c index b0e230d680a..501529ede7e 100644 --- a/quantum/process_keycode/process_connection.c +++ b/quantum/process_keycode/process_connection.c @@ -1,24 +1,34 @@ // Copyright 2024 Nick Brassel (@tzarc) // SPDX-License-Identifier: GPL-2.0-or-later -#include "outputselect.h" +#include "connection.h" #include "process_connection.h" bool process_connection(uint16_t keycode, keyrecord_t *record) { if (record->event.pressed) { switch (keycode) { case QK_OUTPUT_NEXT: - set_output(OUTPUT_AUTO); // This should cycle through the outputs going forward. Ensure `docs/keycodes.md`, `docs/features/bluetooth.md` are updated when it does. + connection_next_host(); return false; - case QK_OUTPUT_USB: - set_output(OUTPUT_USB); - return false; - case QK_OUTPUT_BLUETOOTH: - set_output(OUTPUT_BLUETOOTH); + case QK_OUTPUT_PREV: + connection_prev_host(); return false; - case QK_OUTPUT_PREV: + case QK_OUTPUT_AUTO: + connection_set_host(CONNECTION_HOST_AUTO); + return false; case QK_OUTPUT_NONE: + connection_set_host(CONNECTION_HOST_NONE); + return false; + case QK_OUTPUT_USB: + connection_set_host(CONNECTION_HOST_USB); + return false; + case QK_OUTPUT_BLUETOOTH: + connection_set_host(CONNECTION_HOST_BLUETOOTH); + return false; case QK_OUTPUT_2P4GHZ: + connection_set_host(CONNECTION_HOST_2P4GHZ); + return false; + case QK_BLUETOOTH_PROFILE_NEXT: case QK_BLUETOOTH_PROFILE_PREV: case QK_BLUETOOTH_UNPAIR: diff --git a/quantum/quantum.c b/quantum/quantum.c index adb14d64b61..0bb6ee0a914 100644 --- a/quantum/quantum.c +++ b/quantum/quantum.c @@ -20,7 +20,7 @@ # include "process_backlight.h" #endif -#ifdef BLUETOOTH_ENABLE +#ifdef CONNECTION_ENABLE # include "process_connection.h" #endif @@ -436,7 +436,7 @@ bool process_record_quantum(keyrecord_t *record) { #ifdef LAYER_LOCK_ENABLE process_layer_lock(keycode, record) && #endif -#ifdef BLUETOOTH_ENABLE +#ifdef CONNECTION_ENABLE process_connection(keycode, record) && #endif true)) { diff --git a/tmk_core/protocol/host.c b/tmk_core/protocol/host.c index df805c827c2..453952049fe 100644 --- a/tmk_core/protocol/host.c +++ b/tmk_core/protocol/host.c @@ -31,8 +31,11 @@ along with this program. If not, see . #endif #ifdef BLUETOOTH_ENABLE +# ifndef CONNECTION_ENABLE +# error CONNECTION_ENABLE required and not enabled +# endif +# include "connection.h" # include "bluetooth.h" -# include "outputselect.h" #endif #ifdef NKRO_ENABLE @@ -74,7 +77,7 @@ led_t host_keyboard_led_state(void) { /* send report */ void host_keyboard_send(report_keyboard_t *report) { #ifdef BLUETOOTH_ENABLE - if (where_to_send() == OUTPUT_BLUETOOTH) { + if (connection_get_host() == CONNECTION_HOST_BLUETOOTH) { bluetooth_send_keyboard(report); return; } @@ -111,7 +114,7 @@ void host_nkro_send(report_nkro_t *report) { void host_mouse_send(report_mouse_t *report) { #ifdef BLUETOOTH_ENABLE - if (where_to_send() == OUTPUT_BLUETOOTH) { + if (connection_get_host() == CONNECTION_HOST_BLUETOOTH) { bluetooth_send_mouse(report); return; } @@ -147,7 +150,7 @@ void host_consumer_send(uint16_t usage) { last_consumer_usage = usage; #ifdef BLUETOOTH_ENABLE - if (where_to_send() == OUTPUT_BLUETOOTH) { + if (connection_get_host() == CONNECTION_HOST_BLUETOOTH) { bluetooth_send_consumer(usage); return; }