1/*
2 * Copyright 2006-2010, Haiku.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		J��r��me Duval
7 *
8 * References:
9 *   Google search "technic doc genius" , http://www.bebits.com/app/2152
10 */
11
12#include <stdlib.h>
13#include <string.h>
14#include <unistd.h>
15
16#include <Debug.h>
17#include <InterfaceDefs.h>
18#include <SerialPort.h>
19
20#include "EasyPenInputDevice.h"
21#include "keyboard_mouse_driver.h"
22
23/*
24IN ABSOLUTE MODE (MM Series)
25
26                      Least                          Most
27
28  Bytes       Bit to  Significant                    Significant   Bit to
29
30Transmitted   Start   Bit                            Bit           Stop
31
32                      0   1   2   3    4    5    6    7   8
33
34-----------   ----- ---------------------------------------------  ------
35
361st Byte       0     Fa  Fb  Fc  Sy   Sx   T    PR   PH  P          1
37
382nd Byte       0     X0  X1  X2  X3   X4   X5   X6   0   P          1
39
403rd Byte       0     X7  X8  X9  X10  X11  X12  X13  0   P          1
41
424th Byte       0     Y0  Y1  Y2  Y3   Y4   Y5   Y6   0   P          1
43
445th Byte       0     Y7  Y8  Y9  Y10  Y11  Y12  Y13  0   P          1
45
46*/
47
48#if DEBUG
49        inline void LOG(const char *fmt, ...) { char buf[1024]; va_list ap; va_start(ap, fmt); vsprintf(buf, fmt, ap); va_end(ap); \
50                fputs(buf, EasyPenInputDevice::sLogFile); fflush(EasyPenInputDevice::sLogFile); }
51        #define LOG_ERR(text...) LOG(text)
52FILE *EasyPenInputDevice::sLogFile = NULL;
53#else
54        #define LOG(text...)
55        #define LOG_ERR(text...) fprintf(stderr, text)
56#endif
57
58#define CALLED() LOG("%s\n", __PRETTY_FUNCTION__)
59
60const static uint32 kTabletThreadPriority = B_FIRST_REAL_TIME_PRIORITY + 4;
61
62struct tablet_device {
63	tablet_device(BSerialPort *port);
64	~tablet_device();
65
66	input_device_ref device_ref;
67	thread_id device_watcher;
68	bool active;
69	BSerialPort *serial;
70	EasyPenInputDevice *owner;
71};
72
73
74extern "C"
75BInputServerDevice *
76instantiate_input_device()
77{
78	return new EasyPenInputDevice();
79}
80
81
82EasyPenInputDevice::EasyPenInputDevice()
83{
84#if DEBUG
85	sLogFile = fopen("/var/log/easypen_device_log.log", "a");
86#endif
87}
88
89
90EasyPenInputDevice::~EasyPenInputDevice()
91{
92	CALLED();
93	for (int32 i = 0; i < fDevices.CountItems(); i++)
94		delete (tablet_device *)fDevices.ItemAt(i);
95
96#if DEBUG
97	fclose(sLogFile);
98#endif
99}
100
101
102status_t
103EasyPenInputDevice::InitCheck()
104{
105	CALLED();
106
107	BSerialPort *serial = new BSerialPort();
108	int count = serial->CountDevices();
109	char devName[B_OS_NAME_LENGTH];
110	char byte;
111
112	for (int i=0; i<count; i++) {
113		serial->GetDeviceName(i, devName);
114		serial->SetDataRate(B_9600_BPS);
115		serial->SetDataBits(B_DATA_BITS_8);
116		serial->SetStopBits(B_STOP_BITS_1);
117		serial->SetParityMode(B_ODD_PARITY);
118		serial->SetFlowControl(B_HARDWARE_CONTROL+B_SOFTWARE_CONTROL);
119		serial->SetBlocking(true);
120		serial->SetTimeout(800000);
121
122		if (serial->Open(devName) < B_OK) {
123			LOG("Fail to open %s\n", devName);
124			continue;
125		}
126		snooze(250000);
127		serial->Write("z9", 2); // 8 data bits,odd  parity <command>                     z 9
128		serial->Write("", 1); //	Reset                <command>                NUL
129		serial->Write("DP", 2);		// mode command    D   trigger command    P
130		snooze(250000);
131		serial->ClearInput();
132		serial->Write("twP", 3);	// Self-Test, Send Test Results, trigger command    P
133		serial->Read(&byte, 1);
134		if ((byte & 0xf7) != 0x87) {
135			LOG("Self test failed for %s %x\n", devName, byte & 0xf7);
136			continue;
137		}
138		tablet_device *device = new tablet_device(serial);
139		device->owner = this;
140
141		input_device_ref *devices[2];
142		devices[0] = &device->device_ref;
143		devices[1] = NULL;
144
145		fDevices.AddItem(device);
146		RegisterDevices(devices);
147
148		serial = new BSerialPort();
149	}
150
151	delete serial;
152
153	LOG("Found %ld devices\n", fDevices.CountItems());
154
155	get_click_speed(&fClickSpeed);
156
157	return fDevices.CountItems() > 0 ? B_OK : B_ERROR;
158}
159
160
161status_t
162EasyPenInputDevice::Start(const char *name, void *cookie)
163{
164	tablet_device *device = (tablet_device *)cookie;
165
166	LOG("%s(%s)\n", __PRETTY_FUNCTION__, name);
167
168	char threadName[B_OS_NAME_LENGTH];
169	snprintf(threadName, B_OS_NAME_LENGTH, "%s watcher", name);
170
171	device->active = true;
172	device->device_watcher = spawn_thread(DeviceWatcher, threadName,
173		kTabletThreadPriority, device);
174
175	if (device->device_watcher < B_OK)
176		return device->device_watcher;
177	resume_thread(device->device_watcher);
178
179	return B_OK;
180}
181
182
183status_t
184EasyPenInputDevice::Stop(const char *name, void *cookie)
185{
186	tablet_device *device = (tablet_device *)cookie;
187
188	LOG("%s(%s)\n", __PRETTY_FUNCTION__, name);
189
190	device->active = false;
191	if (device->device_watcher >= 0) {
192		suspend_thread(device->device_watcher);
193		resume_thread(device->device_watcher);
194		status_t dummy;
195		wait_for_thread(device->device_watcher, &dummy);
196	}
197
198	return B_OK;
199}
200
201
202status_t
203EasyPenInputDevice::Control(const char *name, void *cookie,
204						  uint32 command, BMessage *message)
205{
206	LOG("%s(%s, code: %lu)\n", __PRETTY_FUNCTION__, name, command);
207
208	if (command == B_CLICK_SPEED_CHANGED)
209		get_click_speed(&fClickSpeed);
210
211	return B_OK;
212}
213
214
215int32
216EasyPenInputDevice::DeviceWatcher(void *arg)
217{
218	tablet_device *dev = (tablet_device *)arg;
219	EasyPenInputDevice *owner = dev->owner;
220
221	tablet_movement movements;
222	tablet_movement old_movements;
223	BMessage *message;
224	uint8 byte;
225	uint8 bytes[4];
226	bigtime_t lastClickTimeStamp = 0LL;
227
228	memset(&movements, 0, sizeof(movements));
229	memset(&old_movements, 0, sizeof(old_movements));
230
231	dev->serial->Write(":@FRb", 5);
232
233	while (dev->active) {
234		byte = 0;
235		while(!byte)
236			dev->serial->Read(&byte, 1);
237		if (byte & 0x40 || !(byte & 0x80)) { // 7th bit on or 8th bit off
238			snooze(10000); // this is a realtime thread, and something is wrong...
239			continue;
240		}
241
242		if (dev->serial->Read(bytes, 4) != 4)
243			continue;
244
245		if (*(uint32*)bytes & 0x80808080)
246			continue;
247
248		movements.buttons = byte & 0x7;
249		movements.timestamp = system_time();
250		movements.xpos = (bytes[1] << 7) | bytes[0];
251		movements.ypos = (bytes[3] << 7) | bytes[2];
252
253		uint32 buttons = old_movements.buttons ^ movements.buttons;
254
255		LOG("%s: buttons: 0x%lx, x: %f, y: %f, clicks:%ld, wheel_x:%ld, wheel_y:%ld\n", dev->device_ref.name, movements.buttons,
256			movements.xpos, movements.ypos, movements.clicks, movements.wheel_xdelta, movements.wheel_ydelta);
257
258		movements.xpos /= 1950.0;
259		movements.ypos /= 1500.0;
260
261		LOG("%s: x: %f, y: %f, \n", dev->device_ref.name, movements.xpos, movements.ypos);
262
263		if (buttons != 0) {
264			message = new BMessage(B_MOUSE_UP);
265			if ((buttons & movements.buttons) > 0) {
266				message->what = B_MOUSE_DOWN;
267				if(lastClickTimeStamp + owner->fClickSpeed <= movements.timestamp)
268					movements.clicks = 1;
269				else
270					movements.clicks++;
271				lastClickTimeStamp = movements.timestamp;
272				message->AddInt32("clicks", movements.clicks);
273				LOG("B_MOUSE_DOWN\n");
274			} else {
275				LOG("B_MOUSE_UP\n");
276			}
277
278			message->AddInt64("when", movements.timestamp);
279			message->AddInt32("buttons", movements.buttons);
280			message->AddFloat("x", movements.xpos);
281			message->AddFloat("y", movements.ypos);
282			owner->EnqueueMessage(message);
283		}
284
285		if (movements.xpos != 0.0 || movements.ypos != 0.0) {
286			message = new BMessage(B_MOUSE_MOVED);
287			if (message) {
288				message->AddInt64("when", movements.timestamp);
289				message->AddInt32("buttons", movements.buttons);
290				message->AddFloat("x", movements.xpos);
291				message->AddFloat("y", movements.ypos);
292				message->AddFloat("be:tablet_x", movements.xpos);
293				message->AddFloat("be:tablet_y", movements.ypos);
294				message->AddFloat("be:tablet_pressure", movements.pressure);
295				message->AddInt32("be:tablet_eraser", movements.eraser);
296				if (movements.tilt_x != 0.0 || movements.tilt_y != 0.0) {
297					message->AddFloat("be:tablet_tilt_x", movements.tilt_x);
298					message->AddFloat("be:tablet_tilt_y", movements.tilt_y);
299				}
300
301				owner->EnqueueMessage(message);
302			}
303		}
304
305		if ((movements.wheel_ydelta != 0) || (movements.wheel_xdelta != 0)) {
306			message = new BMessage(B_MOUSE_WHEEL_CHANGED);
307			if (message) {
308				message->AddInt64("when", movements.timestamp);
309				message->AddFloat("be:wheel_delta_x", movements.wheel_xdelta);
310				message->AddFloat("be:wheel_delta_y", movements.wheel_ydelta);
311
312				owner->EnqueueMessage(message);
313			}
314		}
315
316		old_movements = movements;
317
318	}
319
320	return 0;
321}
322
323
324// tablet_device
325tablet_device::tablet_device(BSerialPort *port)
326{
327	serial = port;
328	device_watcher = -1;
329	active = false;
330	device_ref.name = strdup("Genius EasyPen");
331	device_ref.type = B_POINTING_DEVICE;
332	device_ref.cookie = this;
333};
334
335
336tablet_device::~tablet_device()
337{
338	free(device_ref.name);
339}
340
341