summaryrefslogtreecommitdiff
path: root/src/tc1kpen.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/tc1kpen.c')
-rw-r--r--src/tc1kpen.c1438
1 files changed, 1438 insertions, 0 deletions
diff --git a/src/tc1kpen.c b/src/tc1kpen.c
new file mode 100644
index 0000000..8db55d2
--- /dev/null
+++ b/src/tc1kpen.c
@@ -0,0 +1,1438 @@
+/*
+ * Copyright 2007 Vaclav Krpec
+ */
+
+/*
+ * This file is part of tc1kpen X11 input driver.
+ *
+ * tc1kpen is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * tc1kpen is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with tc1kpen. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+
+#define NEED_EVENTS
+
+#include <X11/X.h>
+#include <X11/Xproto.h>
+
+#ifdef XINPUT
+#include <X11/extensions/XI.h>
+#include <X11/extensions/XIproto.h>
+#include <X11/extensions/randr.h>
+#include "extnsionst.h"
+#include "extinit.h"
+#else
+#include "inputstr.h"
+#endif
+
+#include "compiler.h"
+
+#include "os.h"
+#include "xf86.h"
+#include "xf86Xinput.h"
+
+
+#include "tc1kpen.h"
+
+
+
+/*
+ * TC1000 tablet driver
+ */
+
+static const char *tc1kpen_optionDefaults[] = {
+ /* Serial port */
+ "Device", "/dev/ttyS0",
+ "BaudRate", "19200",
+
+ /* Buttons */
+ "ButtonMap", "1 2 3 4 5 6 7 8 9 10 11 12",
+ "ButtonModes", "2EMU 3EMUtap",
+ "ButtonModeSwitch", "12",
+ "Btn3EmuTimeout", "200",
+
+ /* Pointer on-screen coordinates area */
+ "CoordMinX", "0",
+ "CoordMinY", "0",
+ "CoordMaxX", "8600",
+ "CoordMaxY", "6500",
+
+ /* Pointer side-buttons area */
+ "SideBtnsYOffset", "9100",
+ "SideBtnsSize", "200",
+ "SideBtnsY", "9650",
+ "SideBtn1X", "1250",
+ "SideBtn2X", "1770",
+ "SideBtn3X", "2300",
+
+ /* Transformations */
+ "InvertX", "no",
+ "InvertY", "no",
+ "SwapXY", "no",
+ "Rotate", "no",
+
+ /* End of table */
+ NULL };
+
+
+/*
+ ********************************************************************
+ * FUNCTIONS
+ ********************************************************************
+ */
+
+/*
+ * Reads input from serial device
+ * Unpacks and parses the packet
+ * Format of 5 bytes data packet for TC1K tablet follows:
+ *
+ * Byte 1
+ * bit 7 Phasing bit always 1
+ * bit 6 Buttons status change
+ * bit 5 Proximity
+ * bit 4 0
+ * bit 3 0
+ * bit 2 0
+ * bit 1 Pen button switch
+ * bit 0 Pen tip switch
+ *
+ * Byte 2
+ * bit 7 0
+ * bits 6-0 = X6 - X0
+ *
+ * Byte 3
+ * bit 7 0
+ * bits 6-0 = X13 - X7
+ *
+ * Byte 4
+ * bit 7 0
+ * bits 6-0 = Y6 - Y0
+ *
+ * Byte 5
+ * bit 7 0
+ * bits 6-0 = Y13 - Y7
+ *
+ * Returns 0 on error, != 0 on success
+ */
+
+static Bool tc1kpen_readTTYS( int fd,
+ int *stat,
+ int *prox,
+ int *buttons,
+ int *x, int *y)
+{
+ int i, found = 0;
+ unsigned char data[BUFFER_SIZE];
+
+ debug(5);
+
+ i = xf86ReadSerial(fd, data, BUFFER_SIZE);
+
+ if (i <= 0)
+ {
+ error("Error reading input device");
+
+ return FALSE;
+ }
+
+ debug(4, "%d packets read at once", i / PACKET_SIZE);
+
+ /* Look through the data backwards to find the last full and valid position */
+ for (i -= PACKET_SIZE; i >= 0; i--)
+ {
+ if (data[i] & PHASING_BIT)
+ {
+ found = 1;
+
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ debug(4, "No valid packet found in input");
+
+ return FALSE;
+ }
+
+ debug(4, "Packet hex dump: MSB %02x %02x %02x %02x %02x LSB",
+ data[i+4], data[i+3], data[i+2], data[i+1], data[i]);
+
+ *stat = (int)(data[i] & STATUS_BIT);
+
+ *prox = (int)(data[i] & PROXIMITY_BIT);
+
+ *buttons = (int)(data[i] & (TIP_BIT | BUTTON_BIT));
+
+ *x = (int)(data[i + 1] & 0x7f) + ((int)(data[i + 2] & 0x7f) << 7);
+ *y = (int)(data[i + 3] & 0x7f) + ((int)(data[i + 4] & 0x7f) << 7);
+
+ debug(4, "Proximity: %s, status bit %s, buttons == 0x%02x, coords == [%d,%d]",
+ (*prox ? "yes" : "no"), (*stat ? "set" : "clear"),
+ *buttons, *x, *y);
+
+ return TRUE;
+}
+
+
+
+/*
+ ********************************************************************
+ * EVENT SENDERS
+ ********************************************************************
+ */
+
+/*
+ * Send proximity event
+ */
+
+static void tc1kpen_proximityEvent(InputInfoPtr info, int prox)
+{
+ tc1kpen_devicePtr pen = (tc1kpen_devicePtr)info->private;
+ DeviceIntPtr device = info->dev;
+
+ debug(3, "Pen %s", (prox ? "approached" : "distanced"));
+
+ /* Re-set screen, just to be sure */
+ if (prox) xf86XInputSetScreen(info, pen->screen_no, pen->x, pen->y);
+
+ if (device != inputInfo.pointer)
+ {
+ xf86PostProximityEvent(device, prox, 0, 2, pen->x, pen->y);
+
+ debug(2, "%sroximity event posted", (prox ? "P" : "No p"));
+ }
+}
+
+
+/*
+ * Send button event
+ */
+
+static void tc1kpen_buttonEvent(InputInfoPtr info,
+ int button, int side_btn, int change)
+{
+ tc1kpen_devicePtr pen = (tc1kpen_devicePtr)info->private;
+ DeviceIntPtr device = info->dev;
+
+ /* Side button shift */
+ button += side_btn * SIDE_BTNS_NUMBER;
+
+ debug(3, "Button %d %s", button, (change ? "pressed" : "released"));
+
+ xf86PostButtonEvent(device, IS_ABSOLUTE, button, change, 0, 2, pen->x, pen->y);
+
+ debug(1, "Button %d %s event posted", button, (change ? "press" : "release"));
+
+ /* Pen button mode switch */
+ if (button == pen->btn_mode_switch && change)
+ {
+ pen->btn_mode = pen->btn_mode->next;
+
+ debug(3, "Pen button mode %s set", pen->btn_mode->id);
+ }
+}
+
+
+/*
+ * Send motion event
+ */
+
+static void tc1kpen_motionEvent(InputInfoPtr info)
+{
+ tc1kpen_devicePtr pen = (tc1kpen_devicePtr)info->private;
+ DeviceIntPtr device = info->dev;
+
+ xf86PostMotionEvent(device, IS_ABSOLUTE, 0, 2, pen->x, pen->y);
+
+ debug(2, "Motion event to [%d,%d] posted", pen->x, pen->y);
+}
+
+
+/*
+ ********************************************************************
+ * PEN BUTTONS STATUS CHANGE RESOLVING
+ ********************************************************************
+ */
+
+/*
+ * 2 buttons HW mode
+ */
+
+static int tc1kpen_btnMod_2HW( InputInfoPtr info,
+ int status, int buttons,
+ int side_btn, unsigned prox_cnt)
+{
+ static int old_buttons = 0;
+
+ int btn_diff = old_buttons - buttons;
+
+ int btn_change = btn_diff & SIGN(btn_diff);
+
+ switch (ABS(btn_diff))
+ {
+ /* No change */
+ case 0: return 0;
+
+ /* Both pen tip and button changed */
+ case 3: tc1kpen_buttonEvent(info, 1, side_btn, btn_change);
+
+ /* Pen button changed */
+ case 2: tc1kpen_buttonEvent(info, 3, side_btn, btn_change);
+ break;
+
+ /* Pen tip changed */
+ case 1: tc1kpen_buttonEvent(info, 1, side_btn, btn_change);
+ break;
+
+ default:
+ error("Unexpected buttons difference: %d", btn_diff);
+ }
+
+ old_buttons = buttons;
+
+ return btn_diff;
+}
+
+
+/*
+ * 2 buttons emulation mode
+ */
+
+static int tc1kpen_btnMod_2EMU( InputInfoPtr info,
+ int status, int buttons,
+ int side_btn, unsigned prox_cnt)
+{
+ static int button = 0;
+
+ int change = 0;
+
+ if (XOR(button, buttons & TIP_BIT))
+ {
+ if (change = button)
+ {
+ tc1kpen_buttonEvent(info, button, side_btn, 0);
+
+ button = 0;
+ }
+ else if (change = !status)
+ {
+ button = buttons;
+
+ tc1kpen_buttonEvent(info, button, side_btn, 1);
+ }
+ }
+
+ return change;
+}
+
+
+/*
+ * 3 buttons emulation mode (no tap)
+ */
+
+static int tc1kpen_btnMod_3EMU( InputInfoPtr info,
+ int status, int buttons,
+ int side_btn, unsigned prox_cnt)
+{
+ static int button = 0;
+ static int timer = 0;
+
+ int change = 0;
+
+ if (XOR(button, buttons & TIP_BIT))
+ {
+ if (change = button)
+ {
+ tc1kpen_buttonEvent(info, button, side_btn, 0);
+
+ button = 0;
+
+ timer = 0;
+ }
+ else if (change = !status)
+ {
+ button = buttons;
+
+ tc1kpen_buttonEvent(info, button, side_btn, 1);
+ }
+ }
+ else
+ {
+ if (!(status || button))
+ {
+ if (buttons & BUTTON_BIT)
+ {
+ timer = GetTimeInMillis();
+ }
+ else if (timer)
+ {
+ tc1kpen_devicePtr pen = (tc1kpen_devicePtr)info->private;
+ int t = GetTimeInMillis();
+
+ if (t - timer <= pen->btn3emu_timeout)
+ {
+ tc1kpen_buttonEvent(info, 2, side_btn, 1);
+ tc1kpen_buttonEvent(info, 2, side_btn, 0);
+ }
+ }
+ }
+ }
+
+ return change;
+}
+
+
+/*
+ * 3 buttons emulation mode (with tap)
+ */
+
+static int tc1kpen_btnMod_3EMUt(InputInfoPtr info,
+ int status, int buttons,
+ int side_btn, unsigned prox_cnt)
+{
+ static int button = 0;
+ static int timer = 0;
+ static int btn_inc = 0;
+ static unsigned btn_prox= 0;
+
+ int change = 0;
+
+ if (XOR(button, buttons & TIP_BIT))
+ {
+ if (change = button)
+ {
+ tc1kpen_buttonEvent(info, button, side_btn, 0);
+
+ button = 0;
+
+ timer = 0;
+ }
+ else if (change = !status)
+ {
+ if (prox_cnt != btn_prox) btn_inc = 0;
+
+ button = buttons + btn_inc;
+
+ tc1kpen_buttonEvent(info, button, side_btn, 1);
+
+ btn_inc = 0;
+ }
+ }
+ else
+ {
+ if (!(status || button))
+ {
+ if (buttons & BUTTON_BIT)
+ {
+ timer = GetTimeInMillis();
+ }
+ else if (timer)
+ {
+ tc1kpen_devicePtr pen = (tc1kpen_devicePtr)info->private;
+ int t = GetTimeInMillis();
+
+ if (t - timer <= pen->btn3emu_timeout)
+ {
+ btn_prox = prox_cnt;
+
+ btn_inc ^= 0x01;
+ }
+ }
+ }
+ }
+
+ return change;
+}
+
+
+/*
+ * Pen button modes container
+ */
+
+static tc1kpen_btnMode tc1kpen_btn_modes[] = {
+ {
+ .id = "2HW",
+ .name = "2-button HW mode",
+ .desc = "Pen tip is button 1, pen button is button 3",
+ .func = tc1kpen_btnMod_2HW,
+ .next = NULL,
+ },
+ {
+ .id = "2EMU",
+ .name = "2-button emulated mode",
+ .desc =
+ "Pen tip is button 1, button 3 is emulated by "
+ "pen tip press while pen button held",
+ .func = tc1kpen_btnMod_2EMU,
+ .next = NULL,
+ },
+ {
+ .id = "3EMU",
+ .name = "3-button emulated mode (no tap)",
+ .desc =
+ "Pen tip is button 1, button 2 is emulated by "
+ "pen button click, button 3 is emulated by "
+ "pen tip press while pen button held",
+ .func = tc1kpen_btnMod_3EMU,
+ .next = NULL,
+ },
+ {
+ .id = "3EMUtap",
+ .name = "3-button emulated mode (with tap)",
+ .desc =
+ "Pen tip is button 1 or button 2, "
+ "depending on current state "
+ "(switched by pen button click, "
+ "reset by button 2 use or by leaving proximity). "
+ "Button 3 is emulated by pen tip press "
+ "while pen button held",
+ .func = tc1kpen_btnMod_3EMUt,
+ .next = NULL,
+ },
+};
+
+
+
+/*
+ ********************************************************************
+ * PEN POSITION RESOLVING
+ ********************************************************************
+ */
+
+/*
+ * Transforms HW coordinates to on-screen coordinates
+ */
+
+static int tc1kpen_transformCoords(tc1kpen_devicePtr pen, int hw_x, int hw_y)
+{
+ int motion = 0;
+
+ int x, y, width, height;
+ int inv_x, inv_y, swap_xy;
+
+ Rotation rotation;
+
+ int swp;
+
+ debug(5);
+
+ /* Resolution */
+ width = screenInfo.screens[pen->screen_no]->width;
+ height = screenInfo.screens[pen->screen_no]->height;
+
+ debug(4, "Current resolution: %dx%d", width, height);
+
+ /* Rotation */
+ rotation = xf86GetRotation(screenInfo.screens[pen->screen_no]);
+
+ debug(4, "Current rotation: %d", rotation);
+
+ /* Transformation */
+ switch (rotation)
+ {
+ default:
+ error("Unknown rotation: %d", rotation);
+
+ case RR_Rotate_0:
+ /* Normal */
+ inv_x = pen->inv_x;
+ inv_y = pen->inv_y ^ 1;
+ swap_xy = pen->swap_xy;
+
+ debug(4, "Using 0 deg. rotation transforms");
+
+ break;
+
+ case RR_Rotate_90:
+ /* Left */
+ inv_x = pen->inv_x;
+ inv_y = pen->inv_y;
+ swap_xy = pen->swap_xy ^ 1;
+
+ debug(4, "Using 90 deg. rotation transforms");
+
+ /* Swap WH */
+ swp = width;
+ width = height;
+ height = swp;
+
+ break;
+
+ case RR_Rotate_180:
+ /* Inverted */
+ inv_x = pen->inv_x ^ 1;
+ inv_y = pen->inv_y;
+ swap_xy = pen->swap_xy;
+
+ debug(4, "Using 180 deg. rotation transforms");
+
+ break;
+
+ case RR_Rotate_270:
+ /* Right */
+ inv_x = pen->inv_x ^ 1;
+ inv_y = pen->inv_y ^ 1;
+ swap_xy = pen->swap_xy ^ 1;
+
+ debug(4, "Using 270 deg. rotation transforms");
+
+ /* Swap WH */
+ swp = width;
+ width = height;
+ height = swp;
+
+ break;
+ }
+
+ /* Shift fix */
+ hw_x -= pen->min_x;
+ hw_y -= pen->min_y;
+
+ /* Scale */
+ x = (hw_x * width) / pen->x_scr_range;
+ y = (hw_y * height) / pen->y_scr_range;
+
+ debug(4, "Scaled coodrs: [%d,%d]", x, y);
+
+ debug(4, "Inversions: [%s,%s], XY swapping: %s",
+ (inv_x ? "yes" : "no"),
+ (inv_y ? "yes" : "no"),
+ (swap_xy ? "yes" : "no"));
+
+ /* Invert */
+ if (inv_x) x = width - x;
+ if (inv_y) y = height - y;
+
+ /* Swap XY */
+ if (swap_xy)
+ {
+ swp = y;
+ y = x;
+ x = swp;
+ }
+
+ debug(4, "Transformed coords: [%d,%d]", x, y);
+
+ /* Store actual coordinates */
+ if (motion = (pen->x != x)) pen->x = x;
+
+ if (pen->y != y)
+ {
+ pen->y = y;
+
+ motion = 1;
+ }
+
+ debugCond(motion, 4, "Coords have changed");
+
+ return motion;
+}
+
+
+/*
+ * Resolves pen position (side-button or screen)
+ * Returns a non-zero if there was a motion
+ * Stores side-button number if in side-buttons area
+ * (negative value means side-button miss)
+ */
+
+static int tc1kpen_resolvePosition( InputInfoPtr info,
+ int x, int y,
+ int *side_btn)
+{
+ tc1kpen_devicePtr pen = (tc1kpen_devicePtr)info->private;
+
+ int motion = 0;
+
+ debug(5);
+
+ /* In side-buttons area */
+ if (y >= pen->side_btns_min_y)
+ {
+ debug(4, "In side buttons area: [%d,%d]", x, y);
+
+ *side_btn = -1;
+
+ if (pen->side_btns_yl < y && y < pen->side_btns_yh)
+ {
+ int i;
+
+ tc1kpen_sideBtnXArea *btns_x = pen->side_btns_x;
+
+ for (i = 0; i < SIDE_BTNS_NUMBER; i++)
+ {
+ if (btns_x[i].l < x && x < btns_x[i].h)
+ {
+ *side_btn = i + 1;
+ break;
+ }
+ }
+ }
+
+ debug(4, "In side button %d area (-1 means miss)", *side_btn);
+ }
+
+ /* On screen */
+ else
+ {
+ debug(4, "On screen: [%d,%d]", x, y);
+
+ /* Transform HW coords to screen coords */
+ motion = tc1kpen_transformCoords(pen, x, y);
+ }
+
+ return motion;
+}
+
+
+
+
+/*
+ * Device input callback
+ */
+
+static void tc1kpen_readInput(InputInfoPtr info)
+{
+ /* Saved status and proximity bit */
+ static int old_stat = 0;
+ static int old_prox = 0;
+
+ /* Proximity bit changes counter */
+ static unsigned prox_cnt = 0;
+
+ /* Status bit, proximity bit, button bits and coordinates */
+ int stat, prox, buttons, x, y;
+
+ /* Motion flag and side button number */
+ int motion, side_btn = 0;
+
+ tc1kpen_devicePtr pen = (tc1kpen_devicePtr)info->private;
+
+ /* Get input */
+ if (!tc1kpen_readTTYS(info->fd, &stat, &prox, &buttons, &x, &y)) return;
+
+ /* Resolve pen position */
+ motion = tc1kpen_resolvePosition(info, x, y, &side_btn);
+
+ /* Handle proximity change */
+ if (prox ^ old_prox)
+ {
+ tc1kpen_proximityEvent(info, prox);
+
+ prox_cnt++;
+
+ old_prox = prox;
+
+ /* Pen away */
+ if (!prox) return;
+ }
+
+ /* Handle position change */
+ if (motion) tc1kpen_motionEvent(info);
+
+ /* Resolve pen buttons status change */
+ if (stat ^ old_stat && side_btn ^ SIGN(side_btn))
+ {
+ pen->btn_mode->func(info, stat, buttons, side_btn, prox_cnt);
+
+ old_stat = stat;
+ }
+
+ debug(5);
+}
+
+
+
+/*
+ ********************************************************************
+ * X11 DEVICE CONTROLL AND DRIVER CONTROLL
+ ********************************************************************
+ */
+
+/*
+ * Device pointer controll callback
+ */
+
+static void tc1kpen_ptrCtrl(DeviceIntPtr device, PtrCtrl *ctrl)
+{
+ debug(5);
+}
+
+
+/*
+ * Device LED control callback
+ */
+
+static void tc1kpen_ledCtrl()
+{
+ debug(5);
+}
+
+
+#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) < 2
+/*
+ * Coordinates conversion procedure
+ * Abandoned as of XInput ABI v2.0
+ */
+static Bool tc1kpen_convProc( InputInfoPtr info, int first, int num,
+ int x, int y, int v2, int v3, int v4, int v5,
+ int *scr_x, int *scr_y)
+{
+ if (0 != first || 2 != num) return FALSE;
+
+ *scr_x = x;
+ *scr_y = y;
+
+ debug(4, "Conversion proc: coords: [%d,%d]", *scr_x, *scr_y);
+
+ return TRUE;
+}
+#endif
+
+
+/*
+ * Device initialization
+ */
+
+static int tc1kpen_ctrlInit( DeviceIntPtr device,
+ InputInfoPtr info,
+ tc1kpen_devicePtr pen)
+{
+ /* Device can report proximity */
+ if (!InitProximityClassDeviceStruct(device))
+ {
+ error("Unable to initialize ProximityClassRec");
+ return INIT_FAILED;
+ }
+
+ /* Device can be focused */
+ if (!InitFocusClassDeviceStruct(device))
+ {
+ error("Unable to initialize FocusClassRec");
+ return INIT_FAILED;
+ }
+
+ /* Device can report button events */
+ if (!InitButtonClassDeviceStruct(device, pen->btn_no, pen->btn_map))
+ {
+ error("Unable to initialize ButtonClassRec");
+ return INIT_FAILED;
+ }
+
+ /* Device has LEDs */
+ if (!InitLedFeedbackClassDeviceStruct(device, tc1kpen_ledCtrl))
+ {
+ error("Unable to initialize LedFeedbackClassRec");
+ return INIT_FAILED;
+ }
+
+ /* Device has a pointer */
+ if (!InitPtrFeedbackClassDeviceStruct(device, tc1kpen_ptrCtrl))
+ {
+ error("Unable to initialize PtrFeedbackClassRec");
+ return INIT_FAILED;
+ }
+
+ /* Device reports absolute 2D coordinates */
+ if (!InitValuatorClassDeviceStruct(device, 2, xf86GetMotionEvents, info->history_size, Absolute))
+ {
+ error("Unable to initialize ValuatorClassRec");
+ return INIT_FAILED;
+ }
+
+#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) < 2
+ /* ValuatorAxisRec abandoned as of XInput ABI v2.0 */
+ InitValuatorAxisStruct(device, 0, pen->min_x, pen->max_x, 9500, 0, 9500);
+ InitValuatorAxisStruct(device, 1, pen->min_y, pen->max_y, 10500, 0, 10500);
+#endif
+
+ /* Allocate motion history buffer */
+ xf86MotionHistoryAllocate(info);
+
+ debug(0, "Device initialized");
+
+ return Success;
+}
+
+
+/*
+ * Enable device
+ */
+
+static int tc1kpen_ctrlOn( DeviceIntPtr device,
+ InputInfoPtr info,
+ tc1kpen_devicePtr pen)
+{
+ if (device->public.on)
+ {
+ warning("Device is already on");
+ return Success;
+ }
+
+ /* Open serial port */
+ if (info->fd >= 0)
+ {
+ error("Serial port has already been opened");
+ return ON_FAILED;
+ }
+
+ info->fd = xf86OpenSerial(info->options);
+
+ if (info->fd < 0)
+ {
+ error("Unable to open serial device");
+ return ON_FAILED;
+ }
+
+ /* Enable device */
+ xf86AddEnabledDevice(info);
+
+ device->public.on = TRUE;
+
+ debug(0, "Device switched on");
+
+ return Success;
+}
+
+
+/*
+ * Disable device
+ */
+
+static int tc1kpen_ctrlOff( DeviceIntPtr device,
+ InputInfoPtr info,
+ tc1kpen_devicePtr pen)
+{
+ if (!device->public.on)
+ {
+ warning("Device is already off");
+
+ return Success;
+ }
+
+ if (info->fd < 0)
+ {
+ error("Serial port hasn't been opened yet");
+ return OFF_FAILED;
+ }
+
+ /* Disable device */
+ xf86RemoveEnabledDevice(info);
+
+ /* Close serial port */
+ xf86CloseSerial(info->fd);
+ info->fd = -1;
+
+ device->public.on = FALSE;
+
+ debug(0, "Device switched off");
+
+ return Success;
+}
+
+
+/*
+ * Device control callback
+ */
+
+static int tc1kpen_control( DeviceIntPtr device,
+ int mode)
+{
+ int status = INTERNAL_ERROR;
+
+ InputInfoPtr info = device->public.devicePrivate;
+ tc1kpen_devicePtr pen = (tc1kpen_devicePtr)info->private;
+
+ debug(4, "Mode: %d", mode);
+
+ switch (mode)
+ {
+ case DEVICE_INIT:
+ status = tc1kpen_ctrlInit(device, info, pen);
+ break;
+
+ case DEVICE_ON:
+ status = tc1kpen_ctrlOn(device, info, pen);
+ break;
+
+ case DEVICE_OFF:
+ case DEVICE_CLOSE:
+ status = tc1kpen_ctrlOff(device, info, pen);
+ break;
+
+ default:
+ error("Unexpected control mode: %d", mode);
+ }
+
+ debug(4, "Status: %d", status);
+
+ return status;
+}
+
+
+/*
+ * Device options processing
+ */
+
+static Bool tc1kpen_processOptions( InputInfoPtr info,
+ tc1kpen_devicePtr pen)
+{
+ /* Collect options */
+ xf86CollectInputOptions(info, tc1kpen_optionDefaults, NULL);
+
+ /* Process the common options. */
+ xf86ProcessCommonOptions(info, info->options);
+
+ /* Set device configuration */
+ pen->screen_no = xf86SetIntOption(info->options, "ScreenNo", 0);
+
+ config("Associated screen: %d", pen->screen_no);
+
+ /* Set buttons configuration */
+ pen->btn_map_str = xf86SetStrOption(info->options, "ButtonMap", "");
+ pen->btn_modes_str = xf86SetStrOption(info->options, "ButtonModes", "");
+ pen->btn_mode_switch = xf86SetIntOption(info->options, "ButtonModeSwitch", 0);
+ pen->btn3emu_timeout = xf86SetIntOption(info->options, "Btn3EmuTimeout", 0);
+
+ config("Pen buttons map: %s", pen->btn_map_str);
+ config("Pen button modes: %s", pen->btn_modes_str);
+ config("Pen button mode switch button: %d", pen->btn_mode_switch);
+ config( "3-buttons emulation mode timeout set to %d ms",
+ pen->btn3emu_timeout);
+
+ /* Set on-screen coordinates configuration */
+ pen->min_x = xf86SetIntOption(info->options, "CoordMinX", 0);
+ pen->max_x = xf86SetIntOption(info->options, "CoordMaxX", 0);
+ pen->min_y = xf86SetIntOption(info->options, "CoordMinY", 0);
+ pen->max_y = xf86SetIntOption(info->options, "CoordMaxY", 0);
+
+ config( "Screen HW area: [%d-%d,%d-%d]", pen->min_x, pen->max_x,
+ pen->min_y, pen->max_y);
+
+ /* Set side-buttons configuration */
+ pen->side_btns_min_y = xf86SetIntOption(info->options, "SideBtnsYOffset", 0);
+ pen->side_btns_size = xf86SetIntOption(info->options, "SideBtnsSize", 0);
+ pen->side_btns_y = xf86SetIntOption(info->options, "SideBtnsY", 0);
+ pen->side_btns_1_x = xf86SetIntOption(info->options, "SideBtn1X", 0);
+ pen->side_btns_2_x = xf86SetIntOption(info->options, "SideBtn2X", 0);
+ pen->side_btns_3_x = xf86SetIntOption(info->options, "SideBtn3X", 0);
+
+ config( "Side buttons: area Y offset: %d, centers: [%d/%d/%d,%d], size: %d",
+ pen->side_btns_min_y,
+ pen->side_btns_1_x,
+ pen->side_btns_2_x,
+ pen->side_btns_3_x,
+ pen->side_btns_y,
+ pen->side_btns_size);
+
+ /* Set transformation configuration */
+ pen->inv_x = xf86SetBoolOption(info->options, "InvertX", 0);
+ pen->inv_y = xf86SetBoolOption(info->options, "InvertY", 0);
+ pen->swap_xy = xf86SetBoolOption(info->options, "SwapXY", 0);
+ pen->rotate = xf86SetStrOption(info->options, "Rotate", "");
+
+ config("Invert X axis: %s", (pen->inv_x ? "yes" : "no"));
+ config("Invert Y axis: %s", (pen->inv_y ? "yes" : "no"));
+ config("Swap X and Y axis: %s", (pen->swap_xy ? "yes" : "no"));
+ config("Rotate: %s", pen->rotate);
+
+ debug(0, "Device options processed");
+
+ return TRUE;
+}
+
+
+/*
+ * Set on-screen configuration
+ */
+
+static Bool tc1kpen_setOnScreenConfig(tc1kpen_devicePtr pen)
+{
+ /* Set HW coords screen range */
+ pen->x_scr_range = pen->max_x - pen->min_x;
+ pen->y_scr_range = pen->max_y - pen->min_y;
+
+ debug(4, "HW res. screen range: %dx%d", pen->x_scr_range, pen->y_scr_range);
+
+ /* Set rotation */
+ if (pen->rotate)
+ {
+ if (!strcmp(pen->rotate, "no"))
+ {
+ /* Do nothing */
+ debug(4, "No rotation set");
+ }
+ else if (!strcmp(pen->rotate, "CW"))
+ {
+ pen->inv_x ^= 1;
+ pen->inv_y ^= 1;
+ pen->swap_xy ^= 1;
+
+ debug(4, "Clockwise rotation set");
+ }
+ else if (!strcmp(pen->rotate, "CCW"))
+ {
+ pen->inv_x ^= 0;
+ pen->inv_y ^= 0;
+ pen->swap_xy ^= 1;
+
+ debug(4, "Counter-clockwise rotation set");
+ }
+ else
+ {
+ error("Unsupported rotation: %s", pen->rotate);
+ return FALSE;
+ }
+ }
+
+ debug(0, "Device on-screen configuration set");
+
+ return TRUE;
+}
+
+
+/*
+ * Set buttons configuration
+ */
+
+static Bool tc1kpen_setButtonsConfig(tc1kpen_devicePtr pen)
+{
+ int i, side_btn_r;
+
+ /* Set buttons mapping */
+ pen->btn_map[0] = 0;
+
+ pen->btn_no = sscanf( pen->btn_map_str,
+ "%d %d %d %d %d %d %d %d %d %d %d %d",
+ (CARD8 *)&pen->btn_map[1],
+ (CARD8 *)&pen->btn_map[2],
+ (CARD8 *)&pen->btn_map[3],
+ (CARD8 *)&pen->btn_map[4],
+ (CARD8 *)&pen->btn_map[5],
+ (CARD8 *)&pen->btn_map[6],
+ (CARD8 *)&pen->btn_map[7],
+ (CARD8 *)&pen->btn_map[8],
+ (CARD8 *)&pen->btn_map[9],
+ (CARD8 *)&pen->btn_map[10],
+ (CARD8 *)&pen->btn_map[11],
+ (CARD8 *)&pen->btn_map[12]);
+
+ if (MAX_BUTTONS > pen->btn_no)
+ {
+ warning("Only %d buttons set by map: %s", pen->btn_no, pen->btn_map_str);
+ warning("Up to %d buttons are supported", MAX_BUTTONS);
+ }
+
+ config( "%d device buttons configured: %d %d %d %d %d %d %d %d %d %d %d %d",
+ pen->btn_no,
+ pen->btn_map[1], pen->btn_map[2], pen->btn_map[3],
+ pen->btn_map[4], pen->btn_map[5], pen->btn_map[6],
+ pen->btn_map[7], pen->btn_map[8], pen->btn_map[9],
+ pen->btn_map[10], pen->btn_map[11], pen->btn_map[12]);
+
+ /* Set button modes */
+ char *mod_end = pen->btn_modes_str;
+
+ while ('\0' != *mod_end)
+ {
+ if (' ' == *mod_end || '\t' == *mod_end)
+ {
+ *mod_end = '\0';
+ }
+
+ mod_end++;
+ }
+
+ tc1kpen_btnMode *mode_list_head = NULL;
+ tc1kpen_btnMode *mode_list_tail = NULL;
+
+ char *mode_id = pen->btn_modes_str;
+
+ while (mode_id != mod_end)
+ {
+ if ('\0' != *mode_id)
+ {
+ int i;
+
+ for (i = 0; i < ARRAYLEN(tc1kpen_btn_modes); i++)
+ {
+ if (!strcmp(mode_id, tc1kpen_btn_modes[i].id))
+ {
+ if (mode_list_tail)
+ {
+ mode_list_tail->next = &(tc1kpen_btn_modes[i]);
+ }
+ else
+ {
+ mode_list_head = &(tc1kpen_btn_modes[i]);
+ }
+
+ mode_list_tail = &(tc1kpen_btn_modes[i]);
+
+ break;
+ }
+ }
+
+ if (i == ARRAYLEN(tc1kpen_btn_modes))
+ {
+ error("Unsupported pen button mode '%s'", mode_id);
+
+ return FALSE;
+ }
+
+ mode_id += strlen(mode_id);
+ }
+ else
+ {
+ mode_id++;
+ }
+ }
+
+ if (!mode_list_head)
+ {
+ error("At least one pen button mode must be set");
+
+ return FALSE;
+ }
+
+ mode_list_tail->next = mode_list_head;
+
+ pen->btn_mode = mode_list_head;
+
+ /* Set side buttons areas */
+ side_btn_r = pen->side_btns_size / 2;
+
+ pen->side_btns_yl = pen->side_btns_y - side_btn_r;
+ pen->side_btns_yh = pen->side_btns_y + side_btn_r;
+
+ pen->side_btns_x[0].l = pen->side_btns_1_x - side_btn_r;
+ pen->side_btns_x[0].h = pen->side_btns_1_x + side_btn_r;
+
+ pen->side_btns_x[1].l = pen->side_btns_2_x - side_btn_r;
+ pen->side_btns_x[1].h = pen->side_btns_2_x + side_btn_r;
+
+ pen->side_btns_x[2].l = pen->side_btns_3_x - side_btn_r;
+ pen->side_btns_x[2].h = pen->side_btns_3_x + side_btn_r;
+
+ debug(4, "Side button 1: [%d,%d] - [%d,%d]",
+ pen->side_btns_x[0].l, pen->side_btns_yl,
+ pen->side_btns_x[0].h, pen->side_btns_yh);
+ debug(4, "Side button 2: [%d,%d] - [%d,%d]",
+ pen->side_btns_x[1].l, pen->side_btns_yl,
+ pen->side_btns_x[1].h, pen->side_btns_yh);
+ debug(4, "Side button 3: [%d,%d] - [%d,%d]",
+ pen->side_btns_x[2].l, pen->side_btns_yl,
+ pen->side_btns_x[2].h, pen->side_btns_yh);
+
+ debug(0, "Device buttons configuration set");
+
+ return TRUE;
+}
+
+
+/*
+ * Set driver configuration
+ */
+
+static Bool tc1kpen_setConfig(tc1kpen_devicePtr pen)
+{
+ /* Correct screen number */
+ if (pen->screen_no >= screenInfo.numScreens || pen->screen_no < 0)
+ {
+ pen->screen_no = 0;
+
+ warning( "%d screens only, device screen set to %d",
+ screenInfo.numScreens, pen->screen_no);
+ }
+
+ if (!tc1kpen_setOnScreenConfig(pen)) return FALSE;
+
+ if (!tc1kpen_setButtonsConfig(pen)) return FALSE;
+
+ return TRUE;
+}
+
+
+/*
+ * Driver data destructor
+ */
+
+static pointer tc1kpen_destroy( InputInfoPtr info,
+ tc1kpen_devicePtr pen)
+{
+ if (pen)
+ {
+ if (pen->rotate) xfree(pen->rotate);
+ if (pen->btn_map_str) xfree(pen->btn_map_str);
+ if (pen->btn_modes_str) xfree(pen->btn_modes_str);
+
+ xfree(pen);
+ }
+
+ if (info)
+ {
+ info->private = NULL;
+
+ xf86DeleteInput(info, 0);
+ }
+
+ return NULL;
+}
+
+
+/*
+ * Driver pre-initialization
+ */
+
+static InputInfoPtr tc1kpen_preInit( InputDriverPtr drv,
+ IDevPtr dev,
+ int flags)
+{
+ InputInfoPtr info = NULL;
+ tc1kpen_devicePtr pen = NULL;
+
+ if (!(info = xf86AllocateInput(drv, 0)))
+ {
+ error("Input record allocation failed");
+
+ return tc1kpen_destroy(info, pen);
+ }
+
+ if (!(pen = xcalloc(1, sizeof(tc1kpen_deviceRec))))
+ {
+ error("Device record allocation failed");
+
+ return tc1kpen_destroy(info, pen);
+ }
+
+ info->private = pen;
+
+ info->name = xstrdup(dev->identifier);
+ info->flags = 0;
+ info->type_name = XI_TABLET;
+ info->conf_idev = dev;
+ info->read_input = tc1kpen_readInput;
+ info->switch_mode = NULL;
+ info->device_control = tc1kpen_control;
+ info->fd = -1;
+ info->history_size = 0;
+
+#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) < 2
+ /* Conversion procedure is no longer needed as of XInput ABI v2.0 */
+ info->conversion_proc = tc1kpen_convProc;
+#endif
+
+ /* Process driver specific options */
+ if (!tc1kpen_processOptions(info, pen))
+ {
+ error("Failed to process options");
+
+ return tc1kpen_destroy(info, pen);
+ }
+
+ /* Set device internal configuration */
+ if (!tc1kpen_setConfig(pen))
+ {
+ error("Failed to set device configuration");
+
+ return tc1kpen_destroy(info, pen);
+ }
+
+ /* Flags */
+ info->flags |= XI86_OPEN_ON_INIT;
+ info->flags |= XI86_CONFIGURED;
+
+ debug(0, "Device pre-initialized");
+
+ return info;
+}
+
+
+/*
+ * Driver un-initialization
+ */
+
+static void tc1kpen_unInit( InputDriverPtr drv,
+ InputInfoPtr info,
+ int flags)
+{
+ tc1kpen_devicePtr pen = (tc1kpen_devicePtr)info->private;
+
+ tc1kpen_destroy(info, pen);
+
+ debug(0, "Finalized");
+}
+
+
+/*
+ * Driver record
+ */
+
+_X_EXPORT InputDriverRec input_driver = {
+ DRIVER_VERSION,
+ DRIVER_ID,
+ NULL,
+ tc1kpen_preInit,
+ tc1kpen_unInit,
+ NULL,
+ 0
+ };
+
+
+
+/*
+ * Module loading/unloading
+ */
+
+static XF86ModuleVersionInfo tc1kpen_versionRec = {
+ DRIVER_ID,
+ MODULEVENDORSTRING,
+ MODINFOSTRING1,
+ MODINFOSTRING2,
+ XORG_VERSION_CURRENT,
+ PACKAGE_VERSION_MAJOR,
+ PACKAGE_VERSION_MINOR,
+ PACKAGE_VERSION_PATCHLEVEL,
+ ABI_CLASS_XINPUT,
+ ABI_XINPUT_VERSION,
+ MOD_CLASS_XINPUT,
+ { 0, 0, 0, 0 }
+ };
+
+static pointer tc1kpen_plug( pointer module,
+ pointer options,
+ int *errmaj,
+ int *errmin)
+{
+ xf86AddInputDriver(&input_driver, module, 0);
+
+ return module;
+}
+
+static void tc1kpen_unplug(pointer p)
+{
+}
+
+
+_X_EXPORT XF86ModuleData DRIVER_MODULE_DATA = {
+ &tc1kpen_versionRec,
+ tc1kpen_plug,
+ tc1kpen_unplug
+ };