* apps/graphics/NxWidgets/nxwm/src/cinput.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 <nuttx/config.h>
#include <cerrno>
#include <fcntl.h>
#include <sched.h>
#include <poll.h>
#include <pthread.h>
#include <assert.h>
#include <unistd.h>
#include <nuttx/nx/nxglib.h>
#include <nuttx/input/mouse.h>
#ifdef CONFIG_TWM4NX_MOUSE
# include <nuttx/nx/nxcursor.h>
# include "graphics/twm4nx/twm4nx_cursor.hxx"
#else
# include <nuttx/input/touchscreen.h>
#endif
#include "graphics/twm4nx/twm4nx_config.hxx"
#include "graphics/twm4nx/ctwm4nx.hxx"
#include "graphics/twm4nx/cinput.hxx"
* Pre-Processor Definitions
****************************************************************************/
#ifndef CONFIG_TWM4NX_NOKEYBOARD
# define KBD_INDEX 0
# ifndef CONFIG_TWM4NX_NOMOUSE
# define MOUSE_INDEX 1
# define NINPUT_DEVICES 2
# else
# define NINPUT_DEVICES 1
# endif
#else
# define MOUSE_INDEX 0
# define NINPUT_DEVICES 1
#endif
* CInput Method Implementations
****************************************************************************/
using namespace Twm4Nx;
* CInput Constructor
*
* @param twm4nx. An instance of the NX server. This will be needed for
* injecting keyboard data.
*/
CInput::CInput(CTwm4Nx *twm4nx)
{
m_twm4nx = twm4nx;
#ifndef CONFIG_TWM4NX_NOKEYBOARD
m_kbdFd = -1;
#endif
#ifndef CONFIG_TWM4NX_NOMOUSE
m_mouseFd = -1;
#endif
m_state = LISTENER_NOTRUNNING;
sem_init(&m_waitSem, 0, 0);
#ifdef CONFIG_TWM4NX_TOUCHSCREEN
m_calib = false;
#endif
}
* CInput Destructor
*/
CInput::~CInput(void)
{
m_state = LISTENER_STOPREQUESTED;
pthread_kill(m_thread, CONFIG_TWM4NX_INPUT_SIGNO);
#ifndef CONFIG_TWM4NX_NOKEYBOARD
if (m_kbdFd >= 0)
{
close(m_kbdFd);
}
#endif
#ifndef CONFIG_TWM4NX_NOKEYBOARD
if (m_mouseFd >= 0)
{
close(m_mouseFd);
}
#endif
}
* Start the keyboard listener thread.
*
* @return True if the keyboard listener thread was correctly started.
*/
bool CInput::start(void)
{
pthread_attr_t attr;
twminfo("Starting listener\n");
pthread_attr_init(&attr);
struct sched_param param;
param.sched_priority = CONFIG_TWM4NX_INPUT_LISTENERPRIO;
pthread_attr_setschedparam(&attr, ¶m);
pthread_attr_setstacksize(&attr, CONFIG_TWM4NX_INPUT_LISTENERSTACK);
m_state = LISTENER_STARTED;
int ret = pthread_create(&m_thread, &attr, listener, (FAR void *)this);
if (ret != 0)
{
twmerr("ERROR: CInput::start: pthread_create failed: %d\n", ret);
return false;
}
pthread_detach(m_thread);
while (m_state == LISTENER_STARTED)
{
sem_wait(&m_waitSem);
}
twminfo("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_TWM4NX_KEYBOARD_DEVPATH.
*
* @return On success, then method returns a valid file descriptor. A
* negated errno value is returned if an irrecoverable error occurs.
*/
#ifndef CONFIG_TWM4NX_NOKEYBOARD
int CInput::keyboardOpen(void)
{
int fd;
do
{
fd = open(CONFIG_TWM4NX_KEYBOARD_DEVPATH, O_RDONLY | O_NONBLOCK);
if (fd < 0)
{
int errcode = errno;
DEBUGASSERT(errcode > 0);
if (errcode != EINTR)
{
#ifdef CONFIG_TWM4NX_KEYBOARD_USBHOST
if (errcode == ENOENT || errcode == ENODEV)
{
twminfo("WAITING for a USB keyboard\n");
sleep(2);
}
else
#endif
{
twmerr("ERROR: Failed to open %s for reading: %d\n",
CONFIG_TWM4NX_KEYBOARD_DEVPATH, errcode);
return -errcode;
}
}
}
}
while (fd < 0);
return fd;
}
#endif
* Open the mouse/touchscreen input devices. Not very interesting for the
* case of standard character device but much more interesting for
* USB mouse devices that may disappear when disconnected but later
* reappear when reconnected. In this case, this function will
* not return until the input device was successfully opened (or
* until an irrecoverable error occurs).
*
* Opens the mouse input device specified by CONFIG_TWM4NX_MOUSE_DEVPATH.
*
* @return On success, then method returns a valid file descriptor. A
* negated errno value is returned if an irrecoverable error occurs.
*/
#ifndef CONFIG_TWM4NX_NOMOUSE
inline int CInput::mouseOpen(void)
{
int fd;
do
{
fd = open(CONFIG_TWM4NX_MOUSE_DEVPATH, O_RDONLY | O_NONBLOCK);
if (fd < 0)
{
int errcode = errno;
DEBUGASSERT(errcode > 0);
if (errcode != EINTR)
{
#ifdef CONFIG_TWM4NX_MOUSE_USBHOST
if (errcode == ENOENT || errcode == ENODEV)
{
twminfo("WAITING for a USB mouse\n");
sleep(2);
}
else
#endif
{
twmerr("ERROR: Failed to open %s for reading: %d\n",
CONFIG_TWM4NX_MOUSE_DEVPATH, errcode);
return -errcode;
}
}
}
}
while (fd < 0);
return fd;
}
#endif
* Read data from the keyboard device and inject the keyboard data
* into NX for proper distribution.
*
* @return On success, then method returns a valid file descriptor. A
* negated errno value is returned if an irrecoverable error occurs.
*/
#ifndef CONFIG_TWM4NX_NOKEYBOARD
int CInput::keyboardInput(void)
{
twminfo("Reading keyboard input\n");
uint8_t rxbuffer[CONFIG_TWM4NX_KEYBOARD_BUFSIZE];
ssize_t nbytes = read(m_kbdFd, rxbuffer,
CONFIG_TWM4NX_KEYBOARD_BUFSIZE);
if (nbytes < 0)
{
int errcode = errno;
DEBUGASSERT(errcode > 0);
if (errcode == EAGAIN)
{
return OK;
}
if (errcode != EINTR)
{
twmerr("ERROR: read %s failed: %d\n",
CONFIG_TWM4NX_KEYBOARD_DEVPATH, errcode);
return -errcode;
}
fwarn("WARNING: Awakened with EINTR\n");
}
else if (nbytes > 0)
{
NXHANDLE server = m_twm4nx->getServer();
int ret = nx_kbdin(server, (uint8_t)nbytes, rxbuffer);
if (ret < 0)
{
twmerr("ERROR: nx_kbdin failed: %d\n", ret);
}
}
return OK;
}
#endif
* Calibrate raw touchscreen input.
*
* @param raw The raw touch sample
* @param scaled The location to return the scaled touch position
* @return On success, this method returns zero (OK). A negated errno
* value is returned if an irrecoverable error occurs.
*/
#ifdef CONFIG_TWM4NX_TOUCHSCREEN
int CInput::scaleTouchData(FAR const struct touch_point_s &raw,
FAR struct nxgl_point_s &scaled)
{
#ifdef CONFIG_NXWM_CALIBRATION_ANISOTROPIC
float leftX = raw.y * m_calData.left.slope + m_calData.left.offset;
float rightX = raw.y * m_calData.right.slope + m_calData.right.offset;
struct SCalibrationLine xLine;
xLine.slope = (float)((int)m_calData.rightX -
(int)m_calData.leftX) / (rightX - leftX);
xLine.offset = (float)m_calData.leftX - leftX * xLine.slope;
float topY = raw.x * m_calData.top.slope + m_calData.top.offset;
float bottomY = raw.x * m_calData.bottom.slope +
m_calData.bottom.offset;
struct SCalibrationLine yLine;
yLine.slope = (float)((int)m_calData.bottomY -
(int)m_calData.topY) / (bottomY - topY);
yLine.offset = (float)m_calData.topY - topY * yLine.slope;
float scaledX = raw.x * xLine.slope + xLine.offset;
float scaledY = raw.y * yLine.slope + yLine.offset;
scaled.x = (nxgl_coord_t)scaledX;
scaled.y = (nxgl_coord_t)scaledY;
twminfo("raw: (%6.2f, %6.2f) scaled: (%6.2f, %6.2f) (%d, %d)\n",
raw.x, raw.y, scaledX, scaledY, scaled.x, scaled.y);
return OK;
#else
b16_t scaledX = raw.x * m_calData.xSlope + m_calData.xOffset;
b16_t scaledY = raw.y * m_calData.ySlope + m_calData.yOffset;
int32_t bigX = b16toi(scaledX + b16HALF);
int32_t bigY = b16toi(scaledY + b16HALF);
struct nxgl_size_s displaySize;
m_twm4nx->getDisplaySize(&displaySize);
if (bigX < 0)
{
scaled.x = 0;
}
else if (bigX >= displaySize.w)
{
scaled.x = displaySize.w - 1;
}
else
{
scaled.x = (nxgl_coord_t)bigX;
}
if (bigY < 0)
{
scaled.y = 0;
}
else if (bigY >= displaySize.h)
{
scaled.y = displaySize.h - 1;
}
else
{
scaled.y = (nxgl_coord_t)bigY;
}
twminfo("raw: (%d, %d) scaled: (%d, %d)\n",
raw.x, raw.y, scaled.x, scaled.y);
return OK;
#endif
}
#endif
* Read data from the mouse/touchscreen device. If the input device is a
* mouse, then update the cursor position. And, in either case, inject
* the mouse data into NX for proper distribution.
*
* @return On success, then method returns zero (OK). A negated errno
* value is returned if an irrecoverable error occurs.
*/
#ifndef CONFIG_TWM4NX_NOMOUSE
int CInput::mouseInput(void)
{
twminfo("Reading XY input\n");
uint8_t rxbuffer[CONFIG_TWM4NX_MOUSE_BUFSIZE];
ssize_t nbytes = read(m_mouseFd, rxbuffer,
CONFIG_TWM4NX_MOUSE_BUFSIZE);
if (nbytes < 0)
{
int errcode = errno;
DEBUGASSERT(errcode > 0);
if (errcode == EAGAIN)
{
return OK;
}
if (errcode != EINTR)
{
twmerr("ERROR: read %s failed: %d\n",
CONFIG_TWM4NX_KEYBOARD_DEVPATH, errcode);
return -errcode;
}
fwarn("WARNING: Awakened with EINTR\n");
}
#ifdef CONFIG_TWM4NX_MOUSE
else if (nbytes < (ssize_t)sizeof(struct mouse_report_s))
{
twmerr("ERROR Unexpected read size=%d, expected=%d\n",
nbytes, sizeof(struct mouse_report_s));
return -EIO;
}
else
{
FAR struct mouse_report_s *rpt =
(FAR struct mouse_report_s *)rxbuffer;
twminfo("Mouse pos: (%d,%d) Buttons: %02x\n",
rtp->x, rpt->y, rpt->buttons);
struct nxgl_point_s pos =
{
.x = rpt->x,
.y = rpt->y
};
int ret = nxcursor_setposition(m_twm4nx, &pos);
if (ret < 0)
{
twmerr("ERROR: nxcursor_setposition failed: %d\n", ret);
}
NXHANDLE server = m_twm4nx->getServer();
ret = nx_mousein(server, rpt->x, rpt->y, rpt->buttons);
if (ret < 0)
{
twmerr("ERROR: nx_mousein failed: %d\n", ret);
}
}
#else
else if (nbytes < (ssize_t)SIZEOF_TOUCH_SAMPLE_S(1))
{
twmerr("ERROR Unexpected read size=%d, expected=%d\n",
nbytes, SIZEOF_TOUCH_SAMPLE_S(1));
return -EIO;
}
else
{
FAR struct touch_sample_s *sample =
(FAR struct touch_sample_s *)rxbuffer;
struct nxgl_point_s touchPos;
int ret;
if (m_calib)
{
ret = scaleTouchData(sample->point[0], touchPos);
if (ret < 0)
{
twmerr("ERROR: scaleTouchData failed: %d\n", ret);
return ret;
}
}
else
{
touchPos.x = sample->point[0].x;
touchPos.y = sample->point[0].y;
}
uint8_t buttons;
if ((sample->point[0].flags & (TOUCH_DOWN | TOUCH_MOVE)) != 0)
{
buttons = MOUSE_BUTTON_1;
}
else if ((sample->point[0].flags & TOUCH_UP) != 0)
{
buttons = 0;
}
else
{
return -EIO;
}
twminfo("Touch pos: (%d,%d)->(%d,%d) Buttons: %02x\n",
sample->point[0].x, sample->point[0].y,
touchPos.x, touchPos.y, buttons);
NXHANDLE server = m_twm4nx->getServer();
ret = nx_mousein(server, touchPos.x, touchPos.y, buttons);
if (ret < 0)
{
twmerr("ERROR: nx_mousein failed: %d\n", ret);
}
}
#endif
return OK;
}
#endif
* This is the heart of the keyboard/mouse listener thread. It
* contains the actual logic that listeners for and dispatches input
* 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 input device. A read error, depending upon the type of the
* error, may simply indicate that a USB device was removed and we
* should wait for the device to be connected.
*/
int CInput::session(void)
{
twminfo("Session started\n");
#ifdef CONFIG_TWM4NX_MOUSE
struct nxgl_size_s size;
m_twm4nx->getDisplaySize(&size);
struct nxgl_point_s pos;
pos.x = size.w / 2,
pos.y = size.h / 2,
m_twm4nx->setCursorPosition(&pos);
m_twm4nx->setCursorImage(&CONFIG_TWM4NX_CURSOR_IMAGE);
m_twm4nx->enableCursor(true);
#endif
int ret = OK;
while (m_state == LISTENER_RUNNING)
{
struct pollfd pfd[NINPUT_DEVICES];
#ifndef CONFIG_TWM4NX_NOKEYBOARD
pfd[KBD_INDEX].fd = m_kbdFd;
pfd[KBD_INDEX].events = POLLIN;
pfd[KBD_INDEX].revents = 0;
#endif
#ifndef CONFIG_TWM4NX_NOMOUSE
pfd[MOUSE_INDEX].fd = m_mouseFd;
pfd[MOUSE_INDEX].events = POLLIN;
pfd[MOUSE_INDEX].revents = 0;
#endif
ret = poll(pfd, NINPUT_DEVICES, -1);
if (ret < 0)
{
int errcode = errno;
if (errcode == EINTR)
{
continue;
}
else
{
twmerr("ERROR: poll() failed");
break;
}
}
#ifndef CONFIG_TWM4NX_NOKEYBOARD
if ((pfd[KBD_INDEX].revents & (POLLERR | POLLHUP)) != 0)
{
twmerr("ERROR: keyboard poll() failed. revents=%08" PRIx32 "\n",
pfd[KBD_INDEX].revents);
ret = -EIO;
break;
}
if ((pfd[KBD_INDEX].revents & POLLIN) != 0)
{
ret = keyboardInput();
if (ret < 0)
{
twmerr("ERROR: keyboardInput() failed: %d\n", ret);
break;
}
}
#endif
#ifndef CONFIG_TWM4NX_NOMOUSE
if ((pfd[MOUSE_INDEX].revents & (POLLERR | POLLHUP)) != 0)
{
twmerr("ERROR: Mouse poll() failed. revents=%08" PRIx32 "\n",
pfd[MOUSE_INDEX].revents);
ret = -EIO;
break;
}
if ((pfd[MOUSE_INDEX].revents & POLLIN) != 0)
{
ret = mouseInput();
if (ret < 0)
{
twmerr("ERROR: mouseInput() failed: %d\n", ret);
break;
}
}
#endif
}
#ifdef CONFIG_TWM4NX_MOUSE
m_twm4nx->enableCursor(false);
#endif
return ret;
}
* The keyboard/mouse listener thread. This is the entry point of a
* thread that listeners for and dispatches keyboard and mouse events
* to the NX server. It simply opens the input devices (using
* CInput::keyboardOpen() and CInput::mouseOpen()) and executes the
* session (via CInput::session()).
*
* If an errors while reading from the input device AND that device is
* configured to use a USB connection, then this function will wait for
* the USB device to be re-connected.
*
* @param arg. The CInput 'this' pointer cast to a void*.
* @return This function normally does not return but may return NULL
* on error conditions.
*/
FAR void *CInput::listener(FAR void *arg)
{
CInput *This = (CInput *)arg;
twminfo("Listener started\n");
#if defined(CONFIG_TWM4NX_KEYBOARD_USBHOST) || defined(CONFIG_TWM4NX_MOUSE_USBHOST)
This->m_state = LISTENER_RUNNING;
sem_post(&This->m_waitSem);
while (This->m_state == LISTENER_RUNNING)
#endif
{
#ifndef CONFIG_TWM4NX_NOKEYBOARD
This->m_kbdFd = This->keyboardOpen();
if (This->m_kbdFd < 0)
{
twmerr("ERROR: open failed: %d\n", This->m_kbdFd);
This->m_state = LISTENER_FAILED;
sem_post(&This->m_waitSem);
return (FAR void *)0;
}
#endif
#ifndef CONFIG_TWM4NX_NOMOUSE
This->m_mouseFd = This->mouseOpen();
if (This->m_mouseFd < 0)
{
twmerr("ERROR: open failed: %d\n", This->m_mouseFd);
This->m_state = LISTENER_FAILED;
sem_post(&This->m_waitSem);
return (FAR void *)0;
}
#endif
#if !defined(CONFIG_TWM4NX_KEYBOARD_USBHOST) && !defined(CONFIG_TWM4NX_MOUSE_USBHOST)
This->m_state = LISTENER_RUNNING;
sem_post(&This->m_waitSem);
#endif
int ret = This->session();
#if defined(CONFIG_TWM4NX_KEYBOARD_USBHOST) || defined(CONFIG_TWM4NX_MOUSE_USBHOST)
if (ret < 0)
{
ferr("ERROR: CInput::session() returned %d\n", ret);
}
#else
DEBUGASSERT(ret == OK);
UNUSED(ret);
#endif
#ifndef CONFIG_TWM4NX_NOKEYBOARD
close(This->m_kbdFd);
This->m_kbdFd = -1;
#endif
#ifndef CONFIG_TWM4NX_NOMOUSE
close(This->m_mouseFd);
This->m_mouseFd = -1;
#endif
}
twminfo("Listener exiting\n");
This->m_state = LISTENER_TERMINATED;
return (FAR void *)0;
}