From ddaf9946eb22aa5ce34559979ed851c1ef1c0e0d Mon Sep 17 00:00:00 2001
From: xs5871 <xs5871@qn-4.net>
Date: Fri, 24 Mar 2023 21:47:56 +0000
Subject: [PATCH] Add support for cirque glidepoint trackpad

---
 kmk/modules/glidepoint.py | 212 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 212 insertions(+)
 create mode 100644 kmk/modules/glidepoint.py

diff --git a/kmk/modules/glidepoint.py b/kmk/modules/glidepoint.py
new file mode 100644
index 000000000..640c124b2
--- /dev/null
+++ b/kmk/modules/glidepoint.py
@@ -0,0 +1,212 @@
+try:
+    from typing import Callable
+except ImportError:
+    pass
+
+from micropython import const
+
+from kmk.keys import AX
+from kmk.modules import Module
+from kmk.utils import Debug
+
+debug = Debug(__name__)
+
+_ADDRESS = const(0x2A)
+
+_MASK_READ = const(0xA0)
+_MASK_WRITE = const(0x80)
+
+_REG_FW_ID = const(0x00)
+_REG_FW_VER = const(0x01)
+_REG_STATUS = const(0x02)
+_REG_SYS_CFG = const(0x03)
+_REG_FEED_CFG1 = const(0x04)
+_REG_FEED_CFG2 = const(0x05)
+_REG_CAL_CFG = const(0x07)
+_REG_AUX_CTL = const(0x08)
+_REG_SAMPLE_RATE = const(0x09)
+_REG_ZIDLE = const(0x0A)
+_REG_Z_SCALER = const(0x0B)
+_REG_SLEEP_INTERVAL = const(0x0C)
+_REG_SLEEP_TIMER = const(0x0D)
+_REG_DATA = const(0x12)
+
+_FEED1_ENABLE = const(0x01)
+_FEED1_ABSOLUTE = const(0x02)
+_FEED1_NO_FILTER = const(0x04)
+_FEED1_NO_X_DATA = const(0x08)
+_FEED1_NO_Y_DATA = const(0x10)
+_FEED1_INVERT_X = const(0x40)
+_FEED1_INVERT_Y = const(0x80)
+
+_FEED2_INTELLIMOUSE = const(0x01)
+_FEED2_NO_TAPS = const(0x02)
+_FEED2_NO_SEC_TAPS = const(0x04)
+_FEED2_NO_SCROLL = const(0x08)
+_FEED2_NO_GLIDEEXTEND = const(0x10)
+_FEED2_SWAP_XY = const(0x80)
+
+
+def with_i2c_lock(rw: Callable[[object, int], None]) -> Callable[[object, int], None]:
+    def _(self, n: int) -> None:
+        if not self._i2c.try_lock():
+            if debug.enabled:
+                debug("can't acquire lock")
+            return
+        try:
+            rw(self, n)
+        finally:
+            self._i2c.unlock()
+
+    return _
+
+
+class AbsoluteHandler:
+    cfg = (
+        _REG_FEED_CFG2,
+        0x00,
+        _REG_FEED_CFG1,
+        _FEED1_ENABLE | _FEED1_NO_FILTER | _FEED1_ABSOLUTE | _FEED1_INVERT_X,
+    )
+
+    def handle(buffer: bytearray, keyboard) -> None:
+        button = buffer[0]
+        x_low = buffer[2]
+        y_low = buffer[3]
+        high = buffer[4]
+        z_lvl = buffer[5]
+
+        x_pos = ((high & 0x0F) << 8) | x_low
+        y_pos = ((high & 0xF0) << 4) | y_low
+
+        if debug.enabled:
+            debug(
+                'buttons:',
+                bin(button),
+                ' x_pos:',
+                x_pos,
+                ' y_pos:',
+                y_pos,
+                'z_lvl:',
+                z_lvl,
+            )
+
+
+class RelativeHandler:
+    cfg = (
+        _REG_FEED_CFG2,
+        0x00,
+        _REG_FEED_CFG1,
+        _FEED1_ENABLE | _FEED1_NO_FILTER | _FEED1_INVERT_X,
+    )
+
+    def handle(buffer: bytearray, keyboard) -> None:
+        button = buffer[0] & 0b00000111
+        x_sign = buffer[0] & 0b00010000
+        y_sign = buffer[0] & 0b00100000
+        x_delta = buffer[1]
+        y_delta = buffer[2]
+        w_delta = buffer[3]
+
+        if x_sign:
+            x_delta -= 0xFF
+        if y_sign:
+            y_delta -= 0xFF
+
+        if x_delta != 0:
+            AX.X.move(keyboard, x_delta)
+        if y_delta != 0:
+            AX.Y.move(keyboard, y_delta)
+
+        if debug.enabled:
+            debug(
+                'buttons:',
+                bin(button),
+                ' x_delta:',
+                x_delta,
+                ' y_delta:',
+                y_delta,
+                ' w_delta',
+                w_delta,
+            )
+
+
+class GlidePoint(Module):
+    def __init__(self, i2c):
+        self._i2c = i2c
+        self._buffer = bytearray(8)
+
+        # self.handler = RelativeHandler
+        self.handler = AbsoluteHandler
+
+    @with_i2c_lock
+    def _read(self, n: int) -> None:
+        self._buffer[0] |= _MASK_READ
+        self._i2c.writeto_then_readfrom(
+            _ADDRESS, self._buffer, self._buffer, out_end=1, in_end=n
+        )
+
+    @with_i2c_lock
+    def _write(self, n: int) -> None:
+        for i in range(0, n, 2):
+            self._buffer[i] |= _MASK_WRITE
+        self._i2c.writeto(_ADDRESS, self._buffer, end=n)
+
+    def _check_firmware(self) -> bool:
+        self._buffer[0] = _REG_FW_ID
+        self._read(2)
+        return self._buffer[:2] == b'\x07:'
+
+    def _clear_status_flags(self) -> None:
+        self._buffer[0] = _REG_STATUS
+        self._buffer[1] = 0x00
+        self._write(2)
+
+    def _configure(self) -> None:
+        self._buffer[0] = _REG_SYS_CFG
+        self._buffer[1] = 0x00
+        for idx, val in enumerate(self.handler.cfg):
+            self._buffer[idx + 2] = val
+        self._write(2 + len(self.handler.cfg))
+
+    def _data_ready(self) -> bool:
+        self._buffer[0] = _REG_STATUS
+        self._read(1)
+        return self._buffer[0] != 0x00
+
+    def during_bootup(self, keyboard):
+        if not self._check_firmware:
+            raise OSError('Firmware ID mismatch')
+
+        self._clear_status_flags()
+        self._configure()
+
+    def before_matrix_scan(self, keyboard):
+        pass
+
+    def after_matrix_scan(self, keyboard):
+        if not self._data_ready():
+            return
+
+        self._buffer[0] = _REG_DATA
+        self._read(6)
+
+        self.handler.handle(self._buffer, keyboard)
+
+        self._clear_status_flags()
+
+    def before_hid_send(self, keyboard):
+        pass
+
+    def after_hid_send(self, keyboard):
+        pass
+
+    def on_powersave_enable(self, keyboard):
+        self._buffer[0] = _REG_SYS_CFG
+        self._buffer[1] = 0x04
+        self._write(2)
+
+    def on_powersave_disable(self, keyboard):
+        self._buffer[0] = _REG_SYS_CFG
+        self._buffer[1] = 0x00
+        self._write(2)