Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for cirque glidepoint trackpad #754

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
212 changes: 212 additions & 0 deletions kmk/modules/glidepoint.py
Original file line number Diff line number Diff line change
@@ -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)