2013-05-03 06:02:03 +00:00
|
|
|
|
/**
|
|
|
|
|
* Copyright (c) 2007-2012, Timothy Stack
|
|
|
|
|
*
|
|
|
|
|
* All rights reserved.
|
2022-03-16 22:38:08 +00:00
|
|
|
|
*
|
2013-05-03 06:02:03 +00:00
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
|
* modification, are permitted provided that the following conditions are met:
|
2022-03-16 22:38:08 +00:00
|
|
|
|
*
|
2013-05-03 06:02:03 +00:00
|
|
|
|
* * Redistributions of source code must retain the above copyright notice, this
|
|
|
|
|
* list of conditions and the following disclaimer.
|
|
|
|
|
* * 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.
|
|
|
|
|
* * Neither the name of Timothy Stack nor the names of its contributors
|
|
|
|
|
* may be used to endorse or promote products derived from this software
|
|
|
|
|
* without specific prior written permission.
|
2022-03-16 22:38:08 +00:00
|
|
|
|
*
|
2013-05-03 06:02:03 +00:00
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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;
|
2022-03-16 22:38:08 +00:00
|
|
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
|
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
2013-05-03 06:02:03 +00:00
|
|
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
|
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
|
*/
|
2009-09-14 01:07:32 +00:00
|
|
|
|
|
2014-03-06 14:58:49 +00:00
|
|
|
|
#include <assert.h>
|
2022-03-16 22:38:08 +00:00
|
|
|
|
#include <errno.h>
|
|
|
|
|
#include <fcntl.h>
|
|
|
|
|
#include <signal.h>
|
|
|
|
|
#include <stdint.h>
|
2009-09-14 01:07:32 +00:00
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
2022-03-16 22:38:08 +00:00
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
|
#include <sys/time.h>
|
2009-09-14 01:07:32 +00:00
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
#include <sys/wait.h>
|
|
|
|
|
#include <termios.h>
|
2022-03-16 22:38:08 +00:00
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
|
|
#include "config.h"
|
2009-09-14 01:07:32 +00:00
|
|
|
|
|
2012-04-17 15:16:41 +00:00
|
|
|
|
#if defined HAVE_NCURSESW_CURSES_H
|
2022-03-16 22:38:08 +00:00
|
|
|
|
# include <ncursesw/curses.h>
|
2012-04-17 15:16:41 +00:00
|
|
|
|
#elif defined HAVE_NCURSESW_H
|
2022-03-16 22:38:08 +00:00
|
|
|
|
# include <ncursesw.h>
|
2012-04-17 15:16:41 +00:00
|
|
|
|
#elif defined HAVE_NCURSES_CURSES_H
|
2022-03-16 22:38:08 +00:00
|
|
|
|
# include <ncurses/curses.h>
|
2012-04-17 15:16:41 +00:00
|
|
|
|
#elif defined HAVE_NCURSES_H
|
2022-03-16 22:38:08 +00:00
|
|
|
|
# include <ncurses.h>
|
2012-04-17 15:16:41 +00:00
|
|
|
|
#elif defined HAVE_CURSES_H
|
2022-03-16 22:38:08 +00:00
|
|
|
|
# include <curses.h>
|
2012-04-17 15:16:41 +00:00
|
|
|
|
#else
|
2022-03-16 22:38:08 +00:00
|
|
|
|
# error "SysV or X/Open-compatible Curses header file required"
|
2012-04-17 15:16:41 +00:00
|
|
|
|
#endif
|
|
|
|
|
|
2009-09-14 01:07:32 +00:00
|
|
|
|
#ifdef HAVE_PTY_H
|
2022-03-16 22:38:08 +00:00
|
|
|
|
# include <pty.h>
|
2009-09-14 01:07:32 +00:00
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#ifdef HAVE_UTIL_H
|
2022-03-16 22:38:08 +00:00
|
|
|
|
# include <util.h>
|
2009-09-14 01:07:32 +00:00
|
|
|
|
#endif
|
|
|
|
|
|
2009-12-24 18:36:01 +00:00
|
|
|
|
#ifdef HAVE_LIBUTIL_H
|
2022-03-16 22:38:08 +00:00
|
|
|
|
# include <libutil.h>
|
2009-12-24 18:36:01 +00:00
|
|
|
|
#endif
|
|
|
|
|
|
2022-03-16 22:38:08 +00:00
|
|
|
|
#include <algorithm>
|
2021-01-08 22:08:52 +00:00
|
|
|
|
#include <map>
|
2009-09-14 01:07:32 +00:00
|
|
|
|
#include <queue>
|
2021-01-08 22:08:52 +00:00
|
|
|
|
#include <sstream>
|
2022-03-16 22:38:08 +00:00
|
|
|
|
#include <string>
|
2021-01-08 22:08:52 +00:00
|
|
|
|
#include <utility>
|
|
|
|
|
|
2022-03-31 15:59:19 +00:00
|
|
|
|
#include "base/auto_fd.hh"
|
2022-04-12 23:07:13 +00:00
|
|
|
|
#include "base/auto_mem.hh"
|
2022-03-16 22:38:08 +00:00
|
|
|
|
#include "base/string_util.hh"
|
|
|
|
|
#include "fmt/format.h"
|
|
|
|
|
#include "ghc/filesystem.hpp"
|
2021-01-08 22:08:52 +00:00
|
|
|
|
#include "styling.hh"
|
2022-03-16 22:38:08 +00:00
|
|
|
|
#include "termios_guard.hh"
|
|
|
|
|
#include "ww898/cp_utf8.hpp"
|
2009-09-14 01:07:32 +00:00
|
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
|
2012-06-05 20:18:59 +00:00
|
|
|
|
/**
|
|
|
|
|
* An RAII class for opening a PTY and forking a child process.
|
|
|
|
|
*/
|
2009-09-14 01:07:32 +00:00
|
|
|
|
class child_term {
|
|
|
|
|
public:
|
|
|
|
|
class error : public std::exception {
|
|
|
|
|
public:
|
2022-03-16 22:38:08 +00:00
|
|
|
|
error(int err) : e_err(err){};
|
2009-09-14 01:07:32 +00:00
|
|
|
|
|
2019-07-30 05:18:32 +00:00
|
|
|
|
int e_err;
|
2009-09-14 01:07:32 +00:00
|
|
|
|
};
|
2019-07-30 05:18:32 +00:00
|
|
|
|
|
|
|
|
|
explicit child_term(bool passin)
|
|
|
|
|
{
|
|
|
|
|
struct winsize ws;
|
|
|
|
|
auto_fd slave;
|
|
|
|
|
|
|
|
|
|
memset(&ws, 0, sizeof(ws));
|
|
|
|
|
|
2022-03-16 22:38:08 +00:00
|
|
|
|
if (isatty(STDIN_FILENO)
|
2024-02-29 06:20:32 +00:00
|
|
|
|
&& tcgetattr(STDIN_FILENO, &this->ct_termios) == -1)
|
|
|
|
|
{
|
2019-07-30 05:18:32 +00:00
|
|
|
|
throw error(errno);
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-16 22:38:08 +00:00
|
|
|
|
if (isatty(STDOUT_FILENO)
|
|
|
|
|
&& ioctl(STDOUT_FILENO, TIOCGWINSZ, &this->ct_winsize) == -1)
|
|
|
|
|
{
|
2019-07-30 05:18:32 +00:00
|
|
|
|
throw error(errno);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ws.ws_col = 80;
|
|
|
|
|
ws.ws_row = 24;
|
|
|
|
|
|
2022-03-16 22:38:08 +00:00
|
|
|
|
if (openpty(this->ct_master.out(), slave.out(), nullptr, nullptr, &ws)
|
2024-02-29 06:20:32 +00:00
|
|
|
|
< 0)
|
|
|
|
|
{
|
2019-07-30 05:18:32 +00:00
|
|
|
|
throw error(errno);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((this->ct_child = fork()) == -1)
|
|
|
|
|
throw error(errno);
|
|
|
|
|
|
|
|
|
|
if (this->ct_child == 0) {
|
|
|
|
|
this->ct_master.reset();
|
|
|
|
|
|
|
|
|
|
if (!passin) {
|
|
|
|
|
dup2(slave, STDIN_FILENO);
|
|
|
|
|
}
|
|
|
|
|
dup2(slave, STDOUT_FILENO);
|
|
|
|
|
|
|
|
|
|
setenv("TERM", "xterm-color", 1);
|
|
|
|
|
} else {
|
|
|
|
|
slave.reset();
|
|
|
|
|
}
|
2009-09-14 01:07:32 +00:00
|
|
|
|
};
|
2019-07-30 05:18:32 +00:00
|
|
|
|
|
|
|
|
|
virtual ~child_term()
|
|
|
|
|
{
|
|
|
|
|
(void) this->wait_for_child();
|
|
|
|
|
|
2022-03-16 22:38:08 +00:00
|
|
|
|
if (isatty(STDIN_FILENO)
|
|
|
|
|
&& tcsetattr(STDIN_FILENO, TCSANOW, &this->ct_termios) == -1)
|
|
|
|
|
{
|
2019-07-30 05:18:32 +00:00
|
|
|
|
perror("tcsetattr");
|
|
|
|
|
}
|
2022-03-16 22:38:08 +00:00
|
|
|
|
if (isatty(STDOUT_FILENO)
|
|
|
|
|
&& ioctl(STDOUT_FILENO, TIOCSWINSZ, &this->ct_winsize) == -1)
|
|
|
|
|
{
|
2019-07-30 05:18:32 +00:00
|
|
|
|
perror("ioctl");
|
|
|
|
|
}
|
2009-09-14 01:07:32 +00:00
|
|
|
|
};
|
|
|
|
|
|
2021-01-08 22:08:52 +00:00
|
|
|
|
int wait_for_child()
|
2019-07-30 05:18:32 +00:00
|
|
|
|
{
|
|
|
|
|
int retval = -1;
|
|
|
|
|
|
|
|
|
|
if (this->ct_child > 0) {
|
|
|
|
|
kill(this->ct_child, SIGTERM);
|
|
|
|
|
this->ct_child = -1;
|
2009-09-14 01:07:32 +00:00
|
|
|
|
|
2022-03-16 22:38:08 +00:00
|
|
|
|
while (wait(&retval) < 0 && (errno == EINTR))
|
|
|
|
|
;
|
2019-07-30 05:18:32 +00:00
|
|
|
|
}
|
2009-09-14 01:07:32 +00:00
|
|
|
|
|
2019-07-30 05:18:32 +00:00
|
|
|
|
return retval;
|
2009-09-14 01:07:32 +00:00
|
|
|
|
};
|
|
|
|
|
|
2024-02-29 06:20:32 +00:00
|
|
|
|
bool is_child() const { return this->ct_child == 0; };
|
2019-07-30 05:18:32 +00:00
|
|
|
|
|
2024-02-29 06:20:32 +00:00
|
|
|
|
pid_t get_child_pid() const { return this->ct_child; };
|
2019-07-30 05:18:32 +00:00
|
|
|
|
|
2024-02-29 06:20:32 +00:00
|
|
|
|
int get_fd() const { return this->ct_master; };
|
2009-09-14 01:07:32 +00:00
|
|
|
|
|
|
|
|
|
protected:
|
|
|
|
|
pid_t ct_child;
|
|
|
|
|
auto_fd ct_master;
|
|
|
|
|
struct termios ct_termios;
|
|
|
|
|
struct winsize ct_winsize;
|
|
|
|
|
};
|
|
|
|
|
|
2012-06-05 20:18:59 +00:00
|
|
|
|
/**
|
|
|
|
|
* @param fd The file descriptor to switch to raw mode.
|
|
|
|
|
* @return Zero on success, -1 on error.
|
|
|
|
|
*/
|
2022-03-16 22:38:08 +00:00
|
|
|
|
static int
|
|
|
|
|
tty_raw(int fd)
|
2009-09-14 01:07:32 +00:00
|
|
|
|
{
|
|
|
|
|
struct termios attr[1];
|
2012-06-05 20:18:59 +00:00
|
|
|
|
|
|
|
|
|
assert(fd >= 0);
|
2019-07-30 05:18:32 +00:00
|
|
|
|
|
2009-09-14 01:07:32 +00:00
|
|
|
|
if (tcgetattr(fd, attr) == -1)
|
2019-07-30 05:18:32 +00:00
|
|
|
|
return -1;
|
|
|
|
|
|
2009-09-14 01:07:32 +00:00
|
|
|
|
attr->c_lflag &= ~(ECHO | ICANON | IEXTEN);
|
|
|
|
|
attr->c_iflag &= ~(ICRNL | INPCK | ISTRIP | IXON);
|
|
|
|
|
attr->c_cflag &= ~(CSIZE | PARENB);
|
|
|
|
|
attr->c_cflag |= (CS8);
|
|
|
|
|
attr->c_oflag &= ~(OPOST);
|
|
|
|
|
attr->c_cc[VMIN] = 1;
|
|
|
|
|
attr->c_cc[VTIME] = 0;
|
2019-07-30 05:18:32 +00:00
|
|
|
|
|
2009-09-14 01:07:32 +00:00
|
|
|
|
return tcsetattr(fd, TCSANOW, attr);
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-16 22:38:08 +00:00
|
|
|
|
static void
|
|
|
|
|
dump_memory(FILE* dst, const char* src, int len)
|
2012-06-05 20:18:59 +00:00
|
|
|
|
{
|
|
|
|
|
int lpc;
|
|
|
|
|
|
|
|
|
|
for (lpc = 0; lpc < len; lpc++) {
|
2020-11-10 06:18:17 +00:00
|
|
|
|
fprintf(dst, "%02x", src[lpc] & 0xff);
|
2012-06-05 20:18:59 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-16 22:38:08 +00:00
|
|
|
|
static std::vector<char>
|
|
|
|
|
hex2bits(const char* src)
|
2012-06-05 20:18:59 +00:00
|
|
|
|
{
|
2021-01-08 22:08:52 +00:00
|
|
|
|
std::vector<char> retval;
|
2012-06-05 20:18:59 +00:00
|
|
|
|
|
2021-01-09 04:48:54 +00:00
|
|
|
|
for (size_t lpc = 0; src[lpc] && isdigit(src[lpc]); lpc += 2) {
|
2019-07-30 05:18:32 +00:00
|
|
|
|
int val;
|
|
|
|
|
|
2021-01-08 22:08:52 +00:00
|
|
|
|
sscanf(&src[lpc], "%2x", &val);
|
|
|
|
|
retval.push_back((char) val);
|
2012-06-05 20:18:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return retval;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-16 22:38:08 +00:00
|
|
|
|
static const char*
|
|
|
|
|
tstamp()
|
2021-02-03 05:58:42 +00:00
|
|
|
|
{
|
|
|
|
|
static char buf[64];
|
|
|
|
|
|
|
|
|
|
struct timeval tv;
|
|
|
|
|
gettimeofday(&tv, nullptr);
|
|
|
|
|
strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S.", localtime(&tv.tv_sec));
|
|
|
|
|
auto dlen = strlen(buf);
|
|
|
|
|
snprintf(&buf[dlen], sizeof(buf) - dlen, "%.06d", tv.tv_usec);
|
|
|
|
|
|
|
|
|
|
return buf;
|
|
|
|
|
}
|
|
|
|
|
|
2009-09-14 01:07:32 +00:00
|
|
|
|
typedef enum {
|
2021-01-08 22:08:52 +00:00
|
|
|
|
CT_WRITE,
|
|
|
|
|
} command_type_t;
|
2009-09-14 01:07:32 +00:00
|
|
|
|
|
2021-01-08 22:08:52 +00:00
|
|
|
|
struct command {
|
|
|
|
|
command_type_t c_type;
|
|
|
|
|
vector<char> c_arg;
|
2012-06-05 20:18:59 +00:00
|
|
|
|
};
|
|
|
|
|
|
2021-01-08 22:08:52 +00:00
|
|
|
|
static struct {
|
2022-03-16 22:38:08 +00:00
|
|
|
|
const char* sd_program_name{nullptr};
|
2021-01-08 22:08:52 +00:00
|
|
|
|
sig_atomic_t sd_looping{true};
|
|
|
|
|
|
|
|
|
|
pid_t sd_child_pid{-1};
|
|
|
|
|
|
|
|
|
|
ghc::filesystem::path sd_actual_name;
|
|
|
|
|
auto_mem<FILE> sd_from_child{fclose};
|
|
|
|
|
ghc::filesystem::path sd_expected_name;
|
2009-09-14 01:07:32 +00:00
|
|
|
|
|
2021-01-08 22:08:52 +00:00
|
|
|
|
deque<struct command> sd_replay;
|
|
|
|
|
} scripty_data;
|
|
|
|
|
|
|
|
|
|
static const std::map<std::string, std::string> CSI_TO_DESC = {
|
|
|
|
|
{")0", "Use alt charset"},
|
|
|
|
|
|
|
|
|
|
{"[?1000l", "Don't Send Mouse X & Y"},
|
|
|
|
|
{"[?1002l", "Don’t Use Cell Motion Mouse Tracking"},
|
|
|
|
|
{"[?1006l", "Don't ..."},
|
|
|
|
|
{"[?1h", "Application cursor keys"},
|
|
|
|
|
{"[?1l", "Normal cursor keys"},
|
|
|
|
|
{"[?47h", "Use alternate screen buffer"},
|
|
|
|
|
{"[?47l", "Use normal screen buffer"},
|
2021-02-06 00:04:34 +00:00
|
|
|
|
{"[2h", "Set Keyboard Action mode"},
|
|
|
|
|
{"[4h", "Set Replace mode"},
|
|
|
|
|
{"[12h", "Set Send/Receive mode"},
|
|
|
|
|
{"[20h", "Set Normal Linefeed mode"},
|
|
|
|
|
{"[2l", "Reset Keyboard Action mode"},
|
|
|
|
|
{"[4l", "Reset Replace mode"},
|
|
|
|
|
{"[12l", "Reset Send/Receive mode"},
|
|
|
|
|
{"[20l", "Reset Normal Linefeed mode"},
|
2021-01-08 22:08:52 +00:00
|
|
|
|
{"[2J", "Erase all"},
|
2012-06-05 20:18:59 +00:00
|
|
|
|
};
|
|
|
|
|
|
2021-01-08 22:08:52 +00:00
|
|
|
|
struct term_machine {
|
|
|
|
|
enum class state {
|
|
|
|
|
NORMAL,
|
|
|
|
|
ESCAPE_START,
|
|
|
|
|
ESCAPE_FIXED_LENGTH,
|
|
|
|
|
ESCAPE_VARIABLE_LENGTH,
|
|
|
|
|
ESCAPE_OSC,
|
2019-07-30 05:18:32 +00:00
|
|
|
|
};
|
|
|
|
|
|
2021-01-08 22:08:52 +00:00
|
|
|
|
struct term_attr {
|
|
|
|
|
term_attr(size_t pos, const std::string& desc)
|
2022-03-16 22:38:08 +00:00
|
|
|
|
: ta_pos(pos), ta_end(pos), ta_desc({desc})
|
|
|
|
|
{
|
2021-01-08 22:08:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
term_attr(size_t pos, size_t end, const std::string& desc)
|
2022-03-16 22:38:08 +00:00
|
|
|
|
: ta_pos(pos), ta_end(end), ta_desc({desc})
|
|
|
|
|
{
|
2021-01-08 22:08:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t ta_pos;
|
|
|
|
|
size_t ta_end;
|
|
|
|
|
std::vector<std::string> ta_desc;
|
|
|
|
|
};
|
|
|
|
|
|
2024-02-29 06:20:32 +00:00
|
|
|
|
term_machine(child_term& ct) : tm_child_term(ct) { this->clear(); }
|
2021-01-08 22:08:52 +00:00
|
|
|
|
|
2024-02-29 06:20:32 +00:00
|
|
|
|
~term_machine() { this->flush_line(); }
|
2021-01-08 22:08:52 +00:00
|
|
|
|
|
2022-03-16 22:38:08 +00:00
|
|
|
|
void clear()
|
|
|
|
|
{
|
2021-01-08 22:08:52 +00:00
|
|
|
|
std::fill(begin(this->tm_line), end(this->tm_line), ' ');
|
|
|
|
|
this->tm_line_attrs.clear();
|
|
|
|
|
this->tm_new_data = false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-16 22:38:08 +00:00
|
|
|
|
void add_line_attr(const std::string& desc)
|
|
|
|
|
{
|
|
|
|
|
if (!this->tm_line_attrs.empty()
|
|
|
|
|
&& this->tm_line_attrs.back().ta_pos == this->tm_cursor_x)
|
|
|
|
|
{
|
2021-01-08 22:08:52 +00:00
|
|
|
|
this->tm_line_attrs.back().ta_desc.emplace_back(desc);
|
|
|
|
|
} else {
|
|
|
|
|
this->tm_line_attrs.emplace_back(this->tm_cursor_x, desc);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-16 22:38:08 +00:00
|
|
|
|
void write_char(char ch)
|
|
|
|
|
{
|
2021-01-08 22:08:52 +00:00
|
|
|
|
if (isprint(ch)) {
|
|
|
|
|
require(ch);
|
|
|
|
|
|
|
|
|
|
this->tm_new_data = true;
|
|
|
|
|
this->tm_line[this->tm_cursor_x++] = (unsigned char) ch;
|
|
|
|
|
} else {
|
|
|
|
|
switch (ch) {
|
|
|
|
|
case '\a':
|
|
|
|
|
this->flush_line();
|
|
|
|
|
fprintf(scripty_data.sd_from_child, "CTRL bell\n");
|
|
|
|
|
break;
|
|
|
|
|
case '\x08':
|
|
|
|
|
this->add_line_attr("backspace");
|
|
|
|
|
if (this->tm_cursor_x > 0) {
|
|
|
|
|
this->tm_cursor_x -= 1;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case '\r':
|
|
|
|
|
this->add_line_attr("carriage-return");
|
|
|
|
|
this->tm_cursor_x = 0;
|
|
|
|
|
break;
|
|
|
|
|
case '\n':
|
|
|
|
|
this->flush_line();
|
|
|
|
|
if (this->tm_cursor_y >= 0) {
|
|
|
|
|
this->tm_cursor_y += 1;
|
|
|
|
|
}
|
|
|
|
|
this->tm_cursor_x = 0;
|
|
|
|
|
break;
|
|
|
|
|
case '\x0e':
|
|
|
|
|
this->tm_shift_start = this->tm_cursor_x;
|
|
|
|
|
break;
|
|
|
|
|
case '\x0f':
|
|
|
|
|
if (this->tm_shift_start != this->tm_cursor_x) {
|
2022-03-16 22:38:08 +00:00
|
|
|
|
this->tm_line_attrs.emplace_back(
|
|
|
|
|
this->tm_shift_start, this->tm_cursor_x, "alt");
|
2021-01-08 22:08:52 +00:00
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
require(ch);
|
|
|
|
|
this->tm_new_data = true;
|
|
|
|
|
this->tm_line[this->tm_cursor_x++] = (unsigned char) ch;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-16 22:38:08 +00:00
|
|
|
|
void flush_line()
|
|
|
|
|
{
|
|
|
|
|
if (std::exchange(this->tm_waiting_on_input, false)
|
|
|
|
|
&& !this->tm_user_input.empty())
|
|
|
|
|
{
|
2021-02-03 05:58:42 +00:00
|
|
|
|
fprintf(stderr, "%s:flush keys\n", tstamp());
|
2021-01-08 22:08:52 +00:00
|
|
|
|
fprintf(scripty_data.sd_from_child, "K ");
|
2022-03-16 22:38:08 +00:00
|
|
|
|
dump_memory(
|
|
|
|
|
scripty_data.sd_from_child, this->tm_user_input.data(), 1);
|
2021-01-08 22:08:52 +00:00
|
|
|
|
fprintf(scripty_data.sd_from_child, "\n");
|
2021-02-03 05:58:42 +00:00
|
|
|
|
this->tm_user_input.erase(this->tm_user_input.begin());
|
2021-01-08 22:08:52 +00:00
|
|
|
|
}
|
|
|
|
|
if (this->tm_new_data || !this->tm_line_attrs.empty()) {
|
2022-03-16 22:38:08 +00:00
|
|
|
|
// fprintf(scripty_data.sd_from_child, "flush %d\n",
|
|
|
|
|
// this->tm_flush_count);
|
2022-03-29 05:00:49 +00:00
|
|
|
|
fprintf(stderr, "%s:flush %zu\n", tstamp(), this->tm_flush_count++);
|
2022-03-16 22:38:08 +00:00
|
|
|
|
fprintf(
|
|
|
|
|
scripty_data.sd_from_child, "S % 3d \u250B", this->tm_cursor_y);
|
2021-01-08 22:08:52 +00:00
|
|
|
|
for (auto uch : this->tm_line) {
|
|
|
|
|
ww898::utf::utf8::write(uch, [](auto ch) {
|
|
|
|
|
fputc(ch, scripty_data.sd_from_child);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
fprintf(scripty_data.sd_from_child, "\u250B\n");
|
|
|
|
|
for (size_t lpc = 0; lpc < this->tm_line_attrs.size(); lpc++) {
|
|
|
|
|
const auto& ta = this->tm_line_attrs[lpc];
|
2022-03-16 22:38:08 +00:00
|
|
|
|
auto full_desc = fmt::format(
|
|
|
|
|
"{}",
|
|
|
|
|
fmt::join(ta.ta_desc.begin(), ta.ta_desc.end(), ", "));
|
2021-01-08 22:08:52 +00:00
|
|
|
|
int line_len;
|
|
|
|
|
|
|
|
|
|
if (ta.ta_pos == ta.ta_end) {
|
2022-03-16 22:38:08 +00:00
|
|
|
|
line_len = fprintf(
|
|
|
|
|
scripty_data.sd_from_child,
|
|
|
|
|
"A %s%s %s",
|
|
|
|
|
repeat("\u00B7", ta.ta_pos).c_str(),
|
|
|
|
|
((lpc + 1 < this->tm_line_attrs.size())
|
|
|
|
|
&& (ta.ta_pos == this->tm_line_attrs[lpc + 1].ta_pos))
|
|
|
|
|
? "\u251C"
|
|
|
|
|
: "\u2514",
|
|
|
|
|
full_desc.c_str());
|
2021-01-08 22:08:52 +00:00
|
|
|
|
line_len -= 2 + ta.ta_pos;
|
|
|
|
|
} else {
|
2022-03-16 22:38:08 +00:00
|
|
|
|
line_len = fprintf(
|
|
|
|
|
scripty_data.sd_from_child,
|
|
|
|
|
"A %s%s%s\u251b %s",
|
|
|
|
|
std::string(ta.ta_pos, ' ').c_str(),
|
|
|
|
|
((lpc + 1 < this->tm_line_attrs.size())
|
|
|
|
|
&& (ta.ta_pos == this->tm_line_attrs[lpc + 1].ta_pos))
|
|
|
|
|
? "\u2518"
|
|
|
|
|
: "\u2514",
|
|
|
|
|
std::string(ta.ta_end - ta.ta_pos - 1, '-').c_str(),
|
|
|
|
|
full_desc.c_str());
|
2021-01-08 22:08:52 +00:00
|
|
|
|
line_len -= 4;
|
2019-07-30 05:18:32 +00:00
|
|
|
|
}
|
2022-03-16 22:38:08 +00:00
|
|
|
|
for (size_t lpc2 = lpc + 1; lpc2 < this->tm_line_attrs.size();
|
2024-02-29 06:20:32 +00:00
|
|
|
|
lpc2++)
|
|
|
|
|
{
|
2021-01-08 22:08:52 +00:00
|
|
|
|
auto bar_pos = 7 + this->tm_line_attrs[lpc2].ta_pos;
|
|
|
|
|
|
|
|
|
|
if (bar_pos < line_len) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2022-03-16 22:38:08 +00:00
|
|
|
|
line_len += fprintf(
|
|
|
|
|
scripty_data.sd_from_child,
|
|
|
|
|
"%s\u2502",
|
|
|
|
|
std::string(bar_pos - line_len, ' ').c_str());
|
2021-01-08 22:08:52 +00:00
|
|
|
|
line_len -= 2;
|
|
|
|
|
}
|
|
|
|
|
fprintf(scripty_data.sd_from_child, "\n");
|
2019-07-30 05:18:32 +00:00
|
|
|
|
}
|
2021-01-08 22:08:52 +00:00
|
|
|
|
this->clear();
|
2019-07-30 05:18:32 +00:00
|
|
|
|
}
|
2021-01-08 22:08:52 +00:00
|
|
|
|
fflush(scripty_data.sd_from_child);
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-16 22:38:08 +00:00
|
|
|
|
std::vector<int> get_m_params()
|
|
|
|
|
{
|
2021-01-08 22:08:52 +00:00
|
|
|
|
std::vector<int> retval;
|
|
|
|
|
size_t index = 1;
|
|
|
|
|
|
|
|
|
|
while (index < this->tm_escape_buffer.size()) {
|
|
|
|
|
int val, last;
|
2019-07-30 05:18:32 +00:00
|
|
|
|
|
2022-03-16 22:38:08 +00:00
|
|
|
|
if (sscanf(&this->tm_escape_buffer[index], "%d%n", &val, &last)
|
2024-02-29 06:20:32 +00:00
|
|
|
|
== 1)
|
|
|
|
|
{
|
2021-01-08 22:08:52 +00:00
|
|
|
|
retval.push_back(val);
|
|
|
|
|
index += last;
|
|
|
|
|
if (this->tm_escape_buffer[index] != ';') {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
index += 1;
|
|
|
|
|
} else {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-07-30 05:18:32 +00:00
|
|
|
|
|
|
|
|
|
return retval;
|
2021-01-08 22:08:52 +00:00
|
|
|
|
}
|
2019-07-30 05:18:32 +00:00
|
|
|
|
|
2024-02-29 06:20:32 +00:00
|
|
|
|
void new_user_input(char ch) { this->tm_user_input.push_back(ch); }
|
2012-06-05 20:18:59 +00:00
|
|
|
|
|
2022-03-16 22:38:08 +00:00
|
|
|
|
void new_input(char ch)
|
|
|
|
|
{
|
2021-01-08 22:08:52 +00:00
|
|
|
|
if (this->tm_unicode_remaining > 0) {
|
|
|
|
|
this->tm_unicode_buffer.push_back(ch);
|
|
|
|
|
this->tm_unicode_remaining -= 1;
|
|
|
|
|
if (this->tm_unicode_remaining == 0) {
|
|
|
|
|
this->tm_new_data = true;
|
2022-03-16 22:38:08 +00:00
|
|
|
|
this->tm_line[this->tm_cursor_x++]
|
|
|
|
|
= ww898::utf::utf8::read([this]() {
|
|
|
|
|
auto retval = this->tm_unicode_buffer.front();
|
2021-01-08 22:08:52 +00:00
|
|
|
|
|
2022-03-16 22:38:08 +00:00
|
|
|
|
this->tm_unicode_buffer.pop_front();
|
|
|
|
|
return retval;
|
2024-02-29 06:20:32 +00:00
|
|
|
|
}).unwrap();
|
2021-01-08 22:08:52 +00:00
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
} else {
|
2022-03-16 22:38:08 +00:00
|
|
|
|
auto utfsize = ww898::utf::utf8::char_size(
|
|
|
|
|
[ch]() { return std::make_pair(ch, 16); });
|
2009-09-14 01:07:32 +00:00
|
|
|
|
|
2021-01-08 22:08:52 +00:00
|
|
|
|
if (utfsize.unwrap() > 1) {
|
|
|
|
|
this->tm_unicode_remaining = utfsize.unwrap() - 1;
|
|
|
|
|
this->tm_unicode_buffer.push_back(ch);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2009-09-14 01:07:32 +00:00
|
|
|
|
|
2021-01-08 22:08:52 +00:00
|
|
|
|
switch (this->tm_state) {
|
|
|
|
|
case state::NORMAL: {
|
|
|
|
|
switch (ch) {
|
|
|
|
|
case '\x1b': {
|
|
|
|
|
this->tm_escape_buffer.clear();
|
|
|
|
|
this->tm_state = state::ESCAPE_START;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default: {
|
|
|
|
|
this->write_char(ch);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case state::ESCAPE_START: {
|
|
|
|
|
switch (ch) {
|
|
|
|
|
case '[': {
|
|
|
|
|
this->tm_escape_buffer.push_back(ch);
|
|
|
|
|
this->tm_state = state::ESCAPE_VARIABLE_LENGTH;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case ']': {
|
|
|
|
|
this->tm_escape_buffer.push_back(ch);
|
|
|
|
|
this->tm_state = state::ESCAPE_OSC;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case '(':
|
|
|
|
|
case ')':
|
|
|
|
|
case '*':
|
|
|
|
|
case '+': {
|
|
|
|
|
this->tm_state = state::ESCAPE_FIXED_LENGTH;
|
|
|
|
|
this->tm_escape_buffer.push_back(ch);
|
|
|
|
|
this->tm_escape_expected_size = 2;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default: {
|
|
|
|
|
this->flush_line();
|
|
|
|
|
switch (ch) {
|
|
|
|
|
case '7':
|
|
|
|
|
fprintf(scripty_data.sd_from_child,
|
|
|
|
|
"CTRL save cursor\n");
|
|
|
|
|
break;
|
|
|
|
|
case '8':
|
|
|
|
|
fprintf(scripty_data.sd_from_child,
|
|
|
|
|
"CTRL restore cursor\n");
|
|
|
|
|
break;
|
|
|
|
|
case '>':
|
|
|
|
|
fprintf(scripty_data.sd_from_child,
|
|
|
|
|
"CTRL Normal keypad\n");
|
|
|
|
|
break;
|
|
|
|
|
default: {
|
|
|
|
|
fprintf(scripty_data.sd_from_child,
|
|
|
|
|
"CTRL %c\n",
|
|
|
|
|
ch);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this->tm_state = state::NORMAL;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case state::ESCAPE_FIXED_LENGTH: {
|
|
|
|
|
this->tm_escape_buffer.push_back(ch);
|
2022-03-16 22:38:08 +00:00
|
|
|
|
if (this->tm_escape_buffer.size()
|
2024-02-29 06:20:32 +00:00
|
|
|
|
== this->tm_escape_expected_size)
|
|
|
|
|
{
|
2021-01-08 22:08:52 +00:00
|
|
|
|
auto iter = CSI_TO_DESC.find(
|
|
|
|
|
std::string(this->tm_escape_buffer.data(),
|
|
|
|
|
this->tm_escape_buffer.size()));
|
|
|
|
|
this->flush_line();
|
|
|
|
|
if (iter == CSI_TO_DESC.end()) {
|
|
|
|
|
fprintf(scripty_data.sd_from_child,
|
|
|
|
|
"CTRL %.*s\n",
|
|
|
|
|
(int) this->tm_escape_buffer.size(),
|
|
|
|
|
this->tm_escape_buffer.data());
|
|
|
|
|
} else {
|
|
|
|
|
fprintf(scripty_data.sd_from_child,
|
|
|
|
|
"CTRL %s\n",
|
|
|
|
|
iter->second.c_str());
|
|
|
|
|
}
|
|
|
|
|
this->tm_state = state::NORMAL;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case state::ESCAPE_VARIABLE_LENGTH: {
|
|
|
|
|
this->tm_escape_buffer.push_back(ch);
|
|
|
|
|
if (isalpha(ch)) {
|
|
|
|
|
auto iter = CSI_TO_DESC.find(
|
|
|
|
|
std::string(this->tm_escape_buffer.data(),
|
|
|
|
|
this->tm_escape_buffer.size()));
|
|
|
|
|
if (iter == CSI_TO_DESC.end()) {
|
|
|
|
|
this->tm_escape_buffer.push_back('\0');
|
|
|
|
|
switch (ch) {
|
2021-02-06 00:04:34 +00:00
|
|
|
|
case 'A': {
|
2021-01-08 22:08:52 +00:00
|
|
|
|
auto amount = this->get_m_params();
|
2021-02-02 05:59:46 +00:00
|
|
|
|
int count = 1;
|
2021-01-08 22:08:52 +00:00
|
|
|
|
|
2021-02-02 05:59:46 +00:00
|
|
|
|
if (!amount.empty()) {
|
|
|
|
|
count = amount[0];
|
|
|
|
|
}
|
2021-02-06 00:04:34 +00:00
|
|
|
|
this->flush_line();
|
|
|
|
|
this->tm_cursor_y -= count;
|
|
|
|
|
if (this->tm_cursor_y < 0) {
|
|
|
|
|
this->tm_cursor_y = 0;
|
|
|
|
|
}
|
2021-01-08 22:08:52 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
2021-02-03 05:58:42 +00:00
|
|
|
|
case 'B': {
|
|
|
|
|
auto amount = this->get_m_params();
|
|
|
|
|
int count = 1;
|
|
|
|
|
|
|
|
|
|
if (!amount.empty()) {
|
|
|
|
|
count = amount[0];
|
|
|
|
|
}
|
|
|
|
|
this->flush_line();
|
|
|
|
|
this->tm_cursor_y += count;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2021-02-06 00:04:34 +00:00
|
|
|
|
case 'C': {
|
|
|
|
|
auto amount = this->get_m_params();
|
|
|
|
|
int count = 1;
|
|
|
|
|
|
|
|
|
|
if (!amount.empty()) {
|
|
|
|
|
count = amount[0];
|
|
|
|
|
}
|
|
|
|
|
this->tm_cursor_x += count;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2021-01-08 22:08:52 +00:00
|
|
|
|
case 'J': {
|
|
|
|
|
auto param = this->get_m_params();
|
|
|
|
|
|
|
|
|
|
this->flush_line();
|
|
|
|
|
|
|
|
|
|
auto region = param.empty() ? 0 : param[0];
|
|
|
|
|
switch (region) {
|
|
|
|
|
case 0:
|
|
|
|
|
fprintf(scripty_data.sd_from_child,
|
|
|
|
|
"CSI Erase Below\n");
|
|
|
|
|
break;
|
|
|
|
|
case 1:
|
|
|
|
|
fprintf(scripty_data.sd_from_child,
|
|
|
|
|
"CSI Erase Above\n");
|
|
|
|
|
break;
|
|
|
|
|
case 2:
|
|
|
|
|
fprintf(scripty_data.sd_from_child,
|
|
|
|
|
"CSI Erase All\n");
|
|
|
|
|
break;
|
|
|
|
|
case 3:
|
|
|
|
|
fprintf(scripty_data.sd_from_child,
|
|
|
|
|
"CSI Erase Saved Lines\n");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
2021-02-06 00:04:34 +00:00
|
|
|
|
case 'K': {
|
|
|
|
|
auto param = this->get_m_params();
|
|
|
|
|
|
|
|
|
|
this->flush_line();
|
|
|
|
|
|
|
|
|
|
auto region = param.empty() ? 0 : param[0];
|
|
|
|
|
switch (region) {
|
|
|
|
|
case 0:
|
|
|
|
|
fprintf(scripty_data.sd_from_child,
|
|
|
|
|
"CSI Erase to Right\n");
|
|
|
|
|
break;
|
|
|
|
|
case 1:
|
|
|
|
|
fprintf(scripty_data.sd_from_child,
|
|
|
|
|
"CSI Erase to Left\n");
|
|
|
|
|
break;
|
|
|
|
|
case 2:
|
|
|
|
|
fprintf(scripty_data.sd_from_child,
|
|
|
|
|
"CSI Erase All\n");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
2021-01-08 22:08:52 +00:00
|
|
|
|
case 'H': {
|
|
|
|
|
auto coords = this->get_m_params();
|
|
|
|
|
|
|
|
|
|
if (coords.empty()) {
|
|
|
|
|
coords = {1, 1};
|
|
|
|
|
}
|
|
|
|
|
this->flush_line();
|
|
|
|
|
this->tm_cursor_y = coords[0];
|
|
|
|
|
this->tm_cursor_x = coords[1] - 1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case 'r': {
|
|
|
|
|
auto region = this->get_m_params();
|
2019-07-30 05:18:32 +00:00
|
|
|
|
|
2021-01-08 22:08:52 +00:00
|
|
|
|
this->flush_line();
|
|
|
|
|
fprintf(scripty_data.sd_from_child,
|
|
|
|
|
"CSI set scrolling region %d-%d\n",
|
|
|
|
|
region[0],
|
|
|
|
|
region[1]);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case 'm': {
|
|
|
|
|
auto attrs = this->get_m_params();
|
|
|
|
|
|
|
|
|
|
if (attrs.empty()) {
|
|
|
|
|
this->add_line_attr("normal");
|
2022-03-16 22:38:08 +00:00
|
|
|
|
} else if ((30 <= attrs[0]) && (attrs[0] <= 37))
|
|
|
|
|
{
|
2021-01-08 22:08:52 +00:00
|
|
|
|
auto xt = xterm_colors();
|
|
|
|
|
|
|
|
|
|
this->add_line_attr(fmt::format(
|
|
|
|
|
"fg({})",
|
|
|
|
|
xt->tc_palette[attrs[0] - 30].xc_hex));
|
|
|
|
|
} else if (attrs[0] == 38) {
|
|
|
|
|
auto xt = xterm_colors();
|
|
|
|
|
|
|
|
|
|
require(attrs[1] == 5);
|
|
|
|
|
this->add_line_attr(fmt::format(
|
|
|
|
|
"fg({})",
|
|
|
|
|
xt->tc_palette[attrs[2]].xc_hex));
|
2022-03-16 22:38:08 +00:00
|
|
|
|
} else if ((40 <= attrs[0]) && (attrs[0] <= 47))
|
|
|
|
|
{
|
2021-01-08 22:08:52 +00:00
|
|
|
|
auto xt = xterm_colors();
|
|
|
|
|
|
|
|
|
|
this->add_line_attr(fmt::format(
|
2022-03-16 22:38:08 +00:00
|
|
|
|
"bg({})",
|
|
|
|
|
xt->tc_palette[attrs[0] - 40].xc_hex));
|
2021-01-08 22:08:52 +00:00
|
|
|
|
} else if (attrs[0] == 48) {
|
|
|
|
|
auto xt = xterm_colors();
|
|
|
|
|
|
|
|
|
|
require(attrs[1] == 5);
|
|
|
|
|
this->add_line_attr(fmt::format(
|
|
|
|
|
"bg({})",
|
|
|
|
|
xt->tc_palette[attrs[2]].xc_hex));
|
|
|
|
|
} else {
|
|
|
|
|
switch (attrs[0]) {
|
|
|
|
|
case 1:
|
|
|
|
|
this->add_line_attr("bold");
|
|
|
|
|
break;
|
|
|
|
|
case 4:
|
|
|
|
|
this->add_line_attr("underline");
|
|
|
|
|
break;
|
|
|
|
|
case 5:
|
|
|
|
|
this->add_line_attr("blink");
|
|
|
|
|
break;
|
|
|
|
|
case 7:
|
|
|
|
|
this->add_line_attr("inverse");
|
|
|
|
|
break;
|
|
|
|
|
default:
|
2022-03-16 22:38:08 +00:00
|
|
|
|
this->add_line_attr(
|
|
|
|
|
this->tm_escape_buffer.data());
|
2021-01-08 22:08:52 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default:
|
2021-02-03 05:58:42 +00:00
|
|
|
|
fprintf(stderr, "%s:missed %c\n", tstamp(), ch);
|
2022-03-16 22:38:08 +00:00
|
|
|
|
this->add_line_attr(
|
|
|
|
|
this->tm_escape_buffer.data());
|
2021-01-08 22:08:52 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
this->flush_line();
|
|
|
|
|
fprintf(scripty_data.sd_from_child,
|
|
|
|
|
"CSI %s\n",
|
|
|
|
|
iter->second.c_str());
|
|
|
|
|
}
|
|
|
|
|
this->tm_state = state::NORMAL;
|
|
|
|
|
} else {
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case state::ESCAPE_OSC: {
|
|
|
|
|
if (ch == '\a') {
|
|
|
|
|
this->tm_escape_buffer.push_back('\0');
|
|
|
|
|
|
|
|
|
|
auto num = this->get_m_params();
|
2022-03-16 22:38:08 +00:00
|
|
|
|
auto semi_index
|
|
|
|
|
= strchr(this->tm_escape_buffer.data(), ';');
|
2021-01-08 22:08:52 +00:00
|
|
|
|
|
|
|
|
|
switch (num[0]) {
|
|
|
|
|
case 0: {
|
|
|
|
|
this->flush_line();
|
|
|
|
|
fprintf(scripty_data.sd_from_child,
|
|
|
|
|
"OSC Set window title: %s\n",
|
|
|
|
|
semi_index + 1);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case 999: {
|
|
|
|
|
this->flush_line();
|
|
|
|
|
this->tm_waiting_on_input = true;
|
|
|
|
|
if (!scripty_data.sd_replay.empty()) {
|
2022-03-16 22:38:08 +00:00
|
|
|
|
const auto& cmd
|
|
|
|
|
= scripty_data.sd_replay.front();
|
2021-01-08 22:08:52 +00:00
|
|
|
|
|
|
|
|
|
this->tm_user_input = cmd.c_arg;
|
|
|
|
|
write(this->tm_child_term.get_fd(),
|
|
|
|
|
this->tm_user_input.data(),
|
|
|
|
|
this->tm_user_input.size());
|
|
|
|
|
|
|
|
|
|
scripty_data.sd_replay.pop_front();
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2009-09-14 01:07:32 +00:00
|
|
|
|
|
2021-01-08 22:08:52 +00:00
|
|
|
|
this->tm_state = state::NORMAL;
|
|
|
|
|
} else {
|
|
|
|
|
this->tm_escape_buffer.push_back(ch);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2009-09-14 01:07:32 +00:00
|
|
|
|
|
2021-01-08 22:08:52 +00:00
|
|
|
|
child_term& tm_child_term;
|
|
|
|
|
bool tm_waiting_on_input{false};
|
|
|
|
|
state tm_state{state::NORMAL};
|
|
|
|
|
std::vector<char> tm_escape_buffer;
|
|
|
|
|
std::deque<uint8_t> tm_unicode_buffer;
|
|
|
|
|
size_t tm_unicode_remaining{0};
|
|
|
|
|
size_t tm_escape_expected_size{0};
|
|
|
|
|
uint32_t tm_line[80];
|
|
|
|
|
bool tm_new_data{false};
|
|
|
|
|
size_t tm_cursor_x{0};
|
|
|
|
|
int tm_cursor_y{-1};
|
|
|
|
|
size_t tm_shift_start{0};
|
|
|
|
|
std::vector<term_attr> tm_line_attrs;
|
|
|
|
|
|
|
|
|
|
std::vector<char> tm_user_input;
|
|
|
|
|
|
|
|
|
|
size_t tm_flush_count{0};
|
|
|
|
|
};
|
2009-09-14 01:07:32 +00:00
|
|
|
|
|
2022-03-16 22:38:08 +00:00
|
|
|
|
static void
|
|
|
|
|
sigchld(int sig)
|
2009-09-14 01:07:32 +00:00
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-16 22:38:08 +00:00
|
|
|
|
static void
|
|
|
|
|
sigpass(int sig)
|
2009-09-14 01:07:32 +00:00
|
|
|
|
{
|
|
|
|
|
kill(scripty_data.sd_child_pid, sig);
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-16 22:38:08 +00:00
|
|
|
|
static void
|
|
|
|
|
usage()
|
2009-09-14 01:07:32 +00:00
|
|
|
|
{
|
2022-03-16 22:38:08 +00:00
|
|
|
|
const char* usage_msg
|
|
|
|
|
= "usage: %s [-h] [-t to_child] [-f from_child] -- <cmd>\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"Recorder for TTY I/O from a child process."
|
|
|
|
|
"\n"
|
|
|
|
|
"Options:\n"
|
|
|
|
|
" -h Print this message, then exit.\n"
|
|
|
|
|
" -n Do not pass the output to the console.\n"
|
|
|
|
|
" -i Pass stdin to the child process instead of connecting\n"
|
|
|
|
|
" the child to the tty.\n"
|
|
|
|
|
" -a <file> The file where the actual I/O from/to the child "
|
|
|
|
|
"process\n"
|
|
|
|
|
" should be stored.\n"
|
|
|
|
|
" -e <file> The file containing the expected I/O from/to the "
|
|
|
|
|
"child\n"
|
|
|
|
|
" process.\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"Examples:\n"
|
|
|
|
|
" To record a session for playback later:\n"
|
|
|
|
|
" $ scripty -a output.0 -- myCursesApp\n"
|
|
|
|
|
"\n"
|
|
|
|
|
" To replay the recorded session:\n"
|
|
|
|
|
" $ scripty -e input.0 -- myCursesApp\n";
|
2019-07-30 05:18:32 +00:00
|
|
|
|
|
2009-09-14 01:07:32 +00:00
|
|
|
|
fprintf(stderr, usage_msg, scripty_data.sd_program_name);
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-16 22:38:08 +00:00
|
|
|
|
int
|
|
|
|
|
main(int argc, char* argv[])
|
2009-09-14 01:07:32 +00:00
|
|
|
|
{
|
|
|
|
|
int c, fd, retval = EXIT_SUCCESS;
|
2021-01-08 22:08:52 +00:00
|
|
|
|
bool passout = true, passin = false, prompt = false;
|
|
|
|
|
auto_mem<FILE> file(fclose);
|
2009-09-14 01:07:32 +00:00
|
|
|
|
|
|
|
|
|
scripty_data.sd_program_name = argv[0];
|
|
|
|
|
scripty_data.sd_looping = true;
|
2019-07-30 05:18:32 +00:00
|
|
|
|
|
2021-01-08 22:08:52 +00:00
|
|
|
|
while ((c = getopt(argc, argv, "ha:e:nip")) != -1) {
|
2019-07-30 05:18:32 +00:00
|
|
|
|
switch (c) {
|
|
|
|
|
case 'h':
|
|
|
|
|
usage();
|
|
|
|
|
exit(retval);
|
|
|
|
|
break;
|
2021-01-08 22:08:52 +00:00
|
|
|
|
case 'a':
|
|
|
|
|
scripty_data.sd_actual_name = optarg;
|
2019-07-30 05:18:32 +00:00
|
|
|
|
break;
|
|
|
|
|
case 'e':
|
2021-01-08 22:08:52 +00:00
|
|
|
|
scripty_data.sd_expected_name = optarg;
|
|
|
|
|
if ((file = fopen(optarg, "r")) == nullptr) {
|
2022-03-16 22:38:08 +00:00
|
|
|
|
fprintf(
|
|
|
|
|
stderr, "%s:error: cannot open %s\n", tstamp(), optarg);
|
2019-07-30 05:18:32 +00:00
|
|
|
|
retval = EXIT_FAILURE;
|
|
|
|
|
} else {
|
|
|
|
|
char line[32 * 1024];
|
|
|
|
|
|
|
|
|
|
while (fgets(line, sizeof(line), file)) {
|
2021-01-08 22:08:52 +00:00
|
|
|
|
if (line[0] == 'K') {
|
2019-07-30 05:18:32 +00:00
|
|
|
|
struct command cmd;
|
|
|
|
|
|
2021-01-08 22:08:52 +00:00
|
|
|
|
cmd.c_type = CT_WRITE;
|
|
|
|
|
cmd.c_arg = hex2bits(&line[2]);
|
|
|
|
|
scripty_data.sd_replay.push_back(cmd);
|
2019-07-30 05:18:32 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'n':
|
|
|
|
|
passout = false;
|
|
|
|
|
break;
|
|
|
|
|
case 'i':
|
|
|
|
|
passin = true;
|
|
|
|
|
break;
|
2021-01-08 22:08:52 +00:00
|
|
|
|
case 'p':
|
|
|
|
|
prompt = true;
|
|
|
|
|
break;
|
2019-07-30 05:18:32 +00:00
|
|
|
|
default:
|
2021-02-03 05:58:42 +00:00
|
|
|
|
fprintf(stderr, "%s:error: unknown flag -- %c\n", tstamp(), c);
|
2019-07-30 05:18:32 +00:00
|
|
|
|
retval = EXIT_FAILURE;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2009-09-14 01:07:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
argc -= optind;
|
|
|
|
|
argv += optind;
|
|
|
|
|
|
2022-03-16 22:38:08 +00:00
|
|
|
|
if (!scripty_data.sd_expected_name.empty()
|
|
|
|
|
&& scripty_data.sd_actual_name.empty())
|
|
|
|
|
{
|
|
|
|
|
scripty_data.sd_actual_name = scripty_data.sd_expected_name.filename();
|
2021-01-08 22:08:52 +00:00
|
|
|
|
scripty_data.sd_actual_name += ".tmp";
|
2009-09-14 01:07:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-01-08 22:08:52 +00:00
|
|
|
|
if (!scripty_data.sd_actual_name.empty()) {
|
2022-03-16 22:38:08 +00:00
|
|
|
|
if ((scripty_data.sd_from_child
|
|
|
|
|
= fopen(scripty_data.sd_actual_name.c_str(), "w"))
|
|
|
|
|
== nullptr)
|
|
|
|
|
{
|
2019-07-30 05:18:32 +00:00
|
|
|
|
fprintf(stderr,
|
2021-01-08 22:08:52 +00:00
|
|
|
|
"error: unable to open %s -- %s\n",
|
|
|
|
|
scripty_data.sd_actual_name.c_str(),
|
2019-07-30 05:18:32 +00:00
|
|
|
|
strerror(errno));
|
|
|
|
|
retval = EXIT_FAILURE;
|
|
|
|
|
}
|
2009-09-14 01:07:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-01-08 22:08:52 +00:00
|
|
|
|
if (scripty_data.sd_from_child != nullptr) {
|
2019-07-30 05:18:32 +00:00
|
|
|
|
fcntl(fileno(scripty_data.sd_from_child), F_SETFD, 1);
|
2021-01-08 22:08:52 +00:00
|
|
|
|
}
|
2019-07-30 05:18:32 +00:00
|
|
|
|
|
2009-09-14 01:07:32 +00:00
|
|
|
|
if (retval != EXIT_FAILURE) {
|
2021-01-08 22:08:52 +00:00
|
|
|
|
guard_termios gt(STDOUT_FILENO);
|
|
|
|
|
fd = open("/tmp/scripty.err", O_WRONLY | O_CREAT | O_APPEND, 0666);
|
|
|
|
|
dup2(fd, STDERR_FILENO);
|
|
|
|
|
close(fd);
|
2021-02-03 05:58:42 +00:00
|
|
|
|
fprintf(stderr, "%s:startup\n", tstamp());
|
2021-01-08 22:08:52 +00:00
|
|
|
|
|
2019-07-30 05:18:32 +00:00
|
|
|
|
child_term ct(passin);
|
|
|
|
|
|
|
|
|
|
if (ct.is_child()) {
|
|
|
|
|
execvp(argv[0], argv);
|
|
|
|
|
perror("execvp");
|
|
|
|
|
exit(-1);
|
|
|
|
|
} else {
|
2021-01-08 22:08:52 +00:00
|
|
|
|
int maxfd;
|
2019-07-30 05:18:32 +00:00
|
|
|
|
struct timeval last, now;
|
|
|
|
|
fd_set read_fds;
|
2021-01-08 22:08:52 +00:00
|
|
|
|
term_machine tm(ct);
|
2021-03-20 20:39:35 +00:00
|
|
|
|
size_t last_replay_size = scripty_data.sd_replay.size();
|
2019-07-30 05:18:32 +00:00
|
|
|
|
|
|
|
|
|
scripty_data.sd_child_pid = ct.get_child_pid();
|
|
|
|
|
signal(SIGINT, sigpass);
|
|
|
|
|
signal(SIGTERM, sigpass);
|
|
|
|
|
|
|
|
|
|
signal(SIGCHLD, sigchld);
|
|
|
|
|
|
2021-01-08 22:08:52 +00:00
|
|
|
|
gettimeofday(&now, nullptr);
|
2019-07-30 05:18:32 +00:00
|
|
|
|
last = now;
|
|
|
|
|
|
|
|
|
|
FD_ZERO(&read_fds);
|
|
|
|
|
FD_SET(STDIN_FILENO, &read_fds);
|
|
|
|
|
FD_SET(ct.get_fd(), &read_fds);
|
|
|
|
|
|
2021-02-03 05:58:42 +00:00
|
|
|
|
fprintf(stderr, "%s:goin in the loop\n", tstamp());
|
2019-07-30 05:18:32 +00:00
|
|
|
|
|
|
|
|
|
tty_raw(STDIN_FILENO);
|
|
|
|
|
|
|
|
|
|
maxfd = max(STDIN_FILENO, ct.get_fd());
|
|
|
|
|
while (scripty_data.sd_looping) {
|
|
|
|
|
fd_set ready_rfds = read_fds;
|
|
|
|
|
struct timeval diff, to;
|
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
|
|
to.tv_sec = 0;
|
|
|
|
|
to.tv_usec = 10000;
|
2021-01-08 22:08:52 +00:00
|
|
|
|
rc = select(maxfd + 1, &ready_rfds, nullptr, nullptr, &to);
|
2021-03-20 06:12:33 +00:00
|
|
|
|
gettimeofday(&now, nullptr);
|
|
|
|
|
timersub(&now, &last, &diff);
|
|
|
|
|
if (diff.tv_sec > 10) {
|
2021-03-20 20:39:35 +00:00
|
|
|
|
fprintf(stderr, "%s:replay timed out!\n", tstamp());
|
2021-03-20 06:12:33 +00:00
|
|
|
|
scripty_data.sd_looping = false;
|
|
|
|
|
kill(ct.get_child_pid(), SIGKILL);
|
|
|
|
|
retval = EXIT_FAILURE;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2019-07-30 05:18:32 +00:00
|
|
|
|
if (rc == 0) {
|
|
|
|
|
} else if (rc < 0) {
|
|
|
|
|
switch (errno) {
|
|
|
|
|
case EINTR:
|
|
|
|
|
break;
|
|
|
|
|
default:
|
2022-03-16 22:38:08 +00:00
|
|
|
|
fprintf(stderr,
|
|
|
|
|
"%s:select %s\n",
|
|
|
|
|
tstamp(),
|
|
|
|
|
strerror(errno));
|
2021-03-20 06:12:33 +00:00
|
|
|
|
kill(ct.get_child_pid(), SIGKILL);
|
2019-07-30 05:18:32 +00:00
|
|
|
|
scripty_data.sd_looping = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
char buffer[1024];
|
|
|
|
|
|
2021-02-03 05:58:42 +00:00
|
|
|
|
fprintf(stderr, "%s:fds ready %d\n", tstamp(), rc);
|
2019-07-30 05:18:32 +00:00
|
|
|
|
if (FD_ISSET(STDIN_FILENO, &ready_rfds)) {
|
|
|
|
|
rc = read(STDIN_FILENO, buffer, sizeof(buffer));
|
|
|
|
|
if (rc < 0) {
|
|
|
|
|
scripty_data.sd_looping = false;
|
|
|
|
|
} else if (rc == 0) {
|
|
|
|
|
FD_CLR(STDIN_FILENO, &read_fds);
|
|
|
|
|
} else {
|
|
|
|
|
log_perror(write(ct.get_fd(), buffer, rc));
|
2021-01-08 22:08:52 +00:00
|
|
|
|
|
|
|
|
|
for (ssize_t lpc = 0; lpc < rc; lpc++) {
|
2022-03-16 22:38:08 +00:00
|
|
|
|
fprintf(stderr,
|
|
|
|
|
"%s:to-child %02x\n",
|
|
|
|
|
tstamp(),
|
|
|
|
|
buffer[lpc] & 0xff);
|
2021-01-08 22:08:52 +00:00
|
|
|
|
tm.new_user_input(buffer[lpc]);
|
2019-07-30 05:18:32 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-03-20 06:12:33 +00:00
|
|
|
|
last = now;
|
2019-07-30 05:18:32 +00:00
|
|
|
|
}
|
|
|
|
|
if (FD_ISSET(ct.get_fd(), &ready_rfds)) {
|
|
|
|
|
rc = read(ct.get_fd(), buffer, sizeof(buffer));
|
2021-02-03 05:58:42 +00:00
|
|
|
|
fprintf(stderr, "%s:read rc %d\n", tstamp(), rc);
|
2019-07-30 05:18:32 +00:00
|
|
|
|
if (rc <= 0) {
|
|
|
|
|
scripty_data.sd_looping = false;
|
|
|
|
|
} else {
|
2021-03-20 20:39:35 +00:00
|
|
|
|
if (passout) {
|
2019-07-30 05:18:32 +00:00
|
|
|
|
log_perror(write(STDOUT_FILENO, buffer, rc));
|
2021-03-20 20:39:35 +00:00
|
|
|
|
}
|
2021-01-08 22:08:52 +00:00
|
|
|
|
if (scripty_data.sd_from_child != nullptr) {
|
|
|
|
|
for (size_t lpc = 0; lpc < rc; lpc++) {
|
2021-02-03 05:58:42 +00:00
|
|
|
|
#if 0
|
|
|
|
|
fprintf(stderr, "%s:from-child %02x\n",
|
|
|
|
|
tstamp(),
|
2021-01-08 22:08:52 +00:00
|
|
|
|
buffer[lpc] & 0xff);
|
2021-02-03 05:58:42 +00:00
|
|
|
|
#endif
|
2021-01-08 22:08:52 +00:00
|
|
|
|
tm.new_input(buffer[lpc]);
|
2022-03-16 22:38:08 +00:00
|
|
|
|
if (scripty_data.sd_replay.size()
|
2024-02-29 06:20:32 +00:00
|
|
|
|
!= last_replay_size)
|
|
|
|
|
{
|
2021-03-20 06:12:33 +00:00
|
|
|
|
last = now;
|
2022-03-16 22:38:08 +00:00
|
|
|
|
last_replay_size
|
|
|
|
|
= scripty_data.sd_replay.size();
|
2021-03-20 06:12:33 +00:00
|
|
|
|
}
|
2021-01-08 22:08:52 +00:00
|
|
|
|
}
|
2019-07-30 05:18:32 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
retval = ct.wait_for_child() || retval;
|
2009-09-14 01:07:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-01-08 22:08:52 +00:00
|
|
|
|
if (retval == EXIT_SUCCESS && !scripty_data.sd_expected_name.empty()) {
|
|
|
|
|
auto cmd = fmt::format("diff -ua {} {}",
|
|
|
|
|
scripty_data.sd_expected_name.string(),
|
|
|
|
|
scripty_data.sd_actual_name.string());
|
|
|
|
|
auto rc = system(cmd.c_str());
|
|
|
|
|
if (rc != 0) {
|
|
|
|
|
if (prompt) {
|
|
|
|
|
char resp[4];
|
|
|
|
|
|
|
|
|
|
printf("Would you like to update the original file? (y/N) ");
|
|
|
|
|
fflush(stdout);
|
|
|
|
|
log_perror(scanf("%3s", resp));
|
|
|
|
|
if (strcasecmp(resp, "y") == 0) {
|
|
|
|
|
printf("Updating: %s -> %s\n",
|
|
|
|
|
scripty_data.sd_actual_name.c_str(),
|
|
|
|
|
scripty_data.sd_expected_name.c_str());
|
|
|
|
|
|
2022-03-16 22:38:08 +00:00
|
|
|
|
auto options
|
|
|
|
|
= ghc::filesystem::copy_options::overwrite_existing;
|
|
|
|
|
ghc::filesystem::copy_file(scripty_data.sd_actual_name,
|
|
|
|
|
scripty_data.sd_expected_name,
|
|
|
|
|
options);
|
|
|
|
|
} else {
|
2021-01-08 22:08:52 +00:00
|
|
|
|
retval = EXIT_FAILURE;
|
|
|
|
|
}
|
2022-03-16 22:38:08 +00:00
|
|
|
|
} else {
|
2021-02-03 05:58:42 +00:00
|
|
|
|
fprintf(stderr, "%s:error: mismatch\n", tstamp());
|
2021-01-08 22:08:52 +00:00
|
|
|
|
retval = EXIT_FAILURE;
|
|
|
|
|
}
|
|
|
|
|
}
|
2009-09-14 01:07:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return retval;
|
|
|
|
|
}
|