// Chatter launcher daemon. Runs always (systemd service), reads the touch device // (event3) WITHOUT grabbing it, and when it sees a multi-finger hold gesture // (>= FINGERS contacts held >= HOLD_MS) while xochitl is the active app, it // switches to Chatter. This is the "return to Chatter from the standard // interface" trigger (xochitl has no plugin API to add a real button). #include #include #include #include #include #include #include #define DEV "/dev/input/event3" #define MAX_SLOTS 16 #define FINGERS 4 // contacts required #define HOLD_MS 700 // how long they must be held #define COOLDOWN_MS 4000 // ignore re-triggers for this long static long now_ms(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return ts.tv_sec * 1000L + ts.tv_nsec / 1000000L; } int main(void) { long lastTrigger = 0; fprintf(stderr, "chatter-launcher: started (fingers=%d hold=%dms)\n", FINGERS, HOLD_MS); // Outer loop: (re)open the device forever, so a read interruption (e.g. the // device sleeping/waking) never kills the daemon. for (;;) { int fd = open(DEV, O_RDONLY); if (fd < 0) { sleep(2); continue; } int slot = 0; int tid[MAX_SLOTS]; for (int i = 0; i < MAX_SLOTS; i++) tid[i] = -1; long holdStart = 0; int triggered = 0; struct input_event ev; while (read(fd, &ev, sizeof(ev)) == (ssize_t)sizeof(ev)) { if (ev.type == EV_ABS) { if (ev.code == ABS_MT_SLOT) { slot = ev.value; if (slot < 0) slot = 0; if (slot >= MAX_SLOTS) slot = MAX_SLOTS - 1; } else if (ev.code == ABS_MT_TRACKING_ID) { tid[slot] = ev.value; // >=0 active, -1 released } } else if (ev.type == EV_SYN && ev.code == SYN_REPORT) { int active = 0; for (int i = 0; i < MAX_SLOTS; i++) if (tid[i] >= 0) active++; const long t = now_ms(); if (active >= FINGERS) { if (holdStart == 0) holdStart = t; else if (!triggered && (t - holdStart) >= HOLD_MS && (t - lastTrigger) >= COOLDOWN_MS) { triggered = 1; lastTrigger = t; if (system("systemctl is-active --quiet xochitl") == 0) { fprintf(stderr, "chatter-launcher: gesture -> switching to Chatter\n"); system("/home/root/chatter/to-chatter.sh >/dev/null 2>&1 &"); } } } else { holdStart = 0; triggered = 0; } } } close(fd); fprintf(stderr, "chatter-launcher: input read ended, reopening\n"); sleep(1); } return 0; }