root/usr.sbin/tpctl/main.c
/*      $NetBSD: main.c,v 1.8 2024/07/12 22:31:40 andvar Exp $  */

/*-
 * Copyright (c) 2002 TAKEMRUA Shin
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of The NetBSD Foundation nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <strings.h>
#include <stdlib.h>
#include <unistd.h>
#include <err.h>
#include <errno.h>
#include <time.h>
#include <termios.h>
#include <sys/fcntl.h>
#include <sys/mman.h>
#ifdef __STDC__
#include <stdarg.h>
#else
#include <varargs.h>
#endif

#include "tpctl.h"

#ifndef lint
#include <sys/cdefs.h>
__RCSID("$NetBSD: main.c,v 1.8 2024/07/12 22:31:40 andvar Exp $");
#endif /* not lint */

void load_data(const char *, struct tpctl_data *);
void save_data(const char *, struct tpctl_data *);
int do_calibration(const char *, struct tp *, struct wsmouse_calibcoords *);
void drawcross(struct fb *, int, int, int, fb_pixel_t);
int check_esc(void *);

int opt_verbose;
int opt_noupdate;
int opt_forceupdate;

static __dead void
usage(void)
{

        fprintf(stderr, "usage: %s [-D dispdev] [-d dev] [-f file] [-hnuv]\n",
            getprogname());
        exit(EXIT_FAILURE);
        /* NOTREACHED */
}

int
main(int argc, char *argv[])
{
        int tpfd, ch;
        struct tp tp;
        struct wsmouse_calibcoords *pref;
        struct tpctl_data data;
        const char *data_file;
        const char *dev_name;
        const char *dispdev_name;

        /* set default values */
        opt_verbose = 0;
        opt_noupdate = 0;
        opt_forceupdate = 0;
        dev_name = TPCTL_TP_DEVICE;
        dispdev_name = TPCTL_FB_DEVICE;
        data_file = TPCTL_DB_FILENAME;

        /* parse command line */
        while ((ch = getopt(argc, argv, "d:D:f:hnuv")) != -1) {
                switch (ch) {
                case 'D':
                        dispdev_name = optarg;
                        break;
                case 'd':
                        dev_name = optarg;
                        break;
                case 'f':
                        data_file = optarg;
                        break;
                case 'h':
                        usage();
                        /* NOTREACHED */
                case 'n':
                        opt_noupdate = 1;
                        break;
                case 'u':
                        opt_forceupdate = 1;
                        break;
                case 'v':
                        opt_verbose = 1;
                        break;
                default:
                        usage();
                        /* NOTREACHED */
                }
        }
        if (argv[optind] != NULL) {
                usage();
                /* NOTREACHED */
        }

        /* load calibration parameters from specified file */
        load_data(data_file, &data);

        /* open touch panel device and initialize touch panel routines */
        if ((tpfd = open(dev_name, O_RDWR)) < 0)
                errx(EXIT_FAILURE, "can't open touch panel");
        if (tp_init(&tp, tpfd) < 0)
                errx(EXIT_FAILURE, "can't initialize touch panel");

        /* find out saved parameters for the touch panel */
        pref = search_data(&data, tp.id);
        if (opt_forceupdate || pref == NULL) {
                /* if the parameters wasn't found or '-f' options was 
                   specified, do 'calibration' */
                struct wsmouse_calibcoords coords;

                /* draw cursors and collect samples */
                if (do_calibration(dispdev_name, &tp, &coords) < 0) {
                        /* ESC key was pressed to abort */
                        exit(EXIT_FAILURE);
                }
                /* update parameters with new one */
                replace_data(&data, tp.id, &coords);
                pref = search_data(&data, tp.id);
        } else {
                /* nothing is updated, 
                   so you don't have to write back the data */
                opt_noupdate = 1;
        }

        if (opt_verbose)
                write_coords(stdout, tp.id, pref);

        /* set calibration parameters into touch panel device */
        if (tp_setcalibcoords(&tp, pref) < 0)
                errx(EXIT_FAILURE, "can't set samples");

        /* save calibration parameters from specified file */
        if (!opt_noupdate)
                save_data(data_file, &data);
        /* dispose data */
        free_data(&data);

        exit(EXIT_SUCCESS);
        /* NOTREACHED */
}

/*
 * load calibration parameters from specified file
 *
 * return:      none (it won't return if some error occurs)
 */
void
load_data(const char *data_file, struct tpctl_data *data)
{
        int error;

        init_data(data);
        error = read_data(data_file, data);
        switch (error) {
        case ERR_NONE:
                break;
        case ERR_NOFILE:
                fprintf(stderr, "%s: can't open %s\n", getprogname(),
                    data_file);
                /* it might be OK... */
                break;
        case ERR_IO:
                fprintf(stderr, "%s: I/O error on %s\n", getprogname(),
                    data_file);
                exit(EXIT_FAILURE);
                break;
        case ERR_SYNTAX:
                fprintf(stderr, "%s: format error at %s, line %d\n",
                    getprogname(), data_file, data->lineno);
                exit(EXIT_FAILURE);
                break;
        case ERR_DUPNAME:
                fprintf(stderr, "%s: duplicate entry at %s, line %d\n",
                    getprogname(), data_file, data->lineno);
                exit(EXIT_FAILURE);
                break;
        default:
                fprintf(stderr, "%s: internal error\n", getprogname());
                exit(EXIT_FAILURE);
                break;
        }
}

/*
 * save calibration parameters to specified file
 *
 * return:      none (it won't return if some error occurs)
 */
