/****************************************************************************
 * apps/examples/fbcon/fbcon_main.c
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * 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 <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/boardctl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <errno.h>
#include <assert.h>
#include <fcntl.h>
#include <nuttx/video/fb.h>
#include <nuttx/nx/nxfonts.h>
#include <nuttx/video/fb.h>
#include <ctype.h>
#include <spawn.h>
#include <nuttx/debug.h>
#include <poll.h>
#include <nuttx/ascii.h>
#include <nuttx/lib/builtin.h>

#ifndef CONFIG_NX
#  error This application requires NX Graphics
#endif

/* NSH Redirection requires Pipes */

#ifndef CONFIG_DEV_PIPE_SIZE
#  error FIFO and Named Pipe Drivers should be enabled in the configuration
#endif

#ifdef CONFIG_NSH_CLE
#  warning FBCON console does not support much VT100/EMACS/CLE type functionality yet
#endif

#if !defined(CONFIG_EXAMPLES_FBCON_PIPE_STDOUT) && \
    !defined(CONFIG_EXAMPLES_FBCON_PIPE_STDOUT)
#  warning FBCON is not configured for either stdout or stderr!
#endif

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* stdout and stderr configuration */

#define READ_PIPE           0
#define WRITE_PIPE          1
#define POLL_BUFSIZE       64
#define VT100_MAX_SEQUENCE  6 /* Max length of VT100 sequence to check      */

/* VT100 codes that can be decoded */

#define VT100_CLEAREOL  {ASCII_ESC, '[', 'K'}                /* Clear line from cursor right            */
#define VT100_CLEARLINE {ASCII_ESC, '[', '2', 'K'}           /* Clear entire line                       */
#define VT100_CURSOROFF {ASCII_ESC, '[', '?', '2', '5', 'l'} /* Cursor OFF                              */
#define VT100_CURSORON  {ASCII_ESC, '[', '?', '2', '5', 'h'} /* Cursor ON                               */
#define VT100_CURSORL   {ASCII_ESC, '[', '*', 'D'}           /* Move cursor left (* = [1...99]) columns */

/* Configuration ************************************************************/

/* Pixel depth.  If none provided, pick the smallest enabled pixel depth    */

#ifdef CONFIG_EXAMPLES_FBCON_BPP_NX_DEFAULT
#  if !defined(CONFIG_NX_DISABLE_1BPP)
#    define FBCON_BPP 1
#  elif !defined(CONFIG_NX_DISABLE_2BPP)
#    define FBCON_BPP 2
#  elif !defined(CONFIG_NX_DISABLE_4BPP)
#    define FBCON_BPP 4
#  elif !defined(CONFIG_NX_DISABLE_8BPP)
#    define FBCON_BPP 8
#  elif !defined(CONFIG_NX_DISABLE_16BPP)
#    define FBCON_BPP 16
#  elif !defined(CONFIG_NX_DISABLE_24BPP)
#    define FBCON_BPP 24
#  elif !defined(CONFIG_NX_DISABLE_32BPP)
#    define FBCON_BPP 32
#  else
#    error "No pixel depth enabled"
#  endif
#elif defined(CONFIG_EXAMPLES_FBCON_1BPP)
#  define FBCON_BPP 1
#elif defined(CONFIG_EXAMPLES_FBCON_2BPP)
#  define FBCON_BPP 2
#elif defined(CONFIG_EXAMPLES_FBCON_4BPP)
#  define FBCON_BPP 4
#elif defined(CONFIG_EXAMPLES_FBCON_8BPP)
#  define FBCON_BPP 8
#elif defined(CONFIG_EXAMPLES_FBCON_16BPP)
#  define FBCON_BPP 16
#elif defined(CONFIG_EXAMPLES_FBCON_24BPP)
#  define FBCON_BPP 24
#elif defined(CONFIG_EXAMPLES_FBCON_32BPP)
#  define FBCON_BPP 32
#endif

/* Select renderer */

#if (FBCON_BPP == 1)
#  define RENDERER nxf_convert_1bpp
#elif (FBCON_BPP == 2)
#  define RENDERER nxf_convert_2bpp
#elif (FBCON_BPP == 4)
#  define RENDERER nxf_convert_4bpp
#elif (FBCON_BPP == 8)
#  define RENDERER nxf_convert_8bpp
#elif (FBCON_BPP == 16)
#  define RENDERER nxf_convert_16bpp
#elif (FBCON_BPP == 24)
#  define RENDERER nxf_convert_24bpp
#elif (FBCON_BPP == 32)
#  define RENDERER nxf_convert_32bpp
#else
#  error "Unsupported CONFIG_EXAMPLES_FBCON_BPP"
#endif

/* Background and font color, if defaults chosen */

#ifndef CONFIG_EXAMPLES_FBCON_BGCOLOR
#  if (FBCON_BPP == 24) || (FBCON_BPP == 32)
#    define FBCON_BGCOLOR 0x00000000
#  elif FBCON_BPP == 16
#    define FBCON_BGCOLOR 0x0000
#  else
#    define FBCON_BGCOLOR 0x0
#  endif
#else
#  define FBCON_BGCOLOR CONFIG_EXAMPLES_FBCON_BGCOLOR
#endif

#ifndef CONFIG_EXAMPLES_FBCON_FCOLOR
#  if (FBCON_BPP == 32) || (FBCON_BPP == 24)
#    define FBCON_FCOLOR 0xffffff
#  elif FBCON_BPP == 16
#    define FBCON_FCOLOR 0xffff
#  else
#    define FBCON_FCOLOR 0xff
#  endif
#else
#  define FBCON_FCOLOR CONFIG_EXAMPLES_FBCON_FCOLOR
#endif

/* Console font ID */

#ifndef CONFIG_EXAMPLES_FBCON_FONTID
#  ifndef NXFONT_DEFAULT
#    error NXFONT_DEFAULT not defined
#  endif
#  define FBCON_FONTID NXFONT_DEFAULT
#else
#  define FBCON_FONTID CONFIG_EXAMPLES_FBCON_FONTID
#endif

/* Font glyph caching */

#ifndef CONFIG_EXAMPLES_FBCON_GLCACHE
#  define FBCON_GLCACHE 16
#else
#  define FBCON_GLCACHE CONFIG_EXAMPLES_FBCON_GLCACHE
#endif

/* Bitmap flags */

#define BMFLAGS_NOGLYPH (1 << 0) /* No glyph available, use space */
#define BM_ISSPACE(bm)  (((bm)->flags & BMFLAGS_NOGLYPH) != 0)

/* Sizes and maximums */

#define MAX_USECNT      255  /* Limit to range of a uint8_t */

/* Line spacing.  Space (in rows) between lines. */

#define FBCON_LINESPACING CONFIG_EXAMPLES_FBCON_LINESPACING

/* Cursor character */

#define FBCON_CURSORCHAR CONFIG_EXAMPLES_FBCON_CURSORCHAR

/* Spawn task */

#define SPAWN_TASK CONFIG_EXAMPLES_FBCON_SPAWN_TASK

/****************************************************************************
 * Private Types
 ****************************************************************************/

enum exitcode_e
{
  FBCON_EXIT_SUCCESS = 0,
  FBCON_EXIT_FAIL,
  FBCON_EXIT_FD,
  FBCON_EXIT_GETVINFO,
  FBCON_EXIT_GETPINFO,
  FBCON_EXIT_FBMEM,
  FBCON_EXIT_FBMEM2,
  FBCON_EXIT_FBIO_OVERLAY_INFO,
  FBCON_EXIT_FBIO_SELECT_OVERLAY,
  FBCON_EXIT_FONTOPEN,
  FBCON_EXIT_SETBGCOLOR,
  FBCON_EXIT_STDOUT_PIPE_FAILED,
  FBCON_EXIT_STDERR_PIPE_FAILED,
  FBCON_EXIT_STDIN_PIPE_FAILED,
  FBCON_EXIT_LOCAL_STDERR_PIPE_FAILED,
  FBCON_EXIT_LOCAL_STDOUT_PIPE_FAILED,
  FBCON_EXIT_POSIX_SPAWN_ACTIONS_INIT_FAILED,
  FBCON_EXIT_POSIX_SPAWN_FAILED,
  FBCON_EXIT_APP_INDEX_UNAVAILABLE,
  FBCON_EXIT_APP_POSIX_ATTRIB_INIT_FAILED,
};

/* Describes one cached glyph bitmap */

struct fbcon_glyph_s
{
  uint8_t                    code;       /* Character code                  */
  uint8_t                    height;     /* Height of this glyph (in rows)  */
  uint8_t                    width;      /* Width of this glyph (in pixels) */
  uint8_t                    stride;     /* Width of the glyph row (bytes)  */
  uint8_t                    usecnt;     /* Use count                       */
  FAR uint8_t               *bitmap;     /* Allocated bitmap memory         */
};

/* Describes on character on the display */

struct fbcon_bitmap_s
{
  uint8_t                    code;       /* Character code                  */
  uint8_t                    flags;      /* See BMFLAGS_*                   */
  struct nxgl_point_s        pos;        /* Character position              */
};

/* Describes the state of one text display */

struct fbcon_state_s
{
  int                        fd_fb;
  FAR void                  *fbcon_font;
  struct fb_videoinfo_s     *vinfo;
  struct fb_planeinfo_s     *pinfo;
#ifdef CONFIG_FB_OVERLAY
  struct fb_overlayinfo_s    oinfo;
#endif
  FAR void                  *fbmem;
#if 0
  /* Revisit needed - no support got dual framebuffers as yet */

