root/src/add-ons/input_server/devices/keyboard/KeyboardInputDevice.cpp
/*
 * 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

//#define TRACE_KEYBOARD_DEVICE
#ifdef TRACE_KEYBOARD_DEVICE

#include <private/shared/FunctionTracer.h>

static  int32           sFunctionDepth = -1;

#       define CALLED(x...) \
                FunctionTracer _ft(debug_printf, this, __PRETTY_FUNCTION__, sFunctionDepth)
#       define TRACE(x...) \
                do { BString _to; \
                        _to.Append(' ', (sFunctionDepth + 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 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);

        // some special handling so that we get "USB" and "AT" instead of "usb"/"at"
        if (name.Length() < 4)
                name.ToUpper();
        else
                name.Capitalize();

        name << " Keyboard " << index;

        return strdup(name.String());
}


//      #pragma mark -


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")
{
        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()
{
        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)
{
        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()
{
        CALLED();
        TRACE("name: %s\n", fDeviceRef.name);

        fFD = open(fPath, O_RDWR);
        if (fFD < 0) {
                // let the control thread handle any error on opening the device
                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()
{
        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)
{
        CALLED();

        if (fThread < 0)
                return B_ERROR;

        // schedule updating the settings from within the control thread
        fSettingsCommand = opcode;
        fUpdateSettings = true;

        return B_OK;
}


// #pragma mark - control thread


/*static*/ int32
KeyboardDevice::_ControlThreadEntry(void* arg)
{
        KeyboardDevice* device = (KeyboardDevice*)arg;
        return device->_ControlThread();
}


int32
KeyboardDevice::_ControlThread()
{
        CALLED();
        TRACE("fPath: %s\n", fPath);

        if (fFD < B_OK) {
                LOG_ERR("KeyboardDevice: error when opening %s: %s\n",
                        fPath, strerror(fFD));
                _ControlThreadCleanup();
                // TOAST!
                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) {
                        // probably the debugger is listening to key events, wait and try
                        // again
                        snooze(100000);
                        continue;
                }
                if (status != B_OK) {
                        _ControlThreadCleanup();
                        // TOAST!
                        return 0;
                }

                // Update the settings from this thread if necessary
                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) {
                        // MENU KEY for Tracker
                        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 // DELETE KEY
                        && (states[fCommandKey >> 3] & (1 << (7 - (fCommandKey & 0x7))))
                        && (states[fControlKey >> 3] & (1 << (7 - (fControlKey & 0x7))))) {
                        LOG_EVENT("TeamMonitor called\n");

                        // show the team monitor
                        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;

                                // ensure that we don't clear a combined B_*_KEY when still
                                // one of the individual B_{LEFT|RIGHT}_*_KEY is pressed
                                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) {
                                // a dead key was completed
                                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) {
                        // start of a dead key
                        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()
{
        // NOTE: Only executed when the control thread detected an error
        // and from within the control thread!

        if (fActive) {
                fThread = -1;
                fOwner->_RemoveDevice(fPath);
        } else {
                // In case active is already false, another thread
                // waits for this thread to quit, and may already hold
                // locks that _RemoveDevice() wants to acquire. In another
                // words, the device is already being removed, so we simply
                // quit here.
        }
}


void
KeyboardDevice::_UpdateSettings(uint32 opcode)
{
        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;
}


//      #pragma mark -


KeyboardInputDevice::KeyboardInputDevice()
        :
        fDevices(2),
        fDeviceListLock("KeyboardInputDevice list"),
        fTeamMonitorWindow(NULL)
{
        CALLED();

        StartMonitoringDevice(kKeyboardDevicesDirectory);
        _RecursiveScan(kKeyboardDevicesDirectory);
}


KeyboardInputDevice::~KeyboardInputDevice()
{
        CALLED();

        if (fTeamMonitorWindow) {
                fTeamMonitorWindow->PostMessage(B_QUIT_REQUESTED);
                fTeamMonitorWindow = NULL;
        }

        StopMonitoringDevice(kKeyboardDevicesDirectory);
        fDevices.MakeEmpty();
}


status_t
KeyboardInputDevice::SystemShuttingDown()
{
        CALLED();
        if (fTeamMonitorWindow)
                fTeamMonitorWindow->PostMessage(SYSTEM_SHUTTING_DOWN);

        return B_OK;
}


status_t
KeyboardInputDevice::InitCheck()
{
        CALLED();
        return BInputServerDevice::InitCheck();
}


status_t
KeyboardInputDevice::Start(const char* name, void* cookie)
{
        CALLED();
        TRACE("name %s\n", name);

        KeyboardDevice* device = (KeyboardDevice*)cookie;

        return device->Start();
}


status_t
KeyboardInputDevice::Stop(const char* name, void* cookie)
{
        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)
{
        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)
{
        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
        // Don't handle B_ENTRY_REMOVED, let the control thread take care of it.
        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)
{
        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;

        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)
{
        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());
        }
}