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