summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--keyboards/spleeb/config.h36
-rw-r--r--keyboards/spleeb/info.json124
-rw-r--r--keyboards/spleeb/keymaps/chrishoage/config.h15
-rw-r--r--keyboards/spleeb/keymaps/chrishoage/keymap.c127
-rw-r--r--keyboards/spleeb/keymaps/chrishoage/rules.mk11
-rw-r--r--keyboards/spleeb/keymaps/default/keymap.c33
-rw-r--r--keyboards/spleeb/lib/glcdfont.c233
-rw-r--r--keyboards/spleeb/mcuconf.h9
-rw-r--r--keyboards/spleeb/readme.md109
-rw-r--r--keyboards/spleeb/rules.mk1
-rw-r--r--keyboards/spleeb/spleeb.c544
-rw-r--r--keyboards/spleeb/spleeb.h149
12 files changed, 1391 insertions, 0 deletions
diff --git a/keyboards/spleeb/config.h b/keyboards/spleeb/config.h
new file mode 100644
index 0000000000..72a09e89c9
--- /dev/null
+++ b/keyboards/spleeb/config.h
@@ -0,0 +1,36 @@
+// Copyright 2022 Chris Hoage (@chrishoage)
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+// Auto mouse layer makes use of the Cirque touchdown indicator which requires
+// the touch sensor to be on the master side
+#define MASTER_RIGHT
+
+// Sync later, led, and mod state for use on OLED on slave side
+#define SPLIT_LAYER_STATE_ENABLE
+#define SPLIT_LED_STATE_ENABLE
+#define SPLIT_MODS_ENABLE
+// Transport dpi and enc mode for display on oled
+#define SPLIT_TRANSACTION_IDS_KB RPC_ID_KB_CONFIG_SYNC
+
+#ifdef POINTING_DEVICE_ENABLE
+# define POINTING_DEVICE_AUTO_MOUSE_ENABLE
+// Absolute mode allows for z/touchdown triggering of auto mouse layer with out
+// moving finger
+# define CIRQUE_PINNACLE_POSITION_MODE CIRQUE_PINNACLE_ABSOLUTE_MODE
+# define POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE
+#endif // POINTING_DEVICE_ENABLE
+
+#define I2C_DRIVER I2CD1
+#define I2C1_SDA_PIN GP16
+#define I2C1_SCL_PIN GP17
+
+#ifdef OLED_ENABLE
+# define OLED_DISPLAY_128X64
+# define OLED_FONT_H "./lib/glcdfont.c"
+#endif // OLED_ENABLE
+
+#define RP2040_BOOTLOADER_DOUBLE_TAP_RESET
+#define RP2040_BOOTLOADER_DOUBLE_TAP_RESET_LED GP17
+#define RP2040_BOOTLOADER_DOUBLE_TAP_RESET_TIMEOUT 1000U
diff --git a/keyboards/spleeb/info.json b/keyboards/spleeb/info.json
new file mode 100644
index 0000000000..0af488135d
--- /dev/null
+++ b/keyboards/spleeb/info.json
@@ -0,0 +1,124 @@
+{
+ "manufacturer": "Chris Hoage",
+ "keyboard_name": "spleeb",
+ "maintainer": "chrishoage",
+ "bootloader": "rp2040",
+ "diode_direction": "COL2ROW",
+ "encoder": {
+ "enabled": true,
+ "rotary": [
+ {
+ "pin_a": "GP4",
+ "pin_b": "GP21",
+ "resolution": 2
+ }
+ ]
+ },
+ "features": {
+ "nkro": true
+ },
+ "matrix_pins": {
+ "cols": ["GP23", "GP20", "GP22", "GP26", "GP27", "GP28", "GP29"],
+ "rows": ["GP5", "GP6", "GP7", "GP8", "GP9"]
+ },
+ "processor": "RP2040",
+ "board": "QMK_PM2040",
+ "secure": {
+ "idle_timeout": 60000,
+ "unlock_sequence": [
+ [5, 0],
+ [5, 1]
+ ],
+ "unlock_timeout": 5000
+ },
+ "split": {
+ "enabled": true,
+ "soft_serial_pin": "GP1",
+ "encoder": {
+ "right": {
+ "rotary": [
+ {
+ "pin_a": "GP21",
+ "pin_b": "GP4"
+ }
+ ]
+ }
+ }
+ },
+ "url": "https://github.com/chrishoage/spleeb",
+ "usb": {
+ "device_version": "0.0.1",
+ "pid": "0x4242",
+ "vid": "0xFEED"
+ },
+ "layouts": {
+ "LAYOUT": {
+ "layout": [
+ { "matrix": [0, 0], "x": 0, "y": 0.6 },
+ { "matrix": [0, 1], "x": 1, "y": 0.6 },
+ { "matrix": [0, 2], "x": 2, "y": 0.2 },
+ { "matrix": [0, 3], "x": 3, "y": 0 },
+ { "matrix": [0, 4], "x": 4, "y": 0.2 },
+ { "matrix": [0, 5], "x": 5, "y": 0.4 },
+ { "matrix": [0, 6], "x": 6, "y": 0.9 },
+ { "matrix": [5, 6], "x": 9.75, "y": 0.9 },
+ { "matrix": [5, 5], "x": 10.75, "y": 0.4 },
+ { "matrix": [5, 4], "x": 11.75, "y": 0.2 },
+ { "matrix": [5, 3], "x": 12.75, "y": 0 },
+ { "matrix": [5, 2], "x": 13.75, "y": 0.2 },
+ { "matrix": [5, 1], "x": 14.75, "y": 0.6 },
+ { "matrix": [5, 0], "x": 15.75, "y": 0.6 },
+ { "matrix": [1, 0], "x": 0, "y": 1.6 },
+ { "matrix": [1, 1], "x": 1, "y": 1.6 },
+ { "matrix": [1, 2], "x": 2, "y": 1.2 },
+ { "matrix": [1, 3], "x": 3, "y": 1 },
+ { "matrix": [1, 4], "x": 4, "y": 1.2 },
+ { "matrix": [1, 5], "x": 5, "y": 1.4 },
+ { "matrix": [1, 6], "x": 6, "y": 1.9 },
+ { "matrix": [6, 6], "x": 9.75, "y": 1.9 },
+ { "matrix": [6, 5], "x": 10.75, "y": 1.4 },
+ { "matrix": [6, 4], "x": 11.75, "y": 1.2 },
+ { "matrix": [6, 3], "x": 12.75, "y": 1 },
+ { "matrix": [6, 2], "x": 13.75, "y": 1.2 },
+ { "matrix": [6, 1], "x": 14.75, "y": 1.6 },
+ { "matrix": [6, 0], "x": 15.75, "y": 1.6 },
+ { "matrix": [2, 0], "x": 0, "y": 2.6 },
+ { "matrix": [2, 1], "x": 1, "y": 2.6 },
+ { "matrix": [2, 2], "x": 2, "y": 2.2 },
+ { "matrix": [2, 3], "x": 3, "y": 2 },
+ { "matrix": [2, 4], "x": 4, "y": 2.2 },
+ { "matrix": [2, 5], "x": 5, "y": 2.4 },
+ { "matrix": [2, 6], "x": 6, "y": 2.9 },
+ { "matrix": [7, 6], "x": 9.75, "y": 2.9 },
+ { "matrix": [7, 5], "x": 10.75, "y": 2.4 },
+ { "matrix": [7, 4], "x": 11.75, "y": 2.2 },
+ { "matrix": [7, 3], "x": 12.75, "y": 2 },
+ { "matrix": [7, 2], "x": 13.75, "y": 2.2 },
+ { "matrix": [7, 1], "x": 14.75, "y": 2.6 },
+ { "matrix": [7, 0], "x": 15.75, "y": 2.6 },
+ { "matrix": [3, 1], "x": 1, "y": 3.6 },
+ { "matrix": [3, 2], "x": 2, "y": 3.2 },
+ { "matrix": [3, 3], "x": 3, "y": 3 },
+ { "matrix": [3, 4], "x": 4, "y": 3.2 },
+ { "matrix": [3, 5], "x": 5, "y": 3.4 },
+ { "matrix": [3, 6], "x": 7.2, "y": 3.15 },
+ { "matrix": [8, 6], "x": 8.6, "y": 3.15 },
+ { "matrix": [8, 5], "x": 10.75, "y": 3.4 },
+ { "matrix": [8, 4], "x": 11.75, "y": 3.2 },
+ { "matrix": [8, 3], "x": 12.75, "y": 3 },
+ { "matrix": [8, 2], "x": 13.75, "y": 3.2 },
+ { "matrix": [8, 1], "x": 14.75, "y": 3.6 },
+ { "matrix": [4, 2], "x": 3, "y": 4.45 },
+ { "matrix": [4, 3], "x": 4, "y": 4.45 },
+ { "matrix": [4, 4], "x": -0.15, "y": 4.65 },
+ { "h": 1.25, "matrix": [4, 5], "x": 0.85, "y": 4.4 },
+ { "h": 1.25, "matrix": [4, 6], "x": 1.85, "y": 4.4 },
+ { "h": 1.25, "matrix": [9, 6], "x": -3.1, "y": 4.6 },
+ { "h": 1.25, "matrix": [9, 5], "x": -2.1, "y": 4.6 },
+ { "matrix": [9, 4], "x": -1.1, "y": 4.85 },
+ { "matrix": [9, 3], "x": 11.75, "y": 4.45 },
+ { "matrix": [9, 2], "x": 12.75, "y": 4.45 }
+ ]
+ }
+ }
+}
diff --git a/keyboards/spleeb/keymaps/chrishoage/config.h b/keyboards/spleeb/keymaps/chrishoage/config.h
new file mode 100644
index 0000000000..0ac75d3041
--- /dev/null
+++ b/keyboards/spleeb/keymaps/chrishoage/config.h
@@ -0,0 +1,15 @@
+// Copyright 2022 Chris Hoage (@chrishoage)
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#define AUTO_MOUSE_TIME 250
+
+#define CIRQUE_PINNACLE_DIAMETER_MM 35
+#define POINTING_DEVICE_ROTATION_180
+
+#define SPLEEB_DRAGSCROLL_REVERSE_X
+#define SPLEEB_ENCODER_MODE_MAP_ENABLE
+
+#define BOOTMAGIC_LITE_ROW 5
+#define BOOTMAGIC_LITE_COLUMN 6
diff --git a/keyboards/spleeb/keymaps/chrishoage/keymap.c b/keyboards/spleeb/keymaps/chrishoage/keymap.c
new file mode 100644
index 0000000000..2650c1aebf
--- /dev/null
+++ b/keyboards/spleeb/keymaps/chrishoage/keymap.c
@@ -0,0 +1,127 @@
+// Copyright 2022 Chris Hoage (@chrishoage)
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include QMK_KEYBOARD_H
+
+// Double tap TD(0) to enter bootloader
+static void enter_qk_boot(qk_tap_dance_state_t *state, void *user_data) {
+ if (state->count >= 2) {
+ reset_keyboard();
+ reset_tap_dance(state);
+ }
+}
+
+enum SpleebLayer { _BASE = 0, _FN, _MOUSE };
+
+qk_tap_dance_action_t tap_dance_actions[] = {[0] = ACTION_TAP_DANCE_FN(enter_qk_boot)};
+
+// clang-format off
+const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
+
+ [_BASE] = LAYOUT(
+ KC_GRV, KC_1, KC_2, KC_3, KC_4, KC_5, KC_ESC, KC_BSPC, KC_6, KC_7, KC_8, KC_9, KC_0, KC_BSLS,
+ KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_MINS, KC_EQL, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_QUOT,
+ KC_LSFT, KC_A, KC_S, KC_D, KC_F, KC_G, KC_LBRC, KC_RBRC, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_RSFT,
+ KC_Z, KC_X, KC_C, KC_V, KC_B, KC_MUTE, ENC_STR, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH,
+ KC_LCTL, KC_LALT, KC_LGUI, KC_ENT, MO(1), MO(1), KC_SPC, KC_RGUI, KC_RALT, KC_RCTL
+ ),
+
+ [_FN] = LAYOUT(
+ KC_PAUS, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_ESC, KC_DEL, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_PSCR,
+ KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_HOME, KC_PGDN, KC_PGUP, KC_END, KC_TRNS, KC_TRNS,
+ KC_CAPS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_LEFT, KC_DOWN, KC_UP, KC_RGHT, KC_TRNS, QK_RBT,
+ KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, ENC_STL, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, TD(0),
+ KC_TRNS, KC_TRNS, KC_TRNS, KC_SPC, KC_TRNS, KC_TRNS, KC_ENT, KC_TRNS, KC_TRNS, KC_TRNS
+ ),
+
+ [_MOUSE] = LAYOUT(
+ KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
+ KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
+ DRGSCRL, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, SNIPING, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
+ KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_BTN3, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
+ KC_TRNS, KC_TRNS, KC_TRNS, KC_BTN1, KC_BTN2, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS
+ ),
+};
+// clang-format on
+
+bool encoder_update_user(uint8_t index, bool clockwise) {
+ if (get_mods() & MOD_MASK_GUI) {
+ // When GUI is held trigger [ ] to move workspaces
+ tap_code(clockwise ? KC_RBRC : KC_LBRC);
+ return false;
+ }
+
+ if (get_mods() & MOD_MASK_CTRL) {
+ // When CTRL is hled trigger page up/down to move tabs (Firefox, VSCode)
+ tap_code(clockwise ? KC_PGDN : KC_PGUP);
+ return false;
+ }
+
+ if (get_mods() & MOD_MASK_ALT) {
+ // When ALT is held trigger up/down to move line up/down
+ tap_code(clockwise ? KC_DOWN : KC_UP);
+ return false;
+ }
+
+ // Defer to encoder_update_kb to trigger spleeb_encoder_mode_trigger
+ return true;
+}
+
+enum spleeb_enc_mode {
+ DEF_DPI,
+ SNP_DPI,
+ VOL,
+ SEL,
+};
+
+void spleeb_encoder_mode_trigger(uint8_t mode, bool clockwise) {
+ dprintf("spleeb_encoder_mode_trigger m: %u, c: %u\n", mode, clockwise);
+ switch (mode) {
+ case DEF_DPI:
+ spleeb_cycle_pointer_default_dpi(clockwise);
+ break;
+ case SNP_DPI:
+ spleeb_cycle_pointer_sniping_dpi(clockwise);
+ break;
+ case VOL:
+ tap_code(clockwise ? KC_VOLU : KC_VOLD);
+ break;
+ case SEL: {
+ bool is_shift = get_mods() & MOD_MASK_SHIFT;
+ uint16_t dir = clockwise ? KC_RIGHT : KC_LEFT;
+ if (is_shift) {
+ tap_code(dir);
+ } else {
+ tap_code16(LSFT(LCTL(dir)));
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+const char *spleeb_encoder_mode_string(uint8_t mode) {
+ switch (mode) {
+ case DEF_DPI:
+ return "df dpi";
+ case SNP_DPI:
+ return "sn dpi";
+ case VOL:
+ return "volume";
+ case SEL:
+ return "select";
+ }
+
+ return get_u8_str(mode, ' ');
+}
+
+void pointing_device_init_user(void) {
+ set_auto_mouse_layer(_MOUSE);
+}
+
+const spleeb_enc_mode_t spleeb_encoder_mode_map[NUM_ENCODERS][SPLEEB_ENCODER_MODE_COUNT] = {
+ [0] = {SPLEEB_ENC_MODE(VOL), SPLEEB_ENC_MODE(SEL)},
+ [1] = {SPLEEB_ENC_MODE(DEF_DPI), SPLEEB_ENC_MODE(SNP_DPI), SPLEEB_ENC_MODE(SEL)},
+};
diff --git a/keyboards/spleeb/keymaps/chrishoage/rules.mk b/keyboards/spleeb/keymaps/chrishoage/rules.mk
new file mode 100644
index 0000000000..117c55fd8b
--- /dev/null
+++ b/keyboards/spleeb/keymaps/chrishoage/rules.mk
@@ -0,0 +1,11 @@
+TAP_DANCE_ENABLE = yes
+BOOTMAGIC_ENABLE = yes
+MOUSEKEY_ENABLE = yes
+EXTRAKEY_ENABLE = yes
+ENCODER_ENABLE = yes
+
+POINTING_DEVICE_ENABLE = yes
+POINTING_DEVICE_DRIVER = cirque_pinnacle_i2c
+
+OLED_ENABLE = yes
+OLED_DRIVER = SSD1306
diff --git a/keyboards/spleeb/keymaps/default/keymap.c b/keyboards/spleeb/keymaps/default/keymap.c
new file mode 100644
index 0000000000..dca49efd34
--- /dev/null
+++ b/keyboards/spleeb/keymaps/default/keymap.c
@@ -0,0 +1,33 @@
+// Copyright 2022 Chris Hoage (@chrishoage)
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include QMK_KEYBOARD_H
+
+// clang-format off
+const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
+
+ [0] = LAYOUT(
+ KC_GRV, KC_1, KC_2, KC_3, KC_4, KC_5, KC_ESC, KC_BSPC, KC_6, KC_7, KC_8, KC_9, KC_0, KC_BSLS,
+ KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_MINS, KC_EQL, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_QUOT,
+ KC_LSFT, KC_A, KC_S, KC_D, KC_F, KC_G, KC_LBRC, KC_RBRC, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_RSFT,
+ KC_Z, KC_X, KC_C, KC_V, KC_B, KC_NO, KC_NO, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH,
+ KC_LCTL, KC_LALT, KC_LGUI, KC_ENT, MO(1), MO(1), KC_SPC, KC_RGUI, KC_RALT, KC_RCTL
+ ),
+
+ [1] = LAYOUT(
+ KC_PAUS, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_ESC, KC_DEL, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_PSCR,
+ KC_LCAP, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_HOME, KC_PGDN, KC_PGUP, KC_END, KC_TRNS, KC_TRNS,
+ KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_LEFT, KC_DOWN, KC_UP, KC_RGHT, KC_TRNS, KC_TRNS,
+ KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
+ KC_TRNS, KC_TRNS, KC_TRNS, KC_SPC, KC_TRNS, KC_TRNS, KC_ENT, KC_TRNS, KC_TRNS, KC_TRNS
+ ),
+
+ [2] = LAYOUT(
+ KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
+ KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
+ KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
+ KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
+ KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS
+ ),
+};
+// clang-format on
diff --git a/keyboards/spleeb/lib/glcdfont.c b/keyboards/spleeb/lib/glcdfont.c
new file mode 100644
index 0000000000..5a7fcdaee2
--- /dev/null
+++ b/keyboards/spleeb/lib/glcdfont.c
@@ -0,0 +1,233 @@
+// Copyright 2022 Chris Hoage (@chrishoage)
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "progmem.h"
+
+// clang-format off
+static const unsigned char PROGMEM font[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x3E, 0x5B, 0x4F, 0x5B, 0x3E, 0x00,
+ 0x3E, 0x6B, 0x4F, 0x6B, 0x3E, 0x00,
+ 0x1C, 0x3E, 0x7C, 0x3E, 0x1C, 0x00,
+ 0x18, 0x3C, 0x7E, 0x3C, 0x18, 0x00,
+ 0x1C, 0x57, 0x7D, 0x57, 0x1C, 0x00,
+ 0x1C, 0x5E, 0x7F, 0x5E, 0x1C, 0x00,
+ 0x00, 0x18, 0x3C, 0x18, 0x00, 0x00,
+ 0xFF, 0xE7, 0xC3, 0xE7, 0xFF, 0x00,
+ 0x00, 0x18, 0x24, 0x18, 0x00, 0x00,
+ 0xFF, 0xE7, 0xDB, 0xE7, 0xFF, 0x00,
+ 0x30, 0x48, 0x3A, 0x06, 0x0E, 0x00,
+ 0x26, 0x29, 0x79, 0x29, 0x26, 0x00,
+ 0x40, 0x7F, 0x05, 0x05, 0x07, 0x00,
+ 0x40, 0x7F, 0x05, 0x25, 0x3F, 0x00,
+ 0x5A, 0x3C, 0xE7, 0x3C, 0x5A, 0x00,
+ 0x7F, 0x3E, 0x1C, 0x1C, 0x08, 0x00,
+ 0x08, 0x1C, 0x1C, 0x3E, 0x7F, 0x00,
+ 0x14, 0x22, 0x7F, 0x22, 0x14, 0x00,
+ 0x5F, 0x5F, 0x00, 0x5F, 0x5F, 0x00,
+ 0x06, 0x09, 0x7F, 0x01, 0x7F, 0x00,
+ 0x00, 0x66, 0x89, 0x95, 0x6A, 0x00,
+ 0x60, 0x60, 0x60, 0x60, 0x60, 0x00,
+ 0x94, 0xA2, 0xFF, 0xA2, 0x94, 0x00,
+ 0x08, 0x04, 0x7E, 0x04, 0x08, 0x00,
+ 0x10, 0x20, 0x7E, 0x20, 0x10, 0x00,
+ 0x08, 0x08, 0x2A, 0x1C, 0x08, 0x00,
+ 0x08, 0x1C, 0x2A, 0x08, 0x08, 0x00,
+ 0x1E, 0x10, 0x10, 0x10, 0x10, 0x00,
+ 0x0C, 0x1E, 0x0C, 0x1E, 0x0C, 0x00,
+ 0x30, 0x38, 0x3E, 0x38, 0x30, 0x00,
+ 0x06, 0x0E, 0x3E, 0x0E, 0x06, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00,
+ 0x00, 0x07, 0x00, 0x07, 0x00, 0x00,
+ 0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00,
+ 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00,
+ 0x23, 0x13, 0x08, 0x64, 0x62, 0x00,
+ 0x36, 0x49, 0x56, 0x20, 0x50, 0x00,
+ 0x00, 0x08, 0x07, 0x03, 0x00, 0x00,
+ 0x00, 0x1C, 0x22, 0x41, 0x00, 0x00,
+ 0x00, 0x41, 0x22, 0x1C, 0x00, 0x00,
+ 0x2A, 0x1C, 0x7F, 0x1C, 0x2A, 0x00,
+ 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00,
+ 0x00, 0x80, 0x70, 0x30, 0x00, 0x00,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x00,
+ 0x00, 0x00, 0x60, 0x60, 0x00, 0x00,
+ 0x20, 0x10, 0x08, 0x04, 0x02, 0x00,
+ 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00,
+ 0x00, 0x42, 0x7F, 0x40, 0x00, 0x00,
+ 0x72, 0x49, 0x49, 0x49, 0x46, 0x00,
+ 0x21, 0x41, 0x49, 0x4D, 0x33, 0x00,
+ 0x18, 0x14, 0x12, 0x7F, 0x10, 0x00,
+ 0x27, 0x45, 0x45, 0x45, 0x39, 0x00,
+ 0x3C, 0x4A, 0x49, 0x49, 0x31, 0x00,
+ 0x41, 0x21, 0x11, 0x09, 0x07, 0x00,
+ 0x36, 0x49, 0x49, 0x49, 0x36, 0x00,
+ 0x46, 0x49, 0x49, 0x29, 0x1E, 0x00,
+ 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x00, 0x40, 0x34, 0x00, 0x00, 0x00,
+ 0x00, 0x08, 0x14, 0x22, 0x41, 0x00,
+ 0x14, 0x14, 0x14, 0x14, 0x14, 0x00,
+ 0x00, 0x41, 0x22, 0x14, 0x08, 0x00,
+ 0x02, 0x01, 0x59, 0x09, 0x06, 0x00,
+ 0x3E, 0x41, 0x5D, 0x59, 0x4E, 0x00,
+ 0x7C, 0x12, 0x11, 0x12, 0x7C, 0x00,
+ 0x7F, 0x49, 0x49, 0x49, 0x36, 0x00,
+ 0x3E, 0x41, 0x41, 0x41, 0x22, 0x00,
+ 0x7F, 0x41, 0x41, 0x41, 0x3E, 0x00,
+ 0x7F, 0x49, 0x49, 0x49, 0x41, 0x00,
+ 0x7F, 0x09, 0x09, 0x09, 0x01, 0x00,
+ 0x3E, 0x41, 0x41, 0x51, 0x73, 0x00,
+ 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00,
+ 0x00, 0x41, 0x7F, 0x41, 0x00, 0x00,
+ 0x20, 0x40, 0x41, 0x3F, 0x01, 0x00,
+ 0x7F, 0x08, 0x14, 0x22, 0x41, 0x00,
+ 0x7F, 0x40, 0x40, 0x40, 0x40, 0x00,
+ 0x7F, 0x02, 0x1C, 0x02, 0x7F, 0x00,
+ 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00,
+ 0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00,
+ 0x7F, 0x09, 0x09, 0x09, 0x06, 0x00,
+ 0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00,
+ 0x7F, 0x09, 0x19, 0x29, 0x46, 0x00,
+ 0x26, 0x49, 0x49, 0x49, 0x32, 0x00,
+ 0x03, 0x01, 0x7F, 0x01, 0x03, 0x00,
+ 0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00,
+ 0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00,
+ 0x3F, 0x40, 0x38, 0x40, 0x3F, 0x00,
+ 0x63, 0x14, 0x08, 0x14, 0x63, 0x00,
+ 0x03, 0x04, 0x78, 0x04, 0x03, 0x00,
+ 0x61, 0x59, 0x49, 0x4D, 0x43, 0x00,
+ 0x00, 0x7F, 0x41, 0x41, 0x41, 0x00,
+ 0x02, 0x04, 0x08, 0x10, 0x20, 0x00,
+ 0x00, 0x41, 0x41, 0x41, 0x7F, 0x00,
+ 0x04, 0x02, 0x01, 0x02, 0x04, 0x00,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x00,
+ 0x00, 0x03, 0x07, 0x08, 0x00, 0x00,
+ 0x20, 0x54, 0x54, 0x78, 0x40, 0x00,
+ 0x7F, 0x28, 0x44, 0x44, 0x38, 0x00,
+ 0x38, 0x44, 0x44, 0x44, 0x20, 0x00,
+ 0x38, 0x44, 0x44, 0x28, 0x7F, 0x00,
+ 0x38, 0x54, 0x54, 0x54, 0x18, 0x00,
+ 0x00, 0x08, 0x7E, 0x09, 0x02, 0x00,
+ 0x18, 0xA4, 0xA4, 0x9C, 0x78, 0x00,
+ 0x7F, 0x08, 0x04, 0x04, 0x78, 0x00,
+ 0x00, 0x44, 0x7D, 0x40, 0x00, 0x00,
+ 0x20, 0x40, 0x40, 0x3D, 0x00, 0x00,
+ 0x7F, 0x10, 0x28, 0x44, 0x00, 0x00,
+ 0x00, 0x41, 0x7F, 0x40, 0x00, 0x00,
+ 0x7C, 0x04, 0x78, 0x04, 0x78, 0x00,
+ 0x7C, 0x08, 0x04, 0x04, 0x78, 0x00,
+ 0x38, 0x44, 0x44, 0x44, 0x38, 0x00,
+ 0xFC, 0x18, 0x24, 0x24, 0x18, 0x00,
+ 0x18, 0x24, 0x24, 0x18, 0xFC, 0x00,
+ 0x7C, 0x08, 0x04, 0x04, 0x08, 0x00,
+ 0x48, 0x54, 0x54, 0x54, 0x24, 0x00,
+ 0x04, 0x04, 0x3F, 0x44, 0x24, 0x00,
+ 0x3C, 0x40, 0x40, 0x20, 0x7C, 0x00,
+ 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00,
+ 0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00,
+ 0x44, 0x28, 0x10, 0x28, 0x44, 0x00,
+ 0x4C, 0x90, 0x90, 0x90, 0x7C, 0x00,
+ 0x44, 0x64, 0x54, 0x4C, 0x44, 0x00,
+ 0x00, 0x08, 0x36, 0x41, 0x00, 0x00,
+ 0x00, 0x00, 0x77, 0x00, 0x00, 0x00,
+ 0x00, 0x41, 0x36, 0x08, 0x00, 0x00,
+ 0x02, 0x01, 0x02, 0x04, 0x02, 0x00,
+ 0x3C, 0x26, 0x23, 0x26, 0x3C, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xE0, 0xF0, 0xF0, 0xF0, 0xE0, 0xEC,
+ 0xEE, 0xF7, 0xF3, 0x70, 0x20, 0x00,
+ 0x7C, 0x7C, 0x7C, 0x7E, 0x00, 0x7E,
+ 0x7E, 0x7E, 0x7F, 0x7F, 0x7F, 0x00,
+ 0x00, 0x80, 0xC0, 0xE0, 0x7E, 0x5B,
+ 0x4F, 0x5B, 0xFE, 0xC0, 0x00, 0x00,
+ 0xC0, 0x00, 0xDC, 0xD7, 0xDE, 0xDE,
+ 0xDE, 0xD7, 0xDC, 0x00, 0xC0, 0x00,
+ 0x00, 0x00, 0x00, 0x04, 0xFA, 0xA1,
+ 0xFA, 0x04, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0F, 0x1F, 0x3F, 0x7F, 0x7F, 0x7F,
+ 0x7F, 0x7F, 0x3F, 0x1E, 0x0C, 0x00,
+ 0x1F, 0x1F, 0x1F, 0x3F, 0x00, 0x3F,
+ 0x3F, 0x3F, 0x7F, 0x7F, 0x7F, 0x00,
+ 0x30, 0x7B, 0x7F, 0x78, 0x30, 0x20,
+ 0x20, 0x30, 0x78, 0x7F, 0x3B, 0x00,
+ 0x03, 0x00, 0x0F, 0x7F, 0x0F, 0x0F,
+ 0x0F, 0x7F, 0x0F, 0x00, 0x03, 0x00,
+ 0x00, 0x00, 0x00, 0xFC, 0x87, 0x95,
+ 0xB5, 0x87, 0xFC, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xBB, 0x81,
+ 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x9B, 0xAD, 0xAD,
+ 0xAD, 0xB3, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xDD, 0xBD, 0xB5,
+ 0xB5, 0xC9, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xE1, 0xEF, 0xEF,
+ 0x81, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x91, 0xB5, 0xB5,
+ 0xB5, 0x85, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x81, 0xB5, 0xB5,
+ 0xB5, 0x85, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x08, 0x74, 0x42,
+ 0x74, 0x08, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x10, 0x18, 0x0C, 0x06,
+ 0x0C, 0x18, 0x10, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x04, 0x04, 0x08, 0x10,
+ 0x24, 0x24, 0x24, 0x24, 0x00, 0x00,
+ 0x00, 0x00, 0xE7, 0xA5, 0xFF, 0x24,
+ 0x24, 0xFF, 0xA5, 0xE7, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+// clang-format on
diff --git a/keyboards/spleeb/mcuconf.h b/keyboards/spleeb/mcuconf.h
new file mode 100644
index 0000000000..30b20fdcbd
--- /dev/null
+++ b/keyboards/spleeb/mcuconf.h
@@ -0,0 +1,9 @@
+// Copyright 2022 Chris Hoage (@chrishoage)
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include_next <mcuconf.h>
+
+#undef RP_I2C_USE_I2C0
+#define RP_I2C_USE_I2C0 TRUE
diff --git a/keyboards/spleeb/readme.md b/keyboards/spleeb/readme.md
new file mode 100644
index 0000000000..0b42352e29
--- /dev/null
+++ b/keyboards/spleeb/readme.md
@@ -0,0 +1,109 @@
+# spleeb
+
+![spleeb](https://i.imgur.com/2rmZa6Mh.jpg)
+
+A 5x7 split keyboard that has support for rotary encoders, an oled and a Ciruqe touchpad
+
+* Keyboard Maintainer: [Chris Hoage](https://github.com/chrishoage)
+* Hardware Supported: Spleeb PCB with a rp2040 MCU (Blok, Elite Pi, etc)
+* Hardware Availability: [https://github.com/chrishoage/spleeb](https://github.com/chrishoage/spleeb)
+
+Make example for this keyboard (after setting up your build environment):
+
+ make spleeb:default
+
+Flashing example for this keyboard:
+
+ make spleeb:default:flash
+
+See the [build environment setup](https://docs.qmk.fm/#/getting_started_build_tools) and the [make instructions](https://docs.qmk.fm/#/getting_started_make_guide) for more information. Brand new to QMK? Start with our [Complete Newbs Guide](https://docs.qmk.fm/#/newbs).
+
+## Encoder Mode Map
+
+Spleeb firmware has support for an encoder mode map similar to the native encoder map in QMK. The difference is the encoder mode map allows one to change behavior or the encoders independently of the layers. In your keymap.c file you can include something the following:
+
+```c
+enum spleeb_enc_mode {
+ DEF_DPI,
+ SNP_DPI,
+ VOL,
+ SEL,
+};
+
+void spleeb_encoder_mode_trigger(uint8_t mode, bool clockwise) {
+ dprintf("spleeb_encoder_mode_trigger m: %u, c: %u\n", mode, clockwise);
+ switch (mode) {
+ case DEF_DPI:
+ spleeb_cycle_pointer_default_dpi(clockwise);
+ break;
+ case SNP_DPI:
+ spleeb_cycle_pointer_sniping_dpi(clockwise);
+ break;
+ case VOL:
+ tap_code(clockwise ? KC_VOLU : KC_VOLD);
+ break;
+ case SEL:
+ bool is_shift = get_mods() & MOD_MASK_SHIFT;
+ uint16_t dir = clockwise ? KC_RIGHT : KC_LEFT;
+ if (is_shift) {
+ tap_code(dir);
+ } else {
+ tap_code16(LSFT(LCTL(dir)));
+ }
+
+ default:
+ break;
+ }
+}
+
+const char *spleeb_encoder_mode_string(uint8_t mode) {
+ switch (mode) {
+ case DEF_DPI:
+ return "df dpi";
+ case SNP_DPI:
+ return "sn dpi";
+ case VOL:
+ return "volume";
+ case SEL:
+ return "select";
+ }
+
+ return get_u8_str(mode, ' ');
+}
+
+void pointing_device_init_user(void) {
+ set_auto_mouse_layer(_MOUSE);
+}
+
+const spleeb_enc_mode_t spleeb_encoder_mode_map[NUM_ENCODERS][SPLEEB_ENCODER_MODE_COUNT] = {
+ [0] = {SPLEEB_ENC_MODE(VOL), SPLEEB_ENC_MODE(SEL)},
+ [1] = {SPLEEB_ENC_MODE(DEF_DPI), SPLEEB_ENC_MODE(SNP_DPI), SPLEEB_ENC_MODE(SEL)},
+};
+```
+
+This will enable 4 encoder modes. On the left side there will be modes for volume control and text selection. On the right side there will be three modes to allow controlling the DPI of the Cirque trackpad and also text selection.
+
+## Custom Keycodes
+
+This firmware defines the following custom keycodes for use in keymap.c. Depending on your defines the pointing or encoder specific keymaps will not be included.
+
+```c
+#define DF_MOD POINTER_DEFAULT_DPI_FORWARD
+#define DF_RMOD POINTER_DEFAULT_DPI_REVERSE
+#define SP_MOD POINTER_SNIPING_DPI_FORWARD
+#define SP_RMOD POINTER_SNIPING_DPI_REVERSE
+#define SNIPING SNIPING_MODE
+#define SNP_TOG SNIPING_MODE_TOGGLE
+#define DRGSCRL DRAGSCROLL_MODE
+#define DRG_TOG DRAGSCROLL_MODE_TOGGLE
+#define ENC_STL ENC_MODE_STEP_LEFT
+#define ENC_STR ENC_MODE_STEP_RIGHT
+```
+
+## Bootloader
+
+Enter the bootloader in 3 ways:
+
+* **Bootmagic reset**: Hold down the key at (0,0) in the matrix (usually the top left key or Escape) and plug in the keyboard
+* **Physical reset button**: Briefly press the button on the back of the PCB - some may have pads you must short instead
+* **Keycode in layout**: Press the key mapped to `QK_BOOT` if it is available
diff --git a/keyboards/spleeb/rules.mk b/keyboards/spleeb/rules.mk
new file mode 100644
index 0000000000..161ec22b16
--- /dev/null
+++ b/keyboards/spleeb/rules.mk
@@ -0,0 +1 @@
+SERIAL_DRIVER = vendor
diff --git a/keyboards/spleeb/spleeb.c b/keyboards/spleeb/spleeb.c
new file mode 100644
index 0000000000..658f30df75
--- /dev/null
+++ b/keyboards/spleeb/spleeb.c
@@ -0,0 +1,544 @@
+// Copyright 2022 Chris Hoage (@chrishoage)
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "spleeb.h"
+#include "transactions.h"
+
+#ifdef CONSOLE_ENABLE
+# include "print.h"
+#endif // CONSOLE_ENABLE
+
+#if defined(POINTING_DEVICE_ENABLE) || defined(SPLEEB_ENCODER_MODE_MAP_ENABLE)
+typedef union {
+ uint16_t raw;
+ struct {
+ uint8_t pointer_default_dpi : 4; // 16 steps available.
+ uint8_t pointer_sniping_dpi : 2; // 4 steps available.
+ uint8_t enc_modes[NUM_ENCODERS];
+ bool is_dragscroll_enabled : 1;
+ bool is_sniping_enabled : 1;
+ } __attribute__((packed));
+} spleeb_config_t;
+
+static spleeb_config_t g_spleeb_config = {0};
+
+/**
+ * \brief Set the value of `config` from EEPROM.
+ *
+ * Note that `is_dragscroll_enabled` and `is_sniping_enabled` are purposefully
+ * ignored since we do not want to persist this state to memory. In practice,
+ * this state is always written to maximize write-performances. Therefore, we
+ * explicitly set them to `false` in this function.
+ */
+static void read_spleeb_config_from_eeprom(spleeb_config_t* config) {
+ config->raw = eeconfig_read_kb() & 0xffff;
+ config->is_dragscroll_enabled = false;
+ config->is_sniping_enabled = false;
+}
+
+/**
+ * \brief Save the value of `config` to eeprom.
+ *
+ * Note that all values are written verbatim, including whether drag-scroll
+ * and/or sniper mode are enabled. `read_spleeb_config_from_eeprom(…)`
+ * resets these 2 values to `false` since it does not make sense to persist
+ * these across reboots of the board.
+ */
+static void write_spleeb_config_to_eeprom(spleeb_config_t* config) {
+ eeconfig_update_kb(config->raw);
+}
+
+void eeconfig_init_kb(void) {
+ g_spleeb_config.raw = 0;
+ g_spleeb_config.pointer_default_dpi = 4;
+
+# ifdef SPLEEB_ENCODER_MODE_MAP_ENABLE
+ for (size_t i = 0; i < NUM_ENCODERS; i++) {
+ if (spleeb_encoder_mode_map[i][0].initalized) {
+ spleeb_enc_mode_t* first_enc_mode = &spleeb_encoder_mode_map[i][0];
+ g_spleeb_config.enc_modes[i] = first_enc_mode->mode;
+ }
+ }
+# endif // SPLEEB_ENCODER_MODE_MAP_ENABLE
+
+ write_spleeb_config_to_eeprom(&g_spleeb_config);
+ eeconfig_init_user();
+}
+
+void matrix_init_kb(void) {
+ read_spleeb_config_from_eeprom(&g_spleeb_config);
+ matrix_init_user();
+}
+
+void spleeb_config_sync_handler(uint8_t initiator2target_buffer_size, const void* initiator2target_buffer, uint8_t target2initiator_buffer_size, void* target2initiator_buffer) {
+ if (initiator2target_buffer_size == sizeof(g_spleeb_config)) {
+ memcpy(&g_spleeb_config, initiator2target_buffer, sizeof(g_spleeb_config));
+ }
+}
+
+void keyboard_post_init_kb(void) {
+ transaction_register_rpc(RPC_ID_KB_CONFIG_SYNC, spleeb_config_sync_handler);
+ keyboard_post_init_user();
+}
+
+void housekeeping_task_kb(void) {
+ if (is_keyboard_master()) {
+ // Keep track of the last state, so that we can tell if we need to propagate to slave.
+ static spleeb_config_t last_spleeb_config = {0};
+ static uint32_t last_sync = 0;
+ bool needs_sync = false;
+
+ // Check if the state values are different.
+ if (memcmp(&g_spleeb_config, &last_spleeb_config, sizeof(g_spleeb_config))) {
+ needs_sync = true;
+ memcpy(&last_spleeb_config, &g_spleeb_config, sizeof(g_spleeb_config));
+ }
+ // Send to slave every 500ms regardless of state change.
+ if (timer_elapsed32(last_sync) > 500) {
+ needs_sync = true;
+ }
+
+ // Perform the sync if requested.
+ if (needs_sync) {
+ if (transaction_rpc_send(RPC_ID_KB_CONFIG_SYNC, sizeof(g_spleeb_config), &g_spleeb_config)) {
+ last_sync = timer_read32();
+ }
+ }
+ }
+ // No need to invoke the user-specific callback, as it's been called
+ // already.
+}
+#endif // defined(POINTING_DEVICE_ENABLE) || defined(SPLEEB_ENCODER_MODE_MAP_ENABLE)
+
+#ifdef SPLEEB_ENCODER_MODE_MAP_ENABLE
+
+/**
+ * \brief Handle the encoder mode action when triggered by encoder_update_kb
+ *
+ * Weakly defined fuction intended to be overridden in a users keymap
+ */
+__attribute__((weak)) void spleeb_encoder_mode_trigger(uint8_t mode, bool clockwise) {}
+
+typedef struct {
+ uint8_t index;
+ spleeb_enc_mode_t* enc_mode;
+} spleeb_found_enc_mode_t;
+
+static spleeb_found_enc_mode_t spleeb_get_found_encoder_mode(spleeb_config_t* config, uint8_t index) {
+ spleeb_found_enc_mode_t found_enc_mode;
+
+ for (size_t i = 0; i < SPLEEB_ENCODER_MODE_COUNT; i++) {
+ spleeb_enc_mode_t* cur_enc_mode = &spleeb_encoder_mode_map[index][i];
+ if (cur_enc_mode->mode == config->enc_modes[index]) {
+ found_enc_mode.index = i;
+ found_enc_mode.enc_mode = cur_enc_mode;
+ break;
+ }
+ }
+
+ return found_enc_mode;
+}
+
+/**
+ * \brief Step through the defined encoder modes for the encoder at the given
+ * index
+ *
+ * Step though the modes defined in spleeb_encoder_mode_map at the users keymap.
+ * Use a null terminator at the first character on the name property for the
+ * enc_mode struct to determine if we've reached the end of the defined encoder
+ * modes. When this happens loop back to the beginning.
+ */
+static void spleeb_step_encoder_mode(spleeb_config_t* config, uint8_t index) {
+ spleeb_found_enc_mode_t cur_enc_mode = spleeb_get_found_encoder_mode(config, index);
+ spleeb_enc_mode_t* next_enc_mode = &spleeb_encoder_mode_map[index][(cur_enc_mode.index + 1) % SPLEEB_ENCODER_MODE_COUNT];
+
+ if (!next_enc_mode->initalized) {
+ next_enc_mode = &spleeb_encoder_mode_map[index][0];
+ }
+
+ if (next_enc_mode->initalized) {
+ config->enc_modes[index] = next_enc_mode->mode;
+ write_spleeb_config_to_eeprom(config);
+ }
+}
+
+bool encoder_update_kb(uint8_t index, bool clockwise) {
+ if (!encoder_update_user(index, clockwise)) {
+ return false;
+ }
+
+ spleeb_encoder_mode_trigger(g_spleeb_config.enc_modes[index], clockwise);
+
+ return true;
+}
+#endif // SPLEEB_ENCODER_MODE_MAP_ENABLE
+
+#ifdef POINTING_DEVICE_ENABLE
+
+/** \brief Return the current value of the pointer's default DPI. */
+static uint16_t get_pointer_default_dpi(spleeb_config_t* config) {
+ return (uint16_t)config->pointer_default_dpi * SPLEEB_DEFAULT_DPI_CONFIG_STEP + SPLEEB_MINIMUM_DEFAULT_DPI;
+}
+
+/** \brief Return the current value of the pointer's sniper-mode DPI. */
+static uint16_t get_pointer_sniping_dpi(spleeb_config_t* config) {
+ return (uint16_t)config->pointer_sniping_dpi * SPLEEB_SNIPING_DPI_CONFIG_STEP + SPLEEB_MINIMUM_SNIPING_DPI;
+}
+
+/** \brief Return the current value of the pointer's default DPI. */
+static uint16_t get_pointer_current_dpi(spleeb_config_t* config) {
+ if (config->is_sniping_enabled) {
+ return get_pointer_sniping_dpi(config);
+ } else {
+ return get_pointer_default_dpi(config);
+ }
+}
+
+/** \brief Set the appropriate DPI for the input config. */
+static void maybe_update_pointing_device_cpi(spleeb_config_t* config) {
+ if (config->is_sniping_enabled) {
+ pointing_device_set_cpi(get_pointer_sniping_dpi(config));
+ } else {
+ pointing_device_set_cpi(get_pointer_default_dpi(config));
+ }
+}
+
+/**
+ * \brief Update the pointer's default DPI to the next or previous step.
+ *
+ * Increases the DPI value if `forward` is `true`, decreases it otherwise.
+ * The increment/decrement steps are equal to SPLEEB_DEFAULT_DPI_CONFIG_STEP.
+ */
+static void step_pointer_default_dpi(spleeb_config_t* config, bool forward) {
+ config->pointer_default_dpi += forward ? 1 : -1;
+ maybe_update_pointing_device_cpi(config);
+}
+
+/**
+ * \brief Update the pointer's sniper-mode DPI to the next or previous step.
+ *
+ * Increases the DPI value if `forward` is `true`, decreases it otherwise.
+ * The increment/decrement steps are equal to SPLEEB_SNIPING_DPI_CONFIG_STEP.
+ */
+static void step_pointer_sniping_dpi(spleeb_config_t* config, bool forward) {
+ config->pointer_sniping_dpi += forward ? 1 : -1;
+ maybe_update_pointing_device_cpi(config);
+}
+
+uint16_t spleeb_get_pointer_default_dpi(void) {
+ return get_pointer_default_dpi(&g_spleeb_config);
+}
+
+uint16_t spleeb_get_pointer_sniping_dpi(void) {
+ return get_pointer_sniping_dpi(&g_spleeb_config);
+}
+
+void spleeb_cycle_pointer_default_dpi_noeeprom(bool forward) {
+ step_pointer_default_dpi(&g_spleeb_config, forward);
+}
+
+void spleeb_cycle_pointer_default_dpi(bool forward) {
+ step_pointer_default_dpi(&g_spleeb_config, forward);
+ write_spleeb_config_to_eeprom(&g_spleeb_config);
+}
+
+void spleeb_cycle_pointer_sniping_dpi_noeeprom(bool forward) {
+ step_pointer_sniping_dpi(&g_spleeb_config, forward);
+}
+
+void spleeb_cycle_pointer_sniping_dpi(bool forward) {
+ step_pointer_sniping_dpi(&g_spleeb_config, forward);
+ write_spleeb_config_to_eeprom(&g_spleeb_config);
+}
+
+bool spleeb_get_pointer_sniping_enabled(void) {
+ return g_spleeb_config.is_sniping_enabled;
+}
+
+void spleeb_set_pointer_sniping_enabled(bool enable) {
+ g_spleeb_config.is_sniping_enabled = enable;
+ maybe_update_pointing_device_cpi(&g_spleeb_config);
+}
+
+bool spleeb_get_pointer_dragscroll_enabled(void) {
+ return g_spleeb_config.is_dragscroll_enabled;
+}
+
+void spleeb_set_pointer_dragscroll_enabled(bool enable) {
+ g_spleeb_config.is_dragscroll_enabled = enable;
+ cirque_pinnacle_enable_cursor_glide(enable);
+ maybe_update_pointing_device_cpi(&g_spleeb_config);
+}
+#endif // POINTING_DEVICE_ENABLE
+
+#ifdef POINTING_DEVICE_ENABLE
+void pointing_device_init_kb(void) {
+ maybe_update_pointing_device_cpi(&g_spleeb_config);
+
+ // only glide on drag scroll
+ cirque_pinnacle_enable_cursor_glide(false);
+
+ set_auto_mouse_enable(true);
+ pointing_device_init_user();
+}
+
+/**
+ * \brief Augment the pointing device behavior.
+ *
+ * Drag-scroll implementation borrowed from https://github.com/qmk/qmk_firmware/pull/18218
+ */
+static void pointing_device_task_spleeb(report_mouse_t* mouse_report) {
+ static int16_t scroll_x = 0;
+ static int16_t scroll_y = 0;
+ if (g_spleeb_config.is_dragscroll_enabled) {
+ scroll_x -= mouse_report->x;
+ scroll_y += mouse_report->y;
+ mouse_report->h = scroll_x / SPLEEB_DRAGSCROLL_DIVISOR;
+ mouse_report->v = scroll_y / SPLEEB_DRAGSCROLL_DIVISOR;
+ mouse_report->x = 0;
+ mouse_report->y = 0;
+ scroll_x -= (int16_t)mouse_report->h * SPLEEB_DRAGSCROLL_DIVISOR;
+ scroll_y -= (int16_t)mouse_report->v * SPLEEB_DRAGSCROLL_DIVISOR;
+ }
+}
+
+report_mouse_t pointing_device_task_kb(report_mouse_t mouse_report) {
+ if (is_keyboard_master()) {
+ pointing_device_task_spleeb(&mouse_report);
+
+ mouse_report = pointing_device_task_user(mouse_report);
+ }
+
+ return mouse_report;
+}
+
+/**
+ * \brief Outputs the Spleeb configuration to console.
+ *
+ * Prints the in-memory configuration structure to console, for debugging.
+ * Includes:
+ * - raw value
+ * - drag-scroll: on/off
+ * - sniping: on/off
+ * - default DPI: internal table index/actual DPI
+ * - sniping DPI: internal table index/actual DPI
+ */
+static void debug_spleeb_config_to_console(spleeb_config_t* config) {
+# ifdef CONSOLE_ENABLE
+ pd_dprintf("(spleeb) process_record_kb: config = {\n"
+ "\traw = 0x%u,\n"
+ "\t{\n"
+ "\t\tis_dragscroll_enabled=%u\n"
+ "\t\tis_sniping_enabled=%u\n"
+ "\t\tdefault_dpi=0x%X (%u)\n"
+ "\t\tsniping_dpi=0x%X (%u)\n"
+ "\t}\n"
+ "}\n",
+ config->raw, config->is_dragscroll_enabled, config->is_sniping_enabled, config->pointer_default_dpi, get_pointer_default_dpi(config), config->pointer_sniping_dpi, get_pointer_sniping_dpi(config));
+# endif // CONSOLE_ENABLE
+}
+#endif // POINTING_DEVICE_ENABLE
+
+bool process_record_kb(uint16_t keycode, keyrecord_t* record) {
+ if (!process_record_user(keycode, record)) {
+#ifdef POINTING_DEVICE_ENABLE
+
+ debug_spleeb_config_to_console(&g_spleeb_config);
+#endif // POINTING_DEVICE_ENABLE
+ return false;
+ }
+#ifdef POINTING_DEVICE_ENABLE
+ switch (keycode) {
+ case POINTER_DEFAULT_DPI_FORWARD:
+ if (record->event.pressed) {
+ spleeb_cycle_pointer_default_dpi(true);
+ }
+ break;
+ case POINTER_DEFAULT_DPI_REVERSE:
+ if (record->event.pressed) {
+ spleeb_cycle_pointer_default_dpi(false);
+ }
+ break;
+ case POINTER_SNIPING_DPI_FORWARD:
+ if (record->event.pressed) {
+ spleeb_cycle_pointer_sniping_dpi(true);
+ }
+ break;
+ case POINTER_SNIPING_DPI_REVERSE:
+ if (record->event.pressed) {
+ spleeb_cycle_pointer_sniping_dpi(false);
+ }
+ break;
+ case SNIPING_MODE:
+ spleeb_set_pointer_sniping_enabled(record->event.pressed);
+ break;
+ case SNIPING_MODE_TOGGLE:
+ if (record->event.pressed) {
+ spleeb_set_pointer_sniping_enabled(!spleeb_get_pointer_sniping_enabled());
+ }
+ break;
+ case DRAGSCROLL_MODE:
+ spleeb_set_pointer_dragscroll_enabled(record->event.pressed);
+ break;
+ case DRAGSCROLL_MODE_TOGGLE:
+ if (record->event.pressed) {
+ spleeb_set_pointer_dragscroll_enabled(!spleeb_get_pointer_dragscroll_enabled());
+ }
+ break;
+ }
+#endif // POINTING_DEVICE_ENABLE
+
+#ifdef SPLEEB_ENCODER_MODE_MAP_ENABLE
+ switch (keycode) {
+ case ENC_MODE_STEP_LEFT:
+ if (record->event.pressed) {
+ spleeb_step_encoder_mode(&g_spleeb_config, 0);
+ }
+ break;
+ case ENC_MODE_STEP_RIGHT:
+ if (record->event.pressed) {
+ spleeb_step_encoder_mode(&g_spleeb_config, 1);
+ }
+ break;
+ }
+#endif // SPLEEB_ENCODER_MODE_MAP_ENABLE
+
+#ifdef POINTING_DEVICE_ENABLE
+ if ((keycode >= POINTER_DEFAULT_DPI_FORWARD && keycode <= ENC_MODE_STEP_RIGHT) || IS_MOUSEKEY(keycode)) {
+ debug_spleeb_config_to_console(&g_spleeb_config);
+ }
+#endif // POINTING_DEVICE_ENABLE
+
+ return true;
+}
+
+#ifdef POINTING_DEVICE_ENABLE
+
+bool is_mouse_record_kb(uint16_t keycode, keyrecord_t* record) {
+ switch (keycode) {
+ case DRAGSCROLL_MODE:
+ case SNIPING_MODE:
+ return true;
+ default:
+ return false;
+ }
+
+ return is_mouse_record_user(keycode, record);
+}
+
+#endif // POINTING_DEVICE_ENABLE
+
+#ifdef OLED_ENABLE
+
+static void render_layer(void) {
+ oled_write_P(PSTR("LAYER: "), false);
+
+ switch (get_highest_layer(layer_state)) {
+ case 0:
+ oled_write_ln_P(PSTR("\xC0\xC1"), false);
+ break;
+ case 1:
+ oled_write_ln_P(PSTR("\xC2\xC3"), false);
+ break;
+ case 2:
+ oled_write_ln_P(PSTR("\xC4\xC5"), false);
+ break;
+ case 3:
+ oled_write_ln_P(PSTR("\xC6\xC7"), false);
+ break;
+ case 4:
+ oled_write_ln_P(PSTR("\xC8\xC9"), false);
+ break;
+ case 5:
+ oled_write_ln_P(PSTR("\xCA\xCB"), false);
+ break;
+ default:
+ oled_write_ln_P(get_u8_str(get_highest_layer(layer_state) + 0x30, ' '), true);
+ }
+
+ oled_write_ln_P("", false);
+}
+
+static void render_mods(void) {
+ uint8_t modifiers = get_mods();
+
+ oled_write_ln_P(PSTR("MODS:"), false);
+ oled_write_ln_P("", false);
+ oled_write_P(PSTR("\325\326"), (modifiers & MOD_MASK_SHIFT));
+ oled_write_P(PSTR("\327\330"), (modifiers & MOD_MASK_CTRL));
+ oled_write_P(PSTR("\331\332"), (modifiers & MOD_MASK_ALT));
+ oled_write_ln_P(PSTR("\333\334"), (modifiers & MOD_MASK_GUI));
+ oled_write_ln_P("", false);
+}
+
+static void render_lock(void) {
+ led_t led_state = host_keyboard_led_state();
+
+ oled_write_P(PSTR("LOCK: "), false);
+ oled_write_P(PSTR("\235\236"), led_state.caps_lock);
+ oled_write_ln_P(PSTR("\275\276"), led_state.num_lock);
+}
+
+static void render_pointer(void) {
+# ifdef POINTING_DEVICE_ENABLE
+ oled_write_ln_P(PSTR("POINTER:"), false);
+ oled_write_ln_P("", false);
+ oled_write_P(PSTR("dpi:"), false);
+ oled_write_ln_P(get_u16_str(get_pointer_current_dpi(&g_spleeb_config), ' '), false);
+ oled_write_ln_P("", false);
+# endif // POINTING_DEVICE_ENABLE
+}
+
+# ifdef SPLEEB_ENCODER_MODE_MAP_ENABLE
+static uint8_t spleeb_get_encoder_mode(spleeb_config_t* config, uint8_t index) {
+ spleeb_found_enc_mode_t found_enc_mode = spleeb_get_found_encoder_mode(config, index);
+ return found_enc_mode.enc_mode->mode;
+}
+
+/**
+ * \brief Map an encoder mode to a string to be displayed on the OLED
+ *
+ * Weakly defined fuction intended to be overridden in a users keymap. My be
+ * omitted if no OLED is used.
+ */
+__attribute__((weak)) const char* spleeb_encoder_mode_string(uint8_t mode) {
+ return get_u8_str(mode, ' ');
+}
+# endif // SPLEEB_ENCODER_MODE_MAP_ENABLE
+
+static void render_encoder(void) {
+# ifdef SPLEEB_ENCODER_MODE_MAP_ENABLE
+ oled_write_ln_P(PSTR("ENCODER:"), false);
+ oled_write_ln_P("", false);
+ oled_write_P(PSTR("R: "), false);
+ oled_write_ln_P(spleeb_encoder_mode_string(spleeb_get_encoder_mode(&g_spleeb_config, 1)), false);
+ oled_write_P(PSTR("L: "), false);
+ oled_write_ln_P(spleeb_encoder_mode_string(spleeb_get_encoder_mode(&g_spleeb_config, 0)), false);
+# endif // SPLEEB_ENCODER_MODE_MAP_ENABLE
+}
+
+static void render_status(void) {
+ render_layer();
+ render_mods();
+ render_lock();
+ render_pointer();
+ render_encoder();
+}
+
+oled_rotation_t oled_init_kb(oled_rotation_t rotation) {
+ return OLED_ROTATION_90;
+}
+
+bool oled_task_kb(void) {
+ if (is_keyboard_master()) {
+ return false;
+ }
+
+ if (!oled_task_user()) {
+ return false;
+ }
+
+ render_status();
+ return false;
+}
+#endif // OLED_ENABLE
diff --git a/keyboards/spleeb/spleeb.h b/keyboards/spleeb/spleeb.h
new file mode 100644
index 0000000000..67d01cdf35
--- /dev/null
+++ b/keyboards/spleeb/spleeb.h
@@ -0,0 +1,149 @@
+// Copyright 2022 Chris Hoage (@chrishoage)
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "quantum.h"
+
+#if defined(SPLEEB_ENCODER_MODE_MAP_ENABLE) && !defined(ENCODER_ENABLE)
+# error "Encoder must be enabled to use encoder mode map"
+#endif
+
+#if defined(SPLEEB_ENCODER_MODE_MAP_ENABLE) && defined(ENCODER_MAP_ENABLE)
+# error "Encoder mode map can not be used with encoder map"
+#endif
+
+enum spleeb_keycodes {
+ POINTER_DEFAULT_DPI_FORWARD = QK_KB,
+ POINTER_DEFAULT_DPI_REVERSE,
+ POINTER_SNIPING_DPI_FORWARD,
+ POINTER_SNIPING_DPI_REVERSE,
+ SNIPING_MODE,
+ SNIPING_MODE_TOGGLE,
+ DRAGSCROLL_MODE,
+ DRAGSCROLL_MODE_TOGGLE,
+ ENC_MODE_STEP_LEFT,
+ ENC_MODE_STEP_RIGHT,
+};
+
+#define DF_MOD POINTER_DEFAULT_DPI_FORWARD
+#define DF_RMOD POINTER_DEFAULT_DPI_REVERSE
+#define SP_MOD POINTER_SNIPING_DPI_FORWARD
+#define SP_RMOD POINTER_SNIPING_DPI_REVERSE
+#define SNIPING SNIPING_MODE
+#define SNP_TOG SNIPING_MODE_TOGGLE
+#define DRGSCRL DRAGSCROLL_MODE
+#define DRG_TOG DRAGSCROLL_MODE_TOGGLE
+#define ENC_STL ENC_MODE_STEP_LEFT
+#define ENC_STR ENC_MODE_STEP_RIGHT
+
+#ifdef POINTING_DEVICE_ENABLE
+# ifndef SPLEEB_MINIMUM_DEFAULT_DPI
+# define SPLEEB_MINIMUM_DEFAULT_DPI 300
+# endif // SPLEEB_MINIMUM_DEFAULT_DPI
+
+# ifndef SPLEEB_DEFAULT_DPI_CONFIG_STEP
+# define SPLEEB_DEFAULT_DPI_CONFIG_STEP 100
+# endif // SPLEEB_DEFAULT_DPI_CONFIG_STEP
+
+# ifndef SPLEEB_MINIMUM_SNIPING_DPI
+# define SPLEEB_MINIMUM_SNIPING_DPI 100
+# endif // SPLEEB_MINIMUM_SNIPING_DPI
+
+# ifndef SPLEEB_SNIPING_DPI_CONFIG_STEP
+# define SPLEEB_SNIPING_DPI_CONFIG_STEP 100
+# endif // SPLEEB_SNIPING_DPI_CONFIG_STEP
+
+# ifndef SPLEEB_DRAGSCROLL_DIVISOR
+# define SPLEEB_DRAGSCROLL_DIVISOR 64
+# endif // !SPLEEB_DRAGSCROLL_DIVISOR
+#endif // POINTING_DEVICE_ENABLE
+
+#ifdef SPLEEB_ENCODER_MODE_MAP_ENABLE
+# ifndef SPLEEB_ENCODER_MODE_COUNT
+# define SPLEEB_ENCODER_MODE_COUNT 4
+# endif
+
+typedef struct {
+ uint8_t mode;
+ // Discriminate between array members which are (un)initialized
+ bool initalized;
+} const spleeb_enc_mode_t;
+
+const spleeb_enc_mode_t spleeb_encoder_mode_map[NUM_ENCODERS][SPLEEB_ENCODER_MODE_COUNT];
+
+// SPLEEB_ENC_MODE initializes the spleeb_enc_mode_t struct such that
+// uninitialized mode_map members can be discriminated against when looking up
+// mapped encoder modes.
+# define SPLEEB_ENC_MODE(mode) \
+ { mode, true }
+#endif // SPLEEB_ENCODER_MODE_MAP_ENABLE
+
+#ifdef POINTING_DEVICE_ENABLE
+
+/** \brief Return the current DPI value for the pointer's default mode. */
+uint16_t spleeb_get_pointer_default_dpi(void);
+
+/**
+ * \brief Update the pointer's default DPI to the next or previous step.
+ *
+ * Increases the DPI value if `forward` is `true`, decreases it otherwise.
+ * The increment/decrement steps are equal to SPLEEB_DEFAULT_DPI_CONFIG_STEP.
+ *
+ * The new value is persisted in EEPROM.
+ */
+void spleeb_cycle_pointer_default_dpi(bool forward);
+
+/**
+ * \brief Same as `spleeb_cycle_pointer_default_dpi`, but do not write to
+ * EEPROM.
+ *
+ * This means that reseting the board will revert the value to the last
+ * persisted one.
+ */
+void spleeb_cycle_pointer_default_dpi_noeeprom(bool forward);
+
+/** \brief Return the current DPI value for the pointer's sniper-mode. */
+uint16_t spleeb_get_pointer_sniping_dpi(void);
+
+/**
+ * \brief Update the pointer's sniper-mode DPI to the next or previous step.
+ *
+ * Increases the DPI value if `forward` is `true`, decreases it otherwise.
+ * The increment/decrement steps are equal to SPLEEB_SNIPING_DPI_CONFIG_STEP.
+ *
+ * The new value is persisted in EEPROM.
+ */
+void spleeb_cycle_pointer_sniping_dpi(bool forward);
+
+/**
+ * \brief Same as `spleeb_cycle_pointer_sniping_dpi`, but do not write to
+ * EEPROM.
+ *
+ * This means that reseting the board will revert the value to the last
+ * persisted one.
+ */
+void spleeb_cycle_pointer_sniping_dpi_noeeprom(bool forward);
+
+/** \brief Whether sniper-mode is enabled. */
+bool spleeb_get_pointer_sniping_enabled(void);
+
+/**
+ * \brief Enable/disable sniper mode.
+ *
+ * When sniper mode is enabled the dpi is reduced to slow down the pointer for
+ * more accurate movements.
+ */
+void spleeb_set_pointer_sniping_enabled(bool enable);
+
+/** \brief Whether drag-scroll is enabled. */
+bool spleeb_get_pointer_dragscroll_enabled(void);
+
+/**
+ * \brief Enable/disable drag-scroll mode.
+ *
+ * When drag-scroll mode is enabled, horizontal and vertical pointer movements
+ * are translated into horizontal and vertical scroll movements.
+ */
+void spleeb_set_pointer_dragscroll_enabled(bool enable);
+#endif // POINTING_DEVICE_ENABLE