/******************************************************************************************** * apps/graphics/nxwm/src/ckeyboard.cxx * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ********************************************************************************************/ /******************************************************************************************** * Included Files ********************************************************************************************/ #include #include #include #include #include #include #include #include #include "graphics/nxwm/nxwmconfig.hxx" #include "graphics/nxwm/ckeyboard.hxx" /******************************************************************************************** * Pre-Processor Definitions ********************************************************************************************/ /******************************************************************************************** * CKeyboard Method Implementations ********************************************************************************************/ using namespace NxWM; /** * CKeyboard Constructor * * @param server. An instance of the NX server. This will be needed for * injecting keyboard data. */ CKeyboard::CKeyboard(NXWidgets::CNxServer *server) { m_server = server; // Save the NX server m_kbdFd = -1; // Device driver is not opened m_state = LISTENER_NOTRUNNING; // The listener thread is not running yet // Initialize the semaphore used to synchronize with the listener thread sem_init(&m_waitSem, 0, 0); } /** * CKeyboard Destructor */ CKeyboard::~CKeyboard(void) { // Stop the listener thread m_state = LISTENER_STOPREQUESTED; // Wake up the listener thread so that it will use our buffer // to receive data // REVISIT: Need wait here for the listener thread to terminate pthread_kill(m_thread, CONFIG_NXWM_KEYBOARD_SIGNO); // Close the keyboard device (or should these be done when the thread exits?) if (m_kbdFd >= 0) { close(m_kbdFd); } } /** * Start the keyboard listener thread. * * @return True if the keyboard listener thread was correctly started. */ bool CKeyboard::start(void) { pthread_attr_t attr; ginfo("Starting listener\n"); // Start a separate thread to listen for keyboard events pthread_attr_init(&attr); struct sched_param param; param.sched_priority = CONFIG_NXWM_KEYBOARD_LISTENERPRIO; pthread_attr_setschedparam(&attr, ¶m); pthread_attr_setstacksize(&attr, CONFIG_NXWM_KEYBOARD_LISTENERSTACK); m_state = LISTENER_STARTED; // The listener thread has been started, but is not yet running int ret = pthread_create(&m_thread, &attr, listener, (FAR void *)this); if (ret != 0) { gerr("ERROR: CKeyboard::start: pthread_create failed: %d\n", ret); return false; } // Detach from the thread pthread_detach(m_thread); // Don't return until we are sure that the listener thread is running // (or until it reports an error). while (m_state == LISTENER_STARTED) { // Wait for the listener thread to wake us up when we really // are connected. sem_wait(&m_waitSem); } // Then return true only if the listener thread reported successful // initialization. ginfo("Listener m_state=%d\n", (int)m_state); return m_state == LISTENER_RUNNING; } /** * Open the keyboard device. Not very interesting for the case of * standard device but much more interesting for a USB keyboard device * that may disappear when the keyboard is disconnect but later reappear * when the keyboard is reconnected. In this case, this function will * not return until the keyboard device was successfully opened (or * until an irrecoverable error occurs. * * Opens the keyboard device specified by CONFIG_NXWM_KEYBOARD_DEVPATH. * * @return On success, then method returns a valid file descriptor that * can be used to redirect stdin. A negated errno value is returned * if an irrecoverable error occurs. */ int CKeyboard::open(void) { int fd; // Loop until we have successfully opened the USB keyboard (or until some // irrecoverable error occurs). do { // Try to open the keyboard device fd = ::open(CONFIG_NXWM_KEYBOARD_DEVPATH, O_RDONLY); if (fd < 0) { int errcode = errno; DEBUGASSERT(errcode > 0); // EINTR should be ignored because it is not really an error at // all. We should retry immediately if (errcode != EINTR) { #ifdef CONFIG_NXWM_KEYBOARD_USBHOST // ENOENT means that the USB device is not yet connected and, // hence, has no entry under /dev. If the USB driver still // exists under /dev (because other threads still have the driver // open), then we might also get ENODEV. if (errcode == ENOENT || errcode == ENODEV) { // REVIST: Can we inject a constant string here to let the // user know that we are waiting for a USB keyboard to be // connected? // Sleep a bit and try again ginfo("WAITING for a USB device\n"); sleep(2); } // Anything else would be really bad. else #endif { // Let the top-level logic decide what it wants to do // about all really bad things gerr("ERROR: Failed to open %s for reading: %d\n", CONFIG_NXWM_KEYBOARD_DEVPATH, errcode); return -errcode; } } } } while (fd < 0); return fd; } /** * This is the heart of the keyboard listener thread. It contains the * actual logic that listeners for and dispatches keyboard events to the * NX server. * * @return If the session terminates gracefully (i.e., because >m_state * is no longer equal to LISTENER_RUNNING), then method returns OK. A * negated errno value is returned if an error occurs while reading from * the keyboard device. A read error, depending upon the type of the * error, may simply indicate that a USB keyboard was removed and we * should wait for the keyboard to be connected. */ int CKeyboard::session(void) { ginfo("Session started\n"); // Loop, reading and dispatching keyboard data while (m_state == LISTENER_RUNNING) { // Read one keyboard sample ginfo("Listening for keyboard input\n"); uint8_t rxbuffer[CONFIG_NXWM_KEYBOARD_BUFSIZE]; ssize_t nbytes = read(m_kbdFd, rxbuffer, CONFIG_NXWM_KEYBOARD_BUFSIZE); // Check for errors if (nbytes < 0) { int errcode = errno; DEBUGASSERT(errcode > 0); // EINTR is not really an error, it simply means that something is // trying to get our attention. We need to check m_state to see // if we were asked to terminate if (errcode != EINTR) { // Let the top-level listener logic decide what to do about // the read failure. gerr("ERROR: read %s failed: %d\n", CONFIG_NXWM_KEYBOARD_DEVPATH, errcode); return -errcode; } fwarn("WARNING: Awakened with EINTR\n"); } // Give the keyboard input to NX else if (nbytes > 0) { // Looks like good keyboard input... process it. // First, get the server handle NXHANDLE handle = m_server->getServer(); // Then inject the keyboard input into NX int ret = nx_kbdin(handle, (uint8_t)nbytes, rxbuffer); if (ret < 0) { gerr("ERROR: nx_kbdin failed: %d\n", ret); //break; ignore the error } } } return OK; } /** * The keyboard listener thread. This is the entry point of a thread * that listeners for and dispatches keyboard events to the NX server. * It simply opens the keyboard device (using CKeyboard::open()) and * executes the session (via CKeyboard::session()). * * If an errors while reading from the keyboard device AND we are * configured to use a USB keyboard, then this function will wait for * the USB keyboard to be re-connected. * * @param arg. The CKeyboard 'this' pointer cast to a void*. * @return This function normally does not return but may return NULL on * error conditions. */ FAR void *CKeyboard::listener(FAR void *arg) { CKeyboard *This = (CKeyboard *)arg; ginfo("Listener started\n"); #ifdef CONFIG_NXWM_KEYBOARD_USBHOST // Indicate that we have successfully started. We might be stuck waiting // for a USB keyboard to be connected, but we are technically running This->m_state = LISTENER_RUNNING; sem_post(&This->m_waitSem); // Loop until we are told to quit while (This->m_state == LISTENER_RUNNING) #endif { // Open/Re-open the keyboard device This->m_kbdFd = This->open(); if (This->m_kbdFd < 0) { gerr("ERROR: open failed: %d\n", This->m_kbdFd); This->m_state = LISTENER_FAILED; sem_post(&This->m_waitSem); return (FAR void *)0; } #ifndef CONFIG_NXWM_KEYBOARD_USBHOST // Indicate that we have successfully initialized This->m_state = LISTENER_RUNNING; sem_post(&This->m_waitSem); #endif // Now execute the session. The session will run until either (1) we // were asked to terminate gracefully (with m_state !=LISTENER_RUNNING), // of if an error occurred while reading from the keyboard device. If // we are configured to use a USB keyboard, then this error, depending // upon what the error is, may indicate that the USB keyboard has been // removed. In that case, we need to continue looping and, hopefully, // the USB keyboard will be reconnected. int ret = This->session(); #ifdef CONFIG_NXWM_KEYBOARD_USBHOST if (ret < 0) { ferr("ERROR: CKeyboard::session() returned %d\n", ret); } #else // No errors from session() are expected DEBUGASSERT(ret == OK); UNUSED(ret); #endif // Close the keyboard device close(This->m_kbdFd); This->m_kbdFd = -1; } // We should get here only if we were asked to terminate via // m_state = LISTENER_STOPREQUESTED (or perhaps if some irrecoverable // error has occurred). ginfo("Listener exiting\n"); This->m_state = LISTENER_TERMINATED; return (FAR void *)0; }