NXWidgets  1.19
ctouchscreen.cxx
Go to the documentation of this file.
1 /********************************************************************************************
2  * NxWidgets/nxwm/src/ctouchscreen.cxx
3  *
4  * Copyright (C) 2012, 2015-2016 Gregory Nutt. All rights reserved.
5  * Author: Gregory Nutt <gnutt@nuttx.org>
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  * notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  * notice, this list of conditions and the following disclaimer in
15  * the documentation and/or other materials provided with the
16  * distribution.
17  * 3. Neither the name NuttX, NxWidgets, nor the names of its contributors
18  * me be used to endorse or promote products derived from this software
19  * without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
28  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  *
34  ********************************************************************************************/
35 
36 /********************************************************************************************
37  * Included Files
38  ********************************************************************************************/
39 
40 #include <nuttx/config.h>
41 
42 #include <cunistd>
43 #include <cerrno>
44 #include <cfcntl>
45 
46 #include <sys/prctl.h>
47 
48 #include <sched.h>
49 #include <pthread.h>
50 #include <assert.h>
51 #include <debug.h>
52 
53 #include <nuttx/nx/nxglib.h>
54 
55 #include "nxconfig.hxx"
56 #include "cwidgetcontrol.hxx"
57 #include "cgraphicsport.hxx"
58 
59 #include "nxwmconfig.hxx"
60 #include "nxwmglyphs.hxx"
61 #include "ctouchscreen.hxx"
62 
63 /********************************************************************************************
64  * Pre-Processor Definitions
65  ********************************************************************************************/
66 /* We want debug output from this file if either input/touchscreen or graphics debug is
67  * enabled.
68  */
69 
70 #if !defined(CONFIG_DEBUG_INPUT) && !defined(CONFIG_DEBUG_GRAPHICS)
71 # undef gerr
72 # undef _info
73 # ifdef CONFIG_CPP_HAVE_VARARGS
74 # define gerr(x...)
75 # define _info(x...)
76 # else
77 # define gerr (void)
78 # define _info (void)
79 # endif
80 #endif
81 
82 /********************************************************************************************
83  * CTouchscreen Method Implementations
84  ********************************************************************************************/
85 
86 using namespace NxWM;
87 
88 /**
89  * CTouchscreen Constructor
90  *
91  * @param server. An instance of the NX server. This will be needed for
92  * injecting mouse data.
93  * @param windowSize. The size of the physical window in pixels. This
94  * is needed for touchscreen scaling.
95  */
96 
97 CTouchscreen::CTouchscreen(NXWidgets::CNxServer *server, struct nxgl_size_s *windowSize)
98 {
99  m_server = server; // Save the NX server
100  m_touchFd = -1; // Device driver is not opened
101  m_state = LISTENER_NOTRUNNING; // The listener thread is not running yet
102  m_enabled = false; // Normal forwarding is not enabled
103  m_capture = false; // There is no thread waiting for touchscreen data
104  m_calibrated = false; // We have no calibration data
105 
106  // Save the window size
107 
108  m_windowSize = *windowSize;
109 
110  // Use the default touch data buffer
111 
112  m_touch = &m_sample;
113 
114  // Initialize the m_waitSem semaphore so that any waits for data will block
115 
116  sem_init(&m_waitSem, 0, 0);
117 }
118 
119 /**
120  * CTouchscreen Destructor
121  */
122 
124 {
125  // Stop the listener thread
126 
128 
129  // Wake up the listener thread so that it will use our buffer
130  // to receive data
131  // REVISIT: Need wait here for the listener thread to terminate
132 
133  (void)pthread_kill(m_thread, CONFIG_NXWM_TOUCHSCREEN_SIGNO);
134 
135  // Close the touchscreen device (or should these be done when the thread exits?)
136 
137  if (m_touchFd >= 0)
138  {
139  std::close(m_touchFd);
140  }
141 
142  // Destroy the semaphores that we created.
143 
144  sem_destroy(&m_waitSem);
145 }
146 
147 /**
148  * Start the touchscreen listener thread.
149  *
150  * @return True if the touchscreen listener thread was correctly started.
151  */
152 
154 {
155  pthread_attr_t attr;
156 
157  _info("Starting listener\n");
158 
159  // Start a separate thread to listen for touchscreen events
160 
161  (void)pthread_attr_init(&attr);
162 
163  struct sched_param param;
164  param.sched_priority = CONFIG_NXWM_TOUCHSCREEN_LISTENERPRIO;
165  (void)pthread_attr_setschedparam(&attr, &param);
166 
167  (void)pthread_attr_setstacksize(&attr, CONFIG_NXWM_TOUCHSCREEN_LISTENERSTACK);
168 
169  m_state = LISTENER_STARTED; // The listener thread has been started, but is not yet running
170 
171  int ret = pthread_create(&m_thread, &attr, listener, (FAR void *)this);
172  if (ret != 0)
173  {
174  ginfo("CTouchscreen::start: pthread_create failed: %d\n", ret);
175  return false;
176  }
177 
178  // Detach from the thread
179 
180  (void)pthread_detach(m_thread);
181 
182  // Don't return until we are sure that the listener thread is running
183  // (or until it reports an error).
184 
185  while (m_state == LISTENER_STARTED)
186  {
187  // Wait for the listener thread to wake us up when we really
188  // are connected.
189 
190  (void)sem_wait(&m_waitSem);
191  }
192 
193  // Then return true only if the listener thread reported successful
194  // initialization.
195 
196  _info("Listener m_state=%d\n", (int)m_state);
197  return m_state == LISTENER_RUNNING;
198 }
199 
200 /**
201  * Provide touchscreen calibration data. If calibration data is received (and
202  * the touchscreen is enabled), then received touchscreen data will be scaled
203  * using the calibration data and forward to the NX layer which dispatches the
204  * touchscreen events in window-relative positions to the correct NX window.
205  *
206  * @param data. A reference to the touchscreen data.
207  */
208 
210 {
211  // Save a copy of the calibration data
212 
213  m_calibData = caldata;
214 
215  // Note that we have calibration data. Data will now be scaled and forwarded
216  // to NX (unless we are still in cpature mode)
217 
218  m_calibrated = true;
219 
220  // Wake up the listener thread so that it will use our buffer
221  // to receive data
222 
223  (void)pthread_kill(m_thread, CONFIG_NXWM_TOUCHSCREEN_SIGNO);
224 }
225 
226 /**
227  * Capture raw driver data. This method will capture mode one raw touchscreen
228  * input. The normal use of this method is for touchscreen calibration.
229  *
230  * This function is not re-entrant: There may be only one thread waiting for
231  * raw touchscreen data.
232  *
233  * @return True if the raw touchscreen data was successfully obtained
234  */
235 
236 bool CTouchscreen::waitRawTouchData(struct touch_sample_s *touch)
237 {
238  _info("Capturing touch input\n");
239 
240  // Setup to cpature raw data into the user provided buffer
241 
242  sched_lock();
243  m_touch = touch;
244  m_capture = true;
245 
246  // Wake up the listener thread so that it will use our buffer
247  // to receive data
248 
249  (void)pthread_kill(m_thread, CONFIG_NXWM_TOUCHSCREEN_SIGNO);
250 
251  // And wait for touch data
252 
253  int ret = OK;
254  while (m_capture)
255  {
256  ret = sem_wait(&m_waitSem);
257  DEBUGASSERT(ret == 0 || errno == EINTR);
258  }
259  sched_unlock();
260 
261  // And return success. The listener thread will have (1) reset both
262  // m_touch and m_capture and (2) posted m_waitSem
263 
264  _info("Returning touch input: %d\n", ret);
265  return ret == OK;
266 }
267 
268 /**
269  * The touchscreen listener thread. This is the entry point of a thread that
270  * listeners for and dispatches touchscreen events to the NX server.
271  *
272  * @param arg. The CTouchscreen 'this' pointer cast to a void*.
273  * @return This function normally does not return but may return NULL on
274  * error conditions.
275  */
276 
277 FAR void *CTouchscreen::listener(FAR void *arg)
278 {
279  CTouchscreen *This = (CTouchscreen *)arg;
280 
281 #if CONFIG_TASK_NAME_SIZE > 0
282  prctl(PR_SET_NAME, "CTouchScreen::listener", 0);
283 #endif
284 
285  _info("Listener started\n");
286 
287  // Open the touchscreen device that we just created.
288 
289  This->m_touchFd = std::open(CONFIG_NXWM_TOUCHSCREEN_DEVPATH, O_RDONLY);
290  if (This->m_touchFd < 0)
291  {
292  gerr("ERROR Failed to open %s for reading: %d\n",
293  CONFIG_NXWM_TOUCHSCREEN_DEVPATH, errno);
294  This->m_state = LISTENER_FAILED;
295  sem_post(&This->m_waitSem);
296  return (FAR void *)0;
297  }
298 
299  // Indicate that we have successfully initialized
300 
301  This->m_state = LISTENER_RUNNING;
302  sem_post(&This->m_waitSem);
303 
304  // Now loop, reading and dispatching touchscreen data
305 
306  while (This->m_state == LISTENER_RUNNING)
307  {
308  // We may be running in one of three states
309  //
310  // 1. Disabled or no calibration data: In this case, just wait for a signal
311  // indicating that the state has changed.
312  // 2. Performing calibration and reporting raw touchscreen data
313  // 3. Normal operation, reading touchscreen data and forwarding it to NX
314 
315  // Check if we need to collect touchscreen data. That is, that we are enabled,
316  // AND have calibration data OR if we need to collect data for the calibration
317  // process.
318 
319  while ((!This->m_enabled || !This->m_calibrated) && !This->m_capture)
320  {
321  // No.. just sleep. This sleep will be awakened by a signal if there
322  // is anything for this thread to do
323 
324  sleep(1);
325 
326  // We woke up here either because the one second elapsed or because we
327  // were signalled. In either case we need to check the conditions and
328  // determine what to do next.
329  }
330 
331  // We are going to collect a sample..
332  //
333  // The sample pointer can change dynamically let's sample it once
334  // and stick with that pointer.
335 
336  struct touch_sample_s *sample = This->m_touch;
337 
338  // Read one touchscreen sample
339 
340  _info("Listening for sample %p\n", sample);
341  DEBUGASSERT(sample);
342  ssize_t nbytes = read(This->m_touchFd, sample,
343  sizeof(struct touch_sample_s));
344 
345  // Check for errors
346 
347  if (nbytes < 0)
348  {
349  // The only expect error is to be interrupt by a signal
350 #if defined(CONFIG_DEBUG_GRAPHICS_ERROR) || defined(CONFIG_DEBUG_ASSERTIONS)
351  int errval = errno;
352 
353  gerr("ERROR: read %s failed: %d\n",
354  CONFIG_NXWM_TOUCHSCREEN_DEVPATH, errval);
355  DEBUGASSERT(errval == EINTR);
356 #endif
357  }
358 
359  // On a truly successful read, the size of the returned data will
360  // be greater than or equal to size of one touchscreen sample. It
361  // be greater only in the case of a multi-touch touchscreen device
362  // when multi-touches are reported.
363 
364  else if (nbytes >= (ssize_t)sizeof(struct touch_sample_s))
365  {
366  // Looks like good touchscreen input... process it
367 
368  This->handleMouseInput(sample);
369  }
370  else
371  {
372  gerr("ERROR Unexpected read size=%d, expected=%d\n",
373  nbytes, sizeof(struct touch_sample_s));
374  }
375  }
376 
377  // We should get here only if we were asked to terminate via
378  // m_state = LISTENER_STOPREQUESTED
379 
380  _info("Listener exiting\n");
382  return (FAR void *)0;
383 }
384 
385 /**
386  * Inject touchscreen data into NX as mouse intput
387  */
388 
389 void CTouchscreen::handleMouseInput(struct touch_sample_s *sample)
390 {
391  _info("Touch id: %d flags: %02x x: %d y: %d h: %d w: %d pressure: %d\n",
392  sample->point[0].id, sample->point[0].flags, sample->point[0].x,
393  sample->point[0].y, sample->point[0].h, sample->point[0].w,
394  sample->point[0].pressure);
395 
396  // Verify the touchscreen data
397 
398  if (sample->npoints < 1 ||
399  ((sample->point[0].flags & TOUCH_POS_VALID) == 0 &&
400  (sample->point[0].flags & TOUCH_UP) == 0))
401  {
402  // The pen is (probably) down, but we have do not have valid
403  // X/Y position data to report. This should not happen.
404 
405  return;
406  }
407 
408  // Was this data captured by some external logic? (probably the
409  // touchscreen calibration logic)
410 
411  if (m_capture && sample != &m_sample)
412  {
413  // Yes.. let waitRawTouchData know that the data is available
414  // and restore normal buffering
415 
416  m_touch = &m_sample;
417  m_capture = false;
418  sem_post(&m_waitSem);
419  return;
420  }
421 
422  // Sanity checks. Re-directed touch data should never reach this point.
423  // After posting m_waitSem, m_touch might change asynchronously.
424 
425  DEBUGASSERT(sample == &m_sample);
426 
427  // Check if normal processing of touchscreen data is enabled. Check if
428  // we have been given calibration data.
429 
430  if (!m_enabled || !m_calibrated)
431  {
432  // No.. we are not yet ready to process touchscreen data (We don't
433  // really every get to this condition.
434 
435  return;
436  }
437 
438  // Now we will inject the touchscreen into NX as mouse input. First
439  // massage the data a little so that it behaves a little more like a
440  // mouse with only a left button
441  //
442  // Was the button up or down?
443 
444  uint8_t buttons;
445  if ((sample->point[0].flags & (TOUCH_DOWN | TOUCH_MOVE)) != 0)
446  {
447  buttons = NX_MOUSE_LEFTBUTTON;
448  }
449  else if ((sample->point[0].flags & TOUCH_UP) != 0)
450  {
451  buttons = NX_MOUSE_NOBUTTONS;
452  }
453  else
454  {
455  // The pen is neither up nor down. This should not happen
456 
457  return;
458  }
459 
460  // Get the "raw" touch coordinates (if they are valid)
461 
462  nxgl_coord_t x;
463  nxgl_coord_t y;
464 
465  if ((sample->point[0].flags & TOUCH_POS_VALID) == 0)
466  {
467  x = 0;
468  y = 0;
469  }
470  else
471  {
472 #ifdef CONFIG_NXWM_CALIBRATION_ANISOTROPIC
473  // We have valid coordinates. Get the raw touch
474  // position from the sample
475 
476  float rawX = (float)sample->point[0].x;
477  float rawY = (float)sample->point[0].y;
478 
479  // Create a line (varying in X) that have the same matching Y values
480  // X lines:
481  //
482  // x2 = slope*y1 + offset
483  //
484  // X value calculated on the left side for the given value of y
485 
486  float leftX = rawY * m_calibData.left.slope + m_calibData.left.offset;
487 
488  // X value calculated on the right side for the given value of y
489 
490  float rightX = rawY * m_calibData.right.slope + m_calibData.right.offset;
491 
492  // Line of X values between (m_calibData.leftX,leftX) and (m_calibData.rightX,rightX) the
493  // are possible solutions:
494  //
495  // x2 = slope * x1 - offset
496 
497  struct SCalibrationLine xLine;
498  xLine.slope = (float)((int)m_calibData.rightX - (int)m_calibData.leftX) / (rightX - leftX);
499  xLine.offset = (float)m_calibData.leftX - leftX * xLine.slope;
500 
501  // Create a line (varying in Y) that have the same matching X value
502  // X lines:
503  //
504  // y2 = slope*x1 + offset
505  //
506  // Y value calculated on the top side for a given value of X
507 
508  float topY = rawX * m_calibData.top.slope + m_calibData.top.offset;
509 
510  // Y value calculated on the bottom side for a give value of X
511 
512  float bottomY = rawX * m_calibData.bottom.slope + m_calibData.bottom.offset;
513 
514  // Line of Y values between (topy,m_calibData.topY) and (bottomy,m_calibData.bottomY) that
515  // are possible solutions:
516  //
517  // y2 = slope * y1 - offset
518 
519  struct SCalibrationLine yLine;
520  yLine.slope = (float)((int)m_calibData.bottomY - (int)m_calibData.topY) / (bottomY - topY);
521  yLine.offset = (float)m_calibData.topY - topY * yLine.slope;
522 
523  // Then scale the raw x and y positions
524 
525  float scaledX = rawX * xLine.slope + xLine.offset;
526  float scaledY = rawY * yLine.slope + yLine.offset;
527 
528  x = (nxgl_coord_t)scaledX;
529  y = (nxgl_coord_t)scaledY;
530 
531  _info("raw: (%6.2f, %6.2f) scaled: (%6.2f, %6.2f) (%d, %d)\n",
532  rawX, rawY, scaledX, scaledY, x, y);
533 #else
534  // We have valid coordinates. Get the raw touch
535  // position from the sample
536 
537  uint32_t rawX = (uint32_t)sample->point[0].x;
538  uint32_t rawY = (uint32_t)sample->point[0].y;
539 
540  // Get the fixed precision, scaled X and Y values
541 
542  b16_t scaledX = rawX * m_calibData.xSlope + m_calibData.xOffset;
543  b16_t scaledY = rawY * m_calibData.ySlope + m_calibData.yOffset;
544 
545  // Get integer scaled X and Y positions and clip
546  // to fix in the window
547 
548  int32_t bigX = b16toi(scaledX + b16HALF);
549  int32_t bigY = b16toi(scaledY + b16HALF);
550 
551  // Clip to the display
552 
553  if (bigX < 0)
554  {
555  x = 0;
556  }
557  else if (bigX >= m_windowSize.w)
558  {
559  x = m_windowSize.w - 1;
560  }
561  else
562  {
563  x = (nxgl_coord_t)bigX;
564  }
565 
566  if (bigY < 0)
567  {
568  y = 0;
569  }
570  else if (bigY >= m_windowSize.h)
571  {
572  y = m_windowSize.h - 1;
573  }
574  else
575  {
576  y = (nxgl_coord_t)bigY;
577  }
578 
579  _info("raw: (%d, %d) scaled: (%d, %d)\n", rawX, rawY, x, y);
580 #endif
581  }
582 
583  // Get the server handle and "inject the mouse data
584 
585  NXHANDLE handle = m_server->getServer();
586  (void)nx_mousein(handle, x, y, buttons);
587 }
struct SCalibrationLine left
volatile bool m_enabled
struct SCalibrationLine right
struct touch_sample_s * m_touch
NXWidgets::CNxServer * m_server
volatile bool m_capture
NXHANDLE getServer(void)
Definition: cnxserver.hxx:133
bool waitRawTouchData(struct touch_sample_s *touch)
volatile bool m_calibrated
struct SCalibrationData m_calibData
struct SCalibrationLine top
enum EListenerState m_state
struct SCalibrationLine bottom
static FAR void * listener(FAR void *arg)
void handleMouseInput(struct touch_sample_s *sample)
CTouchscreen(NXWidgets::CNxServer *server, struct nxgl_size_s *windowSize)
struct nxgl_size_s m_windowSize
void setCalibrationData(const struct SCalibrationData &caldata)
struct touch_sample_s m_sample