diff options
Diffstat (limited to 'src/tc1kpen.c')
-rw-r--r-- | src/tc1kpen.c | 1438 |
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 + }; |