Public bytenuts repository creation
This commit is contained in:
commit
aaaf6cb805
14 changed files with 2576 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
build/
|
||||||
|
.vscode/
|
||||||
|
*.log
|
||||||
|
tags
|
||||||
44
Makefile
Normal file
44
Makefile
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
CC=clang
|
||||||
|
CFLAGS=-g -Wall -MMD -pthread -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE
|
||||||
|
LDFLAGS=-lpthread -lncurses
|
||||||
|
|
||||||
|
DIR_SRC=src
|
||||||
|
DIR_BUILD=build
|
||||||
|
DIR_OBJ=$(DIR_BUILD)/obj
|
||||||
|
DIR_BIN=$(DIR_BUILD)/bin
|
||||||
|
|
||||||
|
TARGET=$(DIR_BIN)/bytenuts
|
||||||
|
|
||||||
|
DEBUG ?= 0
|
||||||
|
ifeq ($(DEBUG), 1)
|
||||||
|
CFLAGS += -DDEBUG
|
||||||
|
endif
|
||||||
|
|
||||||
|
SRCS=$(wildcard $(DIR_SRC)/*.c)
|
||||||
|
OBJS=$(addprefix $(DIR_OBJ)/,$(addsuffix .o,$(basename $(SRCS))))
|
||||||
|
DEPS=$(addprefix $(DIR_OBJ)/,$(addsuffix .d,$(basename $(SRCS))))
|
||||||
|
|
||||||
|
.PHONY: all install uninstall clean
|
||||||
|
|
||||||
|
all: $(TARGET)
|
||||||
|
|
||||||
|
install:
|
||||||
|
ln -s $(realpath $(TARGET)) /usr/local/bin/bytenuts
|
||||||
|
|
||||||
|
uninstall:
|
||||||
|
rm -f /usr/local/bin/bytenuts
|
||||||
|
|
||||||
|
$(TARGET): $(OBJS)
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
@echo "link and compile for $@"
|
||||||
|
@$(CC) $(CFLAGS) $(LDFLAGS) -o $(TARGET) $(OBJS)
|
||||||
|
|
||||||
|
$(DIR_OBJ)/$(DIR_SRC)/%.o: $(DIR_SRC)/%.c
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
@echo "compile $<"
|
||||||
|
@$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf $(DIR_BUILD)
|
||||||
|
|
||||||
|
-include $(DEPS)
|
||||||
125
README.md
Normal file
125
README.md
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
```
|
||||||
|
██████╗ ██╗ ██╗████████╗███████╗███╗ ██╗██╗ ██╗████████╗███████╗
|
||||||
|
██╔══██╗╚██╗ ██╔╝╚══██╔══╝██╔════╝████╗ ██║██║ ██║╚══██╔══╝██╔════╝
|
||||||
|
██████╔╝ ╚████╔╝ ██║ █████╗ ██╔██╗ ██║██║ ██║ ██║ ███████╗
|
||||||
|
██╔══██╗ ╚██╔╝ ██║ ██╔══╝ ██║╚██╗██║██║ ██║ ██║ ╚════██║
|
||||||
|
██████╔╝ ██║ ██║ ███████╗██║ ╚████║╚██████╔╝ ██║ ███████║
|
||||||
|
╚═════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚══════╝
|
||||||
|
```
|
||||||
|
|
||||||
|
Bytenuts aims to be a light weight terminal replacement of GUI based serial communications apps like SecureCRT or ZOC. It allows for simple communication between the PC and serial device.
|
||||||
|
|
||||||
|
Bytenuts has been tested and built on x86-64 and 32-bit ARM Ubuntu 18.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Input queueing - The input line allows users to edit their command before sending the characters over the serial connection
|
||||||
|
- XModem transfers - Users can begin an XModem transfer of a file on disc
|
||||||
|
- 8-bit ANSI color support - 8-bit terminal color can be enabled
|
||||||
|
- Configuration - Bytenuts can be configured with the config file located at `~/.config/bytenuts/config`
|
||||||
|
- Input echoing - Bytenuts can echo user input rather than relying on the connected device to echo
|
||||||
|
- Command history - Simply press up/down arrow to load previous commands
|
||||||
|
- Output history - Use page up/down, home/end, and ctrl + up/down arrow to scroll through the output window
|
||||||
|
- Output logging - Output can be saved to a log file passed in with the `-l` option
|
||||||
|
- Quick commands - Pages of quick commands are loaded from `~/.config/bytenuts/commands[1-10]`
|
||||||
|
|
||||||
|
## Planned Features
|
||||||
|
|
||||||
|
Bytenuts is still very early in development. Here are some features that may get implemented.
|
||||||
|
|
||||||
|
- Other transfer protocols - Maybe Kermit and ZModem
|
||||||
|
|
||||||
|
## Un-Planned Features
|
||||||
|
|
||||||
|
Bytenuts is not supposed to be the be-all end-all application, nor is it supposed to be able to do everything SecureCRT can. Here are some things it probably won't have.
|
||||||
|
|
||||||
|
- Non-serial communications - No SSH, telnet, etc.
|
||||||
|
- Elaborate menuing - Bytenuts functions will be shortcut driven to focus on what's important (the serial communication)
|
||||||
|
- Script loading - Think like how you can load scripts in ZOC on SecureCRT. Write normal Python scripts if you want to do that kind of stuff.
|
||||||
|
- Multi connection support - Open another instance of Bytenuts if you need to
|
||||||
|
- Windows support - No
|
||||||
|
|
||||||
|
## Launch Options
|
||||||
|
|
||||||
|
Launching Bytenuts like `bytenuts -h` provides this help print:
|
||||||
|
|
||||||
|
```
|
||||||
|
USAGE
|
||||||
|
|
||||||
|
bytenuts [OPTIONS] <serial path>
|
||||||
|
|
||||||
|
Configs get loaded from ${HOME}/.bytenuts/config (if file exists)
|
||||||
|
|
||||||
|
OPTIONS
|
||||||
|
|
||||||
|
-h: show this help
|
||||||
|
-b <baud>: set a baud rate (default 115200)
|
||||||
|
-l <path>: log all output to the given file
|
||||||
|
-c <path>: load a config from the given path rather than the default
|
||||||
|
```
|
||||||
|
|
||||||
|
## Navigation
|
||||||
|
|
||||||
|
Controls like backspace, delete, end/home, and left/right arrow work as expected within the input buffer window. For the output window, you can use the following keys to scroll through it:
|
||||||
|
|
||||||
|
- `ctrl + up/down arrow` - Scroll the output window up or down by one line
|
||||||
|
- `page up/down` - Scroll the output window up or down by half of the height of the output window
|
||||||
|
- `shift + home/end` - Jump to the beginning or the end of the output
|
||||||
|
|
||||||
|
If the output window reaches the current line of output, then the output will continue scrolling in real time. Otherwise, the output is paused to continue viewing where you currently are.
|
||||||
|
|
||||||
|
## Configuration File
|
||||||
|
|
||||||
|
There are only three supported configs currently. Each config is defined like `<name>=<value>`. Here is a sample config:
|
||||||
|
|
||||||
|
```
|
||||||
|
colors=1
|
||||||
|
echo=0
|
||||||
|
no_crlf=0
|
||||||
|
```
|
||||||
|
|
||||||
|
- `colors` - enable parsing of 8-bit ANSI color codes
|
||||||
|
- `echo` - echo input to the terminal in app
|
||||||
|
- `no_crlf` - just send a line feed (`\n`) for user input rather than carriage return + line feed (`\r\n`)
|
||||||
|
|
||||||
|
Bytenuts looks for the configs at `~/.config/bytenuts/config`.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
Upon starting bytenuts you get this prompt:
|
||||||
|
|
||||||
|
```
|
||||||
|
Welcome to Bytenuts
|
||||||
|
To quit press ctrl-b q
|
||||||
|
To see all commands, press ctrl-b h
|
||||||
|
```
|
||||||
|
|
||||||
|
The help prompt lists the available commands.
|
||||||
|
|
||||||
|
```
|
||||||
|
Commands (lead with ctrl-b):
|
||||||
|
c: print available quick commands
|
||||||
|
0-9: load the given quick command (0 is 10)
|
||||||
|
p, 0-9: select a different quick commands page (0 is 10)
|
||||||
|
i: view info/stats
|
||||||
|
x: start XModem upload with 128B payloads
|
||||||
|
X: start XModem upload with 1024B payloads
|
||||||
|
h: view this help
|
||||||
|
q: quit Bytenuts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Commands
|
||||||
|
|
||||||
|
You can provide multiple pages of quick commands you can easily load in with `ctrl+b [0-9]` in the files `~/.config/bytenuts/commands<idx>`. Bytenuts will load pages starting from `commands1` until a `commands<idx>` no longer exists. The commands should be newline separated. When a command is loaded, the entire contents of the line is loaded into the input buffer.
|
||||||
|
|
||||||
|
Bytenuts will only store the first ten commands for each page, so the first 10 lines of your command files will be used.
|
||||||
|
|
||||||
|
On startup, command page 1 is selected. You can select which command page is currently loaded by doing `ctrl+p, [0-9]` to load the desired page (note that index 0 will correspond to `commands10`).
|
||||||
|
|
||||||
|
## Bugs
|
||||||
|
|
||||||
|
Check out known bugs in the [issues tab](https://github.com/cookthebook/bytenuts/issues?q=is%3Aissue+is%3Aopen+label%3Abug).
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
Building Bytenuts is very simple. All you need is clang and libncurses. Run `make` in the Bytenuts root directory to build. You can also install the build (creating a link in `/usr/local/bin` to the `build` directory) by running `sudo make install`.
|
||||||
518
src/bytenuts.c
Normal file
518
src/bytenuts.c
Normal file
|
|
@ -0,0 +1,518 @@
|
||||||
|
#include <ncurses.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <sched.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "bytenuts.h"
|
||||||
|
#include "cheerios.h"
|
||||||
|
#include "ingest.h"
|
||||||
|
|
||||||
|
#define USAGE ( \
|
||||||
|
"USAGE\n\n" \
|
||||||
|
"bytenuts [OPTIONS] <serial path>\n" \
|
||||||
|
"\nConfigs get loaded from ${HOME}/.bytenuts/config (if file exists)\n" \
|
||||||
|
"\nOPTIONS\n\n" \
|
||||||
|
"-h: show this help\n" \
|
||||||
|
"-b <baud>: set a baud rate (default 115200)\n" \
|
||||||
|
"-l <path>: log all output to the given file\n" \
|
||||||
|
"-c <path>: load a config from the given path rather than the default\n" \
|
||||||
|
"--colors=<0|1>: turn 8-bit ANSI colors off/on\n" \
|
||||||
|
"--echo=<0|1>: turn input echoing off/on\n" \
|
||||||
|
"--no_crlf=<0|1>: choose to send LF and not CRLF on input\n" \
|
||||||
|
"--escape=<char>: change the default ctrl+b escape character\n" \
|
||||||
|
)
|
||||||
|
|
||||||
|
static int parse_args(int argc, char **argv);
|
||||||
|
static int load_configs();
|
||||||
|
static speed_t string_to_speed(const char *speed);
|
||||||
|
static const char *speed_to_string(speed_t speed);
|
||||||
|
|
||||||
|
static bytenuts_t bytenuts;
|
||||||
|
|
||||||
|
int
|
||||||
|
bytenuts_run(int argc, char **argv)
|
||||||
|
{
|
||||||
|
memset(&bytenuts, 0, sizeof(bytenuts_t));
|
||||||
|
|
||||||
|
if (parse_args(argc, argv)) {
|
||||||
|
printf(USAGE);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (load_configs()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytenuts.serial_fd = serial_open(bytenuts.config.serial_path, bytenuts.config.baud);
|
||||||
|
if (bytenuts.serial_fd < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* use pseudo-terminals for testing purposes */
|
||||||
|
if (!strcmp(bytenuts.config.serial_path, "/dev/ptmx")) {
|
||||||
|
grantpt(bytenuts.serial_fd);
|
||||||
|
unlockpt(bytenuts.serial_fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize ncurses
|
||||||
|
initscr();
|
||||||
|
raw();
|
||||||
|
noecho();
|
||||||
|
#if 0
|
||||||
|
mousemask(BUTTON4_PRESSED | BUTTON5_PRESSED, NULL);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// setup status, output, and input windows
|
||||||
|
bytenuts.status_win = newwin(1, COLS, LINES - 2, 0);
|
||||||
|
wmove(bytenuts.status_win, 0, 0);
|
||||||
|
wrefresh(bytenuts.status_win);
|
||||||
|
bytenuts.out_win = newwin(LINES - 2, COLS, 0, 0);
|
||||||
|
wmove(bytenuts.out_win, 0, 0);
|
||||||
|
wrefresh(bytenuts.out_win);
|
||||||
|
bytenuts.in_win = newwin(1, COLS, LINES - 1, 0);
|
||||||
|
keypad(bytenuts.in_win, TRUE);
|
||||||
|
wtimeout(bytenuts.in_win, 1);
|
||||||
|
wmove(bytenuts.in_win, 0, 0);
|
||||||
|
wrefresh(bytenuts.in_win);
|
||||||
|
|
||||||
|
bytenuts.ingest_status = strdup("");
|
||||||
|
bytenuts.cheerios_status = strdup("");
|
||||||
|
bytenuts_set_status(STATUS_BYTENUTS, "%s", bytenuts.config.serial_path);
|
||||||
|
|
||||||
|
// Initialize in and out threads
|
||||||
|
if (pthread_mutex_init(&bytenuts.lock, NULL)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pthread_mutex_init(&bytenuts.term_lock, NULL)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pthread_cond_init(&bytenuts.stop_cond, NULL)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
cheerios_start(&bytenuts);
|
||||||
|
ingest_start(&bytenuts);
|
||||||
|
|
||||||
|
if (!strcmp(bytenuts.config.serial_path, "/dev/ptmx")) {
|
||||||
|
char info[128];
|
||||||
|
snprintf(info, sizeof(info), "Opened PTY port %s", ptsname(bytenuts.serial_fd));
|
||||||
|
cheerios_info(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&bytenuts.lock);
|
||||||
|
pthread_cond_wait(&bytenuts.stop_cond, &bytenuts.lock);
|
||||||
|
pthread_mutex_unlock(&bytenuts.lock);
|
||||||
|
|
||||||
|
bytenuts_kill();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
bytenuts_stop()
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&bytenuts.lock);
|
||||||
|
pthread_cond_signal(&bytenuts.stop_cond);
|
||||||
|
pthread_mutex_unlock(&bytenuts.lock);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
bytenuts_kill()
|
||||||
|
{
|
||||||
|
ingest_stop();
|
||||||
|
cheerios_stop();
|
||||||
|
|
||||||
|
delwin(bytenuts.status_win);
|
||||||
|
delwin(bytenuts.in_win);
|
||||||
|
delwin(bytenuts.out_win);
|
||||||
|
endwin();
|
||||||
|
|
||||||
|
close(bytenuts.serial_fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
bytenuts_print_stats()
|
||||||
|
{
|
||||||
|
char st_line[128];
|
||||||
|
|
||||||
|
sprintf(st_line, "colors: %s\r\n", bytenuts.config.colors ? "enabled" : "disabled");
|
||||||
|
cheerios_insert(st_line, strlen(st_line));
|
||||||
|
sprintf(st_line, "echo: %s\r\n", bytenuts.config.echo ? "enabled" : "disabled");
|
||||||
|
cheerios_insert(st_line, strlen(st_line));
|
||||||
|
sprintf(st_line, "no_crlf: %s\r\n", bytenuts.config.no_crlf ? "enabled" : "disabled");
|
||||||
|
cheerios_insert(st_line, strlen(st_line));
|
||||||
|
sprintf(st_line, "escape: %c\r\n", bytenuts.config.escape);
|
||||||
|
cheerios_insert(st_line, strlen(st_line));
|
||||||
|
sprintf(st_line, "baud: %s\r\n", speed_to_string(bytenuts.config.baud));
|
||||||
|
cheerios_insert(st_line, strlen(st_line));
|
||||||
|
sprintf(st_line, "config_path: %s\r\n", bytenuts.config.config_path);
|
||||||
|
cheerios_insert(st_line, strlen(st_line));
|
||||||
|
sprintf(st_line, "log_path: %s\r\n", bytenuts.config.log_path);
|
||||||
|
cheerios_insert(st_line, strlen(st_line));
|
||||||
|
sprintf(st_line, "serial_path: %s\r\n", bytenuts.config.serial_path);
|
||||||
|
cheerios_insert(st_line, strlen(st_line));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
bytenuts_set_status(int user, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
int cx, cy;
|
||||||
|
char *new_status;
|
||||||
|
int len;
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
va_start(ap, fmt);
|
||||||
|
len = vsnprintf(NULL, 0, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
new_status = calloc(1, len + 1);
|
||||||
|
va_start(ap, fmt);
|
||||||
|
vsnprintf(new_status, len+1, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
pthread_mutex_lock(&bytenuts.lock);
|
||||||
|
switch (user) {
|
||||||
|
case STATUS_BYTENUTS:
|
||||||
|
if (bytenuts.bytenuts_status)
|
||||||
|
free(bytenuts.bytenuts_status);
|
||||||
|
bytenuts.bytenuts_status = new_status;
|
||||||
|
break;
|
||||||
|
case STATUS_INGEST:
|
||||||
|
if (bytenuts.ingest_status)
|
||||||
|
free(bytenuts.ingest_status);
|
||||||
|
bytenuts.ingest_status = new_status;
|
||||||
|
break;
|
||||||
|
case STATUS_CHEERIOS:
|
||||||
|
if (bytenuts.cheerios_status)
|
||||||
|
free(bytenuts.cheerios_status);
|
||||||
|
bytenuts.cheerios_status = new_status;
|
||||||
|
break;
|
||||||
|
case STATUS_CMDPAGE:
|
||||||
|
if (bytenuts.cmdpg_status)
|
||||||
|
free(bytenuts.cmdpg_status);
|
||||||
|
bytenuts.cmdpg_status = new_status;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
free(new_status);
|
||||||
|
pthread_mutex_unlock(&bytenuts.lock);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&bytenuts.lock);
|
||||||
|
|
||||||
|
pthread_mutex_lock(&bytenuts.term_lock);
|
||||||
|
|
||||||
|
getyx(bytenuts.in_win, cy, cx);
|
||||||
|
curs_set(0);
|
||||||
|
wmove(bytenuts.status_win, 0, 0);
|
||||||
|
|
||||||
|
int width = getmaxx(bytenuts.status_win);
|
||||||
|
for (int i = 0; i < width - 1; i++) {
|
||||||
|
waddch(bytenuts.status_win, '-');
|
||||||
|
}
|
||||||
|
waddch(bytenuts.status_win, '|');
|
||||||
|
wmove(bytenuts.status_win, 0, 0);
|
||||||
|
wprintw(
|
||||||
|
bytenuts.status_win, "|--%s--|--%s--|--%s--|--%s--|",
|
||||||
|
bytenuts.bytenuts_status, bytenuts.ingest_status,
|
||||||
|
bytenuts.cheerios_status, bytenuts.cmdpg_status
|
||||||
|
);
|
||||||
|
|
||||||
|
wmove(bytenuts.in_win, cy, cx);
|
||||||
|
curs_set(1);
|
||||||
|
wrefresh(bytenuts.status_win);
|
||||||
|
refresh();
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&bytenuts.term_lock);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
bytenuts_update_screen_size()
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&bytenuts.term_lock);
|
||||||
|
|
||||||
|
wresize(bytenuts.status_win, 1, COLS);
|
||||||
|
mvwin(bytenuts.status_win, LINES - 2, 0);
|
||||||
|
wrefresh(bytenuts.status_win);
|
||||||
|
|
||||||
|
wresize(bytenuts.out_win, LINES - 2, COLS);
|
||||||
|
mvwin(bytenuts.out_win, 0, 0);
|
||||||
|
wrefresh(bytenuts.out_win);
|
||||||
|
|
||||||
|
wresize(bytenuts.in_win, 1, COLS);
|
||||||
|
mvwin(bytenuts.in_win, LINES - 1, 0);
|
||||||
|
wrefresh(bytenuts.in_win);
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&bytenuts.term_lock);
|
||||||
|
|
||||||
|
cheerios_insert("", 0);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
parse_args(int argc, char **argv)
|
||||||
|
{
|
||||||
|
if (argc < 2)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (argc == 2 && !strcmp(argv[1], "-h"))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/* setup default options first */
|
||||||
|
bytenuts.config = CONFIG_DEFAULT;
|
||||||
|
{
|
||||||
|
char *home = getenv("HOME");
|
||||||
|
char path[] = "/.config/bytenuts/config";
|
||||||
|
bytenuts.config.config_path = malloc(strlen(home) + strlen(path) + 1);
|
||||||
|
sprintf(bytenuts.config.config_path, "%s%s", home, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 1; i < argc - 1; i++) {
|
||||||
|
size_t arg_len = strlen(argv[i]);
|
||||||
|
|
||||||
|
if (!strcmp(argv[i], "-h")) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else if (!strcmp(argv[i], "-b")) {
|
||||||
|
i++;
|
||||||
|
if (i == argc - 1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
bytenuts.config.baud = string_to_speed(argv[i]);
|
||||||
|
}
|
||||||
|
else if (!strcmp(argv[i], "-l")) {
|
||||||
|
i++;
|
||||||
|
if (i == argc - 1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
bytenuts.config.log_path = strdup(argv[i]);
|
||||||
|
}
|
||||||
|
else if (!strcmp(argv[i], "-c")) {
|
||||||
|
i++;
|
||||||
|
if (i == argc - 1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
bytenuts.config.config_path = strdup(argv[i]);
|
||||||
|
}
|
||||||
|
else if (arg_len == 10 && !memcmp(argv[i], "--colors=", 9)) {
|
||||||
|
if (argv[i][9] == '1') {
|
||||||
|
bytenuts.config.colors = 1;
|
||||||
|
} else if (argv[i][9] == '0') {
|
||||||
|
bytenuts.config.colors = 0;
|
||||||
|
}
|
||||||
|
bytenuts.config_overrides[0] = 1;
|
||||||
|
}
|
||||||
|
else if (arg_len == 8 && !memcmp(argv[i], "--echo=", 7)) {
|
||||||
|
if (argv[i][7] == '1') {
|
||||||
|
bytenuts.config.echo = 1;
|
||||||
|
} else if (argv[i][7] == '0') {
|
||||||
|
bytenuts.config.echo = 0;
|
||||||
|
}
|
||||||
|
bytenuts.config_overrides[1] = 1;
|
||||||
|
}
|
||||||
|
else if (arg_len == 11 && !memcmp(argv[i], "--no_crlf=", 10)) {
|
||||||
|
if (argv[i][10] == '1') {
|
||||||
|
bytenuts.config.no_crlf = 1;
|
||||||
|
} else if (argv[i][10] == '0') {
|
||||||
|
bytenuts.config.no_crlf = 0;
|
||||||
|
}
|
||||||
|
bytenuts.config_overrides[2] = 1;
|
||||||
|
}
|
||||||
|
else if (arg_len == 10 && !memcmp(argv[i], "--escape=", 9)) {
|
||||||
|
bytenuts.config.escape = argv[i][9];
|
||||||
|
bytenuts.config_overrides[3] = 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bytenuts.config.serial_path = strdup(argv[argc - 1]);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
load_configs()
|
||||||
|
{
|
||||||
|
FILE *fd = NULL;
|
||||||
|
char line[128];
|
||||||
|
|
||||||
|
fd = fopen(bytenuts.config.config_path, "r");
|
||||||
|
|
||||||
|
if (!fd) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (fgets(line, sizeof(line), fd) != NULL) {
|
||||||
|
if (!bytenuts.config_overrides[0] && !memcmp(line, "colors=", 7)) {
|
||||||
|
if (line[7] == '0')
|
||||||
|
bytenuts.config.colors = 0;
|
||||||
|
else if (line[7] == '1')
|
||||||
|
bytenuts.config.colors = 1;
|
||||||
|
}
|
||||||
|
else if (!bytenuts.config_overrides[1] && !memcmp(line, "echo=", 5)) {
|
||||||
|
if (line[5] == '0')
|
||||||
|
bytenuts.config.echo = 0;
|
||||||
|
else if (line[5] == '1')
|
||||||
|
bytenuts.config.echo = 1;
|
||||||
|
}
|
||||||
|
else if (!bytenuts.config_overrides[2] && !memcmp(line, "no_crlf=", 8)) {
|
||||||
|
if (line[8] == '0')
|
||||||
|
bytenuts.config.no_crlf = 0;
|
||||||
|
else if (line[8] == '1')
|
||||||
|
bytenuts.config.no_crlf = 1;
|
||||||
|
}
|
||||||
|
else if (!bytenuts.config_overrides[3] && !memcmp(line, "escape=", 7)) {
|
||||||
|
bytenuts.config.escape = line[7];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static speed_t
|
||||||
|
string_to_speed(const char *speed)
|
||||||
|
{
|
||||||
|
if (!strcmp("50", speed))
|
||||||
|
return B50;
|
||||||
|
else if (!strcmp("75", speed))
|
||||||
|
return B75;
|
||||||
|
else if (!strcmp("110", speed))
|
||||||
|
return B110;
|
||||||
|
else if (!strcmp("134", speed))
|
||||||
|
return B134;
|
||||||
|
else if (!strcmp("150", speed))
|
||||||
|
return B150;
|
||||||
|
else if (!strcmp("200", speed))
|
||||||
|
return B200;
|
||||||
|
else if (!strcmp("300", speed))
|
||||||
|
return B300;
|
||||||
|
else if (!strcmp("600", speed))
|
||||||
|
return B600;
|
||||||
|
else if (!strcmp("1200", speed))
|
||||||
|
return B1200;
|
||||||
|
else if (!strcmp("1800", speed))
|
||||||
|
return B1800;
|
||||||
|
else if (!strcmp("2400", speed))
|
||||||
|
return B2400;
|
||||||
|
else if (!strcmp("4800", speed))
|
||||||
|
return B4800;
|
||||||
|
else if (!strcmp("9600", speed))
|
||||||
|
return B9600;
|
||||||
|
else if (!strcmp("19200", speed))
|
||||||
|
return B19200;
|
||||||
|
else if (!strcmp("38400", speed))
|
||||||
|
return B38400;
|
||||||
|
else if (!strcmp("57600", speed))
|
||||||
|
return B57600;
|
||||||
|
else if (!strcmp("115200", speed))
|
||||||
|
return B1152000;
|
||||||
|
else if (!strcmp("230400", speed))
|
||||||
|
return B230400;
|
||||||
|
else if (!strcmp("460800", speed))
|
||||||
|
return B460800;
|
||||||
|
else if (!strcmp("500000", speed))
|
||||||
|
return B500000;
|
||||||
|
else if (!strcmp("576000", speed))
|
||||||
|
return B576000;
|
||||||
|
else if (!strcmp("921600", speed))
|
||||||
|
return B921600;
|
||||||
|
else if (!strcmp("1000000", speed))
|
||||||
|
return B1000000;
|
||||||
|
else if (!strcmp("1152000", speed))
|
||||||
|
return B1152000;
|
||||||
|
else if (!strcmp("1500000", speed))
|
||||||
|
return B1500000;
|
||||||
|
else if (!strcmp("2000000", speed))
|
||||||
|
return B2000000;
|
||||||
|
else if (!strcmp("2500000", speed))
|
||||||
|
return B2500000;
|
||||||
|
else if (!strcmp("3000000", speed))
|
||||||
|
return B3000000;
|
||||||
|
else if (!strcmp("3500000", speed))
|
||||||
|
return B3500000;
|
||||||
|
else if (!strcmp("4000000", speed))
|
||||||
|
return B4000000;
|
||||||
|
else
|
||||||
|
return B0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
speed_to_string(speed_t speed)
|
||||||
|
{
|
||||||
|
if (speed == B50)
|
||||||
|
return "B50";
|
||||||
|
else if (speed == B75)
|
||||||
|
return "B75";
|
||||||
|
else if (speed == B110)
|
||||||
|
return "B110";
|
||||||
|
else if (speed == B134)
|
||||||
|
return "B134";
|
||||||
|
else if (speed == B150)
|
||||||
|
return "B150";
|
||||||
|
else if (speed == B200)
|
||||||
|
return "B200";
|
||||||
|
else if (speed == B300)
|
||||||
|
return "B300";
|
||||||
|
else if (speed == B600)
|
||||||
|
return "B600";
|
||||||
|
else if (speed == B1200)
|
||||||
|
return "B1200";
|
||||||
|
else if (speed == B1800)
|
||||||
|
return "B1800";
|
||||||
|
else if (speed == B2400)
|
||||||
|
return "B2400";
|
||||||
|
else if (speed == B4800)
|
||||||
|
return "B4800";
|
||||||
|
else if (speed == B9600)
|
||||||
|
return "B9600";
|
||||||
|
else if (speed == B19200)
|
||||||
|
return "B19200";
|
||||||
|
else if (speed == B38400)
|
||||||
|
return "B38400";
|
||||||
|
else if (speed == B57600)
|
||||||
|
return "B57600";
|
||||||
|
else if (speed == B115200)
|
||||||
|
return "B1152000";
|
||||||
|
else if (speed == B230400)
|
||||||
|
return "B230400";
|
||||||
|
else if (speed == B460800)
|
||||||
|
return "B460800";
|
||||||
|
else if (speed == B500000)
|
||||||
|
return "B500000";
|
||||||
|
else if (speed == B576000)
|
||||||
|
return "B576000";
|
||||||
|
else if (speed == B921600)
|
||||||
|
return "B921600";
|
||||||
|
else if (speed == B1000000)
|
||||||
|
return "B1000000";
|
||||||
|
else if (speed == B1152000)
|
||||||
|
return "B1152000";
|
||||||
|
else if (speed == B1500000)
|
||||||
|
return "B1500000";
|
||||||
|
else if (speed == B2000000)
|
||||||
|
return "B2000000";
|
||||||
|
else if (speed == B2500000)
|
||||||
|
return "B2500000";
|
||||||
|
else if (speed == B3000000)
|
||||||
|
return "B3000000";
|
||||||
|
else if (speed == B3500000)
|
||||||
|
return "B3500000";
|
||||||
|
else if (speed == B4000000)
|
||||||
|
return "B4000000";
|
||||||
|
else
|
||||||
|
return "";
|
||||||
|
}
|
||||||
75
src/bytenuts.h
Normal file
75
src/bytenuts.h
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
#ifndef _BYTENUTES_H_
|
||||||
|
#define _BYTENUTES_H_
|
||||||
|
|
||||||
|
#include <ncurses.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "serial.h"
|
||||||
|
|
||||||
|
/* https://en.wikipedia.org/wiki/Control_character#How_control_characters_map_to_keyboards */
|
||||||
|
#define CTRL(c) ((c)&31)
|
||||||
|
|
||||||
|
typedef struct bytenuts_config_struct {
|
||||||
|
int colors; /* enable terminal colors, default 1 */
|
||||||
|
int echo; /* echo user input to the output window, default 0 */
|
||||||
|
int no_crlf; /* only send LF line endings, not CRLF, default 0 */
|
||||||
|
char escape; /* which character is used for escape sequence */
|
||||||
|
speed_t baud; /* baud rate */
|
||||||
|
char *config_path; /* config file path */
|
||||||
|
char *log_path; /* path to the log file (if it exists) */
|
||||||
|
char *serial_path; /* path to the target serial device */
|
||||||
|
} bytenuts_config_t;
|
||||||
|
|
||||||
|
#define CONFIG_DEFAULT (bytenuts_config_t){ \
|
||||||
|
.colors = 1, \
|
||||||
|
.echo = 0, \
|
||||||
|
.no_crlf = 0, \
|
||||||
|
.escape = 'b', \
|
||||||
|
.baud = B115200, \
|
||||||
|
.config_path = NULL, \
|
||||||
|
.log_path = NULL, \
|
||||||
|
.serial_path = NULL, \
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct bytenuts_struct {
|
||||||
|
int serial_fd;
|
||||||
|
bytenuts_config_t config;
|
||||||
|
int config_overrides[4];
|
||||||
|
WINDOW *status_win;
|
||||||
|
WINDOW *out_win;
|
||||||
|
WINDOW *in_win;
|
||||||
|
pthread_mutex_t lock;
|
||||||
|
pthread_mutex_t term_lock;
|
||||||
|
pthread_cond_t stop_cond;
|
||||||
|
pthread_t in_thr;
|
||||||
|
pthread_t out_thr;
|
||||||
|
char *bytenuts_status;
|
||||||
|
char *ingest_status;
|
||||||
|
char *cheerios_status;
|
||||||
|
char *cmdpg_status;
|
||||||
|
} bytenuts_t;
|
||||||
|
|
||||||
|
/* startup the application */
|
||||||
|
int bytenuts_run(int argc, char **argv);
|
||||||
|
|
||||||
|
/* cleanly stop threads */
|
||||||
|
int bytenuts_stop();
|
||||||
|
|
||||||
|
/* perform kill routing for exiting */
|
||||||
|
void bytenuts_kill();
|
||||||
|
|
||||||
|
/* print some bytenuts stats to the output window */
|
||||||
|
int bytenuts_print_stats();
|
||||||
|
|
||||||
|
#define STATUS_BYTENUTS (0)
|
||||||
|
#define STATUS_INGEST (1)
|
||||||
|
#define STATUS_CHEERIOS (2)
|
||||||
|
#define STATUS_CMDPAGE (3)
|
||||||
|
/* set the status for the given thread */
|
||||||
|
int bytenuts_set_status(int user, const char *fmt, ...);
|
||||||
|
|
||||||
|
int bytenuts_update_screen_size();
|
||||||
|
|
||||||
|
#endif /* _BYTENUTES_H_ */
|
||||||
523
src/cheerios.c
Normal file
523
src/cheerios.c
Normal file
|
|
@ -0,0 +1,523 @@
|
||||||
|
#include <ncurses.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <sched.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "cheerios.h"
|
||||||
|
#include "xmodem.h"
|
||||||
|
|
||||||
|
static cheerios_t cheerios;
|
||||||
|
|
||||||
|
static void *cheerios_thread(void *arg);
|
||||||
|
static int insert_buf(line_buffer_t *lines, const char *buf, size_t len);
|
||||||
|
static int write_lines(line_buffer_t *lines);
|
||||||
|
static int handle_color(line_buffer_t *lines, int line_idx, int *pos);
|
||||||
|
short curs_color(int fg);
|
||||||
|
static int newline(line_buffer_t *lines);
|
||||||
|
|
||||||
|
int
|
||||||
|
cheerios_start(bytenuts_t *bytenuts)
|
||||||
|
{
|
||||||
|
memset(&cheerios, 0, sizeof(cheerios_t));
|
||||||
|
|
||||||
|
cheerios.output = bytenuts->out_win;
|
||||||
|
cheerios.term_lock = &bytenuts->term_lock;
|
||||||
|
cheerios.ser_fd = bytenuts->serial_fd;
|
||||||
|
cheerios.lines.bot = -1;
|
||||||
|
|
||||||
|
cheerios.config = &bytenuts->config;
|
||||||
|
|
||||||
|
if (cheerios.config->log_path) {
|
||||||
|
cheerios.log = fopen(cheerios.config->log_path, "w");
|
||||||
|
if (!cheerios.log) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_cond_init(&cheerios.cond, NULL);
|
||||||
|
pthread_mutex_init(&cheerios.lock, NULL);
|
||||||
|
cheerios.running = 1;
|
||||||
|
pthread_create(&cheerios.thr, NULL, cheerios_thread, NULL);
|
||||||
|
|
||||||
|
if (cheerios.config->colors) {
|
||||||
|
start_color();
|
||||||
|
use_default_colors();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cheerios_pause()
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&cheerios.lock);
|
||||||
|
cheerios.mode = CHEERIOS_MODE_PAUSED;
|
||||||
|
pthread_mutex_unlock(&cheerios.lock);
|
||||||
|
|
||||||
|
cheerios_info("Paused");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cheerios_resume()
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&cheerios.lock);
|
||||||
|
cheerios.mode = CHEERIOS_MODE_NORMAL;
|
||||||
|
pthread_cond_signal(&cheerios.cond);
|
||||||
|
pthread_mutex_unlock(&cheerios.lock);
|
||||||
|
|
||||||
|
cheerios_info("Resumed");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cheerios_goback(int lines)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&cheerios.lock);
|
||||||
|
|
||||||
|
if (lines < 0) { /* go back as far as we can */
|
||||||
|
cheerios.lines.bot = getmaxy(cheerios.output) % cheerios.lines.n_lines;
|
||||||
|
}
|
||||||
|
else { /* just increment the bot pointer */
|
||||||
|
if (cheerios.lines.bot < 0)
|
||||||
|
cheerios.lines.bot = cheerios.lines.n_lines - 1;
|
||||||
|
|
||||||
|
cheerios.lines.bot -= lines;
|
||||||
|
if (cheerios.lines.bot < 0)
|
||||||
|
cheerios.lines.bot = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
write_lines(&cheerios.lines);
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&cheerios.lock);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cheerios_gofwd(int lines)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&cheerios.lock);
|
||||||
|
|
||||||
|
if (lines < 0) { /* go to front */
|
||||||
|
cheerios.lines.bot = -1;
|
||||||
|
}
|
||||||
|
else { /* just increment the bot pointer */
|
||||||
|
if (cheerios.lines.bot < 0)
|
||||||
|
cheerios.lines.bot = cheerios.lines.n_lines - 1;
|
||||||
|
|
||||||
|
cheerios.lines.bot += lines;
|
||||||
|
if (cheerios.lines.bot >= cheerios.lines.n_lines)
|
||||||
|
cheerios.lines.bot = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
write_lines(&cheerios.lines);
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&cheerios.lock);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cheerios_stop()
|
||||||
|
{
|
||||||
|
cheerios.running = 0;
|
||||||
|
pthread_join(cheerios.thr, NULL);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cheerios_input(const char *line)
|
||||||
|
{
|
||||||
|
size_t line_len = strlen(line);
|
||||||
|
|
||||||
|
pthread_mutex_lock(&cheerios.lock);
|
||||||
|
|
||||||
|
if (cheerios.config->echo) {
|
||||||
|
char *line_parsed = calloc(1, line_len + 5 + 1);
|
||||||
|
sprintf(line_parsed, ">> %s\r\n", line);
|
||||||
|
insert_buf(&cheerios.lines, line_parsed, strlen(line_parsed));
|
||||||
|
free(line_parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
write(cheerios.ser_fd, line, strlen(line));
|
||||||
|
if (cheerios.config->no_crlf)
|
||||||
|
write(cheerios.ser_fd, "\n", 1);
|
||||||
|
else
|
||||||
|
write(cheerios.ser_fd, "\r\n", 2);
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&cheerios.lock);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cheerios_info(const char *line)
|
||||||
|
{
|
||||||
|
size_t line_len = strlen(line);
|
||||||
|
char *line_parsed = calloc(1, line_len + 12 + 1);
|
||||||
|
sprintf(line_parsed, "BYTENUTS: %s\r\n", line);
|
||||||
|
|
||||||
|
pthread_mutex_lock(&cheerios.lock);
|
||||||
|
|
||||||
|
if (cheerios.lines.pos != 0) {
|
||||||
|
insert_buf(&cheerios.lines, "\r\n", 2);
|
||||||
|
}
|
||||||
|
insert_buf(&cheerios.lines, line_parsed, strlen(line_parsed));
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&cheerios.lock);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cheerios_insert(const char *buf, size_t len)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&cheerios.lock);
|
||||||
|
insert_buf(&cheerios.lines, buf, len);
|
||||||
|
pthread_mutex_unlock(&cheerios.lock);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
__xmodem_callback(size_t sent, size_t total)
|
||||||
|
{
|
||||||
|
char status_str[1024];
|
||||||
|
|
||||||
|
|
||||||
|
snprintf(
|
||||||
|
status_str, sizeof(status_str),
|
||||||
|
"\r %.02f/%.02fKB (%.01f%%)",
|
||||||
|
(sent / 1024.0),
|
||||||
|
(total / 1024.0),
|
||||||
|
(sent * 100.0 / total)
|
||||||
|
);
|
||||||
|
|
||||||
|
cheerios_insert(status_str, strlen(status_str));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cheerios_xmodem(const char *path, int block_sz)
|
||||||
|
{
|
||||||
|
struct stat st = { 0 };
|
||||||
|
FILE *fd;
|
||||||
|
char info[128];
|
||||||
|
|
||||||
|
stat(path, &st);
|
||||||
|
|
||||||
|
snprintf(
|
||||||
|
info, sizeof(info),
|
||||||
|
"Sending %s (%ld) with %dB payloads",
|
||||||
|
path,
|
||||||
|
st.st_size,
|
||||||
|
block_sz
|
||||||
|
);
|
||||||
|
cheerios_info(info);
|
||||||
|
|
||||||
|
fd = fopen(path, "r");
|
||||||
|
if (!fd) {
|
||||||
|
cheerios_info("Failed to open file!");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
cheerios_pause();
|
||||||
|
if (xmodem_send(
|
||||||
|
cheerios.ser_fd,
|
||||||
|
fileno(fd),
|
||||||
|
st.st_size,
|
||||||
|
block_sz,
|
||||||
|
__xmodem_callback
|
||||||
|
)) {
|
||||||
|
fclose(fd);
|
||||||
|
cheerios_resume();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(fd);
|
||||||
|
cheerios_resume();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cheerios_print_stats()
|
||||||
|
{
|
||||||
|
char st_line[128];
|
||||||
|
|
||||||
|
sprintf(st_line, "output line count: %d\r\n", cheerios.lines.n_lines);
|
||||||
|
cheerios_insert(st_line, strlen(st_line));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cheerios_getmaxy()
|
||||||
|
{
|
||||||
|
int y, x;
|
||||||
|
getmaxyx(cheerios.output, y, x);
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cheerios_getmaxx()
|
||||||
|
{
|
||||||
|
int y, x;
|
||||||
|
getmaxyx(cheerios.output, y, x);
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cheerios_set_window(WINDOW *win)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&cheerios.lock);
|
||||||
|
|
||||||
|
delwin(cheerios.output);
|
||||||
|
cheerios.output = win;
|
||||||
|
write_lines(&cheerios.lines);
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&cheerios.lock);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *
|
||||||
|
cheerios_thread(void *arg)
|
||||||
|
{
|
||||||
|
char buf[1024];
|
||||||
|
ssize_t read_ret = 0;
|
||||||
|
struct pollfd fds;
|
||||||
|
|
||||||
|
while (cheerios.running) {
|
||||||
|
pthread_mutex_lock(&cheerios.lock);
|
||||||
|
|
||||||
|
while (cheerios.mode == CHEERIOS_MODE_PAUSED) {
|
||||||
|
pthread_cond_wait(&cheerios.cond, &cheerios.lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
fds.fd = cheerios.ser_fd;
|
||||||
|
fds.events = POLLIN;
|
||||||
|
fds.revents = 0;
|
||||||
|
|
||||||
|
if (poll(&fds, 1, 1) == 0) {
|
||||||
|
pthread_mutex_unlock(&cheerios.lock);
|
||||||
|
nanosleep(&(struct timespec){ 0, 100000 }, NULL);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
read_ret = read(cheerios.ser_fd, buf, sizeof(buf));
|
||||||
|
if (read_ret > 0) {
|
||||||
|
insert_buf(&cheerios.lines, buf, read_ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&cheerios.lock);
|
||||||
|
nanosleep(&(struct timespec){ 0, 100000 }, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cheerios.log)
|
||||||
|
fclose(cheerios.log);
|
||||||
|
pthread_exit(NULL);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
insert_buf(line_buffer_t *lines, const char *buf, size_t len)
|
||||||
|
{
|
||||||
|
if (lines->n_lines == 0)
|
||||||
|
newline(lines);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < len; i++) {
|
||||||
|
if (cheerios.log && cheerios.mode == CHEERIOS_MODE_NORMAL)
|
||||||
|
fwrite(&buf[i], 1, 1, cheerios.log);
|
||||||
|
|
||||||
|
/* line feed starts a new row */
|
||||||
|
if (buf[i] == '\n') {
|
||||||
|
newline(lines);
|
||||||
|
}
|
||||||
|
/* carriage return just sets pos to 0 */
|
||||||
|
else if (buf[i] == '\r') {
|
||||||
|
lines->pos = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (lines->pos >= lines->line_lens[lines->n_lines - 1]) {
|
||||||
|
lines->line_lens[lines->n_lines - 1] = lines->pos + 1;
|
||||||
|
lines->lines[lines->n_lines - 1] = realloc(
|
||||||
|
lines->lines[lines->n_lines - 1],
|
||||||
|
sizeof(uint8_t) * lines->line_lens[lines->n_lines - 1]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
lines->lines[lines->n_lines - 1][lines->pos] = buf[i];
|
||||||
|
lines->pos++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write_lines(lines);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
write_lines(line_buffer_t *lines)
|
||||||
|
{
|
||||||
|
int row = lines->bot;
|
||||||
|
int rows_printed = 0;
|
||||||
|
int window_height = getmaxy(cheerios.output);
|
||||||
|
|
||||||
|
if (row < 0)
|
||||||
|
row = lines->n_lines - 1;
|
||||||
|
|
||||||
|
pthread_mutex_lock(cheerios.term_lock);
|
||||||
|
|
||||||
|
curs_set(0);
|
||||||
|
werase(cheerios.output);
|
||||||
|
|
||||||
|
while (row >= 0 && rows_printed < window_height) {
|
||||||
|
wmove(cheerios.output, window_height - rows_printed - 1, 0);
|
||||||
|
|
||||||
|
for (int i = 0; i < lines->line_lens[row]; i++) {
|
||||||
|
handle_color(lines, row, &i);
|
||||||
|
|
||||||
|
if (i >= getmaxx(cheerios.output)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < lines->line_lens[row]) {
|
||||||
|
waddch(cheerios.output, lines->lines[row][i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rows_printed++;
|
||||||
|
row--;
|
||||||
|
}
|
||||||
|
|
||||||
|
curs_set(1);
|
||||||
|
wrefresh(cheerios.output);
|
||||||
|
|
||||||
|
pthread_mutex_unlock(cheerios.term_lock);
|
||||||
|
|
||||||
|
if (lines->bot < 0) {
|
||||||
|
bytenuts_set_status(STATUS_CHEERIOS, "scrolling");
|
||||||
|
} else {
|
||||||
|
bytenuts_set_status(STATUS_CHEERIOS, "locked");
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
handle_color(line_buffer_t *lines, int line_idx, int *pos)
|
||||||
|
{
|
||||||
|
short fg, bg = -1;
|
||||||
|
int enable = -1;
|
||||||
|
int p = *pos;
|
||||||
|
uint8_t *line = lines->lines[line_idx];
|
||||||
|
int line_len = lines->line_lens[line_idx];
|
||||||
|
|
||||||
|
if (!cheerios.config->colors)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
while (p < line_len) {
|
||||||
|
/* fg or bg color */
|
||||||
|
if (!memcmp(&line[p], "\e[38;5;", 7) || !memcmp(&line[p], "\e[48;5;", 7)) {
|
||||||
|
char col_str[4] = { 0 };
|
||||||
|
short *col = line[p + 2] == '3' ? &fg : &bg;
|
||||||
|
|
||||||
|
p += 7;
|
||||||
|
if (p >= line_len)
|
||||||
|
break;
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
if (line[p] == 'm') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (line[p] < '0' || line[p] > '9') {
|
||||||
|
col_str[0] = '\0';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
col_str[i] = line[p];
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (col_str[0] == '\0')
|
||||||
|
break;
|
||||||
|
|
||||||
|
*col = strtol(col_str, NULL, 10);
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
/* enable our color */
|
||||||
|
else if (!memcmp(&line[p], "\e[1m", 4)) {
|
||||||
|
p += 4;
|
||||||
|
enable = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* disable our color */
|
||||||
|
else if (!memcmp(&line[p], "\e[0m", 4)) {
|
||||||
|
p += 4;
|
||||||
|
enable = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* this is not a color code */
|
||||||
|
else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enable == 0) {
|
||||||
|
for (int i = 0; i < NCOLOR_PAIRS; i++) {
|
||||||
|
if (lines->enabled_pairs[i]) {
|
||||||
|
wattroff(cheerios.output, COLOR_PAIR(i + 1));
|
||||||
|
lines->enabled_pairs[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (enable == 1 && (fg >= 0 || bg >= 0)) {
|
||||||
|
int pair_pos = -1;
|
||||||
|
|
||||||
|
if (fg < 0)
|
||||||
|
fg = COLOR_WHITE;
|
||||||
|
if (bg < 0)
|
||||||
|
bg = COLOR_BLACK;
|
||||||
|
|
||||||
|
for (int i = 0; i < NCOLOR_PAIRS; i++) {
|
||||||
|
if (lines->color_pairs[i].fg == fg && lines->color_pairs[i].bg == bg) {
|
||||||
|
pair_pos = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pair_pos < 0) {
|
||||||
|
pair_pos = lines->color_pos;
|
||||||
|
init_pair(pair_pos + 1, fg, bg);
|
||||||
|
lines->color_pairs[pair_pos].fg = fg;
|
||||||
|
lines->color_pairs[pair_pos].bg = bg;
|
||||||
|
lines->color_pos = (lines->color_pos + 1) % NCOLOR_PAIRS;
|
||||||
|
}
|
||||||
|
|
||||||
|
lines->enabled_pairs[pair_pos] = 1;
|
||||||
|
wattron(cheerios.output, COLOR_PAIR(pair_pos + 1));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
*pos = p;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
newline(line_buffer_t *lines)
|
||||||
|
{
|
||||||
|
lines->n_lines++;
|
||||||
|
lines->lines = realloc(lines->lines, sizeof(uint8_t *) * lines->n_lines);
|
||||||
|
lines->lines[lines->n_lines - 1] = NULL;
|
||||||
|
lines->line_lens = realloc(lines->line_lens, sizeof(int) * lines->n_lines);
|
||||||
|
lines->line_lens[lines->n_lines - 1] = 0;
|
||||||
|
lines->pos = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
87
src/cheerios.h
Normal file
87
src/cheerios.h
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
#ifndef _CHEERIOS_H_
|
||||||
|
#define _CHEERIOS_H_
|
||||||
|
|
||||||
|
#include <ncurses.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "bytenuts.h"
|
||||||
|
|
||||||
|
typedef struct line_buffer_struct {
|
||||||
|
uint8_t **lines;
|
||||||
|
int *line_lens;
|
||||||
|
int n_lines; /* how many lines do we have */
|
||||||
|
int pos; /* position of the cursor in the current line */
|
||||||
|
int bot; /* index of the bottom line shown */
|
||||||
|
#define NCOLOR_PAIRS (8)
|
||||||
|
/* support 8 color pairs on screen at once.
|
||||||
|
* COLOR_PAIR(pair_pos + 2) will get you your color. */
|
||||||
|
struct { uint8_t fg; uint8_t bg; } color_pairs[NCOLOR_PAIRS];
|
||||||
|
uint8_t enabled_pairs[NCOLOR_PAIRS]; /* which pairs are enabled */
|
||||||
|
int color_pos;
|
||||||
|
} line_buffer_t;
|
||||||
|
|
||||||
|
enum cheerios_mode_enum {
|
||||||
|
CHEERIOS_MODE_NORMAL = 0,
|
||||||
|
CHEERIOS_MODE_PAUSED,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct cheerios_struct {
|
||||||
|
pthread_mutex_t lock;
|
||||||
|
volatile int running;
|
||||||
|
pthread_t thr;
|
||||||
|
WINDOW *output;
|
||||||
|
pthread_mutex_t *term_lock;
|
||||||
|
int ser_fd;
|
||||||
|
line_buffer_t lines;
|
||||||
|
FILE *log;
|
||||||
|
bytenuts_config_t *config;
|
||||||
|
volatile int mode;
|
||||||
|
pthread_cond_t cond;
|
||||||
|
} cheerios_t;
|
||||||
|
|
||||||
|
/* startup the output window thread */
|
||||||
|
int cheerios_start(bytenuts_t *bytenuts);
|
||||||
|
|
||||||
|
/* pause reading from the device */
|
||||||
|
int cheerios_pause();
|
||||||
|
|
||||||
|
/* resume reading from the device */
|
||||||
|
int cheerios_resume();
|
||||||
|
|
||||||
|
/* go back in the log history, this also stops the buffer from scrolling down
|
||||||
|
* if negative, jump to the back of the log */
|
||||||
|
int cheerios_goback(int lines);
|
||||||
|
|
||||||
|
/* go forward in the log history
|
||||||
|
* if a negative number is provided, go back to the start and resume scrolling */
|
||||||
|
int cheerios_gofwd(int lines);
|
||||||
|
|
||||||
|
/* stop the thread and release memory */
|
||||||
|
int cheerios_stop();
|
||||||
|
|
||||||
|
/* send over a line of user input to the output
|
||||||
|
* do not terminate with a newline! */
|
||||||
|
int cheerios_input(const char *line);
|
||||||
|
|
||||||
|
/* output a line only to the terminal for info/prompt purposes
|
||||||
|
* do not terminate with a newline! */
|
||||||
|
int cheerios_info(const char *line);
|
||||||
|
|
||||||
|
/* directly insert a buffer to the output window */
|
||||||
|
int cheerios_insert(const char *buf, size_t len);
|
||||||
|
|
||||||
|
/* send the file at path over xmodem */
|
||||||
|
int cheerios_xmodem(const char *path, int block_sz);
|
||||||
|
|
||||||
|
int cheerios_print_stats();
|
||||||
|
|
||||||
|
/* getter for window height */
|
||||||
|
int cheerios_getmaxy();
|
||||||
|
/* getter for window width */
|
||||||
|
int cheerios_getmaxx();
|
||||||
|
|
||||||
|
/* deletes the old window and sets the output window to the new one */
|
||||||
|
int cheerios_set_window(WINDOW *win);
|
||||||
|
|
||||||
|
#endif /* _CHEERIOS_H_ */
|
||||||
804
src/ingest.c
Normal file
804
src/ingest.c
Normal file
|
|
@ -0,0 +1,804 @@
|
||||||
|
#include <libgen.h>
|
||||||
|
#include <ncurses.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <sched.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "bytenuts.h"
|
||||||
|
#include "cheerios.h"
|
||||||
|
#include "ingest.h"
|
||||||
|
|
||||||
|
static void *ingest_thread(void *arg);
|
||||||
|
|
||||||
|
static ingest_t ingest;
|
||||||
|
|
||||||
|
static int mode_normal(int ch);
|
||||||
|
static int mode_xmodem(int block_sz);
|
||||||
|
static int handle_functions(int ch);
|
||||||
|
static int inbuf_load();
|
||||||
|
static int print_stats();
|
||||||
|
static int auto_complete();
|
||||||
|
static int read_cmd_page(int idx);
|
||||||
|
static void update_cmd_pg_status();
|
||||||
|
|
||||||
|
int
|
||||||
|
ingest_start(bytenuts_t *bytenuts)
|
||||||
|
{
|
||||||
|
char *HOME;
|
||||||
|
|
||||||
|
memset(&ingest, 0, sizeof(ingest));
|
||||||
|
|
||||||
|
ingest.input = bytenuts->in_win;
|
||||||
|
ingest.term_lock = &bytenuts->term_lock;
|
||||||
|
ingest.config = &bytenuts->config;
|
||||||
|
ingest.cmd_pg_cur = -1;
|
||||||
|
|
||||||
|
HOME = getenv("HOME");
|
||||||
|
if (HOME) {
|
||||||
|
char *cwd = getcwd(NULL, 0);
|
||||||
|
int idx = 0;
|
||||||
|
|
||||||
|
chdir(HOME);
|
||||||
|
|
||||||
|
while (!read_cmd_page(idx)) {
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
chdir(cwd);
|
||||||
|
free(cwd);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ingest.cmd_pgs_n > 0) {
|
||||||
|
ingest.cmd_pg_cur = 0;
|
||||||
|
}
|
||||||
|
update_cmd_pg_status();
|
||||||
|
|
||||||
|
ingest.running = 1;
|
||||||
|
if (pthread_create(&ingest.thr, NULL, ingest_thread, NULL)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
ingest_stop()
|
||||||
|
{
|
||||||
|
ingest.running = 0;
|
||||||
|
pthread_join(ingest.thr, NULL);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *
|
||||||
|
ingest_thread(void *arg)
|
||||||
|
{
|
||||||
|
int esc_flag;
|
||||||
|
int ch;
|
||||||
|
|
||||||
|
memset(ingest.inbuf, 0, sizeof(ingest.inbuf));
|
||||||
|
ingest.inpos = 0;
|
||||||
|
|
||||||
|
cheerios_insert(
|
||||||
|
"Welcome to Bytenuts\r\n"
|
||||||
|
"To quit press ctrl-b q\r\n"
|
||||||
|
"To see all commands, press ctrl-b h\r\n",
|
||||||
|
82
|
||||||
|
);
|
||||||
|
bytenuts_set_status(STATUS_INGEST, "normal");
|
||||||
|
|
||||||
|
while (ingest.running) {
|
||||||
|
esc_flag = 0;
|
||||||
|
|
||||||
|
pthread_mutex_lock(ingest.term_lock);
|
||||||
|
ch = wgetch(ingest.input);
|
||||||
|
pthread_mutex_unlock(ingest.term_lock);
|
||||||
|
|
||||||
|
if (ch == ERR) {
|
||||||
|
nanosleep(&(struct timespec){ 0, 100000 }, NULL);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == CTRL(ingest.config->escape)) {
|
||||||
|
int should_quit = 0;
|
||||||
|
int should_continue = 0;
|
||||||
|
|
||||||
|
bytenuts_set_status(STATUS_INGEST, "control");
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
pthread_mutex_lock(ingest.term_lock);
|
||||||
|
ch = wgetch(ingest.input);
|
||||||
|
pthread_mutex_unlock(ingest.term_lock);
|
||||||
|
|
||||||
|
if (ch != ERR)
|
||||||
|
break;
|
||||||
|
nanosleep(&(struct timespec){ 0, 100000 }, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (ch) {
|
||||||
|
case '1':
|
||||||
|
case '2':
|
||||||
|
case '3':
|
||||||
|
case '4':
|
||||||
|
case '5':
|
||||||
|
case '6':
|
||||||
|
case '7':
|
||||||
|
case '8':
|
||||||
|
case '9':
|
||||||
|
case '0':
|
||||||
|
{
|
||||||
|
bytenuts_set_status(STATUS_INGEST, "normal");
|
||||||
|
should_continue = 1;
|
||||||
|
|
||||||
|
if (ingest.cmd_pgs_n == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
int idx;
|
||||||
|
char *toload;
|
||||||
|
size_t toload_len;
|
||||||
|
char **cmds = ingest.cmd_pgs[ingest.cmd_pg_cur].cmds;
|
||||||
|
int cmds_n = ingest.cmd_pgs[ingest.cmd_pg_cur].cmds_n;
|
||||||
|
|
||||||
|
if (ch == '0')
|
||||||
|
ch += 10;
|
||||||
|
idx = ch - '1';
|
||||||
|
|
||||||
|
if (idx >= cmds_n)
|
||||||
|
break;
|
||||||
|
|
||||||
|
toload = cmds[idx];
|
||||||
|
toload_len = strlen(toload);
|
||||||
|
|
||||||
|
memcpy(ingest.inbuf, toload, toload_len);
|
||||||
|
ingest.inpos = toload_len;
|
||||||
|
ingest.inlen = toload_len;
|
||||||
|
inbuf_load();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'c':
|
||||||
|
{
|
||||||
|
bytenuts_set_status(STATUS_INGEST, "normal");
|
||||||
|
should_continue = 1;
|
||||||
|
|
||||||
|
if (ingest.cmd_pgs_n == 0) {
|
||||||
|
cheerios_insert("No quick commands\r\n", 19);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
char **cmds = ingest.cmd_pgs[ingest.cmd_pg_cur].cmds;
|
||||||
|
int cmds_n = ingest.cmd_pgs[ingest.cmd_pg_cur].cmds_n;
|
||||||
|
char *cmd_str;
|
||||||
|
size_t cmd_str_len;
|
||||||
|
|
||||||
|
cmd_str_len = snprintf(
|
||||||
|
NULL, 0,
|
||||||
|
"Quick Commands (pg.%d of %d):\r\n",
|
||||||
|
ingest.cmd_pg_cur + 1,
|
||||||
|
ingest.cmd_pgs_n
|
||||||
|
);
|
||||||
|
cmd_str = calloc(1, cmd_str_len + 1);
|
||||||
|
sprintf(
|
||||||
|
cmd_str,
|
||||||
|
"Quick Commands (pg.%d of %d):\r\n",
|
||||||
|
ingest.cmd_pg_cur + 1,
|
||||||
|
ingest.cmd_pgs_n
|
||||||
|
);
|
||||||
|
|
||||||
|
cheerios_insert(cmd_str, cmd_str_len);
|
||||||
|
for (int i = 0; i < cmds_n; i++) {
|
||||||
|
|
||||||
|
cmd_str_len = snprintf(
|
||||||
|
NULL, 0,
|
||||||
|
"%4d: %s\r\n",
|
||||||
|
i+1, cmds[i]
|
||||||
|
);
|
||||||
|
cmd_str = calloc(1, cmd_str_len + 1);
|
||||||
|
sprintf(cmd_str, "%4d: %s\r\n", i+1, cmds[i]);
|
||||||
|
|
||||||
|
cheerios_insert(cmd_str, cmd_str_len);
|
||||||
|
free(cmd_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'p':
|
||||||
|
{
|
||||||
|
bytenuts_set_status(STATUS_CMDPAGE, "selecting...");
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
pthread_mutex_lock(ingest.term_lock);
|
||||||
|
ch = wgetch(ingest.input);
|
||||||
|
pthread_mutex_unlock(ingest.term_lock);
|
||||||
|
|
||||||
|
if (ch != ERR)
|
||||||
|
break;
|
||||||
|
nanosleep(&(struct timespec){ 0, 100000 }, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
should_continue = 1;
|
||||||
|
bytenuts_set_status(STATUS_INGEST, "normal");
|
||||||
|
|
||||||
|
if (ch >= '1' || ch <= '9') {
|
||||||
|
int pg = ch - '1';
|
||||||
|
|
||||||
|
if (pg >= 0 && pg < ingest.cmd_pgs_n) {
|
||||||
|
ingest.cmd_pg_cur = pg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update_cmd_pg_status();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'i':
|
||||||
|
print_stats();
|
||||||
|
bytenuts_set_status(STATUS_INGEST, "normal");
|
||||||
|
should_continue = 1;
|
||||||
|
break;
|
||||||
|
case 'q':
|
||||||
|
bytenuts_stop();
|
||||||
|
should_quit = 1;
|
||||||
|
break;
|
||||||
|
case 'x':
|
||||||
|
ingest.mode = INGEST_MODE_XMODEM;
|
||||||
|
bytenuts_set_status(STATUS_INGEST, "xmodem");
|
||||||
|
break;
|
||||||
|
case 'X':
|
||||||
|
ingest.mode = INGEST_MODE_XMODEM1K;
|
||||||
|
bytenuts_set_status(STATUS_INGEST, "xmodem1k");
|
||||||
|
break;
|
||||||
|
case 'h':
|
||||||
|
cheerios_insert(
|
||||||
|
"Commands (lead with ctrl-b):\r\n"
|
||||||
|
" c: print available quick commands\r\n"
|
||||||
|
" 0-9: load the given quick command (0 is 10)\r\n"
|
||||||
|
" p: select a different quick commands page\r\n"
|
||||||
|
" i: view info/stats\r\n"
|
||||||
|
" x: start XModem upload with 128B payloads\r\n"
|
||||||
|
" X: start XModem upload with 1024B payloads\r\n"
|
||||||
|
" h: view this help\r\n"
|
||||||
|
" q: quit Bytenuts\r\n",
|
||||||
|
313
|
||||||
|
);
|
||||||
|
bytenuts_set_status(STATUS_INGEST, "normal");
|
||||||
|
should_continue = 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
bytenuts_set_status(STATUS_INGEST, "normal");
|
||||||
|
should_continue = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (should_quit) {
|
||||||
|
break;
|
||||||
|
} else if (should_continue) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (ingest.mode) {
|
||||||
|
case INGEST_MODE_NORMAL:
|
||||||
|
mode_normal(ch);
|
||||||
|
break;
|
||||||
|
case INGEST_MODE_XMODEM:
|
||||||
|
mode_xmodem(128);
|
||||||
|
break;
|
||||||
|
case INGEST_MODE_XMODEM1K:
|
||||||
|
mode_xmodem(1024);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
sched_yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_exit(NULL);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
mode_normal(int ch)
|
||||||
|
{
|
||||||
|
if (handle_functions(ch))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
switch (ch) {
|
||||||
|
case '\n': /* send inputs */
|
||||||
|
if (cheerios_input(ingest.inbuf)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* store in history */
|
||||||
|
ingest.history_pos = ingest.history_len;
|
||||||
|
if (ingest.inlen > 0) {
|
||||||
|
ingest.history_len++;
|
||||||
|
ingest.history = realloc(ingest.history, ingest.history_len * sizeof(char *));
|
||||||
|
ingest.history[ingest.history_pos] = strdup(ingest.inbuf);
|
||||||
|
ingest.history_pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(ingest.inbuf, 0, sizeof(ingest.inbuf));
|
||||||
|
ingest.inpos = 0;
|
||||||
|
ingest.inlen = 0;
|
||||||
|
inbuf_load();
|
||||||
|
|
||||||
|
break;
|
||||||
|
case KEY_UP: /* load in history */
|
||||||
|
if (ingest.history_pos == 0) { /* top of history */
|
||||||
|
break;
|
||||||
|
} else if (ingest.history_pos == ingest.history_len) { /* we have not loaded any history */
|
||||||
|
strcpy(ingest.tmp_history, ingest.inbuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
ingest.history_pos--;
|
||||||
|
strcpy(ingest.inbuf, ingest.history[ingest.history_pos]);
|
||||||
|
ingest.inlen = strlen(ingest.inbuf);
|
||||||
|
ingest.inpos = ingest.inlen;
|
||||||
|
inbuf_load();
|
||||||
|
|
||||||
|
break;
|
||||||
|
case KEY_DOWN: /* load in history */
|
||||||
|
if (ingest.history_pos == ingest.history_len) { /* no history loaded */
|
||||||
|
break;
|
||||||
|
} else if (ingest.history_pos == ingest.history_len - 1) { /* load back temp storage */
|
||||||
|
ingest.history_pos++;
|
||||||
|
strcpy(ingest.inbuf, ingest.tmp_history);
|
||||||
|
ingest.inlen = strlen(ingest.inbuf);
|
||||||
|
ingest.inpos = ingest.inlen;
|
||||||
|
} else {
|
||||||
|
ingest.history_pos++;
|
||||||
|
strcpy(ingest.inbuf, ingest.history[ingest.history_pos]);
|
||||||
|
ingest.inlen = strlen(ingest.inbuf);
|
||||||
|
ingest.inpos = ingest.inlen;
|
||||||
|
}
|
||||||
|
|
||||||
|
inbuf_load();
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
char tmp[1024];
|
||||||
|
int tmp_len;
|
||||||
|
|
||||||
|
if (ingest.inpos == sizeof(ingest.inbuf) - 1)
|
||||||
|
break;
|
||||||
|
|
||||||
|
tmp_len = ingest.inlen - ingest.inpos;
|
||||||
|
memcpy(tmp, &ingest.inbuf[ingest.inpos], tmp_len);
|
||||||
|
ingest.inbuf[ingest.inpos] = (char)ch;
|
||||||
|
ingest.inpos++;
|
||||||
|
ingest.inlen++;
|
||||||
|
memcpy(&ingest.inbuf[ingest.inpos], tmp, tmp_len);
|
||||||
|
|
||||||
|
if (ingest.inlen == sizeof(ingest.inbuf)) {
|
||||||
|
ingest.inbuf[ingest.inlen - 1] = '\0';
|
||||||
|
ingest.inlen--;
|
||||||
|
}
|
||||||
|
|
||||||
|
inbuf_load();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
mode_xmodem(int block_sz)
|
||||||
|
{
|
||||||
|
int quit_flag = 0;
|
||||||
|
|
||||||
|
strcpy(ingest.tmp_history, ingest.inbuf);
|
||||||
|
memset(ingest.inbuf, 0, sizeof(ingest.inbuf));
|
||||||
|
ingest.inlen = 0;
|
||||||
|
ingest.inpos = 0;
|
||||||
|
if (ingest.prepend)
|
||||||
|
free(ingest.prepend);
|
||||||
|
ingest.prepend = strdup("Give me a path (ctrl-c to stop): ");
|
||||||
|
inbuf_load();
|
||||||
|
|
||||||
|
while (ingest.running) {
|
||||||
|
int ch;
|
||||||
|
|
||||||
|
pthread_mutex_lock(ingest.term_lock);
|
||||||
|
ch = wgetch(ingest.input);
|
||||||
|
pthread_mutex_unlock(ingest.term_lock);
|
||||||
|
|
||||||
|
if (ch == ERR || handle_functions(ch)) {
|
||||||
|
nanosleep(&(struct timespec){ 0, 100000 }, NULL);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (ch) {
|
||||||
|
case '\n':
|
||||||
|
pthread_mutex_lock(ingest.term_lock);
|
||||||
|
werase(ingest.input);
|
||||||
|
wmove(ingest.input, 0, 0);
|
||||||
|
wprintw(ingest.input, "Sending...");
|
||||||
|
wrefresh(ingest.input);
|
||||||
|
pthread_mutex_unlock(ingest.term_lock);
|
||||||
|
|
||||||
|
cheerios_gofwd(-1);
|
||||||
|
cheerios_xmodem(ingest.inbuf, block_sz);
|
||||||
|
quit_flag = 1;
|
||||||
|
break;
|
||||||
|
case CTRL('c'):
|
||||||
|
quit_flag = 1;
|
||||||
|
break;
|
||||||
|
case '\t': /* tab complete */
|
||||||
|
auto_complete();
|
||||||
|
inbuf_load();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
char tmp[1024];
|
||||||
|
int tmp_len;
|
||||||
|
|
||||||
|
if (ingest.inpos == sizeof(ingest.inbuf) - 1)
|
||||||
|
break;
|
||||||
|
|
||||||
|
tmp_len = ingest.inlen - ingest.inpos;
|
||||||
|
memcpy(tmp, &ingest.inbuf[ingest.inpos], tmp_len);
|
||||||
|
ingest.inbuf[ingest.inpos] = (char)ch;
|
||||||
|
ingest.inpos++;
|
||||||
|
ingest.inlen++;
|
||||||
|
memcpy(&ingest.inbuf[ingest.inpos], tmp, tmp_len);
|
||||||
|
|
||||||
|
if (ingest.inlen == sizeof(ingest.inbuf)) {
|
||||||
|
ingest.inbuf[ingest.inlen - 1] = '\0';
|
||||||
|
ingest.inlen--;
|
||||||
|
}
|
||||||
|
|
||||||
|
inbuf_load();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (quit_flag) {
|
||||||
|
memset(ingest.inbuf, 0, sizeof(ingest.inbuf));
|
||||||
|
strcpy(ingest.inbuf, ingest.tmp_history);
|
||||||
|
ingest.inlen = strlen(ingest.inbuf);
|
||||||
|
ingest.inpos = ingest.inlen;
|
||||||
|
ingest.mode = INGEST_MODE_NORMAL;
|
||||||
|
free(ingest.prepend);
|
||||||
|
ingest.prepend = NULL;
|
||||||
|
|
||||||
|
bytenuts_set_status(STATUS_INGEST, "normal");
|
||||||
|
inbuf_load();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
handle_functions(int ch)
|
||||||
|
{
|
||||||
|
// char st[128];
|
||||||
|
// sprintf(st, "%d", ch);
|
||||||
|
// cheerios_info(st);
|
||||||
|
|
||||||
|
switch (ch) {
|
||||||
|
case KEY_PPAGE: /* page up */
|
||||||
|
cheerios_goback(cheerios_getmaxy() / 2);
|
||||||
|
return 1;
|
||||||
|
case KEY_NPAGE: /* page down */
|
||||||
|
cheerios_gofwd(cheerios_getmaxy() / 2);
|
||||||
|
return 1;
|
||||||
|
case KEY_SHOME: /* to back of log */
|
||||||
|
cheerios_goback(-1);
|
||||||
|
return 1;
|
||||||
|
case KEY_SEND: /* go to front of log */
|
||||||
|
cheerios_gofwd(-1);
|
||||||
|
return 1;
|
||||||
|
case 566: /* ctrl + up arrow */
|
||||||
|
cheerios_goback(1);
|
||||||
|
return 1;
|
||||||
|
case 525: /* ctrl + down arrow */
|
||||||
|
cheerios_gofwd(1);
|
||||||
|
return 1;
|
||||||
|
case KEY_RESIZE:
|
||||||
|
bytenuts_update_screen_size();
|
||||||
|
return 1;
|
||||||
|
case KEY_BACKSPACE:
|
||||||
|
{
|
||||||
|
if (ingest.inpos == 0) {
|
||||||
|
return 1;
|
||||||
|
} else if (ingest.inpos == ingest.inlen) {
|
||||||
|
ingest.inpos--;
|
||||||
|
ingest.inlen--;
|
||||||
|
ingest.inbuf[ingest.inlen] = '\0';
|
||||||
|
inbuf_load();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char tmp[1024];
|
||||||
|
int tmp_len = ingest.inlen - ingest.inpos;
|
||||||
|
|
||||||
|
memcpy(tmp, &ingest.inbuf[ingest.inpos], ingest.inlen - ingest.inpos);
|
||||||
|
ingest.inpos--;
|
||||||
|
ingest.inlen--;
|
||||||
|
memcpy(&ingest.inbuf[ingest.inpos], tmp, tmp_len);
|
||||||
|
ingest.inbuf[ingest.inlen] = '\0';
|
||||||
|
inbuf_load();
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
case KEY_DC: /* delete key */
|
||||||
|
{
|
||||||
|
char tmp[1024];
|
||||||
|
int tmp_len;
|
||||||
|
|
||||||
|
if (ingest.inpos == ingest.inlen) {
|
||||||
|
return 1;
|
||||||
|
} else if (ingest.inpos == ingest.inlen - 1) {
|
||||||
|
ingest.inbuf[ingest.inpos] = '\0';
|
||||||
|
ingest.inlen--;
|
||||||
|
inbuf_load();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp_len = ingest.inlen - ingest.inpos - 1;
|
||||||
|
memcpy(tmp, &ingest.inbuf[ingest.inpos + 1], tmp_len);
|
||||||
|
ingest.inlen--;
|
||||||
|
memcpy(&ingest.inbuf[ingest.inpos], tmp, tmp_len);
|
||||||
|
ingest.inbuf[ingest.inlen] = '\0';
|
||||||
|
inbuf_load();
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
case KEY_LEFT:
|
||||||
|
if (ingest.inpos == 0)
|
||||||
|
return 1;
|
||||||
|
ingest.inpos--;
|
||||||
|
inbuf_load();
|
||||||
|
return 1;
|
||||||
|
case KEY_RIGHT:
|
||||||
|
if (ingest.inpos == ingest.inlen)
|
||||||
|
return 1;
|
||||||
|
ingest.inpos++;
|
||||||
|
inbuf_load();
|
||||||
|
return 1;
|
||||||
|
case KEY_END:
|
||||||
|
ingest.inpos = ingest.inlen;
|
||||||
|
inbuf_load();
|
||||||
|
return 1;
|
||||||
|
case KEY_HOME:
|
||||||
|
ingest.inpos = 0;
|
||||||
|
inbuf_load();
|
||||||
|
return 1;
|
||||||
|
#if 0
|
||||||
|
case KEY_MOUSE:
|
||||||
|
{
|
||||||
|
MEVENT ev;
|
||||||
|
if (getmouse(&ev) != OK)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (ev.bstate & BUTTON4_PRESSED) { /* scroll up */
|
||||||
|
cheerios_goback(1);
|
||||||
|
} else if (ev.bstate & BUTTON5_PRESSED) { /* scroll down */
|
||||||
|
cheerios_gofwd(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
inbuf_load()
|
||||||
|
{
|
||||||
|
int cx, cy = -1;
|
||||||
|
|
||||||
|
pthread_mutex_lock(ingest.term_lock);
|
||||||
|
|
||||||
|
werase(ingest.input);
|
||||||
|
wmove(ingest.input, 0, 0);
|
||||||
|
if (ingest.prepend)
|
||||||
|
wprintw(ingest.input, "%s", ingest.prepend);
|
||||||
|
|
||||||
|
for (int i = 0; i < ingest.inlen; i++) {
|
||||||
|
if (i == ingest.inpos)
|
||||||
|
getyx(ingest.input, cy, cx);
|
||||||
|
waddch(ingest.input, ingest.inbuf[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cx < 0) {
|
||||||
|
cx = ingest.inlen;
|
||||||
|
cy = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
wmove(ingest.input, cy, cx);
|
||||||
|
wrefresh(ingest.input);
|
||||||
|
|
||||||
|
pthread_mutex_unlock(ingest.term_lock);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
print_stats()
|
||||||
|
{
|
||||||
|
char st_line[128];
|
||||||
|
|
||||||
|
sprintf(st_line, "\r\nSTATS\r\n");
|
||||||
|
cheerios_insert(st_line, strlen(st_line));
|
||||||
|
sprintf(st_line, "input line count: %d\r\n", ingest.history_len);
|
||||||
|
cheerios_insert(st_line, strlen(st_line));
|
||||||
|
|
||||||
|
cheerios_print_stats();
|
||||||
|
bytenuts_print_stats();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
auto_complete()
|
||||||
|
{
|
||||||
|
char cmd[1024];
|
||||||
|
char line[1024];
|
||||||
|
char **matches = NULL;
|
||||||
|
int nmatches = 0;
|
||||||
|
FILE *sh = NULL;
|
||||||
|
char *inbuf_cpy = NULL;
|
||||||
|
char *inbuf_basename = NULL;
|
||||||
|
size_t inbuf_basename_len;
|
||||||
|
|
||||||
|
/* retrieve directory listing with ls */
|
||||||
|
inbuf_cpy = strdup(ingest.inbuf);
|
||||||
|
if (ingest.inlen > 0 && ingest.inbuf[ingest.inlen - 1] == '/') {
|
||||||
|
snprintf(
|
||||||
|
cmd, sizeof(cmd),
|
||||||
|
"cd %s 2> /dev/null && ls -1 2> /dev/null",
|
||||||
|
ingest.inbuf
|
||||||
|
);
|
||||||
|
inbuf_basename = "";
|
||||||
|
} else {
|
||||||
|
snprintf(
|
||||||
|
cmd, sizeof(cmd),
|
||||||
|
"cd $(dirname \"%s\") 2> /dev/null && ls -1 2> /dev/null",
|
||||||
|
ingest.inbuf
|
||||||
|
);
|
||||||
|
inbuf_basename = basename(inbuf_cpy);
|
||||||
|
}
|
||||||
|
inbuf_basename_len = strlen(inbuf_basename);
|
||||||
|
|
||||||
|
sh = popen(cmd, "r");
|
||||||
|
while (fgets(line, sizeof(line), sh)) {
|
||||||
|
size_t line_len = strlen(line);
|
||||||
|
while (line[line_len - 1] == '\n' || line[line_len - 1] == '\r') {
|
||||||
|
line[line_len - 1] = '\0';
|
||||||
|
line_len = strlen(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!memcmp(inbuf_basename, line, inbuf_basename_len)) {
|
||||||
|
nmatches++;
|
||||||
|
matches = realloc(matches, sizeof(char *) * nmatches);
|
||||||
|
matches[nmatches - 1] = strdup(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the cd/ls just failed, ignore output */
|
||||||
|
int ret = pclose(sh);
|
||||||
|
if (ret)
|
||||||
|
goto auto_complete_cleanup;
|
||||||
|
|
||||||
|
if (nmatches == 0)
|
||||||
|
goto auto_complete_cleanup;
|
||||||
|
|
||||||
|
/* check if we can fill the inbuf */
|
||||||
|
int offset = inbuf_basename_len;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
char add = matches[0][offset];
|
||||||
|
|
||||||
|
for (int i = 1; i < nmatches; i++) {
|
||||||
|
if (add != matches[i][offset]) {
|
||||||
|
add = '\0';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (add == '\0')
|
||||||
|
break;
|
||||||
|
|
||||||
|
offset++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset > inbuf_basename_len) {
|
||||||
|
size_t add_len = offset - inbuf_basename_len;
|
||||||
|
|
||||||
|
if (add_len + ingest.inlen >= sizeof(ingest.inbuf))
|
||||||
|
add_len = sizeof(ingest.inbuf) - ingest.inlen - 1;
|
||||||
|
|
||||||
|
memcpy(&ingest.inbuf[ingest.inlen], &matches[0][inbuf_basename_len], add_len);
|
||||||
|
ingest.inlen += add_len;
|
||||||
|
ingest.inpos = ingest.inlen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* auto complete directory '/' */
|
||||||
|
if (nmatches == 1) {
|
||||||
|
struct stat st;
|
||||||
|
|
||||||
|
stat(ingest.inbuf, &st);
|
||||||
|
if (
|
||||||
|
S_ISDIR(st.st_mode) &&
|
||||||
|
ingest.inlen < sizeof(ingest.inbuf) - 1 &&
|
||||||
|
(ingest.inpos == 0 || ingest.inbuf[ingest.inpos - 1] != '/')
|
||||||
|
) {
|
||||||
|
ingest.inbuf[ingest.inpos] = '/';
|
||||||
|
ingest.inpos++;
|
||||||
|
ingest.inlen++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto_complete_cleanup:
|
||||||
|
if (matches) {
|
||||||
|
for (int i = 0; i < nmatches; i++) {
|
||||||
|
free(matches[i]);
|
||||||
|
}
|
||||||
|
free(matches);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inbuf_cpy)
|
||||||
|
free(inbuf_cpy);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
read_cmd_page(int idx)
|
||||||
|
{
|
||||||
|
char fname[128];
|
||||||
|
FILE *fd;
|
||||||
|
char line[1024];
|
||||||
|
|
||||||
|
snprintf(fname, sizeof(fname), ".config/bytenuts/commands%d", idx+1);
|
||||||
|
|
||||||
|
fd = fopen(fname, "r");
|
||||||
|
if (!fd)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
ingest.cmd_pgs_n++;
|
||||||
|
ingest.cmd_pgs = realloc(
|
||||||
|
ingest.cmd_pgs,
|
||||||
|
sizeof(*ingest.cmd_pgs) * ingest.cmd_pgs_n
|
||||||
|
);
|
||||||
|
memset(&ingest.cmd_pgs[idx], 0, sizeof(*ingest.cmd_pgs));
|
||||||
|
|
||||||
|
while (fgets(line, sizeof(line), fd)) {
|
||||||
|
size_t line_len = strlen(line);
|
||||||
|
|
||||||
|
/* strip CRLF */
|
||||||
|
while (line_len > 0 && (line[line_len-1] == '\n' || line[line_len-1] == '\r')) {
|
||||||
|
line_len--;
|
||||||
|
line[line_len] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
ingest.cmd_pgs[idx].cmds_n++;
|
||||||
|
ingest.cmd_pgs[idx].cmds[ingest.cmd_pgs[idx].cmds_n-1] = strdup(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(fd);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
update_cmd_pg_status()
|
||||||
|
{
|
||||||
|
if (ingest.cmd_pgs_n > 0) {
|
||||||
|
bytenuts_set_status(STATUS_CMDPAGE, "pg.%d", ingest.cmd_pg_cur+1);
|
||||||
|
} else {
|
||||||
|
bytenuts_set_status(STATUS_CMDPAGE, "n/a");
|
||||||
|
}
|
||||||
|
}
|
||||||
47
src/ingest.h
Normal file
47
src/ingest.h
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
#ifndef _INGEST_H_
|
||||||
|
#define _INGEST_H_
|
||||||
|
|
||||||
|
#include <ncurses.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
#include "bytenuts.h"
|
||||||
|
|
||||||
|
enum ingest_mode_enum {
|
||||||
|
INGEST_MODE_NORMAL = 0,
|
||||||
|
INGEST_MODE_XMODEM,
|
||||||
|
INGEST_MODE_XMODEM1K,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct ingest_struct {
|
||||||
|
WINDOW *input;
|
||||||
|
pthread_mutex_t *term_lock;
|
||||||
|
pthread_t thr;
|
||||||
|
volatile int running;
|
||||||
|
char *prepend; /* string to be prepended in the prompt */
|
||||||
|
char inbuf[1024];
|
||||||
|
int inpos; /* cursor position within the inbuf itself */
|
||||||
|
int inlen; /* length of the input buffer */
|
||||||
|
char **history; /* array of strings representing the command history */
|
||||||
|
char tmp_history[1024]; /* copy of inbuf before we load in history */
|
||||||
|
int history_len;
|
||||||
|
int history_pos; /* where we currently reside in the history */
|
||||||
|
int mode;
|
||||||
|
bytenuts_config_t *config;
|
||||||
|
#define INGEST_COMMAND_PGSZ (10)
|
||||||
|
/* command pages loaded on startup */
|
||||||
|
struct {
|
||||||
|
char *cmds[INGEST_COMMAND_PGSZ];
|
||||||
|
int cmds_n;
|
||||||
|
} *cmd_pgs;
|
||||||
|
int cmd_pgs_n;
|
||||||
|
/* what command page is currently selected */
|
||||||
|
int cmd_pg_cur;
|
||||||
|
} ingest_t;
|
||||||
|
|
||||||
|
/* startup the input window thread */
|
||||||
|
int ingest_start(bytenuts_t *bytenuts);
|
||||||
|
|
||||||
|
/* stop the input window thread */
|
||||||
|
int ingest_stop();
|
||||||
|
|
||||||
|
#endif /* _INGEST_H_ */
|
||||||
31
src/main.c
Normal file
31
src/main.c
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
#include <sched.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "bytenuts.h"
|
||||||
|
|
||||||
|
static void sigint_handler(int s);
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
struct sigaction act;
|
||||||
|
act.sa_handler = sigint_handler;
|
||||||
|
sigemptyset(&act.sa_mask);
|
||||||
|
act.sa_flags = 0;
|
||||||
|
sigaction(SIGINT, &act, NULL);
|
||||||
|
|
||||||
|
if (bytenuts_run(argc, argv)) {
|
||||||
|
bytenuts_stop();
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sigint_handler(int s)
|
||||||
|
{
|
||||||
|
bytenuts_kill();
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
59
src/serial.c
Normal file
59
src/serial.c
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "serial.h"
|
||||||
|
|
||||||
|
int
|
||||||
|
serial_open(const char *path, speed_t bps)
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
struct termios otio;
|
||||||
|
struct termios ntio;
|
||||||
|
|
||||||
|
fd = open(path, O_RDWR | O_NOCTTY | O_NDELAY);
|
||||||
|
if (fd == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
tcgetattr(fd, &otio);
|
||||||
|
tcgetattr(fd, &ntio);
|
||||||
|
|
||||||
|
cfsetispeed(&ntio, bps);
|
||||||
|
cfsetospeed(&ntio, bps);
|
||||||
|
|
||||||
|
/* https://www.cmrr.umn.edu/~strupp/serial.html */
|
||||||
|
/* 8N1 */
|
||||||
|
ntio.c_cflag |= (CLOCAL | CREAD);
|
||||||
|
ntio.c_cflag &= ~(CSIZE|PARENB|CSTOPB);
|
||||||
|
ntio.c_cflag |= (CS8); /* Set size after setting CSIZE mask */
|
||||||
|
ntio.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON|IXOFF|IXANY);
|
||||||
|
ntio.c_oflag &= ~(OPOST);
|
||||||
|
ntio.c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
|
||||||
|
|
||||||
|
ntio.c_cc[VINTR] = 0; /* Ctrl-c */
|
||||||
|
ntio.c_cc[VQUIT] = 0; /* Ctrl-\ */
|
||||||
|
ntio.c_cc[VERASE] = 0; /* del */
|
||||||
|
ntio.c_cc[VKILL] = 0; /* @ */
|
||||||
|
ntio.c_cc[VEOF] = 4; /* Ctrl-d */
|
||||||
|
ntio.c_cc[VTIME] = 0; /* inter-character timer unused */
|
||||||
|
ntio.c_cc[VMIN] = 0; /* non-blocking */
|
||||||
|
ntio.c_cc[VSWTC] = 0; /* '\0' */
|
||||||
|
ntio.c_cc[VSTART] = 0; /* Ctrl-q */
|
||||||
|
ntio.c_cc[VSTOP] = 0; /* Ctrl-s */
|
||||||
|
ntio.c_cc[VSUSP] = 0; /* Ctrl-z */
|
||||||
|
ntio.c_cc[VEOL] = 0; /* '\0' */
|
||||||
|
ntio.c_cc[VREPRINT] = 0; /* Ctrl-r */
|
||||||
|
ntio.c_cc[VDISCARD] = 0; /* Ctrl-u */
|
||||||
|
ntio.c_cc[VWERASE] = 0; /* Ctrl-w */
|
||||||
|
ntio.c_cc[VLNEXT] = 0; /* Ctrl-v */
|
||||||
|
ntio.c_cc[VEOL2] = 0; /* '\0' */
|
||||||
|
|
||||||
|
tcflush(fd, TCIFLUSH);
|
||||||
|
tcsetattr(fd, TCSANOW, &ntio);
|
||||||
|
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
8
src/serial.h
Normal file
8
src/serial.h
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#ifndef _SERIAL_H_
|
||||||
|
#define _SERIAL_H_
|
||||||
|
|
||||||
|
#include <termios.h>
|
||||||
|
|
||||||
|
int serial_open(const char *path, speed_t bps);
|
||||||
|
|
||||||
|
#endif /* _SERIAL_H_ */
|
||||||
203
src/xmodem.c
Normal file
203
src/xmodem.c
Normal file
|
|
@ -0,0 +1,203 @@
|
||||||
|
#include <poll.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "xmodem.h"
|
||||||
|
|
||||||
|
int _xmodem_wait(int fd, uint8_t *ret, int timeout_ms);
|
||||||
|
|
||||||
|
uint16_t _xmodem_crc(void *buf, size_t len);
|
||||||
|
uint8_t _xmodem_csum(void *buf, size_t len);
|
||||||
|
void _xmodem_status_print(size_t sent, size_t total);
|
||||||
|
|
||||||
|
int
|
||||||
|
xmodem_send(
|
||||||
|
int dest_fd,
|
||||||
|
int in_fd,
|
||||||
|
size_t sz,
|
||||||
|
int block_sz,
|
||||||
|
void (*status_cb)(size_t sent, size_t total)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
uint8_t start_byte;
|
||||||
|
size_t sent;
|
||||||
|
uint8_t *buf;
|
||||||
|
size_t buf_sz;
|
||||||
|
uint8_t packet_num;
|
||||||
|
int retries;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
if (block_sz != 128 && block_sz != 1024) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* wait for start byte, 'C' for CRC, NAK for CSUM */
|
||||||
|
for (int i = 0; i < 11; i++) {
|
||||||
|
if (i == 10) {
|
||||||
|
return XMODEM_ERR_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!_xmodem_wait(dest_fd, &start_byte, XMODEM_TIMEOUT) &&
|
||||||
|
(start_byte == XMODEM_CRC || start_byte == XMODEM_NAK)
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* start sendin' bytes */
|
||||||
|
buf_sz = 3 + block_sz; /* SOH/STX | idx | ~idx | payload */
|
||||||
|
if (start_byte == XMODEM_NAK) {
|
||||||
|
buf_sz += 1;
|
||||||
|
} else {
|
||||||
|
buf_sz += 2;
|
||||||
|
}
|
||||||
|
buf = malloc(buf_sz);
|
||||||
|
sent = 0;
|
||||||
|
packet_num = 1; /* yes, this starts at 1 */
|
||||||
|
|
||||||
|
while (sent < sz) {
|
||||||
|
size_t read_len;
|
||||||
|
|
||||||
|
if (block_sz == 128) {
|
||||||
|
buf[0] = XMODEM_SOH;
|
||||||
|
} else {
|
||||||
|
buf[0] = XMODEM_STX;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[1] = packet_num;
|
||||||
|
buf[2] = ~packet_num;
|
||||||
|
|
||||||
|
if ((sz - sent) >= block_sz) {
|
||||||
|
read_len = block_sz;
|
||||||
|
} else {
|
||||||
|
/* back fill payload with pad */
|
||||||
|
read_len = sz - sent;
|
||||||
|
for (int i = read_len; i < block_sz; i++) {
|
||||||
|
buf[i] = XMODEM_PAD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (read(in_fd, &buf[3], read_len) != read_len) {
|
||||||
|
return XMODEM_ERR_FILEIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start_byte == XMODEM_CRC) {
|
||||||
|
/* write CRC big endian */
|
||||||
|
uint16_t crc = _xmodem_crc(&buf[3], block_sz);
|
||||||
|
buf[buf_sz-2] = (uint8_t)((0xFF00 & crc) >> 8);
|
||||||
|
buf[buf_sz-1] = (uint8_t)(0x00FF & crc);
|
||||||
|
} else {
|
||||||
|
buf[buf_sz-1] = _xmodem_csum(&buf[3], block_sz);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* try to send this packet until we get an ACK */
|
||||||
|
retries = 0;
|
||||||
|
while (1) {
|
||||||
|
uint8_t recv_char;
|
||||||
|
|
||||||
|
if (retries == 10) {
|
||||||
|
ret = XMODEM_ERR_TIMEOUT;
|
||||||
|
goto end_of_transmission;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (write(dest_fd, buf, buf_sz) != buf_sz){
|
||||||
|
return XMODEM_ERR_WRITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_xmodem_wait(dest_fd, &recv_char, XMODEM_TIMEOUT)) {
|
||||||
|
return XMODEM_ERR_READ;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recv_char == XMODEM_ACK) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
retries++;
|
||||||
|
}
|
||||||
|
|
||||||
|
sent += read_len;
|
||||||
|
packet_num++; /* expect overflow */
|
||||||
|
status_cb(sent, sz);
|
||||||
|
}
|
||||||
|
|
||||||
|
end_of_transmission:
|
||||||
|
retries = 0;
|
||||||
|
while (1) {
|
||||||
|
uint8_t ack_char;
|
||||||
|
|
||||||
|
if (retries == 10) {
|
||||||
|
return XMODEM_ERR_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
write(dest_fd, &(uint8_t){XMODEM_EOT}, 1);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!_xmodem_wait(dest_fd, &ack_char, XMODEM_TIMEOUT) &&
|
||||||
|
ack_char == XMODEM_ACK
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
retries++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
_xmodem_wait(int fd, uint8_t *ret, int timeout_ms)
|
||||||
|
{
|
||||||
|
struct pollfd pfd;
|
||||||
|
|
||||||
|
pfd.fd = fd;
|
||||||
|
pfd.events = POLLIN;
|
||||||
|
pfd.revents = 0;
|
||||||
|
|
||||||
|
if (poll(&pfd, 1, timeout_ms) <= 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
read(fd, ret, 1);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t
|
||||||
|
_xmodem_crc(void *buf, size_t len)
|
||||||
|
{
|
||||||
|
uint16_t crc;
|
||||||
|
char i;
|
||||||
|
|
||||||
|
crc = 0;
|
||||||
|
while (--len >= 0)
|
||||||
|
{
|
||||||
|
crc = crc ^ (uint16_t) (*(uint8_t *)buf++) << 8;
|
||||||
|
i = 8;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (crc & 0x8000)
|
||||||
|
crc = crc << 1 ^ 0x1021;
|
||||||
|
else
|
||||||
|
crc = crc << 1;
|
||||||
|
} while(--i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (crc);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t
|
||||||
|
_xmodem_csum(void *buf, size_t len)
|
||||||
|
{
|
||||||
|
uint8_t ret = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < len; i++) {
|
||||||
|
ret += ((uint8_t *)buf)[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
48
src/xmodem.h
Normal file
48
src/xmodem.h
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
#ifndef _XMODEM_H_
|
||||||
|
#define _XMODEM_H_
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "serial.h"
|
||||||
|
|
||||||
|
#define XMODEM_SOH (0x01)
|
||||||
|
#define XMODEM_STX (0x02)
|
||||||
|
#define XMODEM_EOT (0x04)
|
||||||
|
#define XMODEM_ACK (0x06)
|
||||||
|
#define XMODEM_NAK (0x15)
|
||||||
|
#define XMODEM_ETB (0x17)
|
||||||
|
#define XMODEM_CAN (0x18)
|
||||||
|
#define XMODEM_CRC (0x43) /* 'C' */
|
||||||
|
#define XMODEM_PAD (0x1A)
|
||||||
|
|
||||||
|
#define XMODEM_TIMEOUT (10000) /* in ms */
|
||||||
|
|
||||||
|
#define XMODEM_ERR_TIMEOUT (1) /* ACK timeout */
|
||||||
|
#define XMODEM_ERR_WRITE (2) /* Failed to write to file descriptor */
|
||||||
|
#define XMODEM_ERR_FILEIO (3) /* Error on reading from file descriptor */
|
||||||
|
#define XMODEM_ERR_READ (4) /* Failed to read from the serial connection */
|
||||||
|
#define XMODEM_ERR_CANCEL (5) /* Sender cancelled the transmission */
|
||||||
|
#define XMODEM_ERR_DONE (6) /* Sender finished the transmission */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Following this setup: https://pythonhosted.org/xmodem/xmodem.html
|
||||||
|
* Choose to send `sz` bytes from `in_fd` to `dest_fd` via XModem with a payload
|
||||||
|
* length of `block_sz`.
|
||||||
|
*/
|
||||||
|
int xmodem_send(
|
||||||
|
int dest_fd,
|
||||||
|
int in_fd,
|
||||||
|
size_t sz,
|
||||||
|
int block_sz,
|
||||||
|
void (*status_cb)(size_t sent, size_t total)
|
||||||
|
);
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
/* Receive an xmodem message
|
||||||
|
*
|
||||||
|
* If sz is non-negative, then only that many bytes will actually be written
|
||||||
|
* to fd. But the entire xmodem transfer will be completed */
|
||||||
|
int xmodem_recv(int in_fd, int dest_fd, ssize_t sz);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* _XMODEM_H_ */
|
||||||
Loading…
Reference in a new issue