  FAR void                  *fbmem2;
  FAR void                  *act_fbmem;
#endif
  uint32_t                   mem2_yoffset;

  /* The following describe the console */

  nxgl_mxpixel_t             bcolor;    /* Console background color         */
  nxgl_mxpixel_t             fcolor;    /* Console font color               */
  struct nxgl_size_s         wsize;     /* Console size                     */
  uint8_t                    fheight;   /* Max height of a font in pixels   */
  uint8_t                    fwidth;    /* Max width of a font in pixels    */
  uint8_t                    spwidth;   /* The width of a space             */

  /* These describe all text already added to the display */

  uint8_t                    maxglyphs; /* Size of the glyph[] array        */
  uint16_t                   maxchars;  /* Size of the bm[] array           */
  uint32_t                   nchars;    /* Numb of chars in the bm[] array  */

  FAR struct fbcon_bitmap_s *bm;        /* List of bitmaps on the display   */
  FAR struct fbcon_glyph_s  *glyph;     /* Cache of rendered fonts in use   */
  FAR struct fbcon_bitmap_s  cursor;    /* Character to use for cursor      */

  /* VT100 escape sequence processing                                       */

  char                       seq[VT100_MAX_SEQUENCE]; /* Buffered chars     */
  uint8_t                    nseq;                    /* Num buffered chars */
  int                        nwild;                   /* Num wild collected */
  int                        wildval;                 /* Wildcard value     */
};

typedef void (*seqhandler_t)(FAR struct fbcon_state_s *st);

/* Identifies the state of the VT100 escape sequence processing */

enum fbcon_vt100state_e
{
  VT100_NOT_CONSUMED = 0, /* Character is not part of a VT100 escape sequence */
  VT100_CONSUMED,         /* Character was consumed as part of the VT100 escape processing */
  VT100_PROCESSED,        /* The full VT100 escape sequence was processed */
  VT100_ABORT             /* Invalid/unsupported character in buffered escape sequence */
};

struct vt100_sequence_s
{
  FAR const char            *seq;
  seqhandler_t               handler;
  uint8_t                    size;
};

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/
#if 0
/* Revisit needed = no support for dual framebuffers yet */

static int fb_init_mem2(FAR struct fbcon_state_s *st);
#endif
static int fbdev_get_pinfo(int fd, FAR struct fb_planeinfo_s *pinfo);
static int fbcon_initialize(FAR struct fbcon_state_s *st);
static void fbcon_newline(FAR struct fbcon_state_s *st);
static int fbcon_hidechar(FAR FAR struct fbcon_state_s *st,
                          FAR const struct fbcon_bitmap_s *bm);
static enum fbcon_vt100state_e fbcon_vt100(FAR struct fbcon_state_s *st,
                                           char ch);
static void fbcon_fillchar(FAR struct fbcon_state_s *st,
                           FAR const struct nxgl_rect_s *rect,
                           FAR const struct fbcon_bitmap_s *bm);
static int fbcon_fill(FAR struct fbcon_state_s *st,
                      FAR struct nxgl_rect_s *rect,
                      FAR nxgl_mxpixel_t *color);
static void fbcon_fillspace(FAR struct fbcon_state_s *st,
                            FAR const struct nxgl_rect_s *rect,
                            FAR const struct fbcon_bitmap_s *bm);
static int fbcon_bitmap(FAR struct fbcon_state_s     *st,
                        FAR const struct nxgl_rect_s *dest,
                        FAR const uint32_t           *src,
                        unsigned int                 stride);
static int fbcon_backspace(FAR struct fbcon_state_s *st);
static void fbcon_home(FAR struct fbcon_state_s *st);
static inline void fbcon_movedisplay(FAR struct fbcon_state_s *st,
                                     int bottom, int scrollheight);
static inline void fbcon_scroll(FAR struct fbcon_state_s *st,
                                int scrollheight);
static void fbcon_freeglyph(FAR struct fbcon_glyph_s *glyph);
static inline FAR struct fbcon_glyph_s *
                              fbcon_allocglyph(FAR struct fbcon_state_s *st);
static FAR struct fbcon_glyph_s *
                   fbcon_findglyph(FAR struct fbcon_state_s *st, uint8_t ch);
static inline FAR struct fbcon_glyph_s *
                          fbcon_renderglyph(FAR struct fbcon_state_s *st,
                          FAR const struct nx_fontbitmap_s *fbm, uint8_t ch);
static int fbcon_fontsize(FAR void *hfont, uint8_t ch,
                          FAR struct nxgl_size_s *size);
static FAR struct fbcon_glyph_s *
  fbcon_getglyph(FAR struct fbcon_state_s *st, uint8_t ch);
static FAR const struct fbcon_bitmap_s *
  fbcon_addchar(FAR struct fbcon_state_s *st, uint8_t ch);
static void fbcon_write(FAR struct fbcon_state_s *st,
                        FAR char *buffer, size_t buflen);
static bool has_input(int fd);
static void poll_std_streams(FAR struct fbcon_state_s *st);
static void fbcon_putc(FAR struct fbcon_state_s *st, uint8_t ch);
static enum fbcon_vt100state_e fbcon_vt100seq(
                                  FAR struct fbcon_state_s *st, int seqsize);
static FAR const struct vt100_sequence_s *
                  fbcon_vt100part(FAR struct fbcon_state_s *st, int seqsize);
static enum fbcon_vt100state_e fbcon_vt100(FAR struct fbcon_state_s *st,
                                           char ch);
static void fbcon_erasetoeol(FAR struct fbcon_state_s *st);
static void fbcon_clearline(FAR struct fbcon_state_s *st);
static void fbcon_showcursor(FAR struct fbcon_state_s *st);
static void fbcon_hidecursor(FAR struct fbcon_state_s *st);
static void fbcon_cursorl(FAR struct fbcon_state_s *st);

/****************************************************************************
 * Private Data
 ****************************************************************************/

/* Pipes for NSH Shell: stdin, stdout, stderr */

#ifdef CONFIG_EXAMPLES_FBCON_PIPE_STDIN
static int g_nsh_stdin[2];
#endif
static int g_nsh_stdout[2];
static int g_nsh_stderr[2];

/* The VT100 sequences supported by FBCON                                   */

static const char g_erasetoeol[] = VT100_CLEAREOL;
static const char g_clearline[]  = VT100_CLEARLINE;
static const char g_cursoroff[]  = VT100_CURSOROFF;
static const char g_cursoron[]   = VT100_CURSORON;
static const char g_cursorl[]    = VT100_CURSORL;

static const struct vt100_sequence_s g_vt100sequences[] =
{
  {g_erasetoeol, fbcon_erasetoeol, sizeof(g_erasetoeol)},
  {g_clearline,  fbcon_clearline,  sizeof(g_clearline)},
  {g_cursoroff,  fbcon_hidecursor, sizeof(g_cursoroff)},
  {g_cursoron,   fbcon_showcursor, sizeof(g_cursoroff)},
  {g_cursorl,    fbcon_cursorl,    sizeof(g_cursorl)},
  {NULL, NULL, 0}
};

#ifdef CONFIG_EXAMPLES_FBCON_SHOW_WELCOME
#  ifdef CONFIG_EXAMPLES_FBCON_PIPE_STDOUT
static const char g_stdout_hello[] = "Hello FBCON stdout fprintf output!";
#  endif
#  ifdef CONFIG_EXAMPLES_FBCON_PIPE_STDERR
static const char g_stderr_hello[] = "Hello FBCON stderr fprintf output!";
#  endif
#endif

/****************************************************************************
 * Public Data
 ****************************************************************************/

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: fbcon_showcursor
 *
 * Description:
 *   Render the cursor character at the current display position.
 *
 * Input Parameters:
 *   st - pointer to FBCON status structure
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void fbcon_showcursor(FAR struct fbcon_state_s *st)
{
  int lineheight;

  if ((st->cursor.pos.x + st->fwidth) > st->wsize.w)
    {
#ifndef CONFIG_EXAMPLES_FBCON_NOWRAP
      /* No.. move to the next line */

      fbcon_newline(st);
#else
      return OK;
#endif
    }

  /* Check if we need to scroll up */

  lineheight = st->fheight + FBCON_LINESPACING;
  while (st->cursor.pos.y >= st->wsize.h + lineheight)
    {
      fbcon_scroll(st, lineheight);
    }

  /* Render the cursor glyph onto the display. */

  fbcon_fillchar(st, NULL, &st->cursor);
}

/****************************************************************************
 * Name: fbcon_hidecursor
 *
 * Description:
 *   Remove the cursor character at the current display position.
 *
 * Input Parameters:
 *   st - pointer to FBCON status structure
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void fbcon_hidecursor(FAR struct fbcon_state_s *st)
{
  fbcon_hidechar(st, &st->cursor);
}

/****************************************************************************
 * Name: fbcon_cursorl
 *
 * Description:
 *   Move the cursor position to the left n characters.
 *
 * Input Parameters:
 *   st - pointer to FBCON status structure
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void fbcon_cursorl(FAR struct fbcon_state_s *st)
{
  int i;

  /* Revisit needed. It seems backspace 1 less than calculated.
   * This is illogical and yet to be explained and is perhaps related to
   * nsh itself when nsh is the spawned app/task.
   */

  for (i = 1; i < st->wildval; i++)
    {
      fbcon_backspace(st);
    }
}

