⛏️ index : haiku.git

/*
 * Copyright 2005-2008 Stephan Aßmus <superstippi@gmx.de>. All rights reserved.
 * Distributed under the terms of the MIT license.
 */
#include "MasterServerDevice.h"

#include <fstream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <Directory.h>
#include <Entry.h>
#include <InterfaceDefs.h>
#include <Message.h>
#include <NodeMonitor.h>
#include <OS.h>
#include <Path.h>
#include <Screen.h>
#include <View.h>
#include <File.h>

#include "PointingDevice.h"
#include "PointingDeviceFactory.h"

#define DEFAULT_CLICK_SPEED 250000

static const char* kWatchFolder			= "input/wacom/usb";
static const char* kDeviceFolder		= "/dev/input/wacom/usb";
static const char* kDeviceName			= "Wacom Tablet";

//static const char* kPS2MouseThreadName	= "PS/2 Mouse";

// instantiate_input_device
//
// this is where it all starts make sure this function is exported!
BInputServerDevice*
instantiate_input_device()
{
	return (new MasterServerDevice());
}

// constructor
MasterServerDevice::MasterServerDevice()
	: BInputServerDevice(),
	  fDevices(1),
	  fActive(false),
	  fDblClickSpeed(DEFAULT_CLICK_SPEED),
	  fPS2DisablerThread(B_ERROR),
	  fDeviceLock("device list lock")
{
	get_mouse_speed(kDeviceName, &fSpeed);
	get_mouse_acceleration(kDeviceName, &fAcceleration);
	get_click_speed(kDeviceName, &fDblClickSpeed);
	_CalculateAccelerationTable();

	if (_LockDevices()) {
		// do an initial scan of the devfs folder, after that, we should receive
		// node monitor messages for hotplugging support
		_SearchDevices();
	
		fActive = true;
	
		for (int32 i = 0; PointingDevice* device = (PointingDevice*)fDevices.ItemAt(i); i++) {
			device->Start();
		}
		_UnlockDevices();
	}

	StartMonitoringDevice(kWatchFolder);
}

// destructor
MasterServerDevice::~MasterServerDevice()
{
	// cleanup
	_StopAll();
	if (_LockDevices()) {
		while (PointingDevice* device = (PointingDevice*)fDevices.RemoveItem((int32)0))
			delete device;
		_UnlockDevices();
	}
}

// InitCheck
status_t
MasterServerDevice::InitCheck()
{
	return BInputServerDevice::InitCheck();
}

// SystemShuttingDown
status_t
MasterServerDevice::SystemShuttingDown()
{
	// the devices should be stopped anyways,
	// but this is just defensive programming.
	// the pointing devices will have spawned polling threads,
	// we make sure that we block until every thread has bailed out.

	_StopAll();

	PRINT(("---------------------------------\n\n"));

	return (BInputServerDevice::SystemShuttingDown());
}

// Start
//
// start generating events
status_t
MasterServerDevice::Start(const char* device, void* cookie)
{
	// TODO: make this configurable
//	_StartPS2DisablerThread();

	return B_OK;
}

// Stop
status_t
MasterServerDevice::Stop(const char* device, void* cookie)
{
	// stop generating events
	fActive = false;

	_StopAll();

//	_StopPS2DisablerThread();

	return StopMonitoringDevice(kWatchFolder);
}

// Control
status_t
MasterServerDevice::Control(const char* device, void* cookie, uint32 code, BMessage* message)
{
	// respond to changes in the system
	switch (code) {
		case B_MOUSE_SPEED_CHANGED:
			get_mouse_speed(device, &fSpeed);
			_CalculateAccelerationTable();
			break;
		case B_CLICK_SPEED_CHANGED:
			get_click_speed(device, &fDblClickSpeed);
			break;
		case B_MOUSE_ACCELERATION_CHANGED:
			get_mouse_acceleration(device, &fAcceleration);
			_CalculateAccelerationTable();
			break;
		case B_NODE_MONITOR:
			_HandleNodeMonitor(message);
			break;
		default:
			BInputServerDevice::Control(device, cookie, code, message);
			break;
	}
	return B_OK;
}

