* Copyright 2004-2006, Jérôme Duval. All rights reserved.
* Copyright 2005-2010, Axel Dörfler, axeld@pinc-software.de.
* Copyright 2008-2009, Stephan Aßmus, superstippi@gmx.de.
*
* Distributed under the terms of the MIT License.
*/
#include "KeyboardInputDevice.h"
#include <errno.h>
#include <new>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <Application.h>
#include <AutoDeleter.h>
#include <Autolock.h>
#include <Directory.h>
#include <Entry.h>
#include <NodeMonitor.h>
#include <Path.h>
#include <String.h>
#include <keyboard_mouse_driver.h>
#undef TRACE
#ifdef TRACE_KEYBOARD_DEVICE
static int32 sFunctionDepth = -1;
class FunctionTracer {
public:
FunctionTracer(const void* pointer, const char* className,
const char* functionName)
:
fFunctionName(),
fPrepend(),
fPointer(pointer)
{
sFunctionDepth++;
fPrepend.Append(' ', sFunctionDepth * 2);
fFunctionName << className << "::" << functionName << "()";
debug_printf("%p -> %s%s {\n", fPointer, fPrepend.String(),
fFunctionName.String());
}
~FunctionTracer()
{
debug_printf("%p -> %s}\n", fPointer, fPrepend.String());
sFunctionDepth--;
}
static int32 Depth() { return sFunctionDepth; }
private:
BString fFunctionName;
BString fPrepend;
const void* fPointer;
};
# define KD_CALLED(x...) \
FunctionTracer _ft(this, "KeyboardDevice", __FUNCTION__)
# define KID_CALLED(x...) \
FunctionTracer _ft(this, "KeyboardInputDevice", __FUNCTION__)
# define TRACE(x...) \
do { BString _to; \
_to.Append(' ', (FunctionTracer::Depth() + 1) * 2); \
debug_printf("%p -> %s", this, _to.String()); \
debug_printf(x); } while (0)
# define LOG_EVENT(text...) debug_printf(text)
# define LOG_ERR(text...) TRACE(text)
#else
# define TRACE(x...) do {} while (0)
# define KD_CALLED(x...) TRACE(x)
# define KID_CALLED(x...) TRACE(x)
# define LOG_ERR(text...) debug_printf(text)
# define LOG_EVENT(text...) TRACE(x)
#endif
const static uint32 kKeyboardThreadPriority = B_FIRST_REAL_TIME_PRIORITY + 4;
const static char* kKeyboardDevicesDirectory = "/dev/input/keyboard";
extern "C" BInputServerDevice*
instantiate_input_device()
{
return new(std::nothrow) KeyboardInputDevice();
}
static char*
get_short_name(const char* longName)
{
BString string(longName);
BString name;
int32 slash = string.FindLast("/");
string.CopyInto(name, slash + 1, string.Length() - slash);
int32 index = atoi(name.String()) + 1;
int32 previousSlash = string.FindLast("/", slash);
string.CopyInto(name, previousSlash + 1, slash - previousSlash - 1);
if (name.Length() < 4)
name.ToUpper();
else
name.Capitalize();
name << " Keyboard " << index;
return strdup(name.String());
}
KeyboardDevice::KeyboardDevice(KeyboardInputDevice* owner, const char* path)
:
BHandler("keyboard device"),
fOwner(owner),
fFD(-1),
fThread(-1),
fActive(false),
fInputMethodStarted(false),
fKeyboardID(0),
fUpdateSettings(false),
fSettingsCommand(0),
fKeymapLock("keymap lock")
{
KD_CALLED();
strlcpy(fPath, path, B_PATH_NAME_LENGTH);
fDeviceRef.name = get_short_name(path);
fDeviceRef.type = B_KEYBOARD_DEVICE;
fDeviceRef.cookie = this;
if (be_app->Lock()) {
be_app->AddHandler(this);
be_app->Unlock();
}
}
KeyboardDevice::~KeyboardDevice()
{
KD_CALLED();
TRACE("delete\n");
if (fActive)
Stop();
free(fDeviceRef.name);
if (be_app->Lock()) {
be_app->RemoveHandler(this);
be_app->Unlock();
}
}
void
KeyboardDevice::MessageReceived(BMessage* message)
{
KD_CALLED();
if (message->what != B_INPUT_METHOD_EVENT) {
BHandler::MessageReceived(message);
return;
}
int32 opcode;
if (message->FindInt32("be:opcode", &opcode) != B_OK)
return;
if (opcode == B_INPUT_METHOD_STOPPED)
fInputMethodStarted = false;
}
status_t
KeyboardDevice::Start()
{
KD_CALLED();
TRACE("name: %s\n", fDeviceRef.name);
fFD = open(fPath, O_RDWR);
if (fFD < 0) {
fFD = errno;
}
char threadName[B_OS_NAME_LENGTH];
snprintf(threadName, B_OS_NAME_LENGTH, "%s watcher", fDeviceRef.name);
fThread = spawn_thread(_ControlThreadEntry, threadName,
kKeyboardThreadPriority, this);
if (fThread < B_OK)
return fThread;
fActive = true;
resume_thread(fThread);
return fFD >= 0 ? B_OK : B_ERROR;
}
void
KeyboardDevice::Stop()
{
KD_CALLED();
TRACE("name: %s\n", fDeviceRef.name);
fActive = false;
close(fFD);
fFD = -1;
if (fThread >= 0) {
suspend_thread(fThread);
resume_thread(fThread);
status_t dummy;
wait_for_thread(fThread, &dummy);
}
}
status_t
KeyboardDevice::UpdateSettings(uint32 opcode)
{
KD_CALLED();
if (fThread < 0)
return B_ERROR;
fSettingsCommand = opcode;
fUpdateSettings = true;
return B_OK;
}
int32
KeyboardDevice::_ControlThreadEntry(void* arg)
{
KeyboardDevice* device = (KeyboardDevice*)arg;
return device->_ControlThread();
}
int32
KeyboardDevice::_ControlThread()
{
KD_CALLED();
TRACE("fPath: %s\n", fPath);
if (fFD < B_OK) {
LOG_ERR("KeyboardDevice: error when opening %s: %s\n",
fPath, strerror(fFD));
_ControlThreadCleanup();
return B_ERROR;
}
_UpdateSettings(0);
raw_key_info keyInfo;
uint8 activeDeadKey = 0;
uint32 lastKeyCode = 0;
uint32 repeatCount = 1;
uint8 states[16];
bool ctrlAltDelPressed = false;
memset(states, 0, sizeof(states));
if (fKeyboardID == 0) {
if (ioctl(fFD, KB_GET_KEYBOARD_ID, &fKeyboardID,
sizeof(fKeyboardID)) == 0) {
BMessage message(IS_SET_KEYBOARD_ID);
message.AddInt16("id", fKeyboardID);
be_app->PostMessage(&message);
}
}
while (fActive) {
status_t status = ioctl(fFD, KB_READ, &keyInfo, sizeof(keyInfo));
if (status < 0)
status = errno;
if (status == B_INTERRUPTED)
continue;
if (status == B_BUSY) {
snooze(100000);
continue;
}
if (status != B_OK) {
_ControlThreadCleanup();
return 0;
}
if (fUpdateSettings) {
_UpdateSettings(fSettingsCommand);
fUpdateSettings = false;
}
uint32 keycode = keyInfo.keycode;
bool isKeyDown = keyInfo.is_keydown;
LOG_EVENT("KB_READ: %" B_PRIdBIGTIME ", %02x, %02" B_PRIx32 "\n",
keyInfo.timestamp, isKeyDown, keycode);
if (keycode == 0)
continue;
if (isKeyDown && keycode == 0x68) {
bool noOtherKeyPressed = true;
for (int32 i = 0; i < 16; i++) {
if (states[i] != 0) {
noOtherKeyPressed = false;
break;
}
}
if (noOtherKeyPressed) {
BMessenger deskbar("application/x-vnd.Be-TSKB");
if (deskbar.IsValid())
deskbar.SendMessage('BeMn');
}
}
if (keycode < 256) {
if (isKeyDown)
states[(keycode) >> 3] |= (1 << (7 - (keycode & 0x7)));
else
states[(keycode) >> 3] &= (~(1 << (7 - (keycode & 0x7))));
}
if (isKeyDown && keycode == 0x34
&& (states[fCommandKey >> 3] & (1 << (7 - (fCommandKey & 0x7))))
&& (states[fControlKey >> 3] & (1 << (7 - (fControlKey & 0x7))))) {
LOG_EVENT("TeamMonitor called\n");
if (fOwner->fTeamMonitorWindow == NULL)
fOwner->fTeamMonitorWindow = new(std::nothrow) TeamMonitorWindow();
if (fOwner->fTeamMonitorWindow != NULL)
fOwner->fTeamMonitorWindow->Enable();
ctrlAltDelPressed = true;
}
if (ctrlAltDelPressed) {
if (fOwner->fTeamMonitorWindow != NULL) {
BMessage message(kMsgCtrlAltDelPressed);
message.AddBool("key down", isKeyDown);
fOwner->fTeamMonitorWindow->PostMessage(&message);
}
if (!isKeyDown)
ctrlAltDelPressed = false;
}
BAutolock lock(fKeymapLock);
uint32 modifiers = fKeymap.Modifier(keycode);
bool isLock
= (modifiers & (B_CAPS_LOCK | B_NUM_LOCK | B_SCROLL_LOCK)) != 0;
if (modifiers != 0 && (!isLock || isKeyDown)) {
uint32 oldModifiers = fModifiers;
if ((isKeyDown && !isLock)
|| (isKeyDown && !(fModifiers & modifiers)))
fModifiers |= modifiers;
else {
fModifiers &= ~modifiers;
if (fModifiers & (B_LEFT_SHIFT_KEY | B_RIGHT_SHIFT_KEY))
fModifiers |= B_SHIFT_KEY;
if (fModifiers & (B_LEFT_COMMAND_KEY | B_RIGHT_COMMAND_KEY))
fModifiers |= B_COMMAND_KEY;
if (fModifiers & (B_LEFT_CONTROL_KEY | B_RIGHT_CONTROL_KEY))
fModifiers |= B_CONTROL_KEY;
if (fModifiers & (B_LEFT_OPTION_KEY | B_RIGHT_OPTION_KEY))
fModifiers |= B_OPTION_KEY;
}
if (fModifiers != oldModifiers) {
BMessage* message = new BMessage(B_MODIFIERS_CHANGED);
if (message == NULL)
continue;
message->AddInt64("when", keyInfo.timestamp);
message->AddInt32("be:old_modifiers", oldModifiers);
message->AddInt32("modifiers", fModifiers);
message->AddData("states", B_UINT8_TYPE, states, 16);
if (fOwner->EnqueueMessage(message) != B_OK)
delete message;
if (isLock)
_UpdateLEDs();
}
}
uint8 newDeadKey = 0;
if (activeDeadKey == 0 || !isKeyDown)
newDeadKey = fKeymap.ActiveDeadKey(keycode, fModifiers);
char* string = NULL;
char* rawString = NULL;
int32 numBytes = 0, rawNumBytes = 0;
ArrayDeleter<char> stringDeleter;
if (newDeadKey == 0) {
fKeymap.GetChars(keycode, fModifiers, activeDeadKey, &string,
&numBytes);
stringDeleter.SetTo(string);
}
fKeymap.GetChars(keycode, 0, 0, &rawString, &rawNumBytes);
ArrayDeleter<char> rawStringDeleter(rawString);
BMessage* msg = new BMessage;
if (msg == NULL)
continue;
if (numBytes > 0)
msg->what = isKeyDown ? B_KEY_DOWN : B_KEY_UP;
else
msg->what = isKeyDown ? B_UNMAPPED_KEY_DOWN : B_UNMAPPED_KEY_UP;
msg->AddInt64("when", keyInfo.timestamp);
msg->AddInt32("key", keycode);
msg->AddInt32("modifiers", fModifiers);
msg->AddData("states", B_UINT8_TYPE, states, 16);
if (numBytes > 0) {
for (int i = 0; i < numBytes; i++)
msg->AddInt8("byte", (int8)string[i]);
msg->AddData("bytes", B_STRING_TYPE, string, numBytes + 1);
if (rawNumBytes <= 0) {
rawNumBytes = 1;
rawString = string;
}
if (isKeyDown && lastKeyCode == keycode) {
repeatCount++;
msg->AddInt32("be:key_repeat", repeatCount);
} else
repeatCount = 1;
}
if (rawNumBytes > 0)
msg->AddInt32("raw_char", (uint32)((uint8)rawString[0] & 0x7f));
if (newDeadKey == 0) {
if (isKeyDown && !modifiers && activeDeadKey != 0) {
activeDeadKey = 0;
if (fInputMethodStarted) {
_EnqueueInlineInputMethod(B_INPUT_METHOD_CHANGED,
string, true, msg);
_EnqueueInlineInputMethod(B_INPUT_METHOD_STOPPED);
fInputMethodStarted = false;
msg = NULL;
}
}
} else if (isKeyDown
&& _EnqueueInlineInputMethod(B_INPUT_METHOD_STARTED) == B_OK) {
char* string = NULL;
int32 numBytes = 0;
fKeymap.GetChars(keycode, fModifiers, 0, &string, &numBytes);
if (_EnqueueInlineInputMethod(B_INPUT_METHOD_CHANGED, string)
== B_OK)
fInputMethodStarted = true;
activeDeadKey = newDeadKey;
delete[] string;
}
if (msg != NULL && fOwner->EnqueueMessage(msg) != B_OK)
delete msg;
lastKeyCode = isKeyDown ? keycode : 0;
}
return 0;
}
void
KeyboardDevice::_ControlThreadCleanup()
{
if (fActive) {
fThread = -1;
fOwner->_RemoveDevice(fPath);
} else {
}
}
void
KeyboardDevice::_UpdateSettings(uint32 opcode)
{
KD_CALLED();
if (opcode == 0 || opcode == B_KEY_REPEAT_RATE_CHANGED) {
if (get_key_repeat_rate(&fSettings.key_repeat_rate) != B_OK) {
LOG_ERR("error when get_key_repeat_rate\n");
} else if (ioctl(fFD, KB_SET_KEY_REPEAT_RATE,
&fSettings.key_repeat_rate, sizeof(int32)) != B_OK) {
LOG_ERR("error when KB_SET_KEY_REPEAT_RATE, fd:%d\n", fFD);
}
}
if (opcode == 0 || opcode == B_KEY_REPEAT_DELAY_CHANGED) {
if (get_key_repeat_delay(&fSettings.key_repeat_delay) != B_OK) {
LOG_ERR("error when get_key_repeat_delay\n");
} else if (ioctl(fFD, KB_SET_KEY_REPEAT_DELAY,
&fSettings.key_repeat_delay, sizeof(bigtime_t)) != B_OK) {
LOG_ERR("error when KB_SET_KEY_REPEAT_DELAY, fd:%d\n", fFD);
}
}
if (opcode == 0 || opcode == B_KEY_MAP_CHANGED
|| opcode == B_KEY_LOCKS_CHANGED) {
BAutolock lock(fKeymapLock);
fKeymap.RetrieveCurrent();
fModifiers = fKeymap.Map().lock_settings;
_UpdateLEDs();
fControlKey = fKeymap.KeyForModifier(B_LEFT_CONTROL_KEY);
fCommandKey = fKeymap.KeyForModifier(B_LEFT_COMMAND_KEY);
}
}
void
KeyboardDevice::_UpdateLEDs()
{
if (fFD < 0)
return;
char lockIO[3] = {0, 0, 0};
if ((fModifiers & B_NUM_LOCK) != 0)
lockIO[0] = 1;
if ((fModifiers & B_CAPS_LOCK) != 0)
lockIO[1] = 1;
if ((fModifiers & B_SCROLL_LOCK) != 0)
lockIO[2] = 1;
ioctl(fFD, KB_SET_LEDS, &lockIO, sizeof(lockIO));
}
status_t
KeyboardDevice::_EnqueueInlineInputMethod(int32 opcode,
const char* string, bool confirmed, BMessage* keyDown)
{
BMessage* message = new BMessage(B_INPUT_METHOD_EVENT);
if (message == NULL)
return B_NO_MEMORY;
message->AddInt32("be:opcode", opcode);
message->AddBool("be:inline_only", true);
if (string != NULL)
message->AddString("be:string", string);
if (confirmed)
message->AddBool("be:confirmed", true);
if (keyDown)
message->AddMessage("be:translated", keyDown);
if (opcode == B_INPUT_METHOD_STARTED)
message->AddMessenger("be:reply_to", this);
status_t status = fOwner->EnqueueMessage(message);
if (status != B_OK)
delete message;
return status;
}
KeyboardInputDevice::KeyboardInputDevice()
:
fDevices(2),
fDeviceListLock("KeyboardInputDevice list"),
fTeamMonitorWindow(NULL)
{
KID_CALLED();
StartMonitoringDevice(kKeyboardDevicesDirectory);
_RecursiveScan(kKeyboardDevicesDirectory);
}
KeyboardInputDevice::~KeyboardInputDevice()
{
KID_CALLED();
if (fTeamMonitorWindow) {
fTeamMonitorWindow->PostMessage(B_QUIT_REQUESTED);
fTeamMonitorWindow = NULL;
}
StopMonitoringDevice(kKeyboardDevicesDirectory);
fDevices.MakeEmpty();
}
status_t
KeyboardInputDevice::SystemShuttingDown()
{
KID_CALLED();
if (fTeamMonitorWindow)
fTeamMonitorWindow->PostMessage(SYSTEM_SHUTTING_DOWN);
return B_OK;
}
status_t
KeyboardInputDevice::InitCheck()
{
KID_CALLED();
return BInputServerDevice::InitCheck();
}
status_t
KeyboardInputDevice::Start(const char* name, void* cookie)
{
KID_CALLED();
TRACE("name %s\n", name);
KeyboardDevice* device = (KeyboardDevice*)cookie;
return device->Start();
}
status_t
KeyboardInputDevice::Stop(const char* name, void* cookie)
{
KID_CALLED();
TRACE("name %s\n", name);
KeyboardDevice* device = (KeyboardDevice*)cookie;
device->Stop();
return B_OK;
}
status_t
KeyboardInputDevice::Control(const char* name, void* cookie,
uint32 command, BMessage* message)
{
KID_CALLED();
TRACE("KeyboardInputDevice::Control(%s, code: %" B_PRIu32 ")\n", name,
command);
if (command == B_NODE_MONITOR)
_HandleMonitor(message);
else if (command >= B_KEY_MAP_CHANGED
&& command <= B_KEY_REPEAT_RATE_CHANGED) {
KeyboardDevice* device = (KeyboardDevice*)cookie;
device->UpdateSettings(command);
}
return B_OK;
}
status_t
KeyboardInputDevice::_HandleMonitor(BMessage* message)
{
KID_CALLED();
const char* path;
int32 opcode;
if (message->FindInt32("opcode", &opcode) != B_OK
|| (opcode != B_ENTRY_CREATED && opcode != B_ENTRY_REMOVED)
|| message->FindString("path", &path) != B_OK)
return B_BAD_VALUE;
if (opcode == B_ENTRY_CREATED)
return _AddDevice(path);
#if 0
return _RemoveDevice(path);
#else
return B_OK;
#endif
}
KeyboardDevice*
KeyboardInputDevice::_FindDevice(const char* path) const
{
for (int i = fDevices.CountItems() - 1; i >= 0; i--) {
KeyboardDevice* device = fDevices.ItemAt(i);
if (strcmp(device->Path(), path) == 0)
return device;
}
return NULL;
}
status_t
KeyboardInputDevice::_AddDevice(const char* path)
{
KID_CALLED();
TRACE("path: %s\n", path);
BAutolock _(fDeviceListLock);
_RemoveDevice(path);
KeyboardDevice* device = new(std::nothrow) KeyboardDevice(this, path);
if (device == NULL)
return B_NO_MEMORY;
input_device_ref* devices[2];
devices[0] = device->DeviceRef();
devices[1] = NULL;
fDevices.AddItem(device);
return RegisterDevices(devices);
}
status_t
KeyboardInputDevice::_RemoveDevice(const char* path)
{
BAutolock _(fDeviceListLock);
KeyboardDevice* device = _FindDevice(path);
if (device == NULL)
return B_ENTRY_NOT_FOUND;
KID_CALLED();
TRACE("path: %s\n", path);
input_device_ref* devices[2];
devices[0] = device->DeviceRef();
devices[1] = NULL;
UnregisterDevices(devices);
fDevices.RemoveItem(device);
return B_OK;
}
void
KeyboardInputDevice::_RecursiveScan(const char* directory)
{
KID_CALLED();
TRACE("directory: %s\n", directory);
BEntry entry;
BDirectory dir(directory);
while (dir.GetNextEntry(&entry) == B_OK) {
BPath path;
entry.GetPath(&path);
if (entry.IsDirectory())
_RecursiveScan(path.Path());
else
_AddDevice(path.Path());
}
}