1/*
2 * Copyright 2005-2008 Stephan A��mus <superstippi@gmx.de>. All rights reserved.
3 * Distributed under the terms of the MIT license.
4 */
5#include "MasterServerDevice.h"
6
7#include <fstream>
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11
12#include <Directory.h>
13#include <Entry.h>
14#include <InterfaceDefs.h>
15#include <Message.h>
16#include <NodeMonitor.h>
17#include <OS.h>
18#include <Path.h>
19#include <Screen.h>
20#include <View.h>
21#include <File.h>
22
23#include "PointingDevice.h"
24#include "PointingDeviceFactory.h"
25
26#define DEFAULT_CLICK_SPEED 250000
27
28static const char* kWatchFolder			= "input/wacom/usb";
29static const char* kDeviceFolder		= "/dev/input/wacom/usb";
30
31//static const char* kPS2MouseThreadName	= "PS/2 Mouse";
32
33// instantiate_input_device
34//
35// this is where it all starts make sure this function is exported!
36BInputServerDevice*
37instantiate_input_device()
38{
39	return (new MasterServerDevice());
40}
41
42// constructor
43MasterServerDevice::MasterServerDevice()
44	: BInputServerDevice(),
45	  fDevices(1),
46	  fActive(false),
47	  fDblClickSpeed(DEFAULT_CLICK_SPEED),
48	  fPS2DisablerThread(B_ERROR),
49	  fDeviceLock("device list lock")
50{
51	get_mouse_speed(&fSpeed);
52	get_mouse_acceleration(&fAcceleration);
53	get_click_speed(&fDblClickSpeed);
54	_CalculateAccelerationTable();
55
56	if (_LockDevices()) {
57		// do an initial scan of the devfs folder, after that, we should receive
58		// node monitor messages for hotplugging support
59		_SearchDevices();
60
61		fActive = true;
62
63		for (int32 i = 0; PointingDevice* device = (PointingDevice*)fDevices.ItemAt(i); i++) {
64			device->Start();
65		}
66		_UnlockDevices();
67	}
68
69	StartMonitoringDevice(kWatchFolder);
70}
71
72// destructor
73MasterServerDevice::~MasterServerDevice()
74{
75	// cleanup
76	_StopAll();
77	if (_LockDevices()) {
78		while (PointingDevice* device = (PointingDevice*)fDevices.RemoveItem((int32)0))
79			delete device;
80		_UnlockDevices();
81	}
82}
83
84// InitCheck
85status_t
86MasterServerDevice::InitCheck()
87{
88	return BInputServerDevice::InitCheck();
89}
90
91// SystemShuttingDown
92status_t
93MasterServerDevice::SystemShuttingDown()
94{
95	// the devices should be stopped anyways,
96	// but this is just defensive programming.
97	// the pointing devices will have spawned polling threads,
98	// we make sure that we block until every thread has bailed out.
99
100	_StopAll();
101
102	PRINT(("---------------------------------\n\n"));
103
104	return (BInputServerDevice::SystemShuttingDown());
105}
106
107// Start
108//
109// start generating events
110status_t
111MasterServerDevice::Start(const char* device, void* cookie)
112{
113	// TODO: make this configurable
114//	_StartPS2DisablerThread();
115
116	return B_OK;
117}
118
119// Stop
120status_t
121MasterServerDevice::Stop(const char* device, void* cookie)
122{
123	// stop generating events
124	fActive = false;
125
126	_StopAll();
127
128//	_StopPS2DisablerThread();
129
130	return StopMonitoringDevice(kWatchFolder);
131}
132
133// Control
134status_t
135MasterServerDevice::Control(const char* device, void* cookie, uint32 code, BMessage* message)
136{
137	// respond to changes in the system
138	switch (code) {
139		case B_MOUSE_SPEED_CHANGED:
140			get_mouse_speed(device, &fSpeed);
141			_CalculateAccelerationTable();
142			break;
143		case B_CLICK_SPEED_CHANGED:
144			get_click_speed(&fDblClickSpeed);
145			break;
146		case B_MOUSE_ACCELERATION_CHANGED:
147			get_mouse_acceleration(device, &fAcceleration);
148			_CalculateAccelerationTable();
149			break;
150		case B_NODE_MONITOR:
151			_HandleNodeMonitor(message);
152			break;
153		default:
154			BInputServerDevice::Control(device, cookie, code, message);
155			break;
156	}
157	return B_OK;
158}
159
160// #pragma mark -
161
162// _SearchDevices
163void
164MasterServerDevice::_SearchDevices()
165{
166	if (_LockDevices()) {
167		// construct a PointingDevice for every devfs entry we find
168		BDirectory dir(kDeviceFolder);
169		if (dir.InitCheck() >= B_OK) {
170			// traverse the contents of the folder to see if the
171			// entry of that device still exists
172			entry_ref ref;
173			while (dir.GetNextRef(&ref) >= B_OK) {
174				PRINT(("examining devfs entry '%s'\n", ref.name));
175				// don't add the control device
176				if (strcmp(ref.name, "control") != 0) {
177					BPath path(&ref);
178					if (path.InitCheck() >= B_OK) {
179						// add the device
180						_AddDevice(path.Path());
181					}
182				}
183			}
184		} else
185			PRINT(("folder '%s' not found\n", kDeviceFolder));
186
187		PRINT(("done examing devfs\n"));
188		_UnlockDevices();
189	}
190}
191
192// _StopAll
193void
194MasterServerDevice::_StopAll()
195{
196	if (_LockDevices()) {
197		for (int32 i = 0; PointingDevice* device
198				= (PointingDevice*)fDevices.ItemAt(i); i++)
199			device->Stop();
200		_UnlockDevices();
201	}
202}
203
204// _AddDevice
205void
206MasterServerDevice::_AddDevice(const char* path)
207{
208	if (_LockDevices()) {
209		// get the device from the factory
210		PointingDevice* device = PointingDeviceFactory::DeviceFor(this, path);
211		// add it to our list
212		if (device && device->InitCheck() >= B_OK
213			&& fDevices.AddItem((void*)device)) {
214			PRINT(("pointing device added (%s)\n", path));
215			// start device polling only if we're started
216			if (fActive)
217				device->Start();
218			input_device_ref device = { (char *)"Wacom Tablet",
219				B_POINTING_DEVICE, (void*)this };
220			input_device_ref* deviceList[2] = { &device, NULL };
221			RegisterDevices(deviceList);
222		} else {
223
224			PRINT(("pointing device not added (%s)\n", path));
225			if (device) {
226				PRINT(("  vendor: %0*x, product: %0*x\n", 4, device->VendorID(),
227					4, device->ProductID()));
228			}
229
230			delete device;
231		}
232		_UnlockDevices();
233	}
234}
235
236// _HandleNodeMonitor
237void
238MasterServerDevice::_HandleNodeMonitor(BMessage* message)
239{
240	int32 opcode;
241	if (message->FindInt32("opcode", &opcode) < B_OK)
242		return;
243
244	switch (opcode) {
245		case B_ENTRY_CREATED:
246			// extract info to create an entry_ref structure
247			const char* name;
248			ino_t directory;
249			dev_t device;
250			if (message->FindString("name", &name) >= B_OK
251				&& strcmp(name, "control") != 0
252				&& message->FindInt64("directory", (int64*)&directory) >= B_OK
253				&& message->FindInt32("device", (int32*)&device) >= B_OK) {
254				// make a path from that info
255				entry_ref ref(device, directory, name);
256				BPath path(&ref);
257				if (path.InitCheck() >= B_OK) {
258					// add the device
259					_AddDevice(path.Path());
260				}
261			}
262			break;
263		case B_ENTRY_REMOVED: {
264			// I cannot figure out how to see if this is actually
265			// the directory that we're node monitoring, and even if it is,
266			// we would have to look at the directories contents to
267			// see which PointingDevice we might want to remove
268			BDirectory dir(kDeviceFolder);
269			if (dir.InitCheck() >= B_OK) {
270				// for each device in our list, see if the corresponding
271				// entry in the device folder still exists
272				for (int32 i = 0; PointingDevice* pointingDevice
273						= (PointingDevice*)fDevices.ItemAt(i); i++) {
274					// traverse the contents of the folder
275					// if the entry for this device is there,
276					// we can abort the search
277					entry_ref ref;
278					dir.Rewind();
279					bool found = false;
280					while (dir.GetNextRef(&ref) >= B_OK) {
281						BPath path(&ref);
282						if (strcmp(pointingDevice->DevicePath(),
283								path.Path()) == 0) {
284							found = true;
285							break;
286						}
287					}
288					// remove the device if the devfs entry was not found
289					if (!found) {
290
291						PRINT(("removing device '%s'\n", pointingDevice->DevicePath()));
292
293						if (_LockDevices()) {
294							if (fDevices.RemoveItem((void*)pointingDevice)) {
295
296								delete pointingDevice;
297								i--;
298							}
299							_UnlockDevices();
300						}
301					}
302				}
303			}
304			break;
305		}
306	}
307}
308
309// _CalculateAccelerationTable
310//
311// calculates the acceleration table. This is recalculated anytime we find out that
312// the current acceleration or speed has changed.
313void
314MasterServerDevice::_CalculateAccelerationTable()
315{
316	// This seems to work alright.
317	for (int x = 1; x <= 128; x++){
318		fAccelerationTable[x] = (x / (128 * (1 - (((float)fSpeed + 8192) / 262144))))
319			* (x / (128 * (1 - (((float)fSpeed + 8192) / 262144))))
320			+ ((((float)fAcceleration + 4000) / 262144)
321			* (x / (128 * (1 - (((float)fSpeed + 8192) / 262144)))));
322	}
323
324	// sets the bottom of the table to be the inverse of the first half.
325	// position 0 -> 128 positive movement, 255->129 negative movement
326	for (int x = 255; x > 128; x--){
327		fAccelerationTable[x] = -(fAccelerationTable[(255 - x)]);
328	}
329
330	// Location 0 will be 0.000, which is unacceptable, otherwise, single step moves are lost
331	// To work around this, we'll make it 1/2 of the smallest increment.
332	fAccelerationTable[0] = fAccelerationTable[1] / 2;
333	fAccelerationTable[255] = -fAccelerationTable[0];
334}
335
336// _ps2_disabler
337//
338// find the PS/2 device thread and suspend its execution
339// TODO: make this configurable
340// In case you're wondering... this behaviour is useful for notebook
341// users who are annoyed by accidentally hitting their touchpad while
342// typing. "Turning it off entirely" by suspending the PS/2 thread is
343// just a compfort thing, but should of course be made configurable...
344//int32
345//MasterServerDevice::_ps2_disabler(void* cookie)
346//{
347//	MasterServerDevice* master = (MasterServerDevice*)cookie;
348//
349//	thread_id haltedThread = B_ERROR;
350//
351//	while (master->fActive) {
352//		// search for at least one running device
353//		bool suspendPS2 = false;
354//		if (master->_LockDevices()) {
355//			for (int32 i = 0; PointingDevice* device = (PointingDevice*)master->fDevices.ItemAt(i); i++) {
356//				if (device->DisablePS2()) {
357//					suspendPS2 = true;
358//					break;
359//				}
360//			}
361//			master->_UnlockDevices();
362//		}
363//
364//		if (suspendPS2){
365//			// find and suspend PS/2 thread (if not already done)
366//			if (haltedThread < B_OK) {
367//				haltedThread = find_thread(kPS2MouseThreadName);
368//				if (haltedThread >= B_OK) {
369//					suspend_thread(haltedThread);
370//				}
371//			}
372//		} else {
373//			// reenable PS/2 thread
374//			if (haltedThread >= B_OK) {
375//				resume_thread(haltedThread);
376//				haltedThread = B_ERROR;
377//			}
378//		}
379//
380//		// sleep a little while
381//		snooze(2000000);
382//	}
383//
384//	// reenable PS/2 thread in any case before we die
385//	if (haltedThread >= B_OK) {
386//		resume_thread(haltedThread);
387//	}
388//
389//	return B_OK;
390//}
391//
392//// _StartPS2DisablerThread
393//void
394//MasterServerDevice::_StartPS2DisablerThread()
395//{
396//	if (fPS2DisablerThread < B_OK) {
397//		fPS2DisablerThread = spawn_thread(_ps2_disabler, "PS/2 Mouse disabler", B_LOW_PRIORITY, this);
398//		if (fPS2DisablerThread >= B_OK)
399//			resume_thread(fPS2DisablerThread);
400//	}
401//}
402//
403//// _StopPS2DisablerThread
404//void
405//MasterServerDevice::_StopPS2DisablerThread()
406//{
407//	if (fPS2DisablerThread >= B_OK) {
408//		status_t err;
409//		wait_for_thread(fPS2DisablerThread, &err);
410//	}
411//	fPS2DisablerThread = B_ERROR;
412//}
413
414// _LockDevices
415bool
416MasterServerDevice::_LockDevices()
417{
418	return fDeviceLock.Lock();
419}
420
421// _UnlockDevices
422void
423MasterServerDevice::_UnlockDevices()
424{
425	fDeviceLock.Unlock();
426}
427
428