aboutsummaryrefslogtreecommitdiff
path: root/index.js
blob: ff1d1449a1e3024c592b7e2767435274b3874172 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#!/usr/bin/env node
const Device = require("./lib/device");
const Motion = require("./lib/motion");

//const clone = x => JSON.parse(JSON.stringify(x)); // Deep
//const clone = x => Object.assign({}, x); // Shallow

const path = "/dev/input/"+process.argv[2];
console.log("Opening %s", path);

const device = new Device(path);
const motion = new Motion(device, 600/*ms for long*/);

device.on("open", () => { console.log(device.id); device.grab(); });
//device.on("EV_ABS", e => console.log(e));
//device.on("EV_SYN", e => console.log(e));

motion.on("error", console.error)
      //.on("short", e => console.log("short", e))
      //.on("long", e => console.log("long",  e))
      .on("short", doShort).on("long", doLong);

const flipBits = (n,w) => parseInt(n.toString(2).padStart(w,0).split("").reverse().join(""),2);

const eucDist   = (a,b)   => Math.sqrt((a.x-b.x)**2 + (a.y-b.y)**2);
//const avgDist   = (a,b)   => (eucDist(a.i,b.i) + eucDist(a.f,b.f))/2;
const selfDist  =  a      => eucDist(a.i, a.f);
const getAngle2 = (a,b)   => Math.atan2(a.y-b.y, b.x-a.x)*180/Math.PI;
const getAngle  =  a      => getAngle2(a.i,a.f);
const isLeft    = (a,b,c) => ((b.x-a.x)*(c.y-a.y) - (b.y-a.y)*(c.x-a.x))>0; // What side of a-b is c on

const state = {
  homed: false,
  homes: [],
  angle: 0,
  fingerDist: 0,
};

const dotRadius = 0.3; // Tap/reach boundary in terms of between-finger distance
const swipeLength = 0.2; // Tap/swipe threshold in terms of between-finger distance

function doLong(e) {
  if (e.length < 3) return; // Pinky finger optional!

  const between = [];
  for (let i=0;i<e.length;i++)
    for (let j=i+1;j<e.length;j++)
      between.push({a:i, b:j, dist:eucDist(e[i].m, e[j].m)});
  between.sort((a,b) => a.dist-b.dist);

  const fingerDist = between[0].dist;
  if (e.map(p => selfDist(p)).reduce((a,d) =>
    a || (d >= fingerDist*swipeLength), false)) return; // Moved too far

  const biggest = between.pop();
  const sides = e.reduce((a,p,i) => {
    if (i != biggest.a && i != biggest.b)
      a.push({i:i,s:isLeft(e[biggest.a].m, e[biggest.b].m, p.m)});
    return a; }, []);
  const side = 2*sides.reduce((a,s)=>a+s.s,0)>sides.length;

  const indexFinger = biggest[side?"b":"a"];

  state.homes = [];
  state.homes.push(e[indexFinger].m);
  sides.map(f => Object.assign({d:eucDist(e[indexFinger].m, e[f.i].m)}, f))
    .sort((a,b) => a.d-b.d).forEach(f => state.homes.push(e[f.i].m));
  state.homes.push(e[biggest[side?"a":"b"]].m);

  state.angle = getAngle2(state.homes[0], state.homes[state.homes.length-1]);
  state.fingerDist = fingerDist;
  state.homed = true;

  //console.log(JSON.stringify(state.homes), state.angle, state.fingerDist);
  console.log("Homed");
}

const prettyFingers = n => "("+n.toString(2).padStart(state.homes.length,0)
  .split("").map(f => f=="1"?"#":" ").reverse().join("")+")";

function doShort(e) {
  if (!state.homed) return;

  // Gesture types:
  // - Tap:
  //   - inside tap       | 0
  //   - reaching tap     | 1
  // - Swipe:
  //   - Vertical:
  //     - from in down   | 2
  //     - from up in     | 3
  //     - swipe up       | 4
  //   - Horizontal:
  //     - from in left   | 5
  //     - from in right  | 6

  const isTap = (e.reduce((a,p) => a+selfDist(p),0) /
    e.length) < state.fingerDist * swipeLength;

  if (isTap) {
    const fingers = e.map(p => state.homes
      .map((f,i) => ({i,d:eucDist(f, p.m)}))
      .sort((a,b)=>a.d-b.d)[0]);
    const fingerCode = fingers.reduce((a,f) => a+2**f.i, 0);

    // TODO reaching taps
    console.log("tap", prettyFingers(fingerCode));

  } else { // TODO swipes
    console.log("swipe");
  }
}