xref: /haiku/src/add-ons/input_server/devices/tablet/TabletInputDevice.cpp (revision 1214ef1b2100f2b3299fc9d8d6142e46f70a4c3f)
1 /*****************************************************************************/
2 // Tablet input server device addon
3 // Adapted by Jerome Duval and written by Stefano Ceccherini
4 //
5 // TabletInputDevice.cpp
6 //
7 // Copyright (c) 2005 Haiku Project
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining a
10 // copy of this software and associated documentation files (the "Software"),
11 // to deal in the Software without restriction, including without limitation
12 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
13 // and/or sell copies of the Software, and to permit persons to whom the
14 // Software is furnished to do so, subject to the following conditions:
15 //
16 // The above copyright notice and this permission notice shall be included
17 // in all copies or substantial portions of the Software.
18 //
19 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
22 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 // DEALINGS IN THE SOFTWARE.
26 /*****************************************************************************/
27 
28 // TODO: Use strlcpy instead of strcpy
29 
30 #include "TabletInputDevice.h"
31 #include "kb_mouse_settings.h"
32 #include "kb_mouse_driver.h"
33 
34 #include <stdlib.h>
35 #include <unistd.h>
36 
37 #include <Debug.h>
38 #include <Directory.h>
39 #include <Entry.h>
40 #include <NodeMonitor.h>
41 #include <Path.h>
42 #include <String.h>
43 
44 #if DEBUG
45         inline void LOG(const char *fmt, ...) { char buf[1024]; va_list ap; va_start(ap, fmt); vsprintf(buf, fmt, ap); va_end(ap); \
46                 fputs(buf, TabletInputDevice::sLogFile); fflush(TabletInputDevice::sLogFile); }
47         #define LOG_ERR(text...) LOG(text)
48 FILE *TabletInputDevice::sLogFile = NULL;
49 #else
50         #define LOG(text...)
51         #define LOG_ERR(text...) fprintf(stderr, text)
52 #endif
53 
54 #define CALLED() LOG("%s\n", __PRETTY_FUNCTION__)
55 
56 static TabletInputDevice *sSingletonTabletDevice = NULL;
57 
58 const static uint32 kTabletThreadPriority = B_FIRST_REAL_TIME_PRIORITY + 4;
59 const static char *kTabletDevicesDirectory = "/dev/input/tablet";
60 
61 // "/dev/" is automatically prepended by StartMonitoringDevice()
62 const static char *kTabletDevicesDirectoryUSB = "input/tablet/usb";
63 
64 struct tablet_device {
65 	tablet_device(const char *path);
66 	~tablet_device();
67 
68 	input_device_ref device_ref;
69 	char path[B_PATH_NAME_LENGTH];
70 	int fd;
71 	thread_id device_watcher;
72 	mouse_settings settings;
73 	bool active;
74 };
75 
76 
77 // forward declarations
78 static char *get_short_name(const char *longName);
79 
80 
81 extern "C"
82 BInputServerDevice *
83 instantiate_input_device()
84 {
85 	return new TabletInputDevice();
86 }
87 
88 
89 TabletInputDevice::TabletInputDevice()
90 {
91 	ASSERT(sSingletonTabletDevice == NULL);
92 	sSingletonTabletDevice = this;
93 
94 #if DEBUG
95 	sLogFile = fopen("/var/log/tablet_device_log.log", "a");
96 #endif
97 	CALLED();
98 
99 	StartMonitoringDevice(kTabletDevicesDirectoryUSB);
100 }
101 
102 
103 TabletInputDevice::~TabletInputDevice()
104 {
105 	CALLED();
106 	StopMonitoringDevice(kTabletDevicesDirectoryUSB);
107 
108 	for (int32 i = 0; i < fDevices.CountItems(); i++)
109 		delete (tablet_device *)fDevices.ItemAt(i);
110 
111 #if DEBUG
112 	fclose(sLogFile);
113 #endif
114 }
115 
116 
117 status_t
118 TabletInputDevice::InitFromSettings(void *cookie, uint32 opcode)
119 {
120 	CALLED();
121 	tablet_device *device = (tablet_device *)cookie;
122 
123 	// retrieve current values
124 
125 	if (get_mouse_map(&device->settings.map) != B_OK)
126 		LOG_ERR("error when get_mouse_map\n");
127 	else
128 		ioctl(device->fd, MS_SET_MAP, &device->settings.map);
129 
130 	if (get_click_speed(&device->settings.click_speed) != B_OK)
131 		LOG_ERR("error when get_click_speed\n");
132 	else
133 		ioctl(device->fd, MS_SET_CLICKSPEED, &device->settings.click_speed);
134 
135 	if (get_mouse_speed(&device->settings.accel.speed) != B_OK)
136 		LOG_ERR("error when get_mouse_speed\n");
137 	else {
138 		if (get_mouse_acceleration(&device->settings.accel.accel_factor) != B_OK)
139 			LOG_ERR("error when get_mouse_acceleration\n");
140 		else {
141 			mouse_accel accel;
142 			ioctl(device->fd, MS_GET_ACCEL, &accel);
143 			accel.speed = device->settings.accel.speed;
144 			accel.accel_factor = device->settings.accel.accel_factor;
145 			ioctl(device->fd, MS_SET_ACCEL, &device->settings.accel);
146 		}
147 	}
148 
149 	if (get_mouse_type(&device->settings.type) != B_OK)
150 		LOG_ERR("error when get_mouse_type\n");
151 	else
152 		ioctl(device->fd, MS_SET_TYPE, &device->settings.type);
153 
154 	return B_OK;
155 
156 }
157 
158 
159 status_t
160 TabletInputDevice::InitCheck()
161 {
162 	CALLED();
163 	RecursiveScan(kTabletDevicesDirectory);
164 
165 	return B_OK;
166 }
167 
168 
169 status_t
170 TabletInputDevice::Start(const char *name, void *cookie)
171 {
172 	tablet_device *device = (tablet_device *)cookie;
173 
174 	LOG("%s(%s)\n", __PRETTY_FUNCTION__, name);
175 
176 	device->fd = open(device->path, O_RDWR);
177 	if (device->fd<0)
178 		return B_ERROR;
179 
180 	InitFromSettings(device);
181 
182 	char threadName[B_OS_NAME_LENGTH];
183 	snprintf(threadName, B_OS_NAME_LENGTH, "%s watcher", name);
184 
185 	device->active = true;
186 	device->device_watcher = spawn_thread(DeviceWatcher, threadName,
187 		kTabletThreadPriority, device);
188 
189 	resume_thread(device->device_watcher);
190 
191 	return B_OK;
192 }
193 
194 
195 status_t
196 TabletInputDevice::Stop(const char *name, void *cookie)
197 {
198 	tablet_device *device = (tablet_device *)cookie;
199 
200 	LOG("%s(%s)\n", __PRETTY_FUNCTION__, name);
201 
202 	close(device->fd);
203 
204 	device->active = false;
205 	if (device->device_watcher >= 0) {
206 		suspend_thread(device->device_watcher);
207 		resume_thread(device->device_watcher);
208 		status_t dummy;
209 		wait_for_thread(device->device_watcher, &dummy);
210 	}
211 
212 	return B_OK;
213 }
214 
215 
216 status_t
217 TabletInputDevice::Control(const char *name, void *cookie,
218 						  uint32 command, BMessage *message)
219 {
220 	LOG("%s(%s, code: %lu)\n", __PRETTY_FUNCTION__, name, command);
221 
222 	if (command == B_NODE_MONITOR)
223 		HandleMonitor(message);
224 	else if (command >= B_MOUSE_TYPE_CHANGED
225 		&& command <= B_MOUSE_ACCELERATION_CHANGED) {
226 		InitFromSettings(cookie, command);
227 	}
228 	return B_OK;
229 }
230 
231 
232 // TODO: Test this. USB doesn't work on my machine
233 status_t
234 TabletInputDevice::HandleMonitor(BMessage *message)
235 {
236 	CALLED();
237 	int32 opcode = 0;
238 	status_t status;
239 	if ((status = message->FindInt32("opcode", &opcode)) < B_OK)
240 	        return status;
241 
242 	if ((opcode != B_ENTRY_CREATED)
243 	        && (opcode != B_ENTRY_REMOVED))
244 	        return B_OK;
245 
246 
247 	BEntry entry;
248 	BPath path;
249 	dev_t device;
250 	ino_t directory;
251 	const char *name = NULL;
252 
253 	message->FindInt32("device", &device);
254 	message->FindInt64("directory", &directory);
255 	message->FindString("name", &name);
256 
257 	entry_ref ref(device, directory, name);
258 
259 	if ((status = entry.SetTo(&ref)) != B_OK)
260 	        return status;
261 	if ((status = entry.GetPath(&path)) != B_OK)
262 	        return status;
263 	if ((status = path.InitCheck()) != B_OK)
264 	        return status;
265 
266 	if (opcode == B_ENTRY_CREATED)
267 	        AddDevice(path.Path());
268 	else
269 	        RemoveDevice(path.Path());
270 
271 	return status;
272 }
273 
274 
275 status_t
276 TabletInputDevice::AddDevice(const char *path)
277 {
278 	CALLED();
279 
280 	tablet_device *device = new tablet_device(path);
281 	if (!device) {
282 		LOG("No memory\n");
283 		return B_NO_MEMORY;
284 	}
285 
286 	input_device_ref *devices[2];
287 	devices[0] = &device->device_ref;
288 	devices[1] = NULL;
289 
290 	fDevices.AddItem(device);
291 
292 	return RegisterDevices(devices);
293 }
294 
295 
296 status_t
297 TabletInputDevice::RemoveDevice(const char *path)
298 {
299 	CALLED();
300 	int32 i = 0;
301 	tablet_device *device = NULL;
302 	while ((device = (tablet_device *)fDevices.ItemAt(i)) != NULL) {
303 		if (!strcmp(device->path, path)) {
304 			fDevices.RemoveItem(device);
305 			delete device;
306 			return B_OK;
307 		}
308 	}
309 
310 	return B_ENTRY_NOT_FOUND;
311 }
312 
313 
314 int32
315 TabletInputDevice::DeviceWatcher(void *arg)
316 {
317 	tablet_device *dev = (tablet_device *)arg;
318 
319 	tablet_movement movements;
320 	tablet_movement old_movements;
321 	uint32 buttons_state = 0;
322 	BMessage *message;
323 	while (dev->active) {
324 		memset(&movements, 0, sizeof(movements));
325 		if (ioctl(dev->fd, MS_READ, &movements) != B_OK) {
326 			snooze(10000); // this is a realtime thread, and something is wrong...
327 			continue;
328 		}
329 
330 		uint32 buttons = buttons_state ^ movements.buttons;
331 
332 		LOG("%s: buttons: 0x%lx, x: %f, y: %f, clicks:%ld, wheel_x:%ld, wheel_y:%ld\n", dev->device_ref.name, movements.buttons,
333 			movements.xpos, movements.ypos, movements.clicks, movements.wheel_xdelta, movements.wheel_ydelta);
334 
335 		movements.xpos /= 10206.0;
336 		movements.ypos /= 7422.0;
337 		float x = movements.xpos;
338 		float y = movements.ypos;
339 
340 		LOG("%s: x: %f, y: %f, \n", dev->device_ref.name, x, y);
341 
342 		if (buttons != 0) {
343 			message = new BMessage(B_MOUSE_UP);
344 			if ((buttons & movements.buttons) > 0) {
345 				message->what = B_MOUSE_DOWN;
346 				message->AddInt32("clicks", movements.clicks);
347 				LOG("B_MOUSE_DOWN\n");
348 			} else {
349 				LOG("B_MOUSE_UP\n");
350 			}
351 
352 			message->AddInt64("when", movements.timestamp);
353 			message->AddInt32("buttons", movements.buttons);
354 			message->AddFloat("x", movements.xpos);
355 			message->AddFloat("y", movements.ypos);
356 			sSingletonTabletDevice->EnqueueMessage(message);
357 			buttons_state = movements.buttons;
358 		}
359 
360 		if (movements.xpos != 0.0 || movements.ypos != 0.0) {
361 			message = new BMessage(B_MOUSE_MOVED);
362 			if (message) {
363 				message->AddInt64("when", movements.timestamp);
364 				message->AddInt32("buttons", movements.buttons);
365 				message->AddFloat("x", x);
366 				message->AddFloat("y", y);
367 				message->AddFloat("be:tablet_x", movements.xpos);
368 				message->AddFloat("be:tablet_y", movements.ypos);
369 				message->AddFloat("be:tablet_pressure", movements.pressure);
370 				message->AddInt32("be:tablet_eraser", movements.eraser);
371 				if (movements.tilt_x != 0.0 || movements.tilt_y != 0.0) {
372 					message->AddFloat("be:tablet_tilt_x", movements.tilt_x);
373 					message->AddFloat("be:tablet_tilt_y", movements.tilt_y);
374 				}
375 
376 				sSingletonTabletDevice->EnqueueMessage(message);
377 			}
378 		}
379 
380 		if ((movements.wheel_ydelta != 0) || (movements.wheel_xdelta != 0)) {
381 			message = new BMessage(B_MOUSE_WHEEL_CHANGED);
382 			if (message) {
383 				message->AddInt64("when", movements.timestamp);
384 				message->AddFloat("be:wheel_delta_x", movements.wheel_xdelta);
385 				message->AddFloat("be:wheel_delta_y", movements.wheel_ydelta);
386 
387 				sSingletonTabletDevice->EnqueueMessage(message);
388 			}
389 		}
390 
391 		old_movements = movements;
392 
393 	}
394 
395 	return 0;
396 }
397 
398 
399 // tablet_device
400 tablet_device::tablet_device(const char *driver_path)
401 {
402 	fd = -1;
403 	device_watcher = -1;
404 	active = false;
405 	strcpy(path, driver_path);
406 	device_ref.name = get_short_name(path);
407 	device_ref.type = B_POINTING_DEVICE;
408 	device_ref.cookie = this;
409 };
410 
411 
412 tablet_device::~tablet_device()
413 {
414 	free(device_ref.name);
415 }
416 
417 
418 void
419 TabletInputDevice::RecursiveScan(const char *directory)
420 {
421         CALLED();
422 	BEntry entry;
423 	BDirectory dir(directory);
424 	while (dir.GetNextEntry(&entry) == B_OK) {
425 		BPath path;
426 		entry.GetPath(&path);
427 
428 		if (entry.IsDirectory())
429 			RecursiveScan(path.Path());
430 		else
431 			AddDevice(path.Path());
432 	}
433 }
434 
435 
436 static char *
437 get_short_name(const char *longName)
438 {
439 	BString string(longName);
440 	BString name;
441 
442 	int32 slash = string.FindLast("/");
443 	string.CopyInto(name, slash + 1, string.Length() - slash);
444 	int32 index = atoi(name.String()) + 1;
445 
446 	int32 previousSlash = string.FindLast("/", slash);
447 	string.CopyInto(name, previousSlash + 1, slash - previousSlash - 1);
448 	name << " Tablet " << index;
449 
450 	return strdup(name.String());
451 }
452