nuttx-apps/graphics/twm4nx/apps/cnxterm.cxx
Xiang Xiao e76ab9c868 Remove all fclose with stdin, stdout and stderr
since it is wrong to close the builtin stream and specially note
https://pubs.opengroup.org/onlinepubs/9699919799/functions/fclose.html:

Since after the call to fclose() any use of stream results in
undefined behavior, fclose() should not be used on stdin, stdout,
or stderr except immediately before process termination (see XBD
Process Termination), so as to avoid triggering undefined behavior
in other standard interfaces that rely on these streams. If there
are any atexit() handlers registered by the application, such a
call to fclose() should not occur until the last handler is
finishing. Once fclose() has been used to close stdin, stdout, or
stderr, there is no standard way to reopen any of these streams.

and it is also unnecessary because the stream always get flushed.

Signed-off-by: Xiang Xiao <xiaoxiang@xiaomi.com>
2020-10-29 20:43:59 +09:00

673 lines
18 KiB
C++

/////////////////////////////////////////////////////////////////////////////
// apps/graphics/twm4nx/src/cnxterm.cxx
// NxTerm window
//
// Copyright (C) 2019 Gregory Nutt. All rights reserved.
// Author: Gregory Nutt <gnutt@nuttx.org>
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in
// the documentation and/or other materials provided with the
// distribution.
// 3. Neither the name NuttX nor the names of its contributors may be
// used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
// Included Files
/////////////////////////////////////////////////////////////////////////////
#include <nuttx/config.h>
#include <cstdio>
#include <cstdlib>
#include <cunistd>
#include <cfcntl>
#include <ctime>
#include <cassert>
#include <sys/boardctl.h>
#include <semaphore.h>
#include <debug.h>
#include "nshlib/nshlib.h"
#include "graphics/nxwidgets/cwidgetcontrol.hxx"
#include "graphics/nxglyphs.hxx"
#include "graphics/twm4nx/twm4nx_config.hxx"
#include "graphics/twm4nx/ctwm4nx.hxx"
#include "graphics/twm4nx/cwindow.hxx"
#include "graphics/twm4nx/cwindowfactory.hxx"
#include "graphics/twm4nx/cmainmenu.hxx"
#include "graphics/twm4nx/apps/nxterm_config.hxx"
#include "graphics/twm4nx/apps/cnxterm.hxx"
/////////////////////////////////////////////////////////////////////////////
// Pre-Processor Definitions
/////////////////////////////////////////////////////////////////////////////
// Configuration ////////////////////////////////////////////////////////////
#ifdef CONFIG_NSH_USBKBD
# warning You probably do not really want CONFIG_NSH_USBKBD, try CONFIG_TWM4NX_KEYBOARD_USBHOST
#endif
/////////////////////////////////////////////////////////////////////////////
// Private Types
/////////////////////////////////////////////////////////////////////////////
namespace Twm4Nx
{
/////////////////////////////////////////////////////////////////////////////
// Private Types
/////////////////////////////////////////////////////////////////////////////
/**
* This structure is used to pass start up parameters to the NxTerm task
* and to assure the the NxTerm is successfully started.
*/
struct SNxTerm
{
FAR void *console; /**< The console 'this' pointer use with on_exit() */
sem_t exclSem; /**< Sem that gives exclusive access to this structure */
sem_t waitSem; /**< Sem that posted when the task is initialized */
NXTKWINDOW hwnd; /**< Window handle */
NXTERM nxterm; /**< NxTerm handle */
int minor; /**< Next device minor number */
struct nxterm_window_s wndo; /**< Describes the NxTerm window */
bool success; /**< True if successfully initialized */
};
/////////////////////////////////////////////////////////////////////////////
// Private Data
/////////////////////////////////////////////////////////////////////////////
/**
* This global data structure is used to pass start parameters to NxTerm
* task and to assure that the NxTerm is successfully started.
*/
static struct SNxTerm GNxTermVars;
}
/////////////////////////////////////////////////////////////////////////////
// CNxTerm Method Implementations
/////////////////////////////////////////////////////////////////////////////
using namespace Twm4Nx;
/**
* CNxTerm constructor
*
* @param twm4nx. The Twm4Nx session instance
*/
CNxTerm::CNxTerm(FAR CTwm4Nx *twm4nx)
{
// Save the context data
m_twm4nx = twm4nx;
m_nxtermWindow = (FAR CWindow *)0;
// The NxTerm is not running
m_pid = (pid_t)-1;
m_NxTerm = (NXTERM)0;
}
/**
* CNxTerm destructor
*/
CNxTerm::~CNxTerm(void)
{
// There would be a problem if we were stopped with the NxTerm task
// running... that should never happen but we'll check anyway:
stop();
}
/**
* CNxTerm initializers. Perform miscellaneous post-construction
* initialization that may fail (and hence is not appropriate to be
* done in the constructor)
*
* @return True if the NxTerm application was successfully initialized.
*/
bool CNxTerm::initialize(void)
{
// Call CWindowFactory::createWindow() to create a window for the NxTerm
// application. Customizations:
//
// Flags: WFLAGS_NO_MENU_BUTTON indicates that there is no menu associated
// with the NxTerm application window
// Null Icon manager means to use the system, common Icon Manager
NXWidgets::CNxString name("NuttShell");
uint8_t wflags = (WFLAGS_NO_MENU_BUTTON | WFLAGS_HIDDEN);
FAR CWindowFactory *factory = m_twm4nx->getWindowFactory();
m_nxtermWindow = factory->createWindow(name, &CONFIG_TWM4NX_NXTERM_ICON,
(FAR CIconMgr *)0, wflags);
if (m_nxtermWindow == (FAR CWindow *)0)
{
twmerr("ERROR: Failed to create CWindow\n");
return false;
}
// Configure events needed by the NxTerm applications
struct SAppEvents events;
events.eventObj = (FAR void *)this;
events.redrawEvent = EVENT_NXTERM_REDRAW;
events.resizeEvent = EVENT_NXTERM_RESIZE;
events.mouseEvent = EVENT_NXTERM_XYINPUT;
events.kbdEvent = EVENT_NXTERM_KBDINPUT;
events.closeEvent = EVENT_NXTERM_CLOSE;
events.deleteEvent = EVENT_NXTERM_DELETE;
bool success = m_nxtermWindow->configureEvents(events);
if (!success)
{
delete m_nxtermWindow;
m_nxtermWindow = (FAR CWindow *)0;
return false;
}
return m_nxtermWindow->showWindow();
}
/**
* Start the application (perhaps in the minimized state).
*
* @return True if the application was successfully started.
*/
bool CNxTerm::run(void)
{
// Some sanity checking
if (m_pid >= 0 || m_NxTerm != 0)
{
twmerr("ERROR: All ready running or connected\n");
return false;
}
// Get exclusive access to the global data structure
if (sem_wait(&GNxTermVars.exclSem) != 0)
{
// This might fail if a signal is received while we are waiting.
twmerr("ERROR: Failed to get semaphore\n");
return false;
}
// Get the widget control associated with the NXTK window
NXWidgets::CWidgetControl *control = m_nxtermWindow->getWidgetControl();
// Get the window handle from the widget control
GNxTermVars.hwnd = control->getWindowHandle();
// Describe the NxTerm
GNxTermVars.wndo.wcolor[0] = CONFIG_TWM4NX_NXTERM_WCOLOR;
GNxTermVars.wndo.fcolor[0] = CONFIG_TWM4NX_NXTERM_FONTCOLOR;
GNxTermVars.wndo.fontid = CONFIG_TWM4NX_NXTERM_FONTID;
// Remember the device minor number (before it is incremented)
m_minor = GNxTermVars.minor;
// Get the size of the window
if (!m_nxtermWindow->getWindowSize(&GNxTermVars.wndo.wsize))
{
twmerr("ERROR: getWindowSize() failed\n");
return false;
}
// Start the NxTerm task
GNxTermVars.console = (FAR void *)this;
GNxTermVars.success = false;
GNxTermVars.nxterm = 0;
sched_lock();
m_pid = task_create("NxTerm", CONFIG_TWM4NX_NXTERM_PRIO,
CONFIG_TWM4NX_NXTERM_STACKSIZE, nxterm,
(FAR char * const *)0);
// Did we successfully start the NxTerm task?
bool success = true;
if (m_pid < 0)
{
twmerr("ERROR: Failed to create the NxTerm task\n");
success = false;
}
else
{
// Wait for up to two seconds for the task to initialize
struct timespec abstime;
clock_gettime(CLOCK_REALTIME, &abstime);
abstime.tv_sec += 2;
int ret = sem_timedwait(&GNxTermVars.waitSem, &abstime);
sched_unlock();
if (ret == OK && GNxTermVars.success)
{
#ifdef CONFIG_NXTERM_NXKBDIN
// Re-direct NX keyboard input to the new NxTerm driver
DEBUGASSERT(GNxTermVars.nxterm != 0);
m_nxtermWindow->redirectNxTerm(GNxTermVars.nxterm);
#endif
// Save the handle to use in the stop method
m_NxTerm = GNxTermVars.nxterm;
}
else
{
// sem_timedwait failed OR the NxTerm task reported a
// failure. Stop the application
twmerr("ERROR: Failed start the NxTerm task\n");
stop();
success = false;
}
}
sem_post(&GNxTermVars.exclSem);
return success;
}
/**
* This is the close window event handler. It will stop the NxTerm
* application thread.
*/
void CNxTerm::stop(void)
{
// Delete the NxTerm task if it is still running (this could strand
// resources).
if (m_pid >= 0)
{
pid_t pid = m_pid;
m_pid = (pid_t)-1;
// Then delete the NSH task, possibly stranding resources
task_delete(pid);
}
// Destroy the NX console device
if (m_NxTerm)
{
#ifdef CONFIG_NXTERM_NXKBDIN
// Re-store NX keyboard input routing
m_nxtermWindow->redirectNxTerm((NXTERM)0);
#endif
// Unlink the NxTerm driver
// Construct the driver name using this minor number
char devname[32];
snprintf(devname, 32, "/dev/nxterm%d", m_minor);
unlink(devname);
m_NxTerm = 0;
}
}
/**
* This is the NxTerm task. This function first redirects output to the
* console window then calls to start the NSH logic.
*/
int CNxTerm::nxterm(int argc, char *argv[])
{
// To stop compiler complaining about "jump to label crosses initialization
// of 'int fd'
int fd = -1;
int ret = OK;
// Use the window handle to create the NX console
struct boardioc_nxterm_create_s nxcreate;
nxcreate.nxterm = (FAR void *)0;
nxcreate.hwnd = GNxTermVars.hwnd;
nxcreate.wndo = GNxTermVars.wndo;
nxcreate.type = BOARDIOC_XTERM_FRAMED;
nxcreate.minor = GNxTermVars.minor;
ret = boardctl(BOARDIOC_NXTERM, (uintptr_t)&nxcreate);
if (ret < 0)
{
twmerr("ERROR: boardctl(BOARDIOC_NXTERM) failed: %d\n", errno);
goto errout;
}
GNxTermVars.nxterm = nxcreate.nxterm;
DEBUGASSERT(GNxTermVars.nxterm != NULL);
// Construct the driver name using this minor number
char devname[32];
snprintf(devname, 32, "/dev/nxterm%d", GNxTermVars.minor);
// Increment the minor number while it is protect by the semaphore
GNxTermVars.minor++;
// Open the NxTerm driver
#ifdef CONFIG_NXTERM_NXKBDIN
fd = open(devname, O_RDWR);
#else
fd = open(devname, O_WRONLY);
#endif
if (fd < 0)
{
twmerr("ERROR: Failed open the console device\n");
unlink(devname);
goto errout;
}
// Now re-direct stdout and stderr so that they use the NX console driver.
// Notes: (1) stdin is retained (file descriptor 0, probably the the serial
// console). (2) Don't bother trying to put debug instrumentation in the
// following because it will end up in the NxTerm window.
std::fflush(stdout);
std::fflush(stderr);
#ifdef CONFIG_NXTERM_NXKBDIN
std::dup2(fd, 0);
#endif
std::dup2(fd, 1);
std::dup2(fd, 2);
// And we can close our original driver file descriptor
if (fd > 2)
{
std::close(fd);
}
// Inform the parent thread that we successfully initialized
GNxTermVars.success = true;
sem_post(&GNxTermVars.waitSem);
// Run the NSH console
#ifdef CONFIG_NSH_CONSOLE
nsh_consolemain(argc, argv);
#endif
// We get here if the NSH console should exits. nsh_consolemain() ALWAYS
// exits by calling nsh_exit() (which is a pointer to nsh_consoleexit())
// which, in turn, calls exit()
return EXIT_SUCCESS;
errout:
GNxTermVars.nxterm = 0;
GNxTermVars.success = false;
sem_post(&GNxTermVars.waitSem);
return EXIT_FAILURE;
}
/**
* Handle Twm4Nx events. This overrides a method from CTwm4NXEvent
*
* @param eventmsg. The received NxWidget WINDOW event message.
* @return True if the message was properly handled. false is
* return on any failure.
*/
bool CNxTerm::event(FAR struct SEventMsg *eventmsg)
{
bool success = true;
switch (eventmsg->eventID)
{
case EVENT_NXTERM_REDRAW: // Redraw event
redraw(); // Redraw the whole window
break;
case EVENT_NXTERM_RESIZE: // Resize event
resize(); // Size the NxTerm window
break;
case EVENT_NXTERM_CLOSE: // Window close event
stop(); // Stop the NxTerm thread
break;
default:
success = false;
break;
}
return success;
}
/**
* Redraw the entire window. The application has been maximized, resized or
* moved to the top of the hierarchy. This method is call from CTwm4Nx when
* the application window must be displayed
*/
void CNxTerm::redraw(void)
{
// Get the size of the window
struct nxgl_size_s windowSize;
m_nxtermWindow->getWindowSize(&windowSize);
// Redraw the entire NxTerm window
struct boardioc_nxterm_ioctl_s iocargs;
struct nxtermioc_redraw_s redraw;
redraw.handle = m_NxTerm;
redraw.rect.pt1.x = 0;
redraw.rect.pt1.y = 0;
redraw.rect.pt2.x = windowSize.w - 1;
redraw.rect.pt2.y = windowSize.h - 1;
redraw.more = false;
iocargs.cmd = NXTERMIOC_NXTERM_REDRAW;
iocargs.arg = (uintptr_t)&redraw;
boardctl(BOARDIOC_NXTERM_IOCTL, (uintptr_t)&iocargs);
}
/**
* Inform NxTerm of a new window size.
*/
void CNxTerm::resize(void)
{
struct nxtermioc_resize_s resize;
// Get the size of the window
resize.handle = m_NxTerm;
m_nxtermWindow->getWindowSize(&resize.size);
// Inform NxTerm of the new size
struct boardioc_nxterm_ioctl_s iocargs;
iocargs.cmd = NXTERMIOC_NXTERM_RESIZE;
iocargs.arg = (uintptr_t)&resize;
boardctl(BOARDIOC_NXTERM_IOCTL, (uintptr_t)&iocargs);
}
/////////////////////////////////////////////////////////////////////////////
// CNxTermFactory Method Implementations
/////////////////////////////////////////////////////////////////////////////
/**
* CNxTermFactory Initializer. Performs parts of the instance
* construction that may fail. In this implementation, it will
* initialize the NSH library and register an menu item in the
* Main Menu.
*/
bool CNxTermFactory::initialize(FAR CTwm4Nx *twm4nx)
{
// Save the session instance
m_twm4nx = twm4nx;
// Initialize the NSH library
if (!nshlibInitialize())
{
twmerr("ERROR: Failed to initialize the NSH library\n");
return false;
}
// Register an entry with the Main menu. When selected, this will
// Case the start
FAR CMainMenu *cmain = twm4nx->getMainMenu();
return cmain->addApplication(this);
}
/**
* One time NSH initialization. This function must be called exactly
* once during the boot-up sequence to initialize the NSH library.
*
* @return True on successful initialization
*/
bool CNxTermFactory::nshlibInitialize(void)
{
// Initialize the global data structure
sem_init(&GNxTermVars.exclSem, 0, 1);
sem_init(&GNxTermVars.waitSem, 0, 0);
// Initialize the NSH library
nsh_initialize();
// If the Telnet console is selected as a front-end, then start the
// Telnet daemon.
#ifdef CONFIG_NSH_TELNET
int ret = nsh_telnetstart(AF_UNSPEC);
if (ret < 0)
{
// The daemon is NOT running!
return false;
}
#endif
return true;
}
/**
* Handle CNxTermFactory events. This overrides a method from
* CTwm4NXEvent
*
* @param eventmsg. The received NxWidget WINDOW event message.
* @return True if the message was properly handled. false is
* return on any failure.
*/
bool CNxTermFactory::event(FAR struct SEventMsg *eventmsg)
{
bool success = true;
switch (eventmsg->eventID)
{
case EVENT_NXTERM_START: // Main menu selection
startFunction(); // Create a new NxTerm instance
break;
default:
success = false;
break;
}
return success;
}
/**
* Create and start a new instance of CNxTerm.
*/
bool CNxTermFactory::startFunction(void)
{
// Instantiate the Nxterm application, providing only the session session
// instance to the constructor
CNxTerm *nxterm = new CNxTerm(m_twm4nx);
if (!nxterm)
{
twmerr("ERROR: Failed to instantiate CNxTerm\n");
return false;
}
// Initialize the NxTerm application
if (!nxterm->initialize())
{
twmerr("ERROR: Failed to initialize CNxTerm instance\n");
delete nxterm;
return false;
}
// Start the NxTerm application instance
if (!nxterm->run())
{
twmerr("ERROR: Failed to start the NxTerm application\n");
delete nxterm;
return false;
}
return true;
}