1/*****************************************************************************/
2// Mouse input server device addon
3// Written by Stefano Ceccherini
4// Adapted for serial mice by Oscar Lesta
5//
6// MouseInputDevice.cpp
7//
8// Copyright (c) 2004 Haiku Project
9//
10// Permission is hereby granted, free of charge, to any person obtaining a
11// copy of this software and associated documentation files (the "Software"),
12// to deal in the Software without restriction, including without limitation
13// the rights to use, copy, modify, merge, publish, distribute, sublicense,
14// and/or sell copies of the Software, and to permit persons to whom the
15// Software is furnished to do so, subject to the following conditions:
16//
17// The above copyright notice and this permission notice shall be included
18// in all copies or substantial portions of the Software.
19//
20// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26// DEALINGS IN THE SOFTWARE.
27/*****************************************************************************/
28
29#include "MouseInputDevice.h"
30#include "kb_mouse_settings.h"
31#include "keyboard_mouse_driver.h"
32
33#include <stdlib.h>
34#include <unistd.h>
35
36#include <Debug.h>
37#include <Directory.h>
38#include <Entry.h>
39#include <NodeMonitor.h>
40#include <Path.h>
41#include <String.h>
42
43#include "SerialMouse.h"
44
45//------------------------------------------------------------------------------
46
47#if DEBUG
48        inline void LOG(const char *fmt, ...) { char buf[1024]; va_list ap; va_start(ap, fmt); vsprintf(buf, fmt, ap); va_end(ap); \
49                fputs(buf, MouseInputDevice::sLogFile); fflush(MouseInputDevice::sLogFile); }
50        #define LOG_ERR(text...) LOG(text)
51FILE *MouseInputDevice::sLogFile = NULL;
52#else
53        #define LOG(text...)
54        #define LOG_ERR(text...) fprintf(stderr, text)
55#endif
56
57#define CALLED() LOG("%s\n", __PRETTY_FUNCTION__)
58
59static MouseInputDevice *sSingletonMouseDevice = NULL;
60
61
62const static uint32 kMouseThreadPriority = B_FIRST_REAL_TIME_PRIORITY + 4;
63
64//------------------------------------------------------------------------------
65
66struct mouse_device {
67	mouse_device();
68	~mouse_device();
69
70	status_t init_check();	// > 0 if a mouse was detected.
71
72	input_device_ref device_ref;
73	SerialMouse* sm;
74	thread_id device_watcher;
75	mouse_settings settings;
76	bool active;
77};
78
79
80extern "C"
81BInputServerDevice* instantiate_input_device()
82{
83	return new MouseInputDevice();
84}
85
86//------------------------------------------------------------------------------
87// #pragma mark -
88
89MouseInputDevice::MouseInputDevice()
90{
91	ASSERT(sSingletonMouseDevice == NULL);
92	sSingletonMouseDevice = this;
93
94#if DEBUG
95	sLogFile = fopen("/var/log/serial_mouse.log", "w");
96#endif
97	CALLED();
98}
99
100MouseInputDevice::~MouseInputDevice()
101{
102	CALLED();
103
104	for (int32 i = 0; i < fDevices.CountItems(); i++)
105		delete (mouse_device*) fDevices.ItemAt(i);
106
107#if DEBUG
108	fclose(sLogFile);
109#endif
110}
111
112// SerialMouse does not know anything about mouse_settings, I choose to let
113// mouse_device hold that instead (bercause mice type, button mapping,
114// speed/accel, etc., are all handled here, not in SerialMouse).
115
116status_t
117MouseInputDevice::InitFromSettings(void* cookie, uint32 opcode)
118{
119	CALLED();
120	mouse_device* device = (mouse_device*) cookie;
121
122	// retrieve current values.
123	// TODO: shouldn't we use sane values if we get errors here?
124
125	if (get_mouse_map(&device->settings.map) != B_OK)
126		LOG_ERR("error when get_mouse_map\n");
127
128	if (get_click_speed(&device->settings.click_speed) != B_OK)
129		LOG_ERR("error when get_click_speed\n");
130
131	if (get_mouse_speed(&device->settings.accel.speed) != B_OK)
132		LOG_ERR("error when get_mouse_speed\n");
133	else
134	{
135		if (get_mouse_acceleration(&device->settings.accel.accel_factor) != B_OK)
136			LOG_ERR("error when get_mouse_acceleration\n");
137	}
138
139	if (get_mouse_type(&device->settings.type) != B_OK)
140		LOG_ERR("error when get_mouse_type\n");
141
142	return B_OK;
143}
144
145status_t
146MouseInputDevice::InitCheck()
147{
148	CALLED();
149
150	// TODO: We should iterate here to support more than just one mouse.
151	//       (I've tried, but kept entering and endless loop or crashing
152	//        the input_server :-( ).
153
154	mouse_device* device = new mouse_device();
155	if (!device)
156	{
157		LOG("No memory\n");
158		return B_NO_MEMORY;
159	}
160
161	if (device->init_check() <= B_OK)
162	{
163		LOG("The mouse was eaten by a rabid cat.\n");
164		delete device;
165		return B_ERROR;
166	}
167
168	input_device_ref* devices[2];
169	devices[0] = &device->device_ref;
170	devices[1] = NULL;
171
172	fDevices.AddItem(device);
173
174	return RegisterDevices(devices);
175}
176
177
178status_t
179MouseInputDevice::Start(const char* name, void* cookie)
180{
181	mouse_device* device = (mouse_device*) cookie;
182
183	LOG("%s(%s)\n", __PRETTY_FUNCTION__, name);
184
185	InitFromSettings(device);
186
187	char threadName[B_OS_NAME_LENGTH];
188	// "Microsoft watcher" sounded even more akward than this...
189	snprintf(threadName, B_OS_NAME_LENGTH, "%s Mouse", name);
190
191	device->active = true;
192	device->device_watcher = spawn_thread(DeviceWatcher, threadName,
193										kMouseThreadPriority, device);
194
195	resume_thread(device->device_watcher);
196
197	return B_OK;
198}
199
200
201status_t
202MouseInputDevice::Stop(const char* name, void* cookie)
203{
204	mouse_device* device = (mouse_device*) cookie;
205
206	LOG("%s(%s)\n", __PRETTY_FUNCTION__, name);
207
208	device->active = false;
209
210	if (device->device_watcher >= 0)
211	{
212		suspend_thread(device->device_watcher);
213		resume_thread(device->device_watcher);
214		status_t dummy;
215		wait_for_thread(device->device_watcher, &dummy);
216	}
217
218	return B_OK;
219}
220
221
222status_t
223MouseInputDevice::Control(const char* name, void* cookie,
224						  uint32 command, BMessage* message)
225{
226	LOG("%s(%s, code: %lu)\n", __PRETTY_FUNCTION__, name, command);
227
228	if (command >= B_MOUSE_TYPE_CHANGED &&
229		command <= B_MOUSE_ACCELERATION_CHANGED)
230	{
231		InitFromSettings(cookie, command);
232	}
233
234	return B_OK;
235}
236
237
238int32
239MouseInputDevice::DeviceWatcher(void* arg)
240{
241	mouse_device* dev = (mouse_device*) arg;
242
243	mouse_movement movements;
244	uint32 buttons_state = 0;
245	uint8  clicks_count = 0;
246	bigtime_t last_click_time = 0;
247	BMessage* message;
248
249	CALLED();
250
251	while (dev->active)
252	{
253		memset(&movements, 0, sizeof(movements));
254		if (dev->sm->GetMouseEvent(&movements) != B_OK) {
255			snooze(10000); // this is a realtime thread, and something is wrong...
256			continue;
257		}
258/*
259		LOG("%s: buttons: 0x%lx, x: %ld, y: %ld, clicks:%ld, wheel_x:%ld, \
260			wheel_y:%ld\n", dev->device_ref.name, movements.buttons,
261			movements.xdelta, movements.ydelta, movements.clicks,
262			movements.wheel_xdelta, movements.wheel_ydelta);
263*/
264		// TODO: Apply buttons mapping here.
265		uint32 buttons = buttons_state ^ movements.buttons;
266
267	  	if (movements.buttons) {
268
269  			if ((movements.timestamp - last_click_time) <= dev->settings.click_speed)
270				clicks_count = (clicks_count % 3) + 1;
271  			else
272  				clicks_count = 1;
273
274			last_click_time = movements.timestamp;
275  		}
276
277		if (buttons != 0) {
278			message = new BMessage(B_MOUSE_UP);
279
280			message->AddInt64("when", movements.timestamp);
281			message->AddInt32("buttons", movements.buttons);
282
283			if ((buttons & movements.buttons) > 0) {
284				message->what = B_MOUSE_DOWN;
285				message->AddInt32("clicks", clicks_count);
286				LOG("B_MOUSE_DOWN\n");
287			} else {
288				LOG("B_MOUSE_UP\n");
289			}
290
291			message->AddInt32("x", movements.xdelta);
292			message->AddInt32("y", movements.ydelta);
293
294			sSingletonMouseDevice->EnqueueMessage(message);
295			buttons_state = movements.buttons;
296		}
297
298		if (movements.xdelta != 0 || movements.ydelta != 0) {
299			int32 xdelta = movements.xdelta;
300			int32 ydelta = movements.ydelta;
301
302			// TODO: properly scale these values.
303			// (s >> 14, a >> 8) or (s >> 15, a >> 11) feels more or less OK
304			// with the default values: yields to speed = 2; accel_factor = 32
305			// Maybe we should use floats and then round to nearest integer?
306			uint32 speed = (dev->settings.accel.speed >> 15);
307			uint32 accel_factor = (dev->settings.accel.accel_factor >> 11);
308
309//			LOG("accel.enabled? = %s\n", (dev->settings.accel.enabled) ? "Yes" : "No");
310//			LOG("speed = %ld; accel_factor = %ld\n", speed, accel_factor);
311
312			if (speed && accel_factor) {
313				xdelta *= speed;
314				ydelta *= speed;
315
316				// preserve the sign.
317				bool xneg = (xdelta < 0);
318				bool yneg = (ydelta < 0);
319
320				if (movements.xdelta != 0) {
321					xdelta = (xdelta * xdelta) / accel_factor;
322					(xdelta) ? : (xdelta = 1);
323					if (xneg) xdelta *= -1;
324	     		}
325
326				if (movements.ydelta != 0) {
327					ydelta = (ydelta * ydelta) / accel_factor;
328					(ydelta) ? : (ydelta = 1);
329					if (yneg) ydelta *= -1;
330	     		}
331	    	}
332
333//			LOG("%s: x: %ld, y: %ld\n", dev->device_ref.name, xdelta, ydelta);
334
335			message = new BMessage(B_MOUSE_MOVED);
336			if (message) {
337				message->AddInt64("when", movements.timestamp);
338				message->AddInt32("buttons", movements.buttons);
339				message->AddInt32("x", xdelta);
340				message->AddInt32("y", ydelta);
341
342				sSingletonMouseDevice->EnqueueMessage(message);
343			}
344		}
345
346		if ((movements.wheel_ydelta != 0) || (movements.wheel_xdelta != 0)) {
347			message = new BMessage(B_MOUSE_WHEEL_CHANGED);
348			if (message) {
349				message->AddInt64("when", movements.timestamp);
350				message->AddFloat("be:wheel_delta_x", movements.wheel_xdelta);
351				message->AddFloat("be:wheel_delta_y", movements.wheel_ydelta);
352
353				sSingletonMouseDevice->EnqueueMessage(message);
354			}
355		}
356	}
357
358	return B_OK;
359}
360
361//==============================================================================
362// #pragma mark -
363
364// mouse_device
365mouse_device::mouse_device()
366{
367	device_watcher = -1;
368	active = false;
369	sm = new SerialMouse();
370	device_ref.type = B_POINTING_DEVICE;
371	device_ref.cookie = this;
372};
373
374
375status_t
376mouse_device::init_check()
377{
378	status_t result = sm->IsMousePresent();
379
380	if (result > 0)	// Positive values indicate a mouse present.
381		device_ref.name = (char *)sm->MouseDescription();
382
383	return result;
384}
385
386
387mouse_device::~mouse_device()
388{
389	delete sm;
390}
391