// #pragma mark -

// _SearchDevices
void
MasterServerDevice::_SearchDevices()
{
	if (_LockDevices()) {
		// construct a PointingDevice for every devfs entry we find
		BDirectory dir(kDeviceFolder);
		if (dir.InitCheck() >= B_OK) {
			// traverse the contents of the folder to see if the
			// entry of that device still exists
			entry_ref ref;
			while (dir.GetNextRef(&ref) >= B_OK) {
				PRINT(("examining devfs entry '%s'\n", ref.name));
				// don't add the control device
				if (strcmp(ref.name, "control") != 0) {
					BPath path(&ref);
					if (path.InitCheck() >= B_OK) {
						// add the device
						_AddDevice(path.Path());
					}
				}
			}
		} else
			PRINT(("folder '%s' not found\n", kDeviceFolder));
	
		PRINT(("done examing devfs\n"));
		_UnlockDevices();
	}
}

// _StopAll
void
MasterServerDevice::_StopAll()
{
	if (_LockDevices()) {
		for (int32 i = 0; PointingDevice* device
				= (PointingDevice*)fDevices.ItemAt(i); i++)
			device->Stop();
		_UnlockDevices();
	}
}

// _AddDevice
void
MasterServerDevice::_AddDevice(const char* path)
{
	if (_LockDevices()) {
		// get the device from the factory
		PointingDevice* device = PointingDeviceFactory::DeviceFor(this, path);
		// add it to our list
		if (device && device->InitCheck() >= B_OK
			&& fDevices.AddItem((void*)device)) {
			PRINT(("pointing device added (%s)\n", path));
			// start device polling only if we're started
			if (fActive)
				device->Start();
			input_device_ref device = { (char*)kDeviceName, B_POINTING_DEVICE, (void*)this };
			input_device_ref* deviceList[2] = { &device, NULL };
			RegisterDevices(deviceList);
		} else {
	
			PRINT(("pointing device not added (%s)\n", path));
			if (device) {
				PRINT(("  vendor: %0*x, product: %0*x\n", 4, device->VendorID(),
					4, device->ProductID()));
			}
	
			delete device;
		}
		_UnlockDevices();
	}
}

// _HandleNodeMonitor
void
MasterServerDevice::_HandleNodeMonitor(BMessage* message)
{
	int32 opcode;
	if (message->FindInt32("opcode", &opcode) < B_OK)
		return;

	switch (opcode) {
		case B_ENTRY_CREATED:
			// extract info to create an entry_ref structure
			const char* name;
			ino_t directory;
			dev_t device;
			if (message->FindString("name", &name) >= B_OK
				&& strcmp(name, "control") != 0
				&& message->FindInt64("directory", (int64*)&directory) >= B_OK
				&& message->FindInt32("device", (int32*)&device) >= B_OK) {
				// make a path from that info
				entry_ref ref(device, directory, name);
				BPath path(&ref);
				if (path.InitCheck() >= B_OK) {
					// add the device
					_AddDevice(path.Path());
				}
			}
			break;
		case B_ENTRY_REMOVED: {
			// I cannot figure out how to see if this is actually
			// the directory that we're node monitoring, and even if it is,
			// we would have to look at the directories contents to
			// see which PointingDevice we might want to remove
			BDirectory dir(kDeviceFolder);
			if (dir.InitCheck() >= B_OK) {
				// for each device in our list, see if the corresponding
				// entry in the device folder still exists
				for (int32 i = 0; PointingDevice* pointingDevice
						= (PointingDevice*)fDevices.ItemAt(i); i++) {
					// traverse the contents of the folder
					// if the entry for this device is there,
					// we can abort the search
					entry_ref ref;
					dir.Rewind();
					bool found = false;
					while (dir.GetNextRef(&ref) >= B_OK) {
						BPath path(&ref);
						if (strcmp(pointingDevice->DevicePath(),
								path.Path()) == 0) {
							found = true;
							break;
						}
					}
					// remove the device if the devfs entry was not found
					if (!found) {

						PRINT(("removing device '%s'\n", pointingDevice->DevicePath()));

						if (_LockDevices()) {
							if (fDevices.RemoveItem((void*)pointingDevice)) {
								
								delete pointingDevice;
								i--;
							}
							_UnlockDevices();
						}
					}
				}
			}
			break;
		}
	}
}

// _CalculateAccelerationTable
//
// calculates the acceleration table. This is recalculated anytime we find out that
// the current acceleration or speed has changed.
void
MasterServerDevice::_CalculateAccelerationTable()
{
	// This seems to work alright.
	for (int x = 1; x <= 128; x++){
		fAccelerationTable[x] = (x / (128 * (1 - (((float)fSpeed + 8192) / 262144))))
			* (x / (128 * (1 - (((float)fSpeed + 8192) / 262144))))
			+ ((((float)fAcceleration + 4000) / 262144)
			* (x / (128 * (1 - (((float)fSpeed + 8192) / 262144)))));
	}
	
	// sets the bottom of the table to be the inverse of the first half.
	// position 0 -> 128 positive movement, 255->129 negative movement
	for (int x = 255; x > 128; x--){
		fAccelerationTable[x] = -(fAccelerationTable[(255 - x)]);
	}
	
	// Location 0 will be 0.000, which is unacceptable, otherwise, single step moves are lost
	// To work around this, we'll make it 1/2 of the smallest increment.
	fAccelerationTable[0] = fAccelerationTable[1] / 2;
	fAccelerationTable[255] = -fAccelerationTable[0];
}

// _ps2_disabler
//
// find the PS/2 device thread and suspend its execution
// TODO: make this configurable
// In case you're wondering... this behaviour is useful for notebook
// users who are annoyed by accidentally hitting their touchpad while
// typing. "Turning it off entirely" by suspending the PS/2 thread is
// just a compfort thing, but should of course be made configurable...
//int32
//MasterServerDevice::_ps2_disabler(void* cookie)
//{
//	MasterServerDevice* master = (MasterServerDevice*)cookie;
//	
//	thread_id haltedThread = B_ERROR;
//
//	while (master->fActive) {
//		// search for at least one running device
//		bool suspendPS2 = false;
//		if (master->_LockDevices()) {
//			for (int32 i = 0; PointingDevice* device = (PointingDevice*)master->fDevices.ItemAt(i); i++) {
//				if (device->DisablePS2()) {
//					suspendPS2 = true;
//					break;
//				}
//			}
//			master->_UnlockDevices();
//		}
//
//		if (suspendPS2){
//			// find and suspend PS/2 thread (if not already done)
//			if (haltedThread < B_OK) {
//				haltedThread = find_thread(kPS2MouseThreadName);
//				if (haltedThread >= B_OK) {
//					suspend_thread(haltedThread);
//				}
//			}
//		} else {
//			// reenable PS/2 thread
//			if (haltedThread >= B_OK) {
//				resume_thread(haltedThread);
//				haltedThread = B_ERROR;
//			}
//		}
//
//		// sleep a little while
//		snooze(2000000);
//	}
//
//	// reenable PS/2 thread in any case before we die
//	if (haltedThread >= B_OK) {
//		resume_thread(haltedThread);
//	}
//
//	return B_OK;
//}
//
//// _StartPS2DisablerThread
//void
//MasterServerDevice::_StartPS2DisablerThread()
//{
//	if (fPS2DisablerThread < B_OK) {
//		fPS2DisablerThread = spawn_thread(_ps2_disabler, "PS/2 Mouse disabler", B_LOW_PRIORITY, this);
//		if (fPS2DisablerThread >= B_OK)
//			resume_thread(fPS2DisablerThread);
//	}
//}
//
//// _StopPS2DisablerThread
//void
//MasterServerDevice::_StopPS2DisablerThread()
//{
//	if (fPS2DisablerThread >= B_OK) {
//		status_t err;
//		wait_for_thread(fPS2DisablerThread, &err);
//	}
//	fPS2DisablerThread = B_ERROR;
//}

// _LockDevices
bool
MasterServerDevice::_LockDevices()
{
	return fDeviceLock.Lock();
}

// _UnlockDevices
void
MasterServerDevice::_UnlockDevices()
{
	fDeviceLock.Unlock();
}