blob: e2279dca5bfb3a8697901733fd21fce6eea68902 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <hid/acer12.h>
#include <hid/paradise.h>
#include <hid/usages.h>
#include <zircon/device/console.h>
#include <zircon/device/display.h>
#include <zircon/device/input.h>
#include <zircon/process.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#define DEV_INPUT "/dev/class/input"
#define FRAMEBUFFER "/dev/class/framebuffer/000"
#define CLEAR_BTN_SIZE 50
#define I2C_HID_DEBUG 0
enum touch_panel_type {
TOUCH_PANEL_UNKNOWN,
TOUCH_PANEL_ACER12,
TOUCH_PANEL_PARADISE,
};
// Array of colors for each finger
static uint32_t colors[] = {
0x00ff0000,
0x0000ff00,
0x000000ff,
0x00ffff00,
0x00ff00ff,
0x0000ffff,
0x00000000,
0x00f0f0f0,
0x00f00f00,
0x000ff000,
};
static void acer12_touch_dump(acer12_touch_t* rpt) {
printf("report id: %u\n", rpt->rpt_id);
for (int i = 0; i < 5; i++) {
printf("finger %d\n", i);
printf(" finger_id: %u\n", rpt->fingers[i].finger_id);
printf(" tswitch: %u\n", acer12_finger_id_tswitch(rpt->fingers[i].finger_id));
printf(" contact: %u\n", acer12_finger_id_contact(rpt->fingers[i].finger_id));
printf(" width: %u\n", rpt->fingers[i].width);
printf(" height: %u\n", rpt->fingers[i].height);
printf(" x: %u\n", rpt->fingers[i].x);
printf(" y: %u\n", rpt->fingers[i].y);
}
printf("scan_time: %u\n", rpt->scan_time);
printf("contact count: %u\n", rpt->contact_count);
}
static void paradise_touch_dump(paradise_touch_t* rpt) {
printf("report id: %u\n", rpt->rpt_id);
printf("pad: %#02x\n", rpt->pad);
printf("contact count: %u\n", rpt->contact_count);
for (int i = 0; i < 5; i++) {
printf("finger %d\n", i);
printf(" flags: %#02x\n", rpt->fingers[i].flags);
printf(" tswitch: %u\n", paradise_finger_flags_tswitch(rpt->fingers[i].flags));
printf(" confidence: %u\n", paradise_finger_flags_confidence(rpt->fingers[i].flags));
printf(" finger_id: %u\n", rpt->fingers[i].finger_id);
printf(" x: %u\n", rpt->fingers[i].x);
printf(" y: %u\n", rpt->fingers[i].y);
}
printf("scan_time: %u\n", rpt->scan_time);
}
static uint32_t scale32(uint32_t z, uint32_t screen_dim, uint32_t rpt_dim) {
return (z * screen_dim) / rpt_dim;
}
static void draw_points(uint32_t* pixels, uint32_t color, uint32_t x, uint32_t y, uint8_t width, uint8_t height, uint32_t fbwidth, uint32_t fbheight) {
uint32_t xrad = (width + 1) / 2;
uint32_t yrad = (height + 1) / 2;
uint32_t xmin = (xrad > x) ? 0 : x - xrad;
uint32_t xmax = (xrad > fbwidth - x) ? fbwidth : x + xrad;
uint32_t ymin = (yrad > y) ? 0 : y - yrad;
uint32_t ymax = (yrad > fbheight - y) ? fbheight : y + yrad;
for (uint32_t px = xmin; px < xmax; px++) {
for (uint32_t py = ymin; py < ymax; py++) {
*(pixels + py * fbwidth + px) = color;
}
}
}
static uint32_t get_color(uint8_t c) {
return colors[c];
}
static void clear_screen(void* buf, ioctl_display_get_fb_t* fb) {
memset(buf, 0xff, fb->info.pixelsize * fb->info.stride * fb->info.height);
draw_points((uint32_t*)buf, 0xff00ff, fb->info.stride - (CLEAR_BTN_SIZE / 2), (CLEAR_BTN_SIZE / 2),
CLEAR_BTN_SIZE, CLEAR_BTN_SIZE, fb->info.stride, fb->info.height);
}
static void process_acer12_touchscreen_input(void* buf, size_t len, int vcfd, uint32_t* pixels,
ioctl_display_get_fb_t* fb) {
acer12_touch_t* rpt = buf;
if (len < sizeof(*rpt)) {
printf("bad report size: %zd < %zd\n", len, sizeof(*rpt));
return;
}
#if I2C_HID_DEBUG
acer12_touch_dump(rpt);
#endif
for (uint8_t c = 0; c < 5; c++) {
if (!acer12_finger_id_tswitch(rpt->fingers[c].finger_id % 10)) continue;
uint32_t x = scale32(rpt->fingers[c].x, fb->info.width, ACER12_X_MAX);
uint32_t y = scale32(rpt->fingers[c].y, fb->info.height, ACER12_Y_MAX);
uint32_t width = 2 * rpt->fingers[c].width;
uint32_t height = 2 * rpt->fingers[c].height;
uint32_t color = get_color(acer12_finger_id_contact(rpt->fingers[c].finger_id));
draw_points(pixels, color, x, y, width, height, fb->info.stride, fb->info.height);
}
if (acer12_finger_id_tswitch(rpt->fingers[0].finger_id)) {
uint32_t x = scale32(rpt->fingers[0].x, fb->info.width, ACER12_X_MAX);
uint32_t y = scale32(rpt->fingers[0].y, fb->info.height, ACER12_Y_MAX);
if (x + CLEAR_BTN_SIZE > fb->info.width && y < CLEAR_BTN_SIZE) {
clear_screen(pixels, fb);
}
}
ssize_t ret = ioctl_display_flush_fb(vcfd);
if (ret < 0) {
printf("failed to flush: %zd\n", ret);
}
}
static void process_paradise_touchscreen_input(void* buf, size_t len, int vcfd, uint32_t* pixels,
ioctl_display_get_fb_t* fb) {
paradise_touch_t* rpt = buf;
if (len < sizeof(*rpt)) {
printf("bad report size: %zd < %zd\n", len, sizeof(*rpt));
return;
}
#if I2C_HID_DEBUG
paradise_touch_dump(rpt);
#endif
for (uint8_t c = 0; c < 5; c++) {
if (!paradise_finger_flags_tswitch(rpt->fingers[c].flags)) continue;
uint32_t x = scale32(rpt->fingers[c].x, fb->info.width, PARADISE_X_MAX);
uint32_t y = scale32(rpt->fingers[c].y, fb->info.height, PARADISE_Y_MAX);
uint32_t width = 10;
uint32_t height = 10;
uint32_t color = get_color(c);
draw_points(pixels, color, x, y, width, height, fb->info.stride, fb->info.height);
}
if (paradise_finger_flags_tswitch(rpt->fingers[0].flags)) {
uint32_t x = scale32(rpt->fingers[0].x, fb->info.width, PARADISE_X_MAX);
uint32_t y = scale32(rpt->fingers[0].y, fb->info.height, PARADISE_Y_MAX);
if (x + CLEAR_BTN_SIZE > fb->info.width && y < CLEAR_BTN_SIZE) {
clear_screen(pixels, fb);
}
}
ssize_t ret = ioctl_display_flush_fb(vcfd);
if (ret < 0) {
printf("failed to flush: %zd\n", ret);
}
}
static void process_acer12_stylus_input(void* buf, size_t len, int vcfd, uint32_t* pixels,
ioctl_display_get_fb_t* fb) {
acer12_stylus_t* rpt = buf;
if (len < sizeof(*rpt)) {
printf("bad report size: %zd < %zd\n", len, sizeof(*rpt));
return;
}
// Don't draw for out of range or hover with no switches.
if (!rpt->status || rpt->status == ACER12_STYLUS_STATUS_INRANGE) return;
uint32_t x = scale32(rpt->x, fb->info.width, ACER12_STYLUS_X_MAX);
uint32_t y = scale32(rpt->y, fb->info.height, ACER12_STYLUS_Y_MAX);
// Pressing the clear button requires contact (not just hover).
if (acer12_stylus_status_tswitch(rpt->status)) {
if (x + CLEAR_BTN_SIZE > fb->info.width && y < CLEAR_BTN_SIZE) {
clear_screen(pixels, fb);
goto flush;
}
}
uint32_t size, color;
size = acer12_stylus_status_tswitch(rpt->status) ? rpt->pressure >> 4 : 4;
switch (rpt->status) {
case 3: // in_range | tip_switch
color = get_color(0);
break;
case 5: // in_range | barrel_switch
color = get_color(1);
break;
case 7: // in_range | tip_switch | barrel_switch
color = get_color(4);
break;
case 9: // in_range | invert
color = get_color(5);
break;
case 17: // in_range | erase (== tip_switch | invert)
color = 0x00ffffff;
size = 32; // fixed size eraser
break;
default:
printf("unknown rpt->status=%u\n", rpt->status);
color = get_color(6);
break;
}
draw_points(pixels, color, x, y, size, size, fb->info.stride, fb->info.height);
flush: ;
ssize_t ret = ioctl_display_flush_fb(vcfd);
if (ret < 0) {
printf("failed to flush: %zd\n", ret);
}
}
int main(int argc, char* argv[]) {
int vcfd = open(FRAMEBUFFER, O_RDWR);
if (vcfd < 0) {
printf("failed to open %s: %d\n", FRAMEBUFFER, errno);
return -1;
}
ioctl_display_get_fb_t fb;
ssize_t ret = ioctl_display_get_fb(vcfd, &fb);
if (ret < 0) {
printf("failed to get FB: %zd\n", ret);
return -1;
}
if (fb.info.pixelsize != 4) {
printf("only 32-bit framebuffers are supported for now!\n");
return -1;
}
printf("format = %d\n", fb.info.format);
printf("width = %d\n", fb.info.width);
printf("height = %d\n", fb.info.height);
printf("stride = %d\n", fb.info.stride);
printf("pixelsize = %d\n", fb.info.pixelsize);
printf("flags = 0x%x\n", fb.info.flags);
size_t size = fb.info.stride * fb.info.pixelsize * fb.info.height;
uintptr_t fbo;
zx_status_t status = _zx_vmar_map(zx_vmar_root_self(), 0, fb.vmo, 0, size,
ZX_VM_FLAG_PERM_READ | ZX_VM_FLAG_PERM_WRITE, &fbo);
if (status < 0) {
printf("couldn't map fb: %d\n", status);
return -1;
}
uint32_t* pixels32 = (uint32_t*)fbo;
// Scan /dev/class/input to find the touchscreen
struct dirent* de;
DIR* dir = opendir(DEV_INPUT);
if (!dir) {
printf("failed to open %s: %d\n", DEV_INPUT, errno);
return -1;
}
int touchfd = -1;
size_t rpt_desc_len = 0;
uint8_t* rpt_desc = NULL;
enum touch_panel_type panel = TOUCH_PANEL_UNKNOWN;
while ((de = readdir(dir)) != NULL) {
char devname[128];
if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) {
continue;
}
snprintf(devname, sizeof(devname), "%s/%s", DEV_INPUT, de->d_name);
touchfd = open(devname, O_RDONLY);
if (touchfd < 0) {
printf("failed to open %s: %d\n", devname, errno);
continue;
}
ret = ioctl_input_get_report_desc_size(touchfd, &rpt_desc_len);
if (ret < 0) {
printf("failed to get report descriptor length for %s: %zd\n", devname, ret);
goto next_node;
}
rpt_desc = malloc(rpt_desc_len);
if (rpt_desc == NULL) {
printf("no memory!\n");
exit(-1);
}
ret = ioctl_input_get_report_desc(touchfd, rpt_desc, rpt_desc_len);
if (ret < 0) {
printf("failed to get report descriptor for %s: %zd\n", devname, ret);
goto next_node;
}
if (is_acer12_touch_report_desc(rpt_desc, rpt_desc_len)) {
panel = TOUCH_PANEL_ACER12;
// Found the touchscreen
printf("touchscreen: %s\n", devname);
break;
}
if (is_paradise_touch_report_desc(rpt_desc, rpt_desc_len)) {
panel = TOUCH_PANEL_PARADISE;
// Found the touchscreen
printf("touchscreen: %s\n", devname);
break;
}
next_node:
rpt_desc_len = 0;
if (rpt_desc != NULL) {
free(rpt_desc);
rpt_desc = NULL;
}
if (touchfd >= 0) {
close(touchfd);
touchfd = -1;
}
}
closedir(dir);
if (touchfd < 0) {
printf("could not find a touchscreen!\n");
return -1;
}
assert(rpt_desc_len > 0);
assert(rpt_desc);
input_report_size_t max_rpt_sz = 0;
ret = ioctl_input_get_max_reportsize(touchfd, &max_rpt_sz);
if (ret < 0) {
printf("failed to get max report size: %zd\n", ret);
return -1;
}
void* buf = malloc(max_rpt_sz);
if (buf == NULL) {
printf("no memory!\n");
return -1;
}
ret = ioctl_console_set_active_vc(vcfd);
if (ret < 0) {
printf("could not set active console: %zd\n", ret);
// not a fatal error
printf("press Alt-Tab to switch consoles\n");
}
clear_screen((void*)fbo, &fb);
while (1) {
ssize_t r = read(touchfd, buf, max_rpt_sz);
if (r < 0) {
printf("touchscreen read error: %zd (errno=%d)\n", r, errno);
break;
}
if (panel == TOUCH_PANEL_ACER12) {
if (*(uint8_t*)buf == ACER12_RPT_ID_TOUCH) {
process_acer12_touchscreen_input(buf, r, vcfd, pixels32, &fb);
} else if (*(uint8_t*)buf == ACER12_RPT_ID_STYLUS) {
process_acer12_stylus_input(buf, r, vcfd, pixels32, &fb);
}
} else if (panel == TOUCH_PANEL_PARADISE) {
if (*(uint8_t*)buf == PARADISE_RPT_ID_TOUCH) {
process_paradise_touchscreen_input(buf, r, vcfd, pixels32, &fb);
}
}
}
free(buf);
free(rpt_desc);
close(touchfd);
_zx_vmar_unmap(zx_vmar_root_self(), fbo, size);
close(vcfd);
return 0;
}