summary refs log tree commit diff
path: root/quantum/process_keycode
diff options
context:
space:
mode:
authorAlex Ong <the.onga@gmail.com>2019-01-04 19:39:14 +1100
committerAlex Ong <the.onga@gmail.com>2019-01-04 19:39:14 +1100
commit47c91fc7f75ae0a477e55b687aa0fc30da0a283c (patch)
tree65ad39452748ff2e6d4a83ce54ede6ca22c9ada9 /quantum/process_keycode
parentac9b88e8ccbbf38762871504cd827ff0d941c426 (diff)
parent563ce3f225d981ce460c12ca5130dfe47af41df0 (diff)
Merge branch 'master' of https://github.com/qmk/qmk_firmware
Diffstat (limited to 'quantum/process_keycode')
-rw-r--r--quantum/process_keycode/process_auto_shift.c2
-rw-r--r--quantum/process_keycode/process_chording.c76
-rw-r--r--quantum/process_keycode/process_chording.h32
-rw-r--r--quantum/process_keycode/process_clicky.c72
-rw-r--r--quantum/process_keycode/process_clicky.h10
-rw-r--r--quantum/process_keycode/process_leader.c49
-rw-r--r--quantum/process_keycode/process_leader.h2
-rw-r--r--quantum/process_keycode/process_tap_dance.c6
-rw-r--r--quantum/process_keycode/process_tap_dance.h1
-rw-r--r--quantum/process_keycode/process_ucis.c8
-rw-r--r--quantum/process_keycode/process_ucis.h6
-rw-r--r--quantum/process_keycode/process_unicode.c7
-rw-r--r--quantum/process_keycode/process_unicode.h5
-rw-r--r--quantum/process_keycode/process_unicode_common.c223
-rw-r--r--quantum/process_keycode/process_unicode_common.h68
-rw-r--r--quantum/process_keycode/process_unicodemap.c4
-rw-r--r--quantum/process_keycode/process_unicodemap.h4
17 files changed, 331 insertions, 244 deletions
diff --git a/quantum/process_keycode/process_auto_shift.c b/quantum/process_keycode/process_auto_shift.c
index 01d99445b0..0d0930ee67 100644
--- a/quantum/process_keycode/process_auto_shift.c
+++ b/quantum/process_keycode/process_auto_shift.c
@@ -173,6 +173,8 @@ bool process_auto_shift(uint16_t keycode, keyrecord_t *record) {
       case KC_DOT:
       case KC_SLSH:
       case KC_GRAVE:
+      case KC_NONUS_BSLASH:
+      case KC_NONUS_HASH:
 #endif
 
         autoshift_flush();
diff --git a/quantum/process_keycode/process_chording.c b/quantum/process_keycode/process_chording.c
deleted file mode 100644
index 6c6ebe300a..0000000000
--- a/quantum/process_keycode/process_chording.c
+++ /dev/null
@@ -1,76 +0,0 @@
-/* Copyright 2016 Jack Humbert
- *
- * 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 "process_chording.h"
-
-bool keys_chord(uint8_t keys[]) {
-  uint8_t keys_size = sizeof(keys)/sizeof(keys[0]);
-  bool pass = true;
-  uint8_t in = 0;
-  for (uint8_t i = 0; i < chord_key_count; i++) {
-    bool found = false;
-    for (uint8_t j = 0; j < keys_size; j++) {
-      if (chord_keys[i] == (keys[j] & 0xFF)) {
-        in++; // detects key in chord
-        found = true;
-        break;
-      }
-    }
-    if (found)
-      continue;
-    if (chord_keys[i] != 0)  {
-      pass = false; // makes sure rest are blank
-    }
-  }
-  return (pass && (in == keys_size));
-}
-
-bool process_chording(uint16_t keycode, keyrecord_t *record) {
-  if (keycode >= QK_CHORDING && keycode <= QK_CHORDING_MAX) {
-    if (record->event.pressed) {
-      if (!chording) {
-        chording = true;
-        for (uint8_t i = 0; i < CHORDING_MAX; i++)
-          chord_keys[i] = 0;
-        chord_key_count = 0;
-        chord_key_down = 0;
-      }
-      chord_keys[chord_key_count] = (keycode & 0xFF);
-      chord_key_count++;
-      chord_key_down++;
-      return false;
-    } else {
-      if (chording) {
-        chord_key_down--;
-        if (chord_key_down == 0) {
-          chording = false;
-          // Chord Dictionary
-          if (keys_chord((uint8_t[]){KC_ENTER, KC_SPACE})) {
-            register_code(KC_A);
-            unregister_code(KC_A);
-            return false;
-          }
-          for (uint8_t i = 0; i < chord_key_count; i++) {
-            register_code(chord_keys[i]);
-            unregister_code(chord_keys[i]);
-            return false;
-          }
-        }
-      }
-    }
-  }
-  return true;
-}
diff --git a/quantum/process_keycode/process_chording.h b/quantum/process_keycode/process_chording.h
deleted file mode 100644
index 8c0f4862a8..0000000000
--- a/quantum/process_keycode/process_chording.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/* Copyright 2016 Jack Humbert
- *
- * 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/>.
- */
-
-#ifndef PROCESS_CHORDING_H
-#define PROCESS_CHORDING_H
-
-#include "quantum.h"
-
-// Chording stuff
-#define CHORDING_MAX 4
-bool chording = false;
-
-uint8_t chord_keys[CHORDING_MAX] = {0};
-uint8_t chord_key_count = 0;
-uint8_t chord_key_down = 0;
-
-bool process_chording(uint16_t keycode, keyrecord_t *record);
-
-#endif
diff --git a/quantum/process_keycode/process_clicky.c b/quantum/process_keycode/process_clicky.c
index 1e950d1113..8238c263f9 100644
--- a/quantum/process_keycode/process_clicky.c
+++ b/quantum/process_keycode/process_clicky.c
@@ -3,11 +3,6 @@
 
 #ifdef AUDIO_CLICKY
 
-#ifdef AUDIO_CLICKY_ON
-bool clicky_enable = true;
-#else // AUDIO_CLICKY_ON
-bool clicky_enable = false;
-#endif // AUDIO_CLICKY_ON
 #ifndef AUDIO_CLICKY_FREQ_DEFAULT
 #define AUDIO_CLICKY_FREQ_DEFAULT 440.0f
 #endif // !AUDIO_CLICKY_FREQ_DEFAULT
@@ -25,8 +20,11 @@ bool clicky_enable = false;
 #endif // !AUDIO_CLICKY_FREQ_RANDOMNESS
 
 float clicky_freq = AUDIO_CLICKY_FREQ_DEFAULT;
+float clicky_rand = AUDIO_CLICKY_FREQ_RANDOMNESS;
 float clicky_song[][2]  = {{AUDIO_CLICKY_FREQ_DEFAULT, 3}, {AUDIO_CLICKY_FREQ_DEFAULT, 1}}; // 3 and 1 --> durations
 
+extern audio_config_t audio_config;
+
 #ifndef NO_MUSIC_MODE
 extern bool music_activated;
 extern bool midi_activated;
@@ -36,31 +34,61 @@ void clicky_play(void) {
 #ifndef NO_MUSIC_MODE
   if (music_activated || midi_activated) return;
 #endif // !NO_MUSIC_MODE
-  clicky_song[0][0] = 2.0f * clicky_freq * (1.0f + AUDIO_CLICKY_FREQ_RANDOMNESS * ( ((float)rand()) / ((float)(RAND_MAX)) ) );
-  clicky_song[1][0] = clicky_freq * (1.0f + AUDIO_CLICKY_FREQ_RANDOMNESS * ( ((float)rand()) / ((float)(RAND_MAX)) ) );
+  clicky_song[0][0] = 2.0f * clicky_freq * (1.0f + clicky_rand * ( ((float)rand()) / ((float)(RAND_MAX)) ) );
+  clicky_song[1][0] = clicky_freq * (1.0f + clicky_rand * ( ((float)rand()) / ((float)(RAND_MAX)) ) );
   PLAY_SONG(clicky_song);
 }
 
+void clicky_freq_up(void) {
+  float new_freq = clicky_freq * AUDIO_CLICKY_FREQ_FACTOR;
+  if (new_freq < AUDIO_CLICKY_FREQ_MAX) {
+    clicky_freq = new_freq;
+  }
+}
+
+void clicky_freq_down(void) {
+  float new_freq = clicky_freq / AUDIO_CLICKY_FREQ_FACTOR;
+  if (new_freq > AUDIO_CLICKY_FREQ_MIN) {
+    clicky_freq = new_freq;
+  }
+}
+
+void clicky_freq_reset(void) {
+  clicky_freq = AUDIO_CLICKY_FREQ_DEFAULT;
+}
+
+void clicky_toggle(void) {
+  audio_config.clicky_enable ^= 1;
+  eeconfig_update_audio(audio_config.raw);
+}
+
+void clicky_on(void) {
+  audio_config.clicky_enable = 1;
+  eeconfig_update_audio(audio_config.raw);
+}
+
+void clicky_off(void) {
+  audio_config.clicky_enable = 0;
+  eeconfig_update_audio(audio_config.raw);
+}
+
+bool is_clicky_on(void) {
+      return (audio_config.clicky_enable != 0);
+}
+
 bool process_clicky(uint16_t keycode, keyrecord_t *record) {
-    if (keycode == CLICKY_TOGGLE && record->event.pressed) { clicky_enable = !clicky_enable; }
+    if (keycode == CLICKY_TOGGLE && record->event.pressed) { clicky_toggle(); }
 
-    if (keycode == CLICKY_RESET && record->event.pressed) { clicky_freq = AUDIO_CLICKY_FREQ_DEFAULT; }
+    if (keycode == CLICKY_ENABLE && record->event.pressed) { clicky_on(); }
+    if (keycode == CLICKY_DISABLE && record->event.pressed) { clicky_off(); }
 
-    if (keycode == CLICKY_UP && record->event.pressed) {
-      float new_freq = clicky_freq * AUDIO_CLICKY_FREQ_FACTOR;
-      if (new_freq < AUDIO_CLICKY_FREQ_MAX) {
-        clicky_freq = new_freq;
-      }
-    }
-    if (keycode == CLICKY_DOWN && record->event.pressed) {
-      float new_freq = clicky_freq / AUDIO_CLICKY_FREQ_FACTOR;
-      if (new_freq > AUDIO_CLICKY_FREQ_MIN) {
-        clicky_freq = new_freq;
-      }
-    }
+    if (keycode == CLICKY_RESET && record->event.pressed) { clicky_freq_reset(); }
+
+    if (keycode == CLICKY_UP && record->event.pressed) { clicky_freq_up(); }
+    if (keycode == CLICKY_DOWN && record->event.pressed) { clicky_freq_down(); }
 
 
-    if ( clicky_enable ) {
+    if ( audio_config.clicky_enable ) {
       if (record->event.pressed) {
         clicky_play();;
       }
diff --git a/quantum/process_keycode/process_clicky.h b/quantum/process_keycode/process_clicky.h
index e274af56f1..f746edb951 100644
--- a/quantum/process_keycode/process_clicky.h
+++ b/quantum/process_keycode/process_clicky.h
@@ -4,4 +4,14 @@
 void clicky_play(void);
 bool process_clicky(uint16_t keycode, keyrecord_t *record);
 
+void clicky_freq_up(void);
+void clicky_freq_down(void);
+void clicky_freq_reset(void);
+
+void clicky_toggle(void);
+void clicky_on(void);
+void clicky_off(void);
+
+bool is_clicky_on(void);
+
 #endif
diff --git a/quantum/process_keycode/process_leader.c b/quantum/process_keycode/process_leader.c
index c87ef115af..897e9eabf6 100644
--- a/quantum/process_keycode/process_leader.c
+++ b/quantum/process_keycode/process_leader.c
@@ -14,7 +14,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef DISABLE_LEADER
+#ifdef LEADER_ENABLE
 
 #include "process_leader.h"
 
@@ -35,25 +35,40 @@ uint16_t leader_time = 0;
 uint16_t leader_sequence[5] = {0, 0, 0, 0, 0};
 uint8_t leader_sequence_size = 0;
 
+void qk_leader_start(void) {
+  if (leading) { return; }
+  leader_start();
+  leading = true;
+  leader_time = timer_read();
+  leader_sequence_size = 0;
+  leader_sequence[0] = 0;
+  leader_sequence[1] = 0;
+  leader_sequence[2] = 0;
+  leader_sequence[3] = 0;
+  leader_sequence[4] = 0;
+}
+
 bool process_leader(uint16_t keycode, keyrecord_t *record) {
   // Leader key set-up
   if (record->event.pressed) {
-    if (!leading && keycode == KC_LEAD) {
-      leader_start();
-      leading = true;
-      leader_time = timer_read();
-      leader_sequence_size = 0;
-      leader_sequence[0] = 0;
-      leader_sequence[1] = 0;
-      leader_sequence[2] = 0;
-      leader_sequence[3] = 0;
-      leader_sequence[4] = 0;
-      return false;
-    }
-    if (leading && timer_elapsed(leader_time) < LEADER_TIMEOUT) {
-      leader_sequence[leader_sequence_size] = keycode;
-      leader_sequence_size++;
-      return false;
+    if (leading) {
+      if (timer_elapsed(leader_time) < LEADER_TIMEOUT) {
+#ifndef LEADER_KEY_STRICT_KEY_PROCESSING
+        if ((keycode >= QK_MOD_TAP && keycode <= QK_MOD_TAP_MAX) || (keycode >= QK_LAYER_TAP && keycode <= QK_LAYER_TAP_MAX)) {
+          keycode = keycode & 0xFF;
+        }
+#endif // LEADER_KEY_STRICT_KEY_PROCESSING
+        leader_sequence[leader_sequence_size] = keycode;
+        leader_sequence_size++;
+#ifdef LEADER_PER_KEY_TIMING
+        leader_time = timer_read();
+#endif
+        return false;
+      }
+    } else {
+      if (keycode == KC_LEAD) {
+        qk_leader_start();
+      }
     }
   }
   return true;
diff --git a/quantum/process_keycode/process_leader.h b/quantum/process_keycode/process_leader.h
index 59c3eed1be..15bccc3f67 100644
--- a/quantum/process_keycode/process_leader.h
+++ b/quantum/process_keycode/process_leader.h
@@ -24,7 +24,7 @@ bool process_leader(uint16_t keycode, keyrecord_t *record);
 
 void leader_start(void);
 void leader_end(void);
-
+void qk_leader_start(void);
 
 #define SEQ_ONE_KEY(key) if (leader_sequence[0] == (key) && leader_sequence[1] == 0 && leader_sequence[2] == 0 && leader_sequence[3] == 0 && leader_sequence[4] == 0)
 #define SEQ_TWO_KEYS(key1, key2) if (leader_sequence[0] == (key1) && leader_sequence[1] == (key2) && leader_sequence[2] == 0 && leader_sequence[3] == 0 && leader_sequence[4] == 0)
diff --git a/quantum/process_keycode/process_tap_dance.c b/quantum/process_keycode/process_tap_dance.c
index 8337806912..16d33dddee 100644
--- a/quantum/process_keycode/process_tap_dance.c
+++ b/quantum/process_keycode/process_tap_dance.c
@@ -16,6 +16,10 @@
 #include "quantum.h"
 #include "action_tapping.h"
 
+#ifndef TAPPING_TERM
+#define TAPPING_TERM 200
+#endif
+
 #ifndef NO_ACTION_ONESHOT
 uint8_t get_oneshot_mods(void);
 #endif
@@ -127,6 +131,7 @@ void preprocess_tap_dance(uint16_t keycode, keyrecord_t *record) {
       if (keycode == action->state.keycode && keycode == last_td)
         continue;
       action->state.interrupted = true;
+      action->state.interrupting_keycode = keycode;
       process_tap_dance_action_on_dance_finished (action);
       reset_tap_dance (&action->state);
     }
@@ -205,5 +210,6 @@ void reset_tap_dance (qk_tap_dance_state_t *state) {
   state->count = 0;
   state->interrupted = false;
   state->finished = false;
+  state->interrupting_keycode = 0;
   last_td = 0;
 }
diff --git a/quantum/process_keycode/process_tap_dance.h b/quantum/process_keycode/process_tap_dance.h
index 8b0a47c49b..ca12f4746e 100644
--- a/quantum/process_keycode/process_tap_dance.h
+++ b/quantum/process_keycode/process_tap_dance.h
@@ -27,6 +27,7 @@ typedef struct
   uint8_t oneshot_mods;
   uint8_t weak_mods;
   uint16_t keycode;
+  uint16_t interrupting_keycode;
   uint16_t timer;
   bool interrupted;
   bool pressed;
diff --git a/quantum/process_keycode/process_ucis.c b/quantum/process_keycode/process_ucis.c
index 86c0937f5e..5de2e41fc3 100644
--- a/quantum/process_keycode/process_ucis.c
+++ b/quantum/process_keycode/process_ucis.c
@@ -32,6 +32,10 @@ void qk_ucis_start_user(void) {
   unicode_input_finish();
 }
 
+__attribute__((weak))
+void qk_ucis_success(uint8_t symbol_index) {
+}
+
 static bool is_uni_seq(char *seq) {
   uint8_t i;
 
@@ -142,6 +146,10 @@ bool process_ucis (uint16_t keycode, keyrecord_t *record) {
     }
     unicode_input_finish();
 
+    if (symbol_found) {
+      qk_ucis_success(i);
+    }
+
     qk_ucis_state.in_progress = false;
     return false;
   }
diff --git a/quantum/process_keycode/process_ucis.h b/quantum/process_keycode/process_ucis.h
index 3f736a709f..b114d839ab 100644
--- a/quantum/process_keycode/process_ucis.h
+++ b/quantum/process_keycode/process_ucis.h
@@ -14,8 +14,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef PROCESS_UCIS_H
-#define PROCESS_UCIS_H
+#pragma once
 
 #include "quantum.h"
 #include "process_unicode_common.h"
@@ -45,7 +44,6 @@ extern const qk_ucis_symbol_t ucis_symbol_table[];
 void qk_ucis_start(void);
 void qk_ucis_start_user(void);
 void qk_ucis_symbol_fallback (void);
+void qk_ucis_success(uint8_t symbol_index);
 void register_ucis(const char *hex);
 bool process_ucis (uint16_t keycode, keyrecord_t *record);
-
-#endif
diff --git a/quantum/process_keycode/process_unicode.c b/quantum/process_keycode/process_unicode.c
index fd008eca12..19beb84520 100644
--- a/quantum/process_keycode/process_unicode.c
+++ b/quantum/process_keycode/process_unicode.c
@@ -17,14 +17,8 @@
 #include "action_util.h"
 #include "eeprom.h"
 
-static uint8_t first_flag = 0;
-
 bool process_unicode(uint16_t keycode, keyrecord_t *record) {
   if (keycode > QK_UNICODE && record->event.pressed) {
-    if (first_flag == 0) {
-      set_unicode_input_mode(eeprom_read_byte(EECONFIG_UNICODEMODE));
-      first_flag = 1;
-    }
     uint16_t unicode = keycode & 0x7FFF;
     unicode_input_start();
     register_hex(unicode);
@@ -32,4 +26,3 @@ bool process_unicode(uint16_t keycode, keyrecord_t *record) {
   }
   return true;
 }
-
diff --git a/quantum/process_keycode/process_unicode.h b/quantum/process_keycode/process_unicode.h
index c525b74f03..0913e99107 100644
--- a/quantum/process_keycode/process_unicode.h
+++ b/quantum/process_keycode/process_unicode.h
@@ -13,12 +13,9 @@
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
-#ifndef PROCESS_UNICODE_H
-#define PROCESS_UNICODE_H
+#pragma once
 
 #include "quantum.h"
 #include "process_unicode_common.h"
 
 bool process_unicode(uint16_t keycode, keyrecord_t *record);
-
-#endif
diff --git a/quantum/process_keycode/process_unicode_common.c b/quantum/process_keycode/process_unicode_common.c
index 7f34ad57cf..3286f45b5d 100644
--- a/quantum/process_keycode/process_unicode_common.c
+++ b/quantum/process_keycode/process_unicode_common.c
@@ -16,94 +16,115 @@
 
 #include "process_unicode_common.h"
 #include "eeprom.h"
+#include <ctype.h>
+#include <string.h>
 
-static uint8_t input_mode;
-uint8_t mods;
+unicode_config_t unicode_config;
+#if UNICODE_SELECTED_MODES != -1
+static uint8_t selected[] = { UNICODE_SELECTED_MODES };
+static uint8_t selected_count = sizeof selected / sizeof *selected;
+static uint8_t selected_index;
+#endif
 
-void set_unicode_input_mode(uint8_t os_target)
-{
-  input_mode = os_target;
-  eeprom_update_byte(EECONFIG_UNICODEMODE, os_target);
+void unicode_input_mode_init(void) {
+  unicode_config.raw = eeprom_read_byte(EECONFIG_UNICODEMODE);
+#if UNICODE_SELECTED_MODES != -1
+  #if UNICODE_CYCLE_PERSIST
+  // Find input_mode in selected modes
+  uint8_t i;
+  for (i = 0; i < selected_count; i++) {
+    if (selected[i] == unicode_config.input_mode) {
+      selected_index = i;
+      break;
+    }
+  }
+  if (i == selected_count) {
+    // Not found: input_mode isn't selected, change to one that is
+    unicode_config.input_mode = selected[selected_index = 0];
+  }
+  #else
+  // Always change to the first selected input mode
+  unicode_config.input_mode = selected[selected_index = 0];
+  #endif
+#endif
+  dprintf("Unicode input mode init to: %u\n", unicode_config.input_mode);
 }
 
 uint8_t get_unicode_input_mode(void) {
-  return input_mode;
+  return unicode_config.input_mode;
 }
 
+void set_unicode_input_mode(uint8_t mode) {
+  unicode_config.input_mode = mode;
+  persist_unicode_input_mode();
+  dprintf("Unicode input mode set to: %u\n", unicode_config.input_mode);
+}
+
+void cycle_unicode_input_mode(uint8_t offset) {
+#if UNICODE_SELECTED_MODES != -1
+  selected_index = (selected_index + offset) % selected_count;
+  unicode_config.input_mode = selected[selected_index];
+  #if UNICODE_CYCLE_PERSIST
+  persist_unicode_input_mode();
+  #endif
+  dprintf("Unicode input mode cycle to: %u\n", unicode_config.input_mode);
+#endif
+}
+
+void persist_unicode_input_mode(void) {
+  eeprom_update_byte(EECONFIG_UNICODEMODE, unicode_config.input_mode);
+}
+
+static uint8_t saved_mods;
+
 __attribute__((weak))
-void unicode_input_start (void) {
-  // save current mods
-  mods = keyboard_report->mods;
-
-  // unregister all mods to start from clean state
-  if (mods & MOD_BIT(KC_LSFT)) unregister_code(KC_LSFT);
-  if (mods & MOD_BIT(KC_RSFT)) unregister_code(KC_RSFT);
-  if (mods & MOD_BIT(KC_LCTL)) unregister_code(KC_LCTL);
-  if (mods & MOD_BIT(KC_RCTL)) unregister_code(KC_RCTL);
-  if (mods & MOD_BIT(KC_LALT)) unregister_code(KC_LALT);
-  if (mods & MOD_BIT(KC_RALT)) unregister_code(KC_RALT);
-  if (mods & MOD_BIT(KC_LGUI)) unregister_code(KC_LGUI);
-  if (mods & MOD_BIT(KC_RGUI)) unregister_code(KC_RGUI);
-
-  switch(input_mode) {
+void unicode_input_start(void) {
+  saved_mods = get_mods(); // Save current mods
+  clear_mods(); // Unregister mods to start from a clean state
+
+  switch (unicode_config.input_mode) {
   case UC_OSX:
-    register_code(KC_LALT);
-    break;
-  case UC_OSX_RALT:
-    register_code(KC_RALT);
+    register_code(UNICODE_OSX_KEY);
     break;
   case UC_LNX:
     register_code(KC_LCTL);
     register_code(KC_LSFT);
-    register_code(KC_U);
-    unregister_code(KC_U);
+    tap_code(KC_U); // TODO: Replace with tap_code16(LCTL(LSFT(KC_U))); and test
     unregister_code(KC_LSFT);
     unregister_code(KC_LCTL);
     break;
   case UC_WIN:
     register_code(KC_LALT);
-    register_code(KC_PPLS);
-    unregister_code(KC_PPLS);
+    tap_code(KC_PPLS);
     break;
   case UC_WINC:
-    register_code(KC_RALT);
-    unregister_code(KC_RALT);
-    register_code(KC_U);
-    unregister_code(KC_U);
+    tap_code(UNICODE_WINC_KEY);
+    tap_code(KC_U);
+    break;
   }
+
   wait_ms(UNICODE_TYPE_DELAY);
 }
 
 __attribute__((weak))
-void unicode_input_finish (void) {
-  switch(input_mode) {
-    case UC_OSX:
-    case UC_WIN:
-      unregister_code(KC_LALT);
-      break;
-    case UC_OSX_RALT:
-      unregister_code(KC_RALT);
-      break;
-    case UC_LNX:
-      register_code(KC_SPC);
-      unregister_code(KC_SPC);
-      break;
+void unicode_input_finish(void) {
+  switch (unicode_config.input_mode) {
+  case UC_OSX:
+    unregister_code(UNICODE_OSX_KEY);
+    break;
+  case UC_LNX:
+    tap_code(KC_SPC);
+    break;
+  case UC_WIN:
+    unregister_code(KC_LALT);
+    break;
   }
 
-  // reregister previously set mods
-  if (mods & MOD_BIT(KC_LSFT)) register_code(KC_LSFT);
-  if (mods & MOD_BIT(KC_RSFT)) register_code(KC_RSFT);
-  if (mods & MOD_BIT(KC_LCTL)) register_code(KC_LCTL);
-  if (mods & MOD_BIT(KC_RCTL)) register_code(KC_RCTL);
-  if (mods & MOD_BIT(KC_LALT)) register_code(KC_LALT);
-  if (mods & MOD_BIT(KC_RALT)) register_code(KC_RALT);
-  if (mods & MOD_BIT(KC_LGUI)) register_code(KC_LGUI);
-  if (mods & MOD_BIT(KC_RGUI)) register_code(KC_RGUI);
+  set_mods(saved_mods); // Reregister previously set mods
 }
 
 __attribute__((weak))
-uint16_t hex_to_keycode(uint8_t hex)
-{
+uint16_t hex_to_keycode(uint8_t hex) {
   if (hex == 0x0) {
     return KC_0;
   } else if (hex < 0xA) {
@@ -116,7 +137,89 @@ uint16_t hex_to_keycode(uint8_t hex)
 void register_hex(uint16_t hex) {
   for(int i = 3; i >= 0; i--) {
     uint8_t digit = ((hex >> (i*4)) & 0xF);
-    register_code(hex_to_keycode(digit));
-    unregister_code(hex_to_keycode(digit));
+    tap_code(hex_to_keycode(digit));
+  }
+}
+
+void send_unicode_hex_string(const char *str) {
+  if (!str) { return; }
+
+  while (*str) {
+    // Find the next code point (token) in the string
+    for (; *str == ' '; str++);
+    size_t n = strcspn(str, " "); // Length of the current token
+    char code_point[n+1];
+    strncpy(code_point, str, n);
+    code_point[n] = '\0'; // Make sure it's null-terminated
+
+    // Normalize the code point: make all hex digits lowercase
+    for (char *p = code_point; *p; p++) {
+      *p = tolower((unsigned char)*p);
+    }
+
+    // Send the code point as a Unicode input string
+    unicode_input_start();
+    send_string(code_point);
+    unicode_input_finish();
+
+    str += n; // Move to the first ' ' (or '\0') after the current token
+  }
+}
+
+bool process_unicode_common(uint16_t keycode, keyrecord_t *record) {
+  if (record->event.pressed) {
+    switch (keycode) {
+    case UNICODE_MODE_FORWARD:
+      cycle_unicode_input_mode(+1);
+      break;
+    case UNICODE_MODE_REVERSE:
+      cycle_unicode_input_mode(-1);
+      break;
+
+    case UNICODE_MODE_OSX:
+      set_unicode_input_mode(UC_OSX);
+#if defined(AUDIO_ENABLE) && defined(UNICODE_SONG_OSX)
+      static float song_osx[][2] = UNICODE_SONG_OSX;
+      PLAY_SONG(song_osx);
+#endif
+      break;
+    case UNICODE_MODE_LNX:
+      set_unicode_input_mode(UC_LNX);
+#if defined(AUDIO_ENABLE) && defined(UNICODE_SONG_LNX)
+      static float song_lnx[][2] = UNICODE_SONG_LNX;
+      PLAY_SONG(song_lnx);
+#endif
+      break;
+    case UNICODE_MODE_WIN:
+      set_unicode_input_mode(UC_WIN);
+#if defined(AUDIO_ENABLE) && defined(UNICODE_SONG_WIN)
+      static float song_win[][2] = UNICODE_SONG_WIN;
+      PLAY_SONG(song_win);
+#endif
+      break;
+    case UNICODE_MODE_BSD:
+      set_unicode_input_mode(UC_BSD);
+#if defined(AUDIO_ENABLE) && defined(UNICODE_SONG_BSD)
+      static float song_bsd[][2] = UNICODE_SONG_BSD;
+      PLAY_SONG(song_bsd);
+#endif
+      break;
+    case UNICODE_MODE_WINC:
+      set_unicode_input_mode(UC_WINC);
+#if defined(AUDIO_ENABLE) && defined(UNICODE_SONG_WINC)
+      static float song_winc[][2] = UNICODE_SONG_WINC;
+      PLAY_SONG(song_winc);
+#endif
+      break;
+    }
   }
+#if   defined(UNICODE_ENABLE)
+  return process_unicode(keycode, record);
+#elif defined(UNICODEMAP_ENABLE)
+  return process_unicode_map(keycode, record);
+#elif defined(UCIS_ENABLE)
+  return process_ucis(keycode, record);
+#else
+  return true;
+#endif
 }
diff --git a/quantum/process_keycode/process_unicode_common.h b/quantum/process_keycode/process_unicode_common.h
index 4d2b04fb39..e608ab76be 100644
--- a/quantum/process_keycode/process_unicode_common.h
+++ b/quantum/process_keycode/process_unicode_common.h
@@ -14,33 +14,71 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef PROCESS_UNICODE_COMMON_H
-#define PROCESS_UNICODE_COMMON_H
+#pragma once
 
 #include "quantum.h"
 
-#ifndef UNICODE_TYPE_DELAY
-#define UNICODE_TYPE_DELAY 10
+#if defined(UNICODE_ENABLE) + defined(UNICODEMAP_ENABLE) + defined(UCIS_ENABLE) > 1
+  #error "Cannot enable more than one Unicode method (UNICODE, UNICODEMAP, UCIS) at the same time"
+#endif
+
+// Keycodes used for starting Unicode input on different platforms
+#ifndef UNICODE_OSX_KEY
+  #define UNICODE_OSX_KEY  KC_LALT
+#endif
+#ifndef UNICODE_WINC_KEY
+  #define UNICODE_WINC_KEY KC_RALT
 #endif
 
-__attribute__ ((unused))
-static uint8_t input_mode;
+// Comma-delimited, ordered list of input modes selected for use (e.g. in cycle)
+// Example: #define UNICODE_SELECTED_MODES UC_WINC, UC_LNX
+#ifndef UNICODE_SELECTED_MODES
+  #define UNICODE_SELECTED_MODES -1
+#endif
+
+// Whether input mode changes in cycle should be written to EEPROM
+#ifndef UNICODE_CYCLE_PERSIST
+  #define UNICODE_CYCLE_PERSIST true
+#endif
 
-void set_unicode_input_mode(uint8_t os_target);
+// Delay between starting Unicode input and sending a sequence, in ms
+#ifndef UNICODE_TYPE_DELAY
+  #define UNICODE_TYPE_DELAY 10
+#endif
+
+enum unicode_input_modes {
+  UC_OSX,   // Mac OS X using Unicode Hex Input
+  UC_LNX,   // Linux using IBus
+  UC_WIN,   // Windows using EnableHexNumpad
+  UC_BSD,   // BSD (not implemented)
+  UC_WINC,  // Windows using WinCompose (https://github.com/samhocevar/wincompose)
+  UC__COUNT // Number of available input modes (always leave at the end)
+};
+
+typedef union {
+  uint32_t raw;
+  struct {
+    uint8_t input_mode : 8;
+  };
+} unicode_config_t;
+
+extern unicode_config_t unicode_config;
+
+void unicode_input_mode_init(void);
 uint8_t get_unicode_input_mode(void);
+void set_unicode_input_mode(uint8_t mode);
+void cycle_unicode_input_mode(uint8_t offset);
+void persist_unicode_input_mode(void);
+
 void unicode_input_start(void);
 void unicode_input_finish(void);
+
 void register_hex(uint16_t hex);
+void send_unicode_hex_string(const char *str);
 
-#define UC_OSX 0  // Mac OS X
-#define UC_LNX 1  // Linux
-#define UC_WIN 2  // Windows 'HexNumpad'
-#define UC_BSD 3  // BSD (not implemented)
-#define UC_WINC 4 // WinCompose https://github.com/samhocevar/wincompose
-#define UC_OSX_RALT 5 // Mac OS X using Right Alt key for Unicode Compose
+bool process_unicode_common(uint16_t keycode, keyrecord_t *record);
 
 #define UC_BSPC	UC(0x0008)
-
 #define UC_SPC	UC(0x0020)
 
 #define UC_EXLM	UC(0x0021)
@@ -145,5 +183,3 @@ void register_hex(uint16_t hex);
 #define UC_RCBR	UC(0x007D)
 #define UC_TILD	UC(0x007E)
 #define UC_DEL	UC(0x007F)
-
-#endif
diff --git a/quantum/process_keycode/process_unicodemap.c b/quantum/process_keycode/process_unicodemap.c
index 47c27b9117..75f35112b1 100644
--- a/quantum/process_keycode/process_unicodemap.c
+++ b/quantum/process_keycode/process_unicodemap.c
@@ -50,7 +50,7 @@ bool process_unicode_map(uint16_t keycode, keyrecord_t *record) {
     const uint32_t* map = unicode_map;
     uint16_t index = keycode - QK_UNICODE_MAP;
     uint32_t code = pgm_read_dword(&map[index]);
-    if (code > 0xFFFF && code <= 0x10ffff && (input_mode == UC_OSX || input_mode == UC_OSX_RALT)) {
+    if (code > 0xFFFF && code <= 0x10ffff && input_mode == UC_OSX) {
       // Convert to UTF-16 surrogate pair
       code -= 0x10000;
       uint32_t lo = code & 0x3ff;
@@ -59,7 +59,7 @@ bool process_unicode_map(uint16_t keycode, keyrecord_t *record) {
       register_hex32(hi + 0xd800);
       register_hex32(lo + 0xdc00);
       unicode_input_finish();
-    } else if ((code > 0x10ffff && (input_mode == UC_OSX || input_mode == UC_OSX_RALT)) || (code > 0xFFFFF && input_mode == UC_LNX)) {
+    } else if ((code > 0x10ffff && input_mode == UC_OSX) || (code > 0xFFFFF && input_mode == UC_LNX)) {
       // when character is out of range supported by the OS
       unicode_map_input_error();
     } else {
diff --git a/quantum/process_keycode/process_unicodemap.h b/quantum/process_keycode/process_unicodemap.h
index 929c88c0b6..f6d64bb86b 100644
--- a/quantum/process_keycode/process_unicodemap.h
+++ b/quantum/process_keycode/process_unicodemap.h
@@ -14,12 +14,10 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef PROCESS_UNICODEMAP_H
-#define PROCESS_UNICODEMAP_H
+#pragma once
 
 #include "quantum.h"
 #include "process_unicode_common.h"
 
 void unicode_map_input_error(void);
 bool process_unicode_map(uint16_t keycode, keyrecord_t *record);
-#endif