/****************************************************************************
 * Name: fbcon_erasetoeol
 *
 * Description:
 *   Handle the erase-to-eol VT100 escape sequence.
 *   Erase from and including cursor position
 *
 * Input Parameters:
 *   st - pointer to FBCON status structure
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void fbcon_erasetoeol(FAR struct fbcon_state_s *st)
{
  struct nxgl_rect_s rect;

  /* Create a bounding box the size of the remaining iine */

  rect.pt1.x = st->cursor.pos.x;
  rect.pt2.x = st->wsize.w - 1;
  rect.pt1.y = st->cursor.pos.y;
  rect.pt2.y = rect.pt1.y + st->fheight + FBCON_LINESPACING - 1;

  /* Clear the region */

  if (fbcon_fill(st, &rect, &st->bcolor) < 0)
    {
      gerr("ERROR: fbcon_fill failed: %d\n", errno);
    }

  /* Because we were clearing from the cursor position, there's no need
   * to modify st->nchar as there are no characters after the cursor.
   */
}

/****************************************************************************
 * Name: fbcon_clearline
 *
 * Description:
 *   Handle the clearline VT100 escape sequence
 *
 * Input Parameters:
 *   st - pointer to FBCON status structure
 *
 * Returned Value:
 *   The index of the match in g_vt100sequences[]
 *
 ****************************************************************************/

static void fbcon_clearline(FAR struct fbcon_state_s *st)
{
  int                        i;
  FAR struct fbcon_bitmap_s *bm;
  struct nxgl_rect_s         rect;

  /* Create a bounding box the size of the iine */

  rect.pt1.x = 0;
  rect.pt2.x = st->wsize.w - 1;
  rect.pt1.y = st->cursor.pos.y + FBCON_LINESPACING;
  rect.pt2.y = st->cursor.pos.y + st->fheight + FBCON_LINESPACING - 1;

  /* Clear the region */

  if (fbcon_fill(st, &rect, &st->bcolor) < 0)
    {
      gerr("ERROR: fbcon_fill failed: %d\n", errno);
    }

  st->cursor.pos.x = st->spwidth;

  /* Decrement nchar for each character within in the bounding box */

  i = st->nchars;
  while (--i > 0)
    {
      bm = &st->bm[i];
      if (bm->pos.y <= rect.pt1.y &&
          bm->pos.y + st->fheight >= rect.pt2.y)
        {
          st->nchars--;
        }
    }
}

/****************************************************************************
 * Name: fbcon_bitmap
 *
 * Description:
 *   Copy a rectangular region of a larger image into the rectangle in the
 *   specified window.
 *
 * Input Parameters:
 *   st     - pointer to FBCON status structure
 *   dest   - Describes the rectangular region on the display that will
 *            receive the bit map.
 *   src    - The start of the source image.
 *   stride - The width of the full source image in pixels.
 *
 * Returned Value:
 *   OK on success; ERROR on failure with errno set appropriately
 *
 ****************************************************************************/

static int fbcon_bitmap(FAR struct fbcon_state_s     *st,
                        FAR const struct nxgl_rect_s *dest,
                        FAR const uint32_t           *src,
                        unsigned int                 stride)
{
  FAR uint32_t *dst;
  FAR uint8_t  *row;
  struct fb_area_s area;
  int x;
  int y;

  area.h = dest->pt2.y - dest->pt1.y + 1;
  area.w = dest->pt2.x - dest->pt1.x + 1;
  area.x = dest->pt1.x;
  area.y = dest->pt1.y;

  row = (FAR uint8_t *)st->fbmem + st->pinfo->stride * area.y;
  for (y = 0; y < area.h; y++)
    {
      dst = ((FAR uint32_t *)row) + area.x;
      for (x = 0; x < area.w; x++)
        {
          *dst++ = *(uint32_t *)src++;
        }

      row += st->pinfo->stride;
    }

  return OK;
}

/****************************************************************************
 * Name: fbcon_movedisplay
 *
 * Description:
 *   This function implements the data movement for the scroll operation.
 *
 * Input Parameters:
 *   st           - pointer to FBCON status structure
 *   bottom       - Start of the vacated area at the bottom to be cleared
 *   scrollheight - The distance the display must be scrolled
 *
 * Returned Value:
 *   OK on success; ERROR on failure with errno set appropriately
 *
 ****************************************************************************/

static inline void fbcon_movedisplay(struct fbcon_state_s *st, int bottom,
                                     int scrollheight)
{
  FAR struct fbcon_bitmap_s *bm;
  struct nxgl_rect_s         rect;
  nxgl_coord_t               row;
  int                        ret;
  int                        i;

  /* Move each row, one at a time.  They could all be moved at once but since
   * the region is cleared, then re-written, the effect would not be good.
   * Below the region is also cleared and re-written,
   * however, in much smaller chunks.
   */

  rect.pt1.x = 0;
  rect.pt2.x = st->wsize.w - 1;

  for (row = FBCON_LINESPACING; row < bottom; row += scrollheight)
    {
      /* Create a bounding box the size of one row of characters */

      rect.pt1.y = row;
      rect.pt2.y = row + scrollheight - 1;

      /* Clear the region */

      ret = fbcon_fill(st, &rect, &st->bcolor);
      if (ret < 0)
        {
          gerr("ERROR: fbcon_fill failed: %d\n", errno);
        }

      /* Fill each character that might lie within in the bounding box */

      for (i = 0; i < st->nchars; i++)
        {
          bm = &st->bm[i];
          if (bm->pos.y <= rect.pt1.y &&
              bm->pos.y + st->fheight >= rect.pt2.y)
            {
              fbcon_fillchar(st, &rect, bm);
            }
        }
    }

  /* Finally, clear the vacated part of the display */

  rect.pt1.y = bottom;
  rect.pt2.y = st->wsize.h - 1;

  ret = fbcon_fill(st, &rect, &st->bcolor);
  if (ret < 0)
    {
      fprintf(stderr, "fbcon_movedisplay: fbcon_fill failed: %d\n", errno);
    }
}

/****************************************************************************
 * Name: fbcon_scroll
 *
 * Description:
 *   Scroll the display up by a certain amount
 *
 * Input Parameters:
 *   st           - pointer to FBCON status structure
 *   scrollheight - The distance the display must be scrolled
 *
 * Returned Value:
 *   OK on success; ERROR on failure with errno set appropriately
 *
 ****************************************************************************/

static inline void fbcon_scroll(struct fbcon_state_s *st, int scrollheight)
{
  int i;
  int j;

  /* Adjust the vertical position of each character */

  for (i = 0; i < st->nchars; )
    {
      FAR struct fbcon_bitmap_s *bm = &st->bm[i];

      /* Has any part of this character scrolled off the screen? */

      if (bm->pos.y < scrollheight + FBCON_LINESPACING)
        {
          /* Yes... Delete the character by moving all of the data */

          for (j = i; j < st->nchars - 1; j++)
            {
              memcpy(&st->bm[j], &st->bm[j + 1],
                     sizeof(struct fbcon_bitmap_s));
            }

          /* Decrement the number of cached characters ('i' is not
           * incremented in this case because it already points to the next
           * character)
           */

          st->nchars--;
        }

      /* No.. just decrement its vertical position (moving it "up" the
       * display by one line).
       */

      else
        {
          bm->pos.y -= scrollheight;

          /* We are keeping this one so increment to the next character */

          i++;
        }
    }

  /* And move the next display position up by one line as well */

  st->cursor.pos.y -= scrollheight;

  /* Move the display in the range of 0-height up one scrollheight. */

  fbcon_movedisplay(st, st->cursor.pos.y, scrollheight);
}

/****************************************************************************
 * Name: fbcon_freeglyph
 *
 * Description:
 *   Clear the specified glyph structure
 *
 * Input Parameters:
 *   glyph - pointer to glyph structure to be freed
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void fbcon_freeglyph(FAR struct fbcon_glyph_s *glyph)
{
  if (glyph->bitmap)
    {
      free(glyph->bitmap);
    }

    memset(glyph, 0, sizeof(struct fbcon_glyph_s));
}

/****************************************************************************
 * Name: fbcon_allocglyph
 *
 * Description:
 *   Allocate a glyph structure
 *
 * Input Parameters:
 *   glyph - pointer to glyph structure to be allocated
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static inline FAR struct fbcon_glyph_s *
                         fbcon_allocglyph(FAR struct fbcon_state_s *st)
{
  FAR struct fbcon_glyph_s *glyph = NULL;
  FAR struct fbcon_glyph_s *luglyph = NULL;
  uint8_t luusecnt;
  int i;

  /* Search through the glyph cache looking for an unused glyph.  Also, keep
   * track of the least used glyph as well.  We need that if we have to
   * replace a glyph in the cache.
   */

  for (i = 0; i < st->maxglyphs; i++)
    {
      /* Is this glyph in use? */

      glyph = &st->glyph[i];
      if (!glyph->usecnt)
        {
          /* No.. return this glyph with a use count of one */

          glyph->usecnt = 1;
          return glyph;
        }

      /* Yes.. check for the least recently used */

      if (!luglyph || glyph->usecnt < luglyph->usecnt)
        {
          luglyph = glyph;
        }
    }

  /* If we get here, the glyph cache is full.  We replace the least used
   * glyph with the one we need now. (luglyph can't be NULL).
   */

  luusecnt = luglyph->usecnt;
  fbcon_freeglyph(luglyph);

  /* But lets decrement all of the usecnts so that the new one one be so
   * far behind in the counts as the older ones.
   */

  if (luusecnt > 1)
    {
      uint8_t decr = luusecnt - 1;

      for (i = 0; i < st->maxglyphs; i++)
        {
          /* Is this glyph in use? */

          glyph = &st->glyph[i];
          if (glyph->usecnt > decr)
            {
              glyph->usecnt -= decr;
            }
        }
    }

  /* Then return the least used glyph */

  luglyph->usecnt = 1;
  return luglyph;
}

