Browse Source

Implement connection keycode logic (#25176)

Joel Challis 1 year ago
parent
commit
c7cb7ba976

+ 1 - 1
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

+ 1 - 0
builddefs/generic_features.mk

@@ -25,6 +25,7 @@ GENERIC_FEATURES = \
     CAPS_WORD \
     COMBO \
     COMMAND \
+    CONNECTION \
     CRC \
     DEFERRED_EXEC \
     DIGITIZER \

+ 0 - 70
drivers/bluetooth/outputselect.c

@@ -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 <http://www.gnu.org/licenses/>.
-*/
-
-#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;
-}

+ 10 - 14
drivers/bluetooth/outputselect.h

@@ -14,21 +14,17 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 #pragma once
 
-#include <stdint.h>
+#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);

+ 147 - 0
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;
+}

+ 110 - 0
quantum/connection/connection.h

@@ -0,0 +1,110 @@
+// Copyright 2025 QMK
+// SPDX-License-Identifier: GPL-2.0-or-later
+#pragma once
+
+#include <stdint.h>
+#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);

+ 18 - 0
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();
 }

+ 6 - 0
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);
 

+ 6 - 0
quantum/keyboard.c

@@ -146,6 +146,9 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #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();

+ 13 - 0
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);
 }

+ 2 - 0
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))

+ 6 - 0
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);
 

+ 17 - 7
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_PREV:
+                connection_prev_host();
+                return false;
+
+            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:
-                set_output(OUTPUT_USB);
+                connection_set_host(CONNECTION_HOST_USB);
                 return false;
             case QK_OUTPUT_BLUETOOTH:
-                set_output(OUTPUT_BLUETOOTH);
+                connection_set_host(CONNECTION_HOST_BLUETOOTH);
                 return false;
-
-            case QK_OUTPUT_PREV:
-            case QK_OUTPUT_NONE:
             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:

+ 2 - 2
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)) {

+ 7 - 4
tmk_core/protocol/host.c

@@ -31,8 +31,11 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #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;
     }