void
save_data(const char *data_file, struct tpctl_data *data)
{
        int error;

        error = write_data(data_file, data);
        switch (error) {
        case ERR_NONE:
                break;
        case ERR_NOFILE:
                fprintf(stderr, "%s: can't open %s\n", getprogname(),
                    data_file);
                exit(EXIT_FAILURE);
                break;
        case ERR_IO:
                fprintf(stderr, "%s: I/O error on %s\n", getprogname(),
                    data_file);
                exit(EXIT_FAILURE);
                break;
        default:
                fprintf(stderr, "%s: internal error\n", getprogname());
                exit(EXIT_FAILURE);
                break;
        }
}

/*
 * draw cursors on frame buffer and collect samples in 
 * wamouse_calibcoords structure.
 *
 * return:      0       succeeded
 *              -1      aborted by user (ESC key was pressed)
 *              (it won't return if some error occurs)
 */
int
do_calibration(const char *dev, struct tp *tp,
    struct wsmouse_calibcoords *coords)
{
        int fbfd;
        struct fb fb;
        int i, x, y, xm, ym, cursize, error, res;

        /* open frame buffer device and initialize frame buffer routine */
        if ((fbfd = open(dev, O_RDWR)) < 0)
                errx(EXIT_FAILURE, "can't open frame buffer");
        if (fb_init(&fb, fbfd) < 0)
                errx(EXIT_FAILURE, "can't map frame buffer");

        memset(coords, 0, sizeof(*coords));
        coords->minx = 0;
        coords->miny = 0;
        coords->maxx = fb.conf.hf_width - 1;
        coords->maxy = fb.conf.hf_height - 1;
        coords->samplelen = 5;

        cursize = 20;
        xm = fb.conf.hf_width/10;
        ym = fb.conf.hf_height/10;

        /* center */
        coords->samples[0].x = fb.conf.hf_width/2;
        coords->samples[0].y = fb.conf.hf_height/2;

        /* top left */
        coords->samples[1].x = xm;
        coords->samples[1].y = ym;

        /* bottom left */
        coords->samples[2].x = xm;
        coords->samples[2].y = fb.conf.hf_height - ym;

        /* bottom right */
        coords->samples[3].x = fb.conf.hf_width - xm;
        coords->samples[3].y = fb.conf.hf_height - ym;

        /* top right */
        coords->samples[4].x = fb.conf.hf_width - xm;
        coords->samples[4].y = ym;

        tp_setrawmode(tp);
        error = 0;
        for (i = 0; i < coords->samplelen; i++) {
                drawcross(&fb,
                    coords->samples[i].x,
                    coords->samples[i].y,
                    cursize, fb.white);
                fb_flush(&fb);
                tp_flush(tp);
                res = tp_get(tp, &x, &y, check_esc, 0 /* stdin */);
                if (res < 0) {
                        error = errno;
                        break;
                }
                if (0 < res) {
                        fb_dispmode(&fb, WSDISPLAYIO_MODE_EMUL);
                        return (-1); /* aborted by user */
                }
                coords->samples[i].rawx = x;
                coords->samples[i].rawy = y;
                drawcross(&fb,
                    coords->samples[i].x,
                    coords->samples[i].y,
                    cursize, fb.black);
                fb_flush(&fb);
                tp_waitup(tp, 200, check_esc, 0 /* stdin */);
        }

        fb_dispmode(&fb, WSDISPLAYIO_MODE_EMUL);
        close(fbfd);

        if (opt_verbose) {
                printf("%s: %dx%d (%dbytes/line) %dbit offset=0x%lx\n",
                    getprogname(),
                    fb.conf.hf_width,
                    fb.conf.hf_height,
                    fb.conf.hf_bytes_per_line,
                    fb.conf.hf_pixel_width,
                    fb.conf.hf_offset);
        }

        if (error) {
                errno = error;
                errx(EXIT_FAILURE, "can't get samples");
        }

        return (0);
}

/*
 * draw cross cursor on frame buffer
 *
 * return:      none
 */
void
drawcross(struct fb *fb, int x, int y, int size, fb_pixel_t pixel)
{
        size /= 2;

        fb_drawline(fb, x,     y - size + 1, x,     y - 1,    pixel);
        fb_drawline(fb, x + 1, y - size + 1, x + 1, y - 1,    pixel);
        fb_drawline(fb, x,     y + 2,        x,     y + size, pixel);
        fb_drawline(fb, x + 1, y + 2,        x + 1, y + size, pixel);

        fb_drawline(fb, x - size + 1, y,     x - 1,    y,     pixel);
        fb_drawline(fb, x - size + 1, y + 1, x - 1,    y + 1, pixel);
        fb_drawline(fb, x + 2,        y,     x + size, y,     pixel);
        fb_drawline(fb, x + 2,        y + 1, x + size, y + 1, pixel);
}

/*
 * check ESC key
 *
 * date:        input file descriptor
 *
 * return:      0       nothing has occurred
 *              1       ESC key was pressed
 *              -1      error
 */
int
check_esc(void *data)
{
        int fd = (int)(intptr_t)data;
        int flg, n, error;
        char buf[1];
        struct termios tm, raw;

        if (tcgetattr(fd, &tm) < 0)
                return (-1);
        raw = tm;
        cfmakeraw(&raw);
        if (tcsetattr(fd, TCSANOW, &raw) < 0)
                return (-1);
        if ((flg = fcntl(fd, F_GETFL)) == -1)
                return (-1);
        if (fcntl(fd, F_SETFL, flg | O_NONBLOCK) == -1)
                return (-1);
        n = read(fd, buf, 1);
        error = errno;
        fcntl(fd, F_SETFL, flg);
        tcsetattr(fd, TCSANOW, &tm);
        if (n < 0)
                return (error == EWOULDBLOCK ? 0 : -1);
        if (n == 0)
                return (0); /* EOF */
        if (*buf == 0x1b)
                return (1); /* ESC */

        return (0);
}