/****************************************************************************
 * Name: fbcon_findglyph
 *
 * Description:
 *   Try and find a glyph in the cache for a given character
 *
 * Input Parameters:
 *   st - pointer to FBCON status structure
 *   ch - the character
 *
 * Returned Value:
 *   a pointer to the glyph structure if found or NULL if not
 *
 ****************************************************************************/

static FAR struct fbcon_glyph_s *
                  fbcon_findglyph(FAR struct fbcon_state_s *st, uint8_t ch)
{
  int i;

  /* Try to find the glyph in the cache of pre-rendered glyphs */

  for (i = 0; i < st->maxglyphs; i++)
    {
      FAR struct fbcon_glyph_s *glyph = &st->glyph[i];
      if (glyph->usecnt > 0 && glyph->code == ch)
        {
          /* Increment the use count (unless it is already at the max) */

          if (glyph->usecnt < MAX_USECNT)
            {
               glyph->usecnt++;
            }

          /* And return the glyph that we found */

          return glyph;
        }
    }

  return NULL;
}

/****************************************************************************
 * Name: fbcon_renderglyph
 *
 * Description:
 *   Render a character as a glyph based on the font bitmap and metrics
 *
 * Input Parameters:
 *   st  - pointer to FBCON status structure
 *   fbm - pointer to the character bitmap/metrics
 *   ch  - the character to be rendered
 *
 * Returned Value:
 *   a pointer to the rendered glyph structure
 *
 ****************************************************************************/

static inline FAR struct fbcon_glyph_s *
                           fbcon_renderglyph(FAR struct fbcon_state_s *st,
                           FAR const struct nx_fontbitmap_s *fbm, uint8_t ch)
{
  FAR struct fbcon_glyph_s *glyph = NULL;
  FAR nxgl_mxpixel_t *ptr;
#if FBCON_BPP < 8
  nxgl_mxpixel_t pixel;
#endif
  int bmsize;
  int row;
  int col;
  int ret;

  /* Make sure that there is room for another glyph */

  ginfo("ch=%c [%02x]\n", isprint(ch) ? ch : '.', ch);

  /* Allocate the glyph (always succeeds) */

  glyph         = fbcon_allocglyph(st);
  glyph->code   = ch;

  /* Get the dimensions of the glyph */

  glyph->width  = fbm->metric.width + fbm->metric.xoffset;
  glyph->height = fbm->metric.height + fbm->metric.yoffset;

  /* Allocate memory to hold the glyph with its offsets */

  glyph->stride = (glyph->width * FBCON_BPP + 7) / 8;
  bmsize        =  glyph->stride * glyph->height;
  glyph->bitmap = (FAR uint8_t *)malloc(bmsize);

  if (glyph->bitmap)
    {
      /* Initialize the glyph memory to the background color */

#if FBCON_BPP < 8
      pixel  = st->bcolor;
#  if FBCON_BPP == 1
      /* Pack 1-bit pixels into a 2-bits */

      pixel &= 0x01;
      pixel  = (pixel) << 1 | pixel;
#  endif
#  if FBCON_BPP < 4
      /* Pack 2-bit pixels into a nibble */

      pixel &= 0x03;
      pixel  = (pixel) << 2 | pixel;
#  endif

      /* Pack 4-bit nibbles into a byte */

      pixel &= 0x0f;
      pixel  = (pixel) << 4 | pixel;

      ptr    = (FAR nxgl_mxpixel_t *)glyph->bitmap;
      for (row = 0; row < glyph->height; row++)
        {
          for (col = 0; col < glyph->stride; col++)
            {
              /* Transfer the packed bytes into the buffer */

              *ptr++ = pixel;
            }
        }

#elif FBCON_BPP == 24
#  error "Additional logic is needed here for 24bpp support"

#else /* FBCON_BPP = {8,16,32} */

      ptr = (FAR nxgl_mxpixel_t *)glyph->bitmap;
      for (row = 0; row < glyph->height; row++)
        {
          /* Just copy the color value into the glyph memory */

          for (col = 0; col < glyph->width; col++)
            {
              *ptr++ = st->bcolor;
            }
        }
#endif

      /* Then render the glyph into the allocated memory */

      ret = RENDERER((FAR nxgl_mxpixel_t *)glyph->bitmap,
                      glyph->height, glyph->width, glyph->stride,
                      fbm, st->fcolor);
      if (ret < 0)
        {
          /* Actually, the RENDERER never returns a failure */

          gerr("ERROR: fbcon_renderglyph: RENDERER failed\n");
          fbcon_freeglyph(glyph);
          glyph = NULL;
        }
    }

  return glyph;
}

/****************************************************************************
 * Name: fbcon_fontsize
 *
 * Description:
 *   Get the size of a given character bitmap
 *
 * Input Parameters:
 *   font - the font of interest
 *   ch   - the character of interest
 *   size _ pointer to the structure to return the character size
 *
 * Returned Value:
 *   Success or ERROR
 *
 ****************************************************************************/

static int fbcon_fontsize(FAR void *hfont, uint8_t ch,
                          FAR struct nxgl_size_s *size)
{
  FAR const struct nx_fontbitmap_s *fbm;

  /* No, it is not cached... Does the code map to a font? */

  fbm = nxf_getbitmap(hfont, ch);
  if (fbm)
    {
      /* Yes.. return the font size */

      size->w = fbm->metric.width + fbm->metric.xoffset;
      size->h = fbm->metric.height + fbm->metric.yoffset;
      return OK;
    }

  return ERROR;
}

/****************************************************************************
 * Name: fbcon_getglyph
 *
 * Description:
 *   Get rendered glyph data for a given character
 *
 * Input Parameters:
 *   st  - pointer to FBCON status structure
 *   ch  - the character to be rendered
 *
 * Returned Value:
 *   a pointer to the glyph data
 *
 ****************************************************************************/

static FAR struct fbcon_glyph_s *
  fbcon_getglyph(FAR struct fbcon_state_s *st, uint8_t ch)
{
  FAR struct fbcon_glyph_s *glyph;
  FAR const struct nx_fontbitmap_s *fbm;

  /* First, try to find the glyph in the cache of pre-rendered glyphs */

  glyph = fbcon_findglyph(st, ch);
  if (!glyph)
    {
      /* No, it is not cached... Does the code map to a font? */

      fbm = nxf_getbitmap(st->fbcon_font, ch);
      if (fbm)
        {
          /* Yes.. render the glyph */

          glyph = fbcon_renderglyph(st, fbm, ch);
        }
    }

  return glyph;
}

/****************************************************************************
 * Name: fbcon_hidechar
 *
 * Description:
 *   Erase a character from the display.
 *
 * Input Parameters:
 *   st  - pointer to FBCON status structure
 *   bm  - pointer to the character bitmap to be erased
 *
 * Returned Value:
 *   Success or error value
 *
 ****************************************************************************/

static int fbcon_hidechar(FAR struct fbcon_state_s *st,
                          FAR const struct fbcon_bitmap_s *bm)
{
  struct nxgl_rect_s bounds;
  struct nxgl_size_s fsize;
  int ret;

  /* Get the size of the font glyph.  If fbcon_fontsize, then the
   * character will have been rendered as a space, and no display
   * modification is required (not an error).
   */

  ret = fbcon_fontsize(st->fbcon_font, bm->code, &fsize);
  if (ret < 0)
    {
      /* It was rendered as a space. */

      return OK;
    }

  /* Construct a bounding box for the glyph */

  bounds.pt1.x = bm->pos.x;
  bounds.pt1.y = bm->pos.y;
  bounds.pt2.x = bm->pos.x + fsize.w - 1;
  bounds.pt2.y = bm->pos.y + fsize.h - 1;

  /* Fill the bitmap region with the background color, erasing the
   * character from the display.
   */

  return fbcon_fill(st, &bounds, &st->bcolor);
}

/****************************************************************************
 * Name: fbcon_addchar
 *
 * Description:
 *   Find or create a bitmap structurefor a character
 *
 * Input Parameters:
 *   st  - pointer to FBCON status structure
 *   ch  - the character to be rendered
 *
 * Returned Value:
 *   Pointer to the character bitmap
 *
 ****************************************************************************/

static FAR const struct fbcon_bitmap_s *
                        fbcon_addchar(FAR struct fbcon_state_s *st,
                        uint8_t ch)
{
  FAR struct fbcon_bitmap_s *bm = NULL;
  FAR struct fbcon_glyph_s *glyph;

  /* Setup the bitmap information */

  bm        = &st->bm[st->nchars];
  bm->code  = ch;
  bm->flags = 0;
  bm->pos.x = st->cursor.pos.x;
  bm->pos.y = st->cursor.pos.y;

  /* Find (or create) the matching glyph */

  glyph = fbcon_getglyph(st, ch);
  if (!glyph)
    {
      /* No, there is no font for this code.  Just mark this as a space. */

      bm->flags |= BMFLAGS_NOGLYPH;

      /* Set up the next character position */

      st->cursor.pos.x += st->spwidth;
    }
  else
    {
      /* Set up the next character position */

      st->cursor.pos.x += glyph->width;
    }

  /* Increment nchars to retain this character */

  if (st->nchars < st->maxchars)
    {
      st->nchars++;
    }

  return bm;
}

/****************************************************************************
 * Name: fbcon_fill
 *
 * Description:
 *   Fill a region with a colour.
 *
 * Input Parameters:
 *   st    - pointer to FBCON status structure
 *   rect  - Describes the rectangular region on the display to be filled
 *   color - The color to fill the rectangle with
 *
 * Returned Value:
 *   OK on success; ERROR on failure with errno set appropriately
 *
 ****************************************************************************/

static int fbcon_fill(FAR struct fbcon_state_s *st,
                      FAR struct nxgl_rect_s *rect,
                      FAR nxgl_mxpixel_t *color)
{
  FAR uint32_t *dest;
  FAR uint8_t *row;
  struct fb_area_s area;
  int x;
  int y;

  area.h = rect->pt2.y - rect->pt1.y + 1;
  area.w = rect->pt2.x - rect->pt1.x + 1;
  area.x = rect->pt1.x;
  area.y = rect->pt1.y;

  row = (FAR uint8_t *)st->fbmem + st->pinfo->stride * area.y;
  for (y = 0; y < area.h; y++)
    {
      dest = (FAR uint32_t *)row + area.x;
      for (x = 0; x < area.w; x++)
        {
          *dest++ = *color;
        }

      row += st->pinfo->stride;
    }

  return OK;
}

/****************************************************************************
 * Name: fbcon_backspace
 *
 * Description:
 *   Remove the last character from the window.
 *
 * Input Parameters:
 *   st - pointer to FBCON status structure
 *
 * Returned Value:
 *   OK on success; ERROR on failure with errno set appropriately
 *
 ****************************************************************************/

static int fbcon_backspace(FAR struct fbcon_state_s *st)
{
  FAR struct fbcon_bitmap_s *bm;
  int ndx;
  int ret = -ENOENT;

  /* Is there a character on the display? */

  if (st->nchars > 0)
    {
      /* Yes.. Get the index to the last bitmap on the display */

      ndx = st->nchars - 1;
      bm  = &st->bm[ndx];

      /* Erase the character from the display */

      ret = fbcon_hidechar(st, bm);

      /* The current position to the location where the last character was */

      st->cursor.pos.x = bm->pos.x;
      st->cursor.pos.y = bm->pos.y;

      /* Decrement nchars to discard this character */

      st->nchars = ndx;
    }

  return ret;
}

/****************************************************************************
 * Name: fbcon_home
 *
 * Description:
 *   Set the next character position to the top-left corner of the display.
 *
 * Input Parameters:
 *   st - pointer to FBCON status structure
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void fbcon_home(FAR struct fbcon_state_s *st)
{
  /* The first character is one space from the left */

  st->cursor.pos.x = st->spwidth;

  /* And FBCON_LINESPACING lines from the top */

  st->cursor.pos.y = FBCON_LINESPACING;

  /* And reset number of characters in the bm buffer */

  st->nchars = 0;
}

/****************************************************************************
 * Name: fbcon_newline
 *
 * Description:
 *   Set the next character position to the beginning of the next line.
 *
 * Input Parameters:
 *   st - pointer to FBCON status structure
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

void fbcon_newline(FAR struct fbcon_state_s *st)
{
  /* Carriage return: The first character is one space from the left */

  st->cursor.pos.x = st->spwidth;

  /* Linefeed: Down the max font height + FBCON_LINESPACING */

  st->cursor.pos.y += (st->fheight + FBCON_LINESPACING);
}

/****************************************************************************
 * Name: fbcon_putc
 *
 * Description:
 *   Render the specified character at the current display position.
 *
 * Input Parameters:
 *   st    - pointer to FBCON status structure
 *   ch  - the character to be rendered
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

void fbcon_putc(FAR struct fbcon_state_s *st, uint8_t ch)
{
  FAR const struct fbcon_bitmap_s *bm;
  int lineheight;

  /* Ignore carriage returns */

  if (ch == '\r')
    {
      return;
    }

  /* Handle backspace (treating both BS and DEL as backspace) */

  if (ch == ASCII_BS || ch == ASCII_DEL)
    {
      fbcon_backspace(st);
      return;
    }

  /* Will another character fit on this line? */

  if (st->cursor.pos.x + st->fwidth > st->wsize.w)
    {
#ifndef CONFIG_EXAMPLES_FBCON_NOWRAP
      /* No.. move to the next line */

      fbcon_newline(st);

      /* If we were about to output a newline character, then don't */

      if (ch == ASCII_LF)
        {
          return;
        }
#else
      /* No.. Ignore all further characters until a newline is encountered */

      if (ch != ASCII_LF)
        {
          return;
        }
#endif
    }

  /* If it is a newline character, then just perform the logical newline
   * operation.
   */

  if (ch == ASCII_LF)
    {
      fbcon_newline(st);
      return;
    }

  /* Check if we need to scroll up  */

  lineheight = st->fheight + FBCON_LINESPACING;
  while (st->cursor.pos.y > st->wsize.h - lineheight)
    {
      fbcon_scroll(st, lineheight);
    }

  /* Find the glyph associated with the character and render it
   * onto the display.
   */

  bm = fbcon_addchar(st, ch);
  if (bm)
    {
      fbcon_fillchar(st, NULL, bm);
    }
}

/****************************************************************************
 * Name: fbcon_fillspace
 *
 * Description:
 *   Handle the special case of a space being displayed
 *
 * Input Parameters:
 *   st  - pointer to FBCON status structure
 *   rect- Describes the rectangular region on the display that will
 *         receive the space.
 *   bm  - pointer to the character bitmap structure with the location
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void fbcon_fillspace(FAR struct fbcon_state_s *st,
  FAR const struct nxgl_rect_s *rect,
  FAR const struct fbcon_bitmap_s *bm)
{
  struct nxgl_rect_s bounds;
  struct nxgl_rect_s intersection;
  int ret;

  /* Construct a bounding box for the glyph */

  bounds.pt1.x = bm->pos.x;
  bounds.pt1.y = bm->pos.y;
  bounds.pt2.x = bm->pos.x + st->spwidth - 1;
  bounds.pt2.y = bm->pos.y + st->fheight - 1;

  /* Should this also be clipped to a region in the window? */

  if (rect != NULL)
    {
      /* Get the intersection of the redraw region and the character bitmap */

      nxgl_rectintersect(&intersection, rect, &bounds);
    }
  else
    {
      /* The intersection is the whole glyph */

      nxgl_rectcopy(&intersection, &bounds);
    }

  /* Check for empty intersections */

  if (!nxgl_nullrect(&intersection))
    {
      /* Fill the bitmap region with the background color, erasing the
       * character from the display.
       */

      ret = fbcon_fill(st, &intersection, &st->bcolor);
      if (ret < 0)
        {
          gerr("ERROR: fill() method failed: %d\n", ret);
        }
    }
}

/****************************************************************************
 * Name: fbcon_fillchar
 *
 * Description:
 *   Implement the character display
 *
 * Input Parameters:
 *   st  - pointer to FBCON status structure
 *   rect- Describes the rectangular region on the display that will
 *         receive the space.
 *   bm  - pointer to the character bitmap structure with the location
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

void fbcon_fillchar(FAR struct fbcon_state_s *st,
                    FAR const struct nxgl_rect_s *rect,
                    FAR const struct fbcon_bitmap_s *bm)
{
  FAR struct fbcon_glyph_s *glyph;
  struct nxgl_rect_s bounds;
  struct nxgl_rect_s intersection;
  struct nxgl_size_s fsize;
  int ret;

  /* Handle the special case of spaces which have no glyph bitmap */

  if (BM_ISSPACE(bm))
    {
      fbcon_fillspace(st, rect, bm);
      return;
    }

  /* Get the size of the font glyph (which may not have been created yet) */

  ret = fbcon_fontsize(st->fbcon_font, bm->code, &fsize);
  if (ret < 0)
    {
      /* This would mean that there is no bitmap for the character code and
       * that the font would be rendered as a space.  But this case should
       * never happen here because the BM_ISSPACE() should have already
       * found all such cases.
       */

      return;
    }

  /* Construct a bounding box for the glyph */

  bounds.pt1.x = bm->pos.x;
  bounds.pt1.y = bm->pos.y;
  bounds.pt2.x = bm->pos.x + fsize.w - 1;
  bounds.pt2.y = bm->pos.y + fsize.h - 1;

  /* Should this also be clipped to a region in the window? */

  if (rect != NULL)
    {
      /* Get the intersection of the redraw region and the character bitmap */

      nxgl_rectintersect(&intersection, rect, &bounds);
    }
  else
    {
      /* The intersection is the whole glyph */

      nxgl_rectcopy(&intersection, &bounds);
    }

  /* Check for empty intersections */

  if (!nxgl_nullrect(&intersection))
    {
      FAR const void *src;

      /* Find (or create) the glyph that goes with this font */

      glyph = fbcon_getglyph(st, bm->code);
      if (!glyph)
        {
          /* Shouldn't happen */

          return;
        }

      /* Blit the font bitmap into the window */

      src = (FAR const void *)glyph->bitmap;

      ret = fbcon_bitmap(st, &intersection, src,
                         (unsigned int)glyph->stride);
      if (ret < 0)
        {
          gerr("ERROR: fbcon_fillchar: fbcon_bitmapwindow failed: %d\n",
               ret);
        }
    }
}

/****************************************************************************
 * Name: fbcon_vt100part
 *
 * Description:
 *   Return the next entry that is a partial match to the sequence.
 *
 * Input Parameters:
 *   st       - Driver data structure
 *   seqsize  - The number of bytes in the sequence
 *
 * Returned Value:
 *   A pointer to the matching sequence in g_vt100sequences[]
 *
 ****************************************************************************/

FAR const struct vt100_sequence_s *
     fbcon_vt100part(FAR struct fbcon_state_s *st, int seqsize)
{
  FAR static const struct vt100_sequence_s *seq;
  static int                                ndx;
  static int                                i;
  static bool                               aborted;
  static int                                numw;
  static bool                               collecting;

  /* Search from the beginning of the sequence table */

  for (ndx = 0; g_vt100sequences[ndx].seq; ndx++)
    {
      seq = &g_vt100sequences[ndx];

      /* Is this sequence big enough? */

      aborted = false;
      numw = 0;
      st->nwild = 0;

      /* compare characters received to those in the decodable sequences    */

      collecting = false;
      for (i = 0; i < seqsize; i++)
        {
          if (seq->seq[i] == '*')
            {
              /* This sequence has a wildcard */

              if ((st->seq[i] < ASCII_0) || (st->seq[i] > ASCII_9))
                {
                  aborted = true;
                  break;
                }

              if (++numw > 1)
                {
                  /* This logic only allows collection of one wildcard
                   *  per VT100 sequence
                   */

                  aborted = true;
                  break;
                }

              collecting = true;
              st->wildval = st->seq[i] - ASCII_0; /* convert from ASCII */
            }
          else if (collecting)
            {
              if ((st->seq[i] >= ASCII_0) && (st->seq[i] <= ASCII_9))
                {
                  st->wildval *= 10;
                  st->wildval += st->seq[i] - ASCII_0;
                  st->nwild++;
                }
              else
                {
                  collecting = false;
                }
            }
          else if (st->seq[i] != seq->seq[i])
            {
              aborted = true;
              break;
            }
        }

      if (!aborted)
        {
          return seq;
        }
    }

  return NULL;
}

/****************************************************************************
 * Name: fbcon_vt100seq
 *
 * Description:
 *   Determine if the new sequence is a part of a supported VT100 escape
 *   sequence.
 *
 * Input Parameters:
 *   st      - Driver data structure
 *   seqsize - The number of bytes in the sequence
 *
 * Returned Value:
 *   state - See enum fbcon_vt100state_e;
 *
 ****************************************************************************/

static enum fbcon_vt100state_e fbcon_vt100seq(
                                   FAR struct fbcon_state_s *st, int seqsize)
{
  FAR const struct vt100_sequence_s *seq;
  enum fbcon_vt100state_e            ret;

  /* Is there any VT100 escape sequence that matches what we have
   * buffered so far?
   */

  seq = fbcon_vt100part(st, seqsize);
  if (seq)
    {
      /* Yes.. if the size of that escape sequence is the same as that
       *  buffered, then we have an exact match.
       */

      if (seqsize >= seq->size + st->nwild)
        {
          /* Process the VT100 sequence */

          seq->handler(st);
          st->nseq = 0;
          return VT100_PROCESSED;
        }

      /* The 'seqsize' is still smaller than the potential match(es).  We
       * will need to collect more characters before we can make a decision.
       * Return an indication that we have consumed the character.
       */

      return VT100_CONSUMED;
    }

  /* We get here on a failure.  The buffer sequence is not part of any
   * supported VT100 escape sequence.  If seqsize > 1 then we need to
   * return a special value because we have to re-process the buffered
   * data.
   */

  ret = seqsize > 1 ? VT100_ABORT : VT100_NOT_CONSUMED;
  return ret;
}

/****************************************************************************
 * Name: fbcon_vt100
 *
 * Description:
 *   Test if the newly received byte is part of a VT100 escape sequence
 *
 * Input Parameters:
 *   st - Driver data structure
 *   ch - The newly received character
 *
 * Returned Value:
 *   state - See enum fbcon_vt100state_e;
 *
 ****************************************************************************/

static enum fbcon_vt100state_e fbcon_vt100(FAR struct fbcon_state_s *st,
                                           char ch)
{
  enum fbcon_vt100state_e ret;
  int seqsize;

  /* If we have no buffered characters, then 'ch' must be the first character
   * of an escape sequence.
   */

  if (st->nseq < 1)
    {
      /* The first character of an escape sequence must be an an escape
       * character (!).
       */

      if (ch != ASCII_ESC)
        {
          return VT100_NOT_CONSUMED;
        }

      /* Add the escape character to the buffer but don't bother with any
       * further checking.
       */

      st->seq[0] = ASCII_ESC;
      st->nseq   = 1;

      return VT100_CONSUMED;
    }

  /* Temporarily add the next character to the buffer */

  seqsize = st->nseq;
  st->seq[seqsize] = ch;

  /* Then check if this sequence is part of an a valid escape sequence */

  seqsize++;
  ret = fbcon_vt100seq(st, seqsize);
  if (ret == VT100_CONSUMED)
    {
      /* The newly added character is indeed part of a VT100 escape sequence
       * (which is still incomplete).  Keep it in the buffer.
       */

      st->nseq = seqsize;
    }

  return ret;
}

/****************************************************************************
 * Name: fbcon_write
 *
 * Description:
 *   Put a sequence of characters on the display.
 *
 * Input Parameters:
 *   st     - pointer to FBCON status structure
 *   buffer - pointer to the character array to be displayed
 *   buflen - number of characters to be displayed
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void fbcon_write(FAR struct fbcon_state_s *st,
                        FAR char *buffer, size_t buflen)
{
  char ch;
  enum fbcon_vt100state_e state;
  ssize_t remaining;

  fbcon_hidecursor(st);
  for (remaining = buflen; remaining > 0; remaining--)
    {
      ch = *buffer++;

#if CONFIG_EXAMPLES_FBCON_VT100_DECODE
      /* Check if this character is part of a VT100 escape sequence */

      do
        {
          /* Is the character part of a VT100 escape sequence? */

          state = fbcon_vt100(st, ch);
          switch (state)
            {
              /* Character is not part of a VT100 escape sequence (and no
               * characters are buffer.
               */

              default:
              case VT100_NOT_CONSUMED:
                {
                  /* We can output the character to the window */

                  fbcon_putc(st, (uint8_t)ch);
                }
              break;

            /* The full VT100 escape sequence was processed (and the new
             * character was consumed)
             */

            case VT100_PROCESSED:

            /* Character was consumed as part of the VT100 escape processing
             * (but the escape sequence is still incomplete.
             */

            case VT100_CONSUMED:
              {
                /* Do nothing... the VT100 logic owns the character */
              }
              break;

            /* Invalid/unsupported character in escape sequence */

            case VT100_ABORT:
              {
                int i;

                /* Add the first unhandled character to the window */

                fbcon_putc(st, (uint8_t)st->seq[0]);

                /* Move all buffer characters down one */

                for (i = 1; i < st->nseq; i++)
                  {
                    st->seq[i - 1] = st->seq[i];
                  }

                st->nseq--;

                /* Then loop again and check if what remains is part of a
                 * VT100 escape sequence.  We could speed this up by
                 * checking if st->seq[0] == ASCII_ESC.
                 */
              }
              break;
            }
        }
      while (state == VT100_ABORT);
#else
    /* Just output the character */

    fbcon_putc(st, ch);
#endif /* CONFIG_EXAMPLES_FBCON_VT100_DECODE */
    }

  fbcon_showcursor(st);
}

/****************************************************************************
 * Name: fbdev_get_pinfo
 *
 * Description:
 *   Get plane information for a framebuffer
 *   Note - does not support dual framebuffer memory
 *
 * Input Parameters:
 *   fd    - file descriptor of the framebuffer
 *   pinfo - pointer to the3 structure for the plane info
 *
 * Returned Value:
 *   OK
 *
 ****************************************************************************/

static int fbdev_get_pinfo(int fd, FAR struct fb_planeinfo_s *pinfo)
{
  if (ioctl(fd, FBIOGET_PLANEINFO, (unsigned long)((uintptr_t)pinfo)) < 0)
    {
      int errcode = errno;
      gerr("ERROR: ioctl(FBIOGET_PLANEINFO) failed: %d\n", errcode);
      return EXIT_FAILURE;
    }

  /* Only these pixel depths are supported.  viinfo.fmt is ignored, only
   * certain color formats are supported.
   */

  if (pinfo->bpp != 32 && pinfo->bpp != 24 &&
      pinfo->bpp != 16 && pinfo->bpp != 8 &&
      pinfo->bpp != 1)
    {
      gerr("ERROR: bpp=%u not supported\n", pinfo->bpp);
      return EXIT_FAILURE;
    }

  return OK;
}

#if 0
/* Revisit needed = no support for dual framebuffers yet */

/****************************************************************************
 * Name: fb_init_mem2
 *
 * Description:
 *   Initialise the memory for the second framebuffer
 *
 * Input Parameters:
 *   st    - pointer to FBCON status structure
 *
 * Returned Value:
 *   OK
 *
 ****************************************************************************/

static int fb_init_mem2(FAR struct fbcon_state_s *st)
{
  int ret;
  uintptr_t buf_offset;
  struct fb_planeinfo_s pinfo;

  memset(&pinfo, 0, sizeof(pinfo));
  pinfo.display = st->pinfo->display + 1;

  if ((ret = fbdev_get_pinfo(st->fd_fb, &pinfo)) < 0)
    {
      return EXIT_FAILURE;
    }

  /* Check bpp */

  if (pinfo.bpp != st->pinfo->bpp)
    {
      gerr("ERROR: fbmem2 is incorrect");
      return -EINVAL;
    }

  /* Check the buffer address offset,
   * It needs to be divisible by pinfo->stride
   */

  buf_offset = pinfo.fbmem - st->fbmem;

  if ((buf_offset % st->pinfo->stride) != 0)
    {
      gerr("ERROR: It is detected that buf_offset(%" PRIuPTR ") "
           "and stride(%d) are not divisible, please ensure "
           "that the driver handles the address offset by itself.\n",
            buf_offset, st->pinfo->stride);
    }

  /* Calculate the address and yoffset of fbmem2 */

  if (buf_offset == 0)
    {
      /* Use consecutive fbmem2. */

      st->mem2_yoffset = st->vinfo->yres;
      st->fbmem2 = pinfo.fbmem + st->mem2_yoffset * pinfo.stride;
      gerr("ERROR: Use consecutive fbmem2 = %p, yoffset = %" PRIu32"\n",
             st->fbmem2, st->mem2_yoffset);
    }
  else
    {
      /* Use non-consecutive fbmem2. */

      st->mem2_yoffset = buf_offset / st->pinfo->stride;
      st->fbmem2 = pinfo.fbmem;
      gerr("ERROR: Use non-consecutive fbmem2 = %p, yoffset = %" PRIu32"\n",
            st->fbmem2, st->mem2_yoffset);
    }

  return OK;
}
#endif

/****************************************************************************
 * Name: fbcon_initialize
 *
 * Description:
 *   Initialise the Framebuffer Console
 *
 * Input Parameters:
 *   st - pointer to FBCON status structure
 *
 * Returned Value:
 *   Success or failure code
 *
 ****************************************************************************/

static int fbcon_initialize(FAR struct fbcon_state_s *st)
{
  struct nxgl_rect_s         rect;
  FAR const struct nx_font_s *fontset;
  int                        ret;

  /* Get the configured font handles */

  st->fbcon_font = nxf_getfonthandle(FBCON_FONTID);
  if (!st->fbcon_font)
    {
      gerr("ERROR: fbcon_main: Failed to get console font handle: %d\n",
           errno);
      return FBCON_EXIT_FONTOPEN;
    }

  st->bcolor = FBCON_BGCOLOR;
  st->fcolor = FBCON_FCOLOR;

  rect.pt1.x = 0;
  rect.pt1.y = 0;
  rect.pt2.x = st->vinfo->xres - 1;
  rect.pt2.y = st->vinfo->yres - 1;
  ret = fbcon_fill(st, &rect, &st->bcolor);
  if (ret < 0)
    {
      gerr("ERROR: fbcon_main: fbcon_setbgcolor failed: %d\n", errno);
      return FBCON_EXIT_SETBGCOLOR;
    }

  fontset         = nxf_getfontset(st->fbcon_font);
  st->fheight     = fontset->mxheight;
  st->fwidth      = fontset->mxwidth;
  st->spwidth     = fontset->spwidth;

  /* we use the entire LCD area for the console */

  st->wsize.h     = st->vinfo->yres;
  st->wsize.w     = st->vinfo->xres;

  st->cursor.code  = FBCON_CURSORCHAR;
  if (FBCON_CURSORCHAR != ASCII_SPACE)
    {
      st->cursor.flags = 0;
    }
  else
    {
      st->cursor.flags = BMFLAGS_NOGLYPH;
    }

  st->nchars       = 0;
  st->maxglyphs    = FBCON_GLCACHE;

  /* The first character is one space from the left
   * and FBCON_LINESEPARATION lines from the top
   */

  st->cursor.pos.x = st->spwidth;
  st->cursor.pos.y = FBCON_LINESPACING;
  st->maxchars     = (st->wsize.w / st->fwidth) *
                     (st->wsize.h / (st->fheight + FBCON_LINESPACING) - 1);

  st->bm = malloc(st->maxchars * sizeof(struct fbcon_bitmap_s));
  if (st->bm == NULL)
    {
      gerr("ERROR: Unable to allocate display buffer memory");
      return -ENOMEM;
    }

  memset(st->bm, 0, st->maxchars * sizeof(struct fbcon_bitmap_s));

  st->glyph = malloc(FBCON_GLCACHE * (sizeof(struct fbcon_glyph_s)));
  if (st->glyph == NULL)
    {
      gerr("ERROR: Unable to allocate glyph cache memory");
      return -ENOMEM;
    }

  memset(st->glyph, 0, FBCON_GLCACHE * (sizeof(struct fbcon_glyph_s)));

  st->nseq  = 0;

  return OK;
}

/****************************************************************************
 * Name: has_input
 *
 * Description:
 *   Return true if a File Descriptor has data to be read.
 *
 * Input Parameters:
 *   fd - File Descriptor to be checked
 *
 * Returned Value:
 *   True if File Descriptor has data to be read; False otherwise
 *
 ****************************************************************************/

static bool has_input(int fd)
{
  int ret;

  /* Poll the File Descriptor for input */

  struct pollfd fdp;
  fdp.fd = fd;
  fdp.events = POLLIN;
  ret = poll(&fdp,  /* File Descriptors                                     */
            1,      /* Number of File Descriptors                           */
            0);     /* Poll Timeout (Milliseconds)                          */

  if (ret > 0)
    {
      /* If poll is OK and there is input */

      if ((fdp.revents & POLLIN) != 0)
        {
          /* Report that there is input */

          return true;
        }

      /* Else report no input */

      return false;
    }
  else if (ret == 0)
    {
      /* If timeout, report no input */

      return false;
    }
  else if (ret < 0)
    {
      /* Handle error */

      fprintf(stderr, "poll failed: %d, fd=%d\n", ret, fd);
      return false;
    }

  /* Never comes here */

  assert(false);
  return false;
}

/****************************************************************************
 * Name: poll_std_streams
 *
 * Description:
 *   Poll NSH stdout and stderr for output and display the output.
 *
 * Input Parameters:
 *   st - pointer to FBCON status structure
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void poll_std_streams(FAR struct fbcon_state_s *st)
{
  ssize_t     num_ch;
  static char buf[POLL_BUFSIZE];

  assert(g_nsh_stdout[READ_PIPE] != 0);
  assert(g_nsh_stderr[READ_PIPE] != 0);
#ifdef CONFIG_EXAMPLES_FBCON_PIPE_STDIN  
  assert(g_nsh_stdin[READ_PIPE] != 0);
#endif

#ifdef CONFIG_EXAMPLES_FBCON_PIPE_STDERR
  /* Poll NSH stderr to check if there's output to be processed */

  if (has_input(g_nsh_stderr[READ_PIPE]))
    {
      /* Write it to display */

      num_ch = read(g_nsh_stderr[READ_PIPE], buf, POLL_BUFSIZE);
      if (num_ch > 0)
        {
          /* display */

          fbcon_write(st, buf, num_ch);
        }
    }
#endif /* CONFIG_EXAMPLES_FBCON_PIPE_STDERR */

#ifdef CONFIG_EXAMPLES_FBCON_PIPE_STDOUT
  /* Poll NSH stdout to check if there's output to be processed */

  if (has_input(g_nsh_stdout[READ_PIPE]))
    {
      /* Read the output from NSH stdout */

      num_ch = read(g_nsh_stdout[READ_PIPE], buf, POLL_BUFSIZE);
      if (num_ch > 0)
        {
          /* Write it to display */

          fbcon_write(st, buf, num_ch);
        }
    }
#endif /* CONFIG_EXAMPLES_FBCON_PIPE_STDOUT */

#ifdef CONFIG_EXAMPLES_FBCON_PIPE_STDIN
  if (has_input(STDIN_FILENO))
    {
      /* Get and process a character */

      ssize_t num = 0;
      char ch;
      num_ch = read(STDIN_FILENO, &ch, 1);

      if (num_ch != 1)
        {
          fprintf(stderr, "STDIN read failed\n");
          return;
        }

      /* copy it to spawned app/process */

      num = write(g_nsh_stdin[WRITE_PIPE], &ch, 1);
      if (num != num_ch)
        {
          fprintf(stderr, "STDIN write failed\n");
          return;
        }

#  if defined(CONFIG_NSH_READLINE)
      fbcon_write(st, buf, num_ch);
#  elif defined(CONFIG_NSH_CLE)
      if (ch == ASCII_LF)
        {
          fbcon_write(st, &ch, 1);
        }
#  endif
    }
#endif /* CONFIG_EXAMPLES_FBCON_PIPE_STDIN */
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: fbcon_main
 *
 * Description:
 *   fbcon entry point
 *
 ****************************************************************************/

int main(int argc, FAR char *argv[])
{
  FAR struct fbcon_state_s   *st;
  int                         index;
  int                         ret;
  pid_t                       pid;
  posix_spawn_file_actions_t  actions;
  posix_spawnattr_t           attr;
  FAR const struct builtin_s *builtin;
  int                         exitcode = FBCON_EXIT_SUCCESS;
  FAR const char             *fbdev   = CONFIG_EXAMPLES_FBCON_DEF_FB;

  /* There is a single optional argument:  The path to the framebuffer
   * driver.
   */

  if (argc == 2)
    {
      fbdev = argv[1];
    }
  else if (argc != 1)
    {
      fprintf(stderr, "ERROR: Single argument required\n");
      fprintf(stderr, "USAGE: %s [<fb-driver-path>]\n", argv[0]);
      return FBCON_EXIT_FAIL;
    }

  st = malloc(sizeof(struct fbcon_state_s));
  if (st == NULL)
    {
      gerr("ERROR: Unable to allocate fbcon_state_s memory");
      return -ENOMEM;
    }

  st->pinfo = malloc(sizeof(struct fb_planeinfo_s));
  if (st->pinfo == NULL)
    {
      gerr("ERROR: Unable to allocate pinfo memory");
      return -ENOMEM;
    }

  st->vinfo = malloc(sizeof(struct fb_videoinfo_s));
  if (st->vinfo == NULL)
    {
      gerr("ERROR: Unable to allocate vinfo memory");
      return -ENOMEM;
    }

  /* Open the framebuffer driver */

  st->fd_fb = open(fbdev, O_RDWR);
  if (st->fd_fb < 0)
    {
      int errcode = errno;
      gerr("ERROR: Failed to open %s: %d\n", fbdev, errcode);
      return FBCON_EXIT_FD;
    }

  /* Get the characteristics of the framebuffer */

  ret = ioctl(st->fd_fb, FBIOGET_VIDEOINFO,
              (unsigned long)((uintptr_t)st->vinfo));
  if (ret < 0)
    {
      int errcode = errno;
      fprintf(stderr, "ERROR: ioctl(FBIOGET_VIDEOINFO) failed: %d\n",
                       errcode);
      exitcode = FBCON_EXIT_GETVINFO;
      goto errout;
    }

#ifdef CONFIG_FB_OVERLAY
  gerr("ERROR: noverlays: %u\n", st->vinfo->noverlays);

  /* Select the first overlay, which should be the composed framebuffer */

  ret = ioctl(st->fd_fb, FBIO_SELECT_OVERLAY, 0);
  if (ret < 0)
    {
      int errcode = errno;
      gerr("ERROR: ioctl(FBIO_SELECT_OVERLAY) failed: %d\n", errcode);
      ret = FBCON_EXIT_FBIO_SELECT_OVERLAY
      goto errout;
    }

  /* Get the first overlay information */

  st->oinfo.overlay = 0;
  ret = ioctl(st->fd_fb, FBIOGET_OVERLAYINFO,
                        (unsigned long)((uintptr_t)&st->oinfo));
  if (ret < 0)
    {
      int errcode = errno;
      gerr("ERROR: ioctl(FBIOGET_OVERLAYINFO) failed: %d\n", errcode);
      ret = FBCON_EXIT_FBIO_OVERLAY_INFO
      goto errout;
    }

  /* select default framebuffer layer */

  ret = ioctl(st->fd_fb, FBIO_SELECT_OVERLAY, FB_NO_OVERLAY);
  if (ret < 0)
    {
      int errcode = errno;
      gerr("ERROR: ioctl(FBIO_SELECT_OVERLAY) failed: %d\n", errcode);
      ret = FBCON_EXIT_FBIO_SELECT_OVERLAY
      goto errout;
    }

#endif

  if ((ret = fbdev_get_pinfo(st->fd_fb, st->pinfo)) < 0)
    {
      ret = FBCON_EXIT_GETPINFO;
      goto errout;
    }

  /* mmap() the framebuffer.
   *
   * NOTE: In the FLAT build the frame buffer address returned by the
   * FBIOGET_PLANEINFO IOCTL command will be the same as the framebuffer
   * address.  mmap(), however, is the preferred way to get the framebuffer
   * address because in the KERNEL build, it will perform the necessary
   * address mapping to make the memory accessible to the application.
   */

  st->fbmem = mmap(NULL, st->pinfo->fblen, PROT_READ | PROT_WRITE,
                     MAP_SHARED | MAP_FILE, st->fd_fb, 0);
  if (st->fbmem == MAP_FAILED)
    {
      gerr("ERROR: ioctl(FBIOGET_PLANEINFO) failed: %d\n", errno);
      ret = FBCON_EXIT_FBMEM;
      goto errout;
    }

#if 0
  /* Revisit needed = no support for dual framebuffers yet */

  if (st->pinfo->yres_virtual == (st->vinfo->yres * 2))
    {
      if ((ret = fb_init_mem2(st)) < 0)
        {
          ret = FBCON_EXIT_FBMEM2;
          goto errout;
        }
    }

#endif
  ret = fbcon_initialize(st);
  if (ret != OK)
    {
      exitcode = ret;
      goto errout;
    }

  fbcon_home(st);
  fbcon_showcursor(st);

  /* Pipe and duplicate STDOUT and/or STDERR */

#ifdef CONFIG_EXAMPLES_FBCON_PIPE_STDOUT
  ret = pipe(g_nsh_stdout);
  if (ret < 0)
    {
      _err("stdout pipe failed: %d\n", errno);
      return ERROR;
    }

  close(STDOUT_FILENO);
  dup2(g_nsh_stdout[WRITE_PIPE], 1);
#endif
#ifdef CONFIG_EXAMPLES_FBCON_PIPE_STDERR
  ret = pipe(g_nsh_stderr);
  if (ret < 0)
    {
      _err("stderr pipe failed: %d\n", errno);
      return ERROR;
    }

  close(STDERR_FILENO);
  dup2(g_nsh_stderr[WRITE_PIPE], 2);
#endif

  ret = posix_spawn_file_actions_init(&actions);
  if (ret != 0)
    {
      gerr("ERROR: unable to init spawn sctiond for"
           "requested task " "\"""%s""\"" ".\n", SPAWN_TASK);
      exitcode = FBCON_EXIT_POSIX_SPAWN_ACTIONS_INIT_FAILED;
      goto errout;
    }

#ifdef CONFIG_EXAMPLES_FBCON_PIPE_STDIN
  /* Spawned app/task may need to intercept and forward STDIN
   * otherwise any waiting input characters can be missed if read by the
   * spawned app/task first.
   */

  ret = pipe(g_nsh_stdin);
  if (ret < 0)
    {
      gerr("ERROR: stdin pipe failed: %d\n", errno);
      exitcode =  FBCON_EXIT_STDIN_PIPE_FAILED;
      goto errout;
    }

  posix_spawn_file_actions_addclose(&actions, STDIN_FILENO);
  posix_spawn_file_actions_adddup2(&actions, g_nsh_stdin[READ_PIPE],
                                   STDIN_FILENO);

#endif /* CONFIG_EXAMPLES_FBCON_PIPE_STDIN */

  index = builtin_isavail(SPAWN_TASK);
  if (index < 0)
    {
      gerr("ERROR: requested task " "\"""%s""\"" " not available.\n",
            SPAWN_TASK);
      exitcode = FBCON_EXIT_APP_INDEX_UNAVAILABLE;
      goto errout;
    }

  builtin = builtin_for_index(index);
  if (builtin == NULL)
    {
      gerr("ERROR: requested task " "\"""%s""\""
          " has no scheduling parameters.\n", SPAWN_TASK);
      exitcode = FBCON_EXIT_APP_INDEX_UNAVAILABLE;
      goto errout;
    }

  /* Set up for app/task spawn */

  ret = posix_spawnattr_init(&attr);
  if (ret != 0)
    {
      gerr("ERROR: unable to init spawn attributes for"
           "requested task " "\"""%s""\"" ".\n", SPAWN_TASK);
      exitcode = FBCON_EXIT_APP_POSIX_ATTRIB_INIT_FAILED;
      goto errout;
    }

  attr.stacksize = builtin->stacksize;
  attr.priority  = builtin->priority;

  /* Spawn required application                                             */

  ret = posix_spawn(&pid,       /* Returned Task ID                         */
                    SPAWN_TASK, /* Task Path                                */
                    &actions,   /* Replace STDIN and/or STDOUT and/or STDIN */
                    &attr,      /* Attributes of app/task                   */
                    NULL,       /* Arguments                                */
                    NULL);      /* No environment                           */
  if (ret < 0)
    {
      int errcode = errno;
      gerr("ERROR: posix_spawn failed: %d\n", errcode);
      exitcode = FBCON_EXIT_POSIX_SPAWN_FAILED;
    }

#ifdef CONFIG_EXAMPLES_FBCON_SHOW_WELCOME
#  ifdef CONFIG_EXAMPLES_FBCON_PIPE_STDOUT
  fprintf(stdout, "%s\n", g_stdout_hello);
  fflush(stdout);
#  endif
#  ifdef CONFIG_EXAMPLES_FBCON_PIPE_STDERR
  fprintf(stderr, "%s\n", g_stderr_hello);
  fflush(stderr);
#  endif
#endif

  for (; ; )
    {
      poll_std_streams(st);
      usleep(10000);
    }

errout:
  close(st->fd_fb);
  fprintf(stderr, "FBCON exiting with error %d\n", exitcode);
  return exitcode;
}
