Merge remote-tracking branch 'origin/work'

http-comm
Ondřej Hruška 7 years ago
commit ca5a7e3de9
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 4
      CMakeLists.txt
  2. 2
      Makefile
  3. 20
      README.md
  4. 3
      build_parser.sh
  5. 6
      esphttpdconfig.mk
  6. 2
      front-end
  7. 2
      libesphttpd
  8. 4
      user/ansi_parser.c
  9. 38
      user/apars_csi.c
  10. 10
      user/cgi_main.c
  11. 12
      user/cgi_network.c
  12. 4
      user/cgi_persist.c
  13. 112
      user/cgi_sockets.c
  14. 12
      user/cgi_sockets.h
  15. 161
      user/cgi_system.c
  16. 196
      user/cgi_term_cfg.c
  17. 14
      user/cgi_wifi.c
  18. 32
      user/persist.c
  19. 19
      user/persist.h
  20. 94
      user/routes.c
  21. 610
      user/screen.c
  22. 45
      user/screen.h
  23. 9
      user/serial.c
  24. 17
      user/syscfg.c
  25. 17
      user/syscfg.h
  26. 4
      user/user_main.c
  27. 33
      user/utf8.c
  28. 10
      user/utf8.h

@ -153,6 +153,7 @@ include_directories(esp_iot_sdk_v1.5.2/include)
add_definitions(
-D__ets__
-DICACHE_FLASH
-DDEBUG_LOGBUF_SIZE=2048
-DUSE_OPTIMIZE_PRINTF=1
-DHTTPD_MAX_CONNECTIONS=5
-DHTTPD_STACKSIZE=1000
@ -160,9 +161,12 @@ add_definitions(
-DICACHE_RODATA_ATTR=
-DFLAG_GZIP=2
-DADMIN_PASSWORD="asdf"
-DGIT_HASH_BACKEND="asdf"
-DGIT_HASH_FRONTEND="asdf"
-DGIT_HASH="blabla"
-DDEBUG_HEAP=1
-DDEBUG_MALLOC=1
-D__TIMEZONE__="UTC"
-DESPFS_HEATSHRINK)
add_executable(ESPTerm ${SOURCE_FILES})

@ -69,7 +69,7 @@ CFLAGS = -Os -std=gnu99 -Werror -Wpointer-arith -Wundef -Wall -Wl,-EL -fno-inli
CFLAGS += -DGIT_HASH_BACKEND='"$(shell git rev-parse --short HEAD)"'
CFLAGS += -DGIT_HASH_FRONTEND='"$(shell cd front-end && git rev-parse --short HEAD)"'
CFLAGS += -DADMIN_PASSWORD=$(ADMIN_PASSWORD)
CFLAGS += -D__TIMEZONE__='"$(shell date +%Z)"'
ifdef GLOBAL_CFLAGS
CFLAGS += $(GLOBAL_CFLAGS)

@ -30,6 +30,26 @@ emulator backend that runs on the ESP8266. The web version will be updated to ma
after each minor release (and sometimes in between for testing; the version currently being show-cased
can be read on the About page of the demo).
### Browser support
Use the above linked online demo to verify compatibility with your browser.
To our knowledge, ESPTerm **works with**:
- Google Chrome (desktop, mobile)
- Firefox (desktop, mobile not tested)
- Safari (desktop, mobile)
- Chromium
- Opera
- Brave
- Konqueror
It **does not work with**:
- Microsoft Edge - runs, but is extremely laggy
- Internet Explorer (any version) - crashes, missing JS features
- Opera Mini - crashes, missing JS and CSS features
- Blackberry browser - not tested, but unlikely
- Old Android Browser (before 4.4?) - not tested, likely missing JS features
## Main features
- **Almost complete VT102 emulation** with some extras from Xterm, eg.

@ -3,3 +3,6 @@
echo "-- Building parser from Ragel source --"
ragel -L -G0 user/ansi_parser.rl -o user/ansi_parser.c
sed -i "s/static const char _ansi_actions\[\]/static const char _ansi_actions\[\] ESP_CONST_DATA/" user/ansi_parser.c
sed -i "s/static const char _ansi_eof_actions\[\]/static const char _ansi_eof_actions\[\] ESP_CONST_DATA/" user/ansi_parser.c

@ -38,9 +38,6 @@ OUTPUT_TYPE = combined
# SPI flash size, in K
ESP_SPI_FLASH_SIZE_K = 1024
# Admin password, used to store settings to flash as defaults
ADMIN_PASSWORD = "19738426"
GLOBAL_CFLAGS = \
-DDEBUG_ROUTER=0 \
-DDEBUG_CAPTDNS=0 \
@ -51,7 +48,7 @@ GLOBAL_CFLAGS = \
-DDEBUG_CGI=0 \
-DDEBUG_WIFI=0 \
-DDEBUG_WS=0 \
-DDEBUG_ANSI=1 \
-DDEBUG_ANSI=0 \
-DDEBUG_ANSI_NOIMPL=1 \
-DDEBUG_INPUT=0 \
-DDEBUG_HEAP=1 \
@ -59,5 +56,6 @@ GLOBAL_CFLAGS = \
-DHTTPD_MAX_BACKLOG_SIZE=8192 \
-DHTTPD_MAX_HEAD_LEN=1024 \
-DHTTPD_MAX_POST_LEN=512 \
-DDEBUG_LOGBUF_SIZE=1024 \
-mforce-l32 \
-DUSE_OPTIMIZE_PRINTF=1

@ -1 +1 @@
Subproject commit e3b21adc451880d3d276b7f6575ecf733a0a6e4d
Subproject commit d59c0e876a93be89e244e010c6ad7380549d5f7f

@ -1 +1 @@
Subproject commit 3479ab3efcb4581669370cde6a607f936ff5515a
Subproject commit 24f9a371eb5c0804dcc6657f99449ef07788140c

@ -9,7 +9,7 @@
/* Ragel constants block */
/* #line 12 "user/ansi_parser.c" */
static const char _ansi_actions[] = {
static const char _ansi_actions[] ESP_CONST_DATA = {
0, 1, 0, 1, 1, 1, 2, 1,
3, 1, 4, 1, 5, 1, 6, 1,
7, 1, 8, 1, 9, 1, 10, 1,
@ -17,7 +17,7 @@ static const char _ansi_actions[] = {
15
};
static const char _ansi_eof_actions[] = {
static const char _ansi_eof_actions[] ESP_CONST_DATA = {
0, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 0, 0, 0, 0
};

@ -551,31 +551,27 @@ do_csi_sgr(CSI_Data *opts)
else if (n >= SGR_FG_BRT_START && n <= SGR_FG_BRT_END) screen_set_fg((Color) ((n - SGR_FG_BRT_START) + 8)); // AIX bright fg
else if (n >= SGR_BG_BRT_START && n <= SGR_BG_BRT_END) screen_set_bg((Color) ((n - SGR_BG_BRT_START) + 8)); // AIX bright bg
// reset color
else if (n == SGR_FG_DEFAULT) screen_set_fg(termconf_live.default_fg); // default fg
else if (n == SGR_BG_DEFAULT) screen_set_bg(termconf_live.default_bg); // default bg
else if (n == SGR_FG_DEFAULT) screen_set_default_fg();
else if (n == SGR_BG_DEFAULT) screen_set_default_bg();
// 256 colors
else if (n == SGR_FG_256 || n == SGR_BG_256) {
if (i < count-2) {
if (opts->n[i + 1] == 5) {
u8 color = (u8) opts->n[i + 2];
bool fg = n == SGR_FG_256;
if (fg) {
screen_set_fg(color);
} else {
screen_set_bg(color);
}
}
else {
ansi_warn("SGR syntax err");
apars_show_context();
break; // abandon further
}
i += 2;
} else {
if (i >= count-2) {
ansi_warn("SGR syntax err");
apars_show_context();
break; // abandon further
}
if (opts->n[i + 1] != 5) {
ansi_warn("SGR syntax err");
apars_show_context();
break; // abandon further
}
u8 color = (u8) opts->n[i + 2];
bool fg = n == SGR_FG_256;
if (fg) screen_set_fg(color);
else screen_set_bg(color);
i += 2;
}
// -- set attr --
else if (n == SGR_BOLD) screen_set_sgr(ATTR_BOLD, 1);
@ -585,7 +581,7 @@ do_csi_sgr(CSI_Data *opts)
else if (n == SGR_BLINK || n == SGR_BLINK_FAST) screen_set_sgr(ATTR_BLINK, 1); // 6 - rapid blink, not supported
else if (n == SGR_STRIKE) screen_set_sgr(ATTR_STRIKE, 1);
else if (n == SGR_FRAKTUR) screen_set_sgr(ATTR_FRAKTUR, 1);
else if (n == SGR_INVERSE) screen_set_sgr_inverse(1);
else if (n == SGR_INVERSE) screen_set_sgr(ATTR_INVERSE, 1);
else if (n == SGR_CONCEAL) screen_set_sgr_conceal(1);
else if (n == SGR_OVERLINE) screen_set_sgr(ATTR_OVERLINE, 1);
// -- clear attr --
@ -595,7 +591,7 @@ do_csi_sgr(CSI_Data *opts)
else if (n == SGR_NO_UNDERLINE) screen_set_sgr(ATTR_UNDERLINE, 0);
else if (n == SGR_NO_BLINK) screen_set_sgr(ATTR_BLINK, 0);
else if (n == SGR_NO_STRIKE) screen_set_sgr(ATTR_STRIKE, 0);
else if (n == SGR_NO_INVERSE) screen_set_sgr_inverse(0);
else if (n == SGR_NO_INVERSE) screen_set_sgr(ATTR_INVERSE, 0);
else if (n == SGR_NO_CONCEAL) screen_set_sgr_conceal(0);
else if (n == SGR_NO_OVERLINE) screen_set_sgr(ATTR_OVERLINE, 0);
else {

@ -37,6 +37,14 @@ httpd_cgi_state ICACHE_FLASH_ATTR tplScreen(HttpdConnData *connData, char *token
sprintf(buff, "%d", termconf->want_all_fn);
tplSend(connData, buff, -1);
}
else if (streq(token, "default_fg")) {
sprintf(buff, "%d", termconf->default_fg);
tplSend(connData, buff, -1);
}
else if (streq(token, "default_bg")) {
sprintf(buff, "%d", termconf->default_bg);
tplSend(connData, buff, -1);
}
return HTTPD_CGI_DONE;
}
@ -54,7 +62,7 @@ tplAbout(HttpdConnData *connData, char *token, void **arg)
tplSend(connData, __DATE__, -1);
}
else if (streq(token, "time")) {
tplSend(connData, __TIME__, -1);
tplSend(connData, __TIME__" "__TIMEZONE__, -1);
}
else if (streq(token, "vers_httpd")) {
tplSend(connData, httpdGetVersion(), -1);

@ -41,6 +41,11 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiNetworkSetParams(HttpdConnData *connData)
return HTTPD_CGI_DONE;
}
WiFiConfigBundle *wificonf_backup = malloc(sizeof(WiFiConfigBundle));
WiFiConfChangeFlags *wcf_backup = malloc(sizeof(WiFiConfChangeFlags));
memcpy(wificonf_backup, wificonf, sizeof(WiFiConfigBundle));
memcpy(wcf_backup, &wifi_change_flags, sizeof(WiFiConfChangeFlags));
// ---- AP DHCP server lease time ----
if (GET_ARG("ap_dhcp_time")) {
@ -192,9 +197,16 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiNetworkSetParams(HttpdConnData *connData)
httpdRedirect(connData, SET_REDIR_SUC);
} else {
cgi_warn("Some WiFi settings did not validate, asking for correction");
memcpy(wificonf, wificonf_backup, sizeof(WiFiConfigBundle));
memcpy(&wifi_change_flags, wcf_backup, sizeof(WiFiConfChangeFlags));
// Some errors, appended to the URL as ?err=
httpdRedirect(connData, redir_url_buf);
}
free(wificonf_backup);
free(wcf_backup);
return HTTPD_CGI_DONE;
}

@ -13,9 +13,7 @@ Cgi/template routines for configuring non-wifi settings
static bool ICACHE_FLASH_ATTR
verify_admin_pw(const char *pw)
{
// This is not really for security, but to prevent someone who
// shouldn't touch those settings from fucking it up.
return streq(pw, STR(ADMIN_PASSWORD)); // the PW comes from the makefile
return streq(pw, persist.admin.pw);
}
httpd_cgi_state ICACHE_FLASH_ATTR

@ -8,7 +8,6 @@
#include "ansi_parser.h"
#include "jstring.h"
#include "uart_driver.h"
#include "cgi_logging.h"
// Heartbeat interval in ms
#define HB_TIME 1000
@ -17,9 +16,12 @@
// Must be less than httpd sendbuf
#define SOCK_BUF_LEN 2000
// flags for screen update timeouts
volatile bool notify_available = true;
volatile bool notify_cooldown = false;
/** True if we sent XOFF to browser to stop uploading,
* and we have to tell it we're ready again */
volatile bool browser_wants_xon = false;
static ETSTimer notifyContentTim;
@ -27,10 +29,14 @@ static ETSTimer notifyLabelsTim;
static ETSTimer notifyCooldownTim;
static ETSTimer heartbeatTim;
volatile int active_clients = 0;
// we're trying to do a kind of mutex here, without the actual primitives
// this might glitch, very rarely.
// it's recommended to put some delay between setting labels and updating the screen.
static void resetHeartbeatTimer(void);
/**
* Cooldown delay is over
* @param arg
@ -57,7 +63,8 @@ notifyContentTimCb(void *arg)
if (!notify_available || notify_cooldown || (max_bl > 2048)) { // do not send if we have anything significant backlogged
// postpone a little
TIMER_START(&notifyContentTim, notifyContentTimCb, 5, 0);
TIMER_START(&notifyContentTim, notifyContentTimCb, 4, 0);
inp_dbg("postpone notify content");
return;
}
notify_available = false;
@ -71,6 +78,7 @@ notifyContentTimCb(void *arg)
cgiWebsocketSend(ws, sock_buff, (int) strlen(sock_buff), flg);
} else {
cgiWebsockBroadcast(URL_WS_UPDATE, sock_buff, (int) strlen(sock_buff), flg);
resetHeartbeatTimer();
}
if (cont == HTTPD_CGI_DONE) break;
@ -93,17 +101,25 @@ notifyContentTimCb(void *arg)
static void ICACHE_FLASH_ATTR
notifyLabelsTimCb(void *arg)
{
Websock *ws = arg;
char sock_buff[SOCK_BUF_LEN];
if (!notify_available || notify_cooldown) {
// postpone a little
TIMER_START(&notifyLabelsTim, notifyLabelsTimCb, 1, 0);
TIMER_START(&notifyLabelsTim, notifyLabelsTimCb, 7, 0);
inp_dbg("postpone notify labels");
return;
}
notify_available = false;
screenSerializeLabelsToBuffer(sock_buff, SOCK_BUF_LEN);
cgiWebsockBroadcast(URL_WS_UPDATE, sock_buff, (int) strlen(sock_buff), 0);
if (ws) {
cgiWebsocketSend(ws, sock_buff, (int) strlen(sock_buff), 0);
} else {
cgiWebsockBroadcast(URL_WS_UPDATE, sock_buff, (int) strlen(sock_buff), 0);
resetHeartbeatTimer();
}
notify_cooldown = true;
notify_available = true;
@ -115,8 +131,11 @@ notifyLabelsTimCb(void *arg)
void ICACHE_FLASH_ATTR
send_beep(void)
{
if (active_clients == 0) return;
// here's some potential for a race error with the other broadcast functions :C
cgiWebsockBroadcast(URL_WS_UPDATE, "B", 1, 0);
resetHeartbeatTimer();
}
@ -124,9 +143,12 @@ send_beep(void)
void ICACHE_FLASH_ATTR
notify_growl(char *msg)
{
if (active_clients == 0) return;
// TODO via timer...
// here's some potential for a race error with the other broadcast functions :C
cgiWebsockBroadcast(URL_WS_UPDATE, msg, (int) strlen(msg), 0);
resetHeartbeatTimer();
}
@ -137,15 +159,17 @@ notify_growl(char *msg)
*/
void ICACHE_FLASH_ATTR screen_notifyChange(ScreenNotifyChangeTopic topic)
{
// this is not the most ideal/cleanest implementation
// PRs are welcome for a nicer update "queue" solution
if (termconf->display_tout_ms == 0) termconf->display_tout_ms = SCR_DEF_DISPLAY_TOUT_MS;
if (active_clients == 0) return;
// this is probably not needed here - ensure timeout is not 0
if (termconf->display_tout_ms == 0)
termconf->display_tout_ms = SCR_DEF_DISPLAY_TOUT_MS;
// NOTE: the timers are restarted if already running
if (topic == CHANGE_LABELS) {
// separate timer from content change timer, to avoid losing that update
TIMER_START(&notifyLabelsTim, notifyLabelsTimCb, termconf->display_tout_ms, 0);
TIMER_START(&notifyLabelsTim, notifyLabelsTimCb, termconf->display_tout_ms+2, 0); // this delay is useful when both are fired at once on screen reset
}
else if (topic == CHANGE_CONTENT) {
// throttle delay
@ -161,7 +185,7 @@ void ICACHE_FLASH_ATTR screen_notifyChange(ScreenNotifyChangeTopic topic)
* @param button - which button, 1-based. 0=none
* @param mods - modifier keys bitmap: meta|alt|shift|ctrl
*/
void ICACHE_FLASH_ATTR sendMouseAction(char evt, int y, int x, int button, u8 mods)
static void ICACHE_FLASH_ATTR sendMouseAction(char evt, int y, int x, int button, u8 mods)
{
// one-based
x++;
@ -230,12 +254,12 @@ void ICACHE_FLASH_ATTR sendMouseAction(char evt, int y, int x, int button, u8 mo
}
/** Socket received a message */
void ICACHE_FLASH_ATTR updateSockRx(Websock *ws, char *data, int len, int flags)
static void ICACHE_FLASH_ATTR updateSockRx(Websock *ws, char *data, int len, int flags)
{
// Add terminator if missing (seems to randomly happen)
data[len] = 0;
ws_dbg("Sock RX str: %s, len %d", data, len);
inp_dbg("Sock RX str: %s, len %d", data, len);
int y, x, m, b;
u8 btnNum;
@ -271,8 +295,9 @@ void ICACHE_FLASH_ATTR updateSockRx(Websock *ws, char *data, int len, int flags)
case 'i':
// requests initial load
ws_dbg("Client requests initial load");
notifyContentTimCb(ws);
inp_dbg("Client requests initial load");
notifyContentTimCb(ws); // delay??
notifyLabelsTimCb(ws);
break;
case 'm':
@ -290,39 +315,80 @@ void ICACHE_FLASH_ATTR updateSockRx(Websock *ws, char *data, int len, int flags)
break;
default:
ws_warn("Bad command.");
inp_warn("Bad command.");
}
}
/** Send a heartbeat msg */
void ICACHE_FLASH_ATTR heartbeatTimCb(void *unused)
static void ICACHE_FLASH_ATTR heartbeatTimCb(void *unused)
{
if (active_clients > 0) {
if (notify_available) {
inp_dbg(".");
// Heartbeat packet - indicate we're still connected
// JS reloads the page if heartbeat is lost for a couple seconds
cgiWebsockBroadcast(URL_WS_UPDATE, ".", 1, 0);
// schedule next tick
TIMER_START(&heartbeatTim, heartbeatTimCb, HB_TIME, 0);
} else {
// postpone...
TIMER_START(&heartbeatTim, heartbeatTimCb, 10, 0);
inp_dbg("postpone heartbeat");
}
}
}
static void ICACHE_FLASH_ATTR resetHeartbeatTimer(void)
{
if (notify_available) {
// Heartbeat packet - indicate we're still connected
// JS reloads the page if heartbeat is lost for a couple seconds
cgiWebsockBroadcast(URL_WS_UPDATE, ".", 1, 0);
TIMER_START(&heartbeatTim, heartbeatTimCb, HB_TIME, 0);
}
static void ICACHE_FLASH_ATTR closeSockCb(Websock *ws)
{
active_clients--;
if (active_clients <= 0) {
active_clients = 0;
if (mouse_tracking.focus_tracking) {
UART_SendAsync("\x1b[O", 3);
}
// stop the timer
os_timer_disarm(&heartbeatTim);
}
}
/** Socket connected for updates */
void ICACHE_FLASH_ATTR updateSockConnect(Websock *ws)
{
ws_info("Socket connected to "URL_WS_UPDATE);
inp_info("Socket connected to "URL_WS_UPDATE);
ws->recvCb = updateSockRx;
ws->closeCb = closeSockCb;
if (active_clients == 0) {
if (mouse_tracking.focus_tracking) {
UART_SendAsync("\x1b[I", 3);
}
TIMER_START(&heartbeatTim, heartbeatTimCb, HB_TIME, 1);
resetHeartbeatTimer();
}
active_clients++;
}
ETSTimer xonTim;
void ICACHE_FLASH_ATTR notify_empty_txbuf_cb(void *unused)
static void ICACHE_FLASH_ATTR notify_empty_txbuf_cb(void *unused)
{
UART_WriteChar(UART1, '+', 100);
cgiWebsockBroadcast(URL_WS_UPDATE, "+", 1, 0);
resetHeartbeatTimer();
browser_wants_xon = false;
}
void notify_empty_txbuf(void)
{
if (browser_wants_xon) {

@ -17,13 +17,13 @@ void notify_growl(char *msg);
// defined in the makefile
#if DEBUG_INPUT
#define ws_warn warn
#define ws_dbg dbg
#define ws_info info
#define inp_warn warn
#define inp_dbg dbg
#define inp_info info
#else
#define ws_warn(...)
#define ws_dbg(...)
#define ws_info(...)
#define inp_warn(...)
#define inp_dbg(...)
#define inp_info(...)
#endif
#endif //CGI_SOCKETS_H

@ -78,7 +78,8 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiPing(HttpdConnData *connData)
httpd_cgi_state ICACHE_FLASH_ATTR
cgiSystemCfgSetParams(HttpdConnData *connData)
{
char buff[50];
char buff[65];
char buff2[65];
char redir_url_buf[100];
char *redir_url = redir_url_buf;
@ -90,53 +91,107 @@ cgiSystemCfgSetParams(HttpdConnData *connData)
return HTTPD_CGI_DONE;
}
if (GET_ARG("uart_baud")) {
cgi_dbg("Baud rate: %s", buff);
int baud = atoi(buff);
if (baud == BIT_RATE_300 ||
baud == BIT_RATE_600 ||
baud == BIT_RATE_1200 ||
baud == BIT_RATE_2400 ||
baud == BIT_RATE_4800 ||
baud == BIT_RATE_9600 ||
baud == BIT_RATE_19200 ||
baud == BIT_RATE_38400 ||
baud == BIT_RATE_57600 ||
baud == BIT_RATE_74880 ||
baud == BIT_RATE_115200 ||
baud == BIT_RATE_230400 ||
baud == BIT_RATE_460800 ||
baud == BIT_RATE_921600 ||
baud == BIT_RATE_1843200 ||
baud == BIT_RATE_3686400) {
sysconf->uart_baudrate = (u32) baud;
} else {
cgi_warn("Bad baud rate %s", buff);
redir_url += sprintf(redir_url, "uart_baud,");
AdminConfigBlock *admin_backup = malloc(sizeof(AdminConfigBlock));
SystemConfigBundle *sysconf_backup = malloc(sizeof(SystemConfigBundle));
memcpy(admin_backup, &persist.admin, sizeof(AdminConfigBlock));
memcpy(sysconf_backup, sysconf, sizeof(SystemConfigBundle));
do {
if (!GET_ARG("pw")) {
warn("Missing admin pw!");
redir_url += sprintf(redir_url, "pw,");
break;
}
}
if (GET_ARG("uart_parity")) {
cgi_dbg("Parity: %s", buff);
int parity = atoi(buff);
if (parity >= 0 && parity <= 2) {
sysconf->uart_parity = (UartParityMode) parity;
} else {
cgi_warn("Bad parity %s", buff);
redir_url += sprintf(redir_url, "uart_parity,");
if (!streq(buff, persist.admin.pw)) {
warn("Bad admin pw!");
redir_url += sprintf(redir_url, "pw,");
break;
}
}
if (GET_ARG("uart_stopbits")) {
cgi_dbg("Stop bits: %s", buff);
int stopbits = atoi(buff);
if (stopbits >= 1 && stopbits <= 3) {
sysconf->uart_stopbits = (UartStopBitsNum) stopbits;
} else {
cgi_warn("Bad stopbits %s", buff);
redir_url += sprintf(redir_url, "uart_stopbits,");
// authenticated OK
if (GET_ARG("pwlock")) {
cgi_dbg("pwlock: %s", buff);
int pwlock = atoi(buff);
if (pwlock < 0 || pwlock >= PWLOCK_MAX) {
cgi_warn("Bad pwlock %s", buff);
redir_url += sprintf(redir_url, "pwlock,");
break;
}
sysconf->pwlock = (enum pwlock) pwlock;
}
}
if (GET_ARG("access_pw")) {
cgi_dbg("access_pw: %s", buff);
if (strlen(buff)) {
strcpy(buff2, buff);
if (!GET_ARG("access_pw2")) {
cgi_warn("Missing repeated access_pw %s", buff);
redir_url += sprintf(redir_url, "access_pw2,");
break;
}
if (!streq(buff, buff2)) {
cgi_warn("Bad repeated access_pw %s", buff);
redir_url += sprintf(redir_url, "access_pw2,");
break;
}
if (strlen(buff) >= 64) {
cgi_warn("Too long access_pw %s", buff);
redir_url += sprintf(redir_url, "access_pw,");
break;
}
cgi_dbg("Changing access PW!");
strncpy(sysconf->access_pw, buff, 64);
}
}
if (GET_ARG("access_name")) {
cgi_dbg("access_name: %s", buff);
if (!strlen(buff) || strlen(buff) >= 32) {
cgi_warn("Too long access_name %s", buff);
redir_url += sprintf(redir_url, "access_name,");
break;
}
strncpy(sysconf->access_name, buff, 32);
}
if (GET_ARG("admin_pw")) {
cgi_dbg("admin_pw: %s", buff);
if (strlen(buff)) {
strcpy(buff2, buff);
if (!GET_ARG("admin_pw2")) {
cgi_warn("Missing repeated admin_pw %s", buff);
redir_url += sprintf(redir_url, "admin_pw2,");
break;
}
if (!streq(buff, buff2)) {
cgi_warn("Bad repeated admin_pw %s", buff);
redir_url += sprintf(redir_url, "admin_pw2,");
break;
}
if (strlen(buff) >= 64) {
cgi_warn("Too long admin_pw %s", buff);
redir_url += sprintf(redir_url, "admin_pw,");
break;
}
cgi_dbg("Changing admin PW!");
strncpy(persist.admin.pw, buff, 64);
}
}
} while (0);
(void)redir_url;
if (redir_url_buf[strlen(SET_REDIR_ERR)] == 0) {
// All was OK
@ -148,9 +203,17 @@ cgiSystemCfgSetParams(HttpdConnData *connData)
httpdRedirect(connData, SET_REDIR_SUC);
} else {
cgi_warn("Some settings did not validate, asking for correction");
// revert any possible changes
memcpy(&persist.admin, admin_backup, sizeof(AdminConfigBlock));
memcpy(sysconf, sysconf_backup, sizeof(SystemConfigBundle));
// Some errors, appended to the URL as ?err=
httpdRedirect(connData, redir_url_buf);
}
free(admin_backup);
free(sysconf_backup);
return HTTPD_CGI_DONE;
}
@ -168,14 +231,12 @@ tplSystemCfg(HttpdConnData *connData, char *token, void **arg)
strcpy(buff, ""); // fallback
if (streq(token, "uart_baud")) {
sprintf(buff, "%d", sysconf->uart_baudrate);
if (streq(token, "pwlock")) {
sprintf(buff, "%d", sysconf->pwlock);
}
else if (streq(token, "uart_parity")) {
sprintf(buff, "%d", sysconf->uart_parity);
}
else if (streq(token, "uart_stopbits")) {
sprintf(buff, "%d", sysconf->uart_stopbits);
if (streq(token, "access_name")) {
sprintf(buff, "%s", sysconf->access_name);
}
tplSend(connData, buff, -1);

@ -9,10 +9,27 @@ Cgi/template routines for configuring non-wifi settings
#include "screen.h"
#include "helpers.h"
#include "cgi_logging.h"
#include "uart_driver.h"
#define SET_REDIR_SUC "/cfg/term"
#define SET_REDIR_ERR SET_REDIR_SUC"?err="
/** convert hex number to int */
static ICACHE_FLASH_ATTR u32
decodehex(const char *buf) {
u32 n = 0;
char c;
while ((c = *buf++) != 0) {
if (c >= '0' && c <= '9') c -= '0';
else if (c >= 'a' && c <= 'f') c -= 'a'-10;
else if (c >= 'A' && c <= 'F') c -= 'A'-10;
else c = 0;
n *= 16;
n += c;
}
return n;
}
/**
* Universal CGI endpoint to set Terminal params.
*/
@ -30,6 +47,11 @@ cgiTermCfgSetParams(HttpdConnData *connData)
redir_url += sprintf(redir_url, SET_REDIR_ERR);
// we'll test if anything was printed by looking for \0 in failed_keys_buf
SystemConfigBundle *sysconf_backup = malloc(sizeof(SystemConfigBundle));
TerminalConfigBundle *termconf_backup = malloc(sizeof(TerminalConfigBundle));
memcpy(sysconf_backup, sysconf, sizeof(SystemConfigBundle));
memcpy(termconf_backup, termconf, sizeof(TerminalConfigBundle));
if (connData->conn == NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
@ -37,63 +59,73 @@ cgiTermCfgSetParams(HttpdConnData *connData)
// width and height must always go together so we can do max size validation
if (GET_ARG("term_width")) {
cgi_dbg("Default screen width: %s", buff);
w = atoi(buff);
if (w > 1) {
if (GET_ARG("term_height")) {
cgi_dbg("Default screen height: %s", buff);
h = atoi(buff);
if (h > 1) {
if (w * h <= MAX_SCREEN_SIZE) {
if (termconf->width != w || termconf->height != h) {
termconf->width = w;
termconf->height = h;
shall_clear_screen = true; // this causes a notify
}
} else {
cgi_warn("Bad dimensions: %d x %d (total %d)", w, h, w*h);
redir_url += sprintf(redir_url, "term_width,term_height,");
}
} else {
cgi_warn("Bad height: \"%s\"", buff);
redir_url += sprintf(redir_url, "term_width,");
}
} else {
do {
cgi_dbg("Default screen width: %s", buff);
w = atoi(buff);
if (w < 1) {
cgi_warn("Bad width: \"%s\"", buff);
redir_url += sprintf(redir_url, "term_width,");
break;
}
if (!GET_ARG("term_height")) {
cgi_warn("Missing height arg!");
// this wont happen normally when the form is used
redir_url += sprintf(redir_url, "term_height,");
break;
}
cgi_dbg("Default screen height: %s", buff);
h = atoi(buff);
if (h < 1) {
cgi_warn("Bad height: \"%s\"", buff);
redir_url += sprintf(redir_url, "term_height,");
break;
}
if (w * h > MAX_SCREEN_SIZE) {
cgi_warn("Bad dimensions: %d x %d (total %d)", w, h, w * h);
redir_url += sprintf(redir_url, "term_width,term_height,");
break;
}
} else {
cgi_warn("Bad width: \"%s\"", buff);
redir_url += sprintf(redir_url, "term_width,");
}
if (termconf->width != w || termconf->height != h) {
termconf->width = w;
termconf->height = h;
shall_clear_screen = true; // this causes a notify
}
} while (0);
}
if (GET_ARG("default_bg")) {
cgi_dbg("Screen default BG: %s", buff);
n = atoi(buff);
if (n >= 0 && n < 16) {
if (termconf->default_bg != n) {
termconf->default_bg = (u8) n;
shall_clear_screen = true;
}
if (buff[0] == '#') {
// decode hex
n = decodehex(buff+1);
n += 256;
} else {
cgi_warn("Bad color %s", buff);
redir_url += sprintf(redir_url, "default_bg,");
n = atoi(buff);
}
if (termconf->default_bg != n) {
termconf->default_bg = n; // this is current not sent through socket, no use to notify
}
}
if (GET_ARG("default_fg")) {
cgi_dbg("Screen default FG: %s", buff);
n = atoi(buff);
if (n >= 0 && n < 16) {
if (termconf->default_fg != n) {
termconf->default_fg = (u8) n;
shall_clear_screen = true;
}
if (buff[0] == '#') {
// decode hex
n = decodehex(buff+1);
n += 256;
} else {
cgi_warn("Bad color %s", buff);
redir_url += sprintf(redir_url, "default_fg,");
n = atoi(buff);
}
if (termconf->default_fg != n) {
termconf->default_fg = n; // this is current not sent through socket, no use to notify
}
}
@ -173,7 +205,7 @@ cgiTermCfgSetParams(HttpdConnData *connData)
if (GET_ARG("theme")) {
cgi_dbg("Screen color theme: %s", buff);
n = atoi(buff);
if (n >= 0 && n <= 5) { // ALWAYS ADJUST WHEN ADDING NEW THEME!
if (n >= 0) {
termconf->theme = (u8) n;
// this can't be notified, page must reload.
} else {
@ -265,6 +297,56 @@ cgiTermCfgSetParams(HttpdConnData *connData)
}
}
if (GET_ARG("uart_baud")) {
cgi_dbg("Baud rate: %s", buff);
int baud = atoi(buff);
if (baud == BIT_RATE_300 ||
baud == BIT_RATE_600 ||
baud == BIT_RATE_1200 ||
baud == BIT_RATE_2400 ||
baud == BIT_RATE_4800 ||
baud == BIT_RATE_9600 ||
baud == BIT_RATE_19200 ||
baud == BIT_RATE_38400 ||
baud == BIT_RATE_57600 ||
baud == BIT_RATE_74880 ||
baud == BIT_RATE_115200 ||
baud == BIT_RATE_230400 ||
baud == BIT_RATE_460800 ||
baud == BIT_RATE_921600 ||
baud == BIT_RATE_1843200 ||
baud == BIT_RATE_3686400) {
sysconf->uart_baudrate = (u32) baud;
} else {
cgi_warn("Bad baud rate %s", buff);
redir_url += sprintf(redir_url, "uart_baud,");
}
}
if (GET_ARG("uart_parity")) {
cgi_dbg("Parity: %s", buff);
int parity = atoi(buff);
if (parity >= 0 && parity <= 2) {
sysconf->uart_parity = (UartParityMode) parity;
} else {
cgi_warn("Bad parity %s", buff);
redir_url += sprintf(redir_url, "uart_parity,");
}
}
if (GET_ARG("uart_stopbits")) {
cgi_dbg("Stop bits: %s", buff);
int stopbits = atoi(buff);
if (stopbits >= 1 && stopbits <= 3) {
sysconf->uart_stopbits = (UartStopBitsNum) stopbits;
} else {
cgi_warn("Bad stopbits %s", buff);
redir_url += sprintf(redir_url, "uart_stopbits,");
}
}
(void)redir_url;
if (redir_url_buf[strlen(SET_REDIR_ERR)] == 0) {
// All was OK
info("Set term params - success, saving...");
@ -288,9 +370,16 @@ cgiTermCfgSetParams(HttpdConnData *connData)
httpdRedirect(connData, SET_REDIR_SUC);
} else {
cgi_warn("Some settings did not validate, asking for correction");
memcpy(sysconf, sysconf_backup, sizeof(SystemConfigBundle));
memcpy(termconf, termconf_backup, sizeof(TerminalConfigBundle));
// Some errors, appended to the URL as ?err=
httpdRedirect(connData, redir_url_buf);
}
free(sysconf_backup);
free(termconf_backup);
return HTTPD_CGI_DONE;
}
@ -346,10 +435,18 @@ tplTermCfg(HttpdConnData *connData, char *token, void **arg)
sprintf(buff, "%d", termconf->theme);
}
else if (streq(token, "default_bg")) {
sprintf(buff, "%d", termconf->default_bg);
if (termconf->default_bg < 256) {
sprintf(buff, "%d", termconf->default_bg);
} else {
sprintf(buff, "#%06X", termconf->default_bg - 256);
}
}
else if (streq(token, "default_fg")) {
sprintf(buff, "%d", termconf->default_fg);
if (termconf->default_fg < 256) {
sprintf(buff, "%d", termconf->default_fg);
} else {
sprintf(buff, "#%06X", termconf->default_fg - 256);
}
}
else if (streq(token, "cursor_shape")) {
sprintf(buff, "%d", termconf->cursor_shape);
@ -357,6 +454,15 @@ tplTermCfg(HttpdConnData *connData, char *token, void **arg)
else if (streq(token, "term_title")) {
strncpy_safe(buff, termconf->title, BUFLEN);
}
else if (streq(token, "uart_baud")) {
sprintf(buff, "%d", sysconf->uart_baudrate);
}
else if (streq(token, "uart_parity")) {
sprintf(buff, "%d", sysconf->uart_parity);
}
else if (streq(token, "uart_stopbits")) {
sprintf(buff, "%d", sysconf->uart_stopbits);
}
else {
for (int btn_i = 1; btn_i <= TERM_BTN_COUNT; btn_i++) {
sprintf(buff2, "btn%d", btn_i);

@ -355,6 +355,11 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
return HTTPD_CGI_DONE;
}
WiFiConfigBundle *wificonf_backup = malloc(sizeof(WiFiConfigBundle));
WiFiConfChangeFlags *wcf_backup = malloc(sizeof(WiFiConfChangeFlags));
memcpy(wificonf_backup, wificonf, sizeof(WiFiConfigBundle));
memcpy(wcf_backup, &wifi_change_flags, sizeof(WiFiConfChangeFlags));
bool sta_turned_on = false;
bool sta_ssid_pw_changed = false;
@ -502,6 +507,8 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
}
}
(void)redir_url;
if (redir_url_buf[strlen(SET_REDIR_ERR)] == 0) {
// All was OK
cgi_info("Set WiFi params - success, applying in 2000 ms");
@ -532,9 +539,16 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
}
} else {
cgi_warn("Some WiFi settings did not validate, asking for correction");
memcpy(wificonf, wificonf_backup, sizeof(WiFiConfigBundle));
memcpy(&wifi_change_flags, wcf_backup, sizeof(WiFiConfChangeFlags));
// Some errors, appended to the URL as ?err=
httpdRedirect(connData, redir_url_buf);
}
free(wificonf_backup);
free(wcf_backup);
return HTTPD_CGI_DONE;
}

@ -97,19 +97,27 @@ compute_checksum(AppConfigBundle *bundle)
return calculateCRC32((uint8_t *) bundle, sizeof(AppConfigBundle) - 4);
}
static void ICACHE_FLASH_ATTR
set_admin_block_defaults(void)
{
persist_info("[Persist] Initing admin config block");
strcpy(persist.admin.pw, DEFAULT_ADMIN_PW);
persist.admin.version = ADMINCONF_VERSION;
}
/**
* Load, verify and apply persistent config
*/
void ICACHE_FLASH_ATTR
persist_load(void)
{
persist_info("[Persist] Loading stored settings from FLASH...");
persist_info("[Persist] Loading settings from FLASH...");
persist_dbg("AppConfigBundle memory map:");
persist_dbg("> WiFiConfigBundle at %4d (error %2d)", wconf_at, wconf_at - 0);
persist_dbg("> SystemConfigBundle at %4d (error %2d)", sconf_at, sconf_at - WIFICONF_SIZE);
persist_dbg("> TerminalConfigBundle at %4d (error %2d)", tconf_at, tconf_at - WIFICONF_SIZE - SYSCONF_SIZE);
persist_dbg("> Checksum at %4d (error %2d)", cksum_at, cksum_at - (APPCONF_SIZE - 4));
persist_dbg("Persist memory map:");
persist_dbg("> wifi at %4d (error %2d)", wconf_at, wconf_at - 0);
persist_dbg("> sys at %4d (error %2d)", sconf_at, sconf_at - WIFICONF_SIZE);
persist_dbg("> term at %4d (error %2d)", tconf_at, tconf_at - WIFICONF_SIZE - SYSCONF_SIZE);
persist_dbg("> cksum at %4d (error %2d)", cksum_at, cksum_at - (APPCONF_SIZE - 4));
persist_dbg("> Total size = %d bytes (error %d)", sizeof(AppConfigBundle), APPCONF_SIZE - sizeof(AppConfigBundle));
bool hard_reset = false;
@ -120,7 +128,8 @@ persist_load(void)
// Verify checksums
if (hard_reset ||
(compute_checksum(&persist.defaults) != persist.defaults.checksum) ||
(compute_checksum(&persist.current) != persist.current.checksum)) {
(compute_checksum(&persist.current) != persist.current.checksum) ||
(persist.admin.version != 0 && (calculateCRC32((uint8_t *) &persist.admin, sizeof(AdminConfigBlock) - 4) != persist.admin.checksum))) {
error("[Persist] Checksum verification: FAILED");
hard_reset = true;
} else {
@ -135,10 +144,18 @@ persist_load(void)
// write them also as defaults
memcpy(&persist.defaults, &persist.current, sizeof(AppConfigBundle));
// reset admin pw
set_admin_block_defaults();
persist_store();
// this also stores them to flash and applies to modules
} else {
if (persist.admin.version == 0) {
set_admin_block_defaults();
persist_store();
}
apply_live_settings();
}
@ -153,6 +170,7 @@ persist_store(void)
// Update checksums before write
persist.current.checksum = compute_checksum(&persist.current);
persist.defaults.checksum = compute_checksum(&persist.defaults);
persist.admin.checksum = calculateCRC32((uint8_t *) &persist.admin, sizeof(AdminConfigBlock) - 4);
if (!system_param_save_with_protect(PERSIST_SECTOR_ID, &persist, sizeof(PersistBlock))) {
error("[Persist] Store to flash failed!");

@ -14,11 +14,13 @@
#include "screen.h"
#include "syscfg.h"
#define DEFAULT_ADMIN_PW "adminpw"
// Changing this could be used to force-erase the config area
// after a firmware upgrade
#define CHECKSUM_SALT 3
#define CHECKSUM_SALT 5
#define APPCONF_SIZE 2048
#define APPCONF_SIZE 1900
/** Struct for current or default settings */
typedef struct { // the entire block should be 1024 bytes long (for compatibility across upgrades)
@ -41,7 +43,7 @@ typedef struct { // the entire block should be 1024 bytes long (for compatibilit
// it grew to a different memory area.
uint8_t _filler_end[
APPCONF_SIZE
- sizeof(uint32_t) // checksum
- 4 // checksum
- WIFICONF_SIZE
- SYSCONF_SIZE
- TERMCONF_SIZE
@ -50,10 +52,21 @@ typedef struct { // the entire block should be 1024 bytes long (for compatibilit
uint32_t checksum; // computed before write and tested on load. If it doesn't match, values are reset to hard defaults.
} AppConfigBundle;
#define ADMINCONF_VERSION 0
#define ADMINCONF_SIZE 256
typedef struct {
u8 version;
char pw[64];
uint8_t _filler[ADMINCONF_SIZE-64-4];
uint32_t checksum;
} AdminConfigBlock;
/** This is the entire data block stored in FLASH */
typedef struct {
AppConfigBundle defaults; // defaults are stored here
AppConfigBundle current; // active settings adjusted by the user
AdminConfigBlock admin;
} PersistBlock;
// Persist holds the data currently loaded from the flash

@ -12,12 +12,79 @@
#include "cgi_network.h"
#include "cgi_term_cfg.h"
#include "cgi_persist.h"
#include "syscfg.h"
#include "persist.h"
#define WIFI_PROTECT 0
#define WIFI_AUTH_NAME "wifi"
#define WIFI_AUTH_PASS "nicitel"
/**
* Password for WiFi config
*/
static int ICACHE_FLASH_ATTR wifiPassFn(HttpdConnData *connData, int no, char *user, int userLen, char *pass, int passLen)
{
if (no == 0) {
os_strcpy(user, sysconf->access_name);
os_strcpy(pass, sysconf->access_pw);
return 1;
}
if (no == 1) {
os_strcpy(user, "admin");
os_strcpy(pass, persist.admin.pw);
return 1;
}
return 0;
}
httpd_cgi_state ICACHE_FLASH_ATTR cgiOptionalPwLock(HttpdConnData *connData)
{
bool protect = false;
http_dbg("Route, %s, pwlock=%d", connData->url, sysconf->pwlock);
switch (sysconf->pwlock) {
case PWLOCK_ALL:
protect = true;
break;
case PWLOCK_SETTINGS_NOTERM:
protect = strstarts(connData->url, "/cfg") && !strstarts(connData->url, "/cfg/term");
break;
case PWLOCK_SETTINGS_ALL:
protect = strstarts(connData->url, "/cfg");
break;
case PWLOCK_MENUS:
protect = strstarts(connData->url, "/cfg") || strstarts(connData->url, "/about") || strstarts(connData->url, "/help");
break;
default:
case PWLOCK_NONE:
break;
}
static int wifiPassFn(HttpdConnData *connData, int no, char *user, int userLen, char *pass, int passLen);
// pages outside the normal scope
if (sysconf->pwlock > PWLOCK_NONE) {
if (strstarts(connData->url, "/system/reset")) protect = true;
}
if (sysconf->pwlock > PWLOCK_SETTINGS_NOTERM) {
if (strstarts(connData->url, "/system/cls")) protect = true;
}
if (sysconf->access_pw[0] == 0) {
http_dbg("Access PW is nil, no protection.");
protect = false;
}
if (protect) {
http_dbg("Page is protected!");
connData->cgiArg = wifiPassFn;
return authBasic(connData);
} else {
http_dbg("Not protected");
return HTTPD_CGI_NOTFOUND;
}
}
/**
* Application routes
@ -25,6 +92,7 @@ static int wifiPassFn(HttpdConnData *connData, int no, char *user, int userLen,
const HttpdBuiltInUrl routes[] ESP_CONST_DATA = {
// redirect func for the captive portal
ROUTE_CGI_ARG("*", cgiRedirectApClientToHostname, "esp-terminal.ap"),
ROUTE_CGI("*", cgiOptionalPwLock),
// --- Web pages ---
ROUTE_TPL_FILE("/", tplScreen, "/term.tpl"),
@ -39,10 +107,6 @@ const HttpdBuiltInUrl routes[] ESP_CONST_DATA = {
ROUTE_CGI("/system/ping/?", cgiPing),
ROUTE_CGI("/system/cls/?", cgiResetScreen),
// --- WiFi config --- (TODO make this conditional and configurable)
#if WIFI_PROTECT
ROUTE_AUTH("/wifi*", wifiPassFn),
#endif
ROUTE_REDIRECT("/cfg/?", "/cfg/wifi"),
ROUTE_TPL_FILE("/cfg/wifi/?", tplWlan, "/cfg_wifi.tpl"),
@ -67,17 +131,3 @@ const HttpdBuiltInUrl routes[] ESP_CONST_DATA = {
ROUTE_END(),
};
// --- Wifi password protection ---
/**
* Password for WiFi config
*/
static int ICACHE_FLASH_ATTR wifiPassFn(HttpdConnData *connData, int no, char *user, int userLen, char *pass, int passLen)
{
if (no == 0) {
os_strcpy(user, WIFI_AUTH_NAME);
os_strcpy(pass, WIFI_AUTH_PASS);
return 1;
}
return 0;
}

@ -8,6 +8,7 @@
#include "jstring.h"
#include "character_sets.h"
#include "utf8.h"
#include "uart_buffer.h"
TerminalConfigBundle * const termconf = &persist.current.termconf;
TerminalConfigBundle termconf_live;
@ -20,11 +21,6 @@ static void utf8_remap(char* out, char g, char charset);
#define W termconf_live.width
#define H termconf_live.height
/**
* Highest permissible value of the color attribute
*/
#define COLOR_MAX 15
/**
* Screen cell data type (16 bits)
*/
@ -32,7 +28,7 @@ typedef struct __attribute__((packed)) {
UnicodeCacheRef symbol : 8;
Color fg;
Color bg;
u8 attrs;
CellAttrs attrs;
} Cell;
/**
@ -64,8 +60,8 @@ static struct {
char last_char[4];
} scr;
#define R0 scr.vm0
#define R1 scr.vm1
#define TOP scr.vm0
#define BTM scr.vm1
#define RH (scr.vm1 - scr.vm0 + 1)
// horizontal edges - will be useful if horizontal margin is implemented
//#define C0 0
@ -78,9 +74,9 @@ typedef struct {
bool hanging; //!< xenl state - cursor half-wrapped
/* SGR */
bool inverse; //!< not in attrs bc it's applied server-side (not sent to browser)
bool conceal; //!< similar to inverse, causes all to be replaced by SP
u8 attrs;
bool inverse; //!< not in attrs bc it's applied immediately when writing the cell
bool conceal; //!< similar to inverse, causes all to be replaced by SP
u16 attrs;
Color fg; //!< Foreground color for writing
Color bg; //!< Background color for writing
@ -158,7 +154,7 @@ static volatile int notifyLock = 0;
if (cursor.hanging && cursor.x != W-1) cursor.hanging = false; \
} while(false)
#define cursor_inside_region() (cursor.y >= R0 && cursor.y <= R1)
#define cursor_inside_region() (cursor.y >= TOP && cursor.y <= BTM)
//region --- Settings ---
@ -207,45 +203,12 @@ terminal_apply_settings_noclear(void)
{
bool changed = false;
// Migrate to v1
if (termconf->config_version < 1) {
persist_dbg("termconf: Updating to version 1");
termconf->display_cooldown_ms = SCR_DEF_DISPLAY_COOLDOWN_MS;
changed = 1;
}
// Migrate to v2
if (termconf->config_version < 2) {
persist_dbg("termconf: Updating to version 2");
termconf->loopback = 0;
termconf->show_config_links = 1;
termconf->show_buttons = 1;
changed = 1;
}
// Migrate to v3
if (termconf->config_version < 3) {
persist_dbg("termconf: Updating to version 3");
for(int i=1; i <= TERM_BTN_COUNT; i++) {
sprintf(termconf->btn_msg[i-1], "%c", i);
}
changed = 1;
}
// Migrate to v4
if (termconf->config_version < 4) {
persist_dbg("termconf: Updating to version 4");
termconf->cursor_shape = CURSOR_BLOCK_BL;
termconf->crlf_mode = false;
changed = 1;
}
// Migrate to v5
if (termconf->config_version < 4) {
persist_dbg("termconf: Updating to version 5");
termconf->want_all_fn = 0;
changed = 1;
}
// // Migrate to v1
// if (termconf->config_version < 1) {
// persist_dbg("termconf: Updating to version %d", 1);
// termconf->display_cooldown_ms = SCR_DEF_DISPLAY_COOLDOWN_MS;
// changed = 1;
// }
termconf->config_version = TERMCONF_VERSION;
@ -285,18 +248,7 @@ screen_init(void)
{
if(DEBUG_HEAP) dbg("Screen buffer size = %d bytes", sizeof(screen));
NOTIFY_LOCK();
Cell sample;
sample.symbol = ' ';
sample.fg = termconf->default_fg;
sample.bg = termconf->default_bg;
sample.attrs = 0;
for (unsigned int i = 0; i < MAX_SCREEN_SIZE; i++) {
memcpy(&screen[i], &sample, sizeof(Cell));
}
screen_reset();
NOTIFY_DONE();
}
/**
@ -325,16 +277,13 @@ screen_reset_on_resize(void)
ansi_dbg("Screen partial reset due to resize");
NOTIFY_LOCK();
cursor.x = 0;
cursor.y = 0;
cursor.hanging = false;
cursor_reset();
scr.vm0 = 0;
scr.vm1 = H-1;
// size is left unchanged
screen_clear(CLEAR_ALL);
unicode_cache_clear();
screen_clear(CLEAR_ALL); // also clears utf cache
NOTIFY_DONE();
}
@ -345,25 +294,17 @@ screen_reset_on_resize(void)
void ICACHE_FLASH_ATTR
screen_reset_sgr(void)
{
cursor.fg = termconf->default_fg;
cursor.bg = termconf->default_bg;
cursor.fg = 0;
cursor.bg = 0;
cursor.attrs = 0;
cursor.inverse = false;
cursor.conceal = false;
}
/**
* Reset the screen - called by ESC c
*/
void ICACHE_FLASH_ATTR
screen_reset(void)
static void ICACHE_FLASH_ATTR
screen_reset_do(bool size, bool labels)
{
ansi_dbg("Screen reset.");
NOTIFY_LOCK();
cursor_reset();
unicode_cache_clear();
// DECopts
scr.cursor_visible = true;
scr.insert_mode = false;
@ -377,17 +318,21 @@ screen_reset(void)
termconf_live.crlf_mode = termconf->crlf_mode;
scr.reverse_video = false;
scr.vm0 = 0;
scr.vm1 = H-1;
state_backup.alternate_active = false;
mouse_tracking.encoding = MTE_SIMPLE;
mouse_tracking.focus_tracking = false;
mouse_tracking.mode = MTM_NONE;
// size is left unchanged
screen_clear(CLEAR_ALL);
if (size) {
W = termconf->width;
H = termconf->height;
}
scr.vm0 = 0;
scr.vm1 = H - 1;
cursor_reset();
screen_clear(CLEAR_ALL); // also clears utf cache
// Set initial tabstops
for (int i = 0; i < TABSTOP_WORDS; i++) {
@ -410,8 +355,30 @@ screen_reset(void)
opt_backup.show_config_links = termconf_live.show_config_links;
NOTIFY_DONE();
if (labels) {
strcpy(termconf_live.title, termconf->title);
for (int i = 1; i <= TERM_BTN_COUNT; i++) {
strcpy(termconf_live.btn[i], termconf->btn[i]);
strcpy(termconf_live.btn_msg[i], termconf->btn_msg[i]);
}
screen_notifyChange(CHANGE_LABELS);
}
}
/**
* Reset the screen - called by ESC c
*/
void ICACHE_FLASH_ATTR
screen_reset(void)
{
ansi_dbg("Screen reset.");
screen_reset_do(true, true);
}
/**
* Swap screen buffer / state
* this is CSI ? 47/1047/1049 h/l
@ -583,27 +550,163 @@ screen_tab_reverse(int count)
/**
* Clear range, inclusive
*
* @param from - starting absolute position
* @param to - ending absolute position
* @param clear_utf - release any encountered utf8
*/
static void ICACHE_FLASH_ATTR
clear_range(unsigned int from, unsigned int to)
clear_range_do(unsigned int from, unsigned int to, bool clear_utf)
{
if (to >= W*H) to = W*H-1;
Color fg = (cursor.inverse) ? cursor.bg : cursor.fg;
Color bg = (cursor.inverse) ? cursor.fg : cursor.bg;
Cell sample;
sample.symbol = ' ';
sample.fg = fg;
sample.bg = bg;
sample.attrs = 0;
sample.fg = cursor.fg;
sample.bg = cursor.bg;
// we discard all attributes except color-set flags
sample.attrs = (CellAttrs) (cursor.attrs & (ATTR_FG | ATTR_BG));
for (unsigned int i = from; i <= to; i++) {
UnicodeCacheRef symbol = screen[i].symbol;
if (IS_UNICODE_CACHE_REF(symbol)) unicode_cache_remove(symbol);
if (clear_utf) {
UnicodeCacheRef symbol = screen[i].symbol;
if (IS_UNICODE_CACHE_REF(symbol)) unicode_cache_remove(symbol);
}
memcpy(&screen[i], &sample, sizeof(Cell));
}
}
/**
* Clear range, inclusive, freeing any encountered UTF8 from the cache
*
* @param from - starting absolute position
* @param to - ending absolute position
*/
static inline void ICACHE_FLASH_ATTR
clear_range_utf(unsigned int from, unsigned int to)
{
clear_range_do(from, to, true);
}
/**
* Clear range, inclusive. Do not release utf characters
*
* @param from - starting absolute position
* @param to - ending absolute position
*/
static inline void ICACHE_FLASH_ATTR
clear_range_noutf(unsigned int from, unsigned int to)
{
clear_range_do(from, to, false);
}
/**
* Free a utf8 reference character in a cell
*
* @param row
* @param col
*/
static inline void ICACHE_FLASH_ATTR
utf_free_cell(int row, int col)
{
//dbg("free cell (row %d) %d", row, col);
UnicodeCacheRef symbol = screen[row * W + col].symbol;
if (IS_UNICODE_CACHE_REF(symbol))
unicode_cache_remove(symbol);
}
/**
* Back-up utf8 reference in a cell (i.e. increment the counter,
* so 1 subsequent free has no effect)
*
* @param row
* @param col
*/
static inline void ICACHE_FLASH_ATTR
utf_backup_cell(int row, int col)
{
//dbg("backup cell (row %d) %d", row, col);
UnicodeCacheRef symbol = screen[row * W + col].symbol;
if (IS_UNICODE_CACHE_REF(symbol))
unicode_cache_inc(symbol);
}
/**
* Duplicate a cell within a row
* @param row - row to work on
* @param dest_col - destination column
* @param src_col - source column
*/
static inline void ICACHE_FLASH_ATTR
copy_cell(int row, int dest_col, int src_col)
{
//dbg("copy cell (row %d) %d -> %d", row, src_col, dest_col);
memcpy(screen+row*W+dest_col, screen+row*W+src_col, sizeof(Cell));
}
/**
* Free all utf8 on a row
*
* @param row
*/
static inline void ICACHE_FLASH_ATTR
utf_free_row(int row)
{
//dbg("free row %d", row);
for (int col = 0; col < W; col++) {
utf_free_cell(row, col);
}
}
/**
* Back-up all utf8 refs on a row
*
* @param row
*/
static inline void ICACHE_FLASH_ATTR
utf_backup_row(int row)
{
//dbg("backup row %d", row);
for (int col = 0; col < W; col++) {
utf_backup_cell(row, col);
}
}
/**
* Duplicate a row
*
* @param dest - destination row number (0-based)
* @param src - source row number (0-based)
*/
static inline void ICACHE_FLASH_ATTR
copy_row(int dest, int src)
{
//dbg("copy row %d -> %d", src, dest);
memcpy(screen + dest * W, screen + src * W, sizeof(Cell) * W);
}
/**
* Clear a row, do nothing with the utf8 cache
*
* @param row
*/
static inline void ICACHE_FLASH_ATTR
clear_row_noutf(int row)
{
clear_range_noutf(row * W, (row + 1) * W - 1);
}
/**
* Clear a row, freeing any utf8 refs
*
* @param row
*/
static inline void ICACHE_FLASH_ATTR
clear_row_utf(int row)
{
clear_range_utf(row * W, (row + 1) * W - 1);
}
/**
* Clear screen area
*/
@ -614,15 +717,17 @@ screen_clear(ClearMode mode)
NOTIFY_LOCK();
switch (mode) {
case CLEAR_ALL:
clear_range(0, W * H - 1);
unicode_cache_clear();
clear_range_noutf(0, W * H - 1);
scr.last_char[0] = 0;
break;
case CLEAR_FROM_CURSOR:
clear_range((cursor.y * W) + cursor.x, W * H - 1);
clear_range_utf((cursor.y * W) + cursor.x, W * H - 1);
break;
case CLEAR_TO_CURSOR:
clear_range(0, (cursor.y * W) + cursor.x);
clear_range_utf(0, (cursor.y * W) + cursor.x);
break;
}
NOTIFY_DONE();
@ -637,15 +742,15 @@ screen_clear_line(ClearMode mode)
NOTIFY_LOCK();
switch (mode) {
case CLEAR_ALL:
clear_range(cursor.y * W, (cursor.y + 1) * W - 1);
clear_row_utf(cursor.y);
break;
case CLEAR_FROM_CURSOR:
clear_range(cursor.y * W + cursor.x, (cursor.y + 1) * W - 1);
clear_range_utf(cursor.y * W + cursor.x, (cursor.y + 1) * W - 1);
break;
case CLEAR_TO_CURSOR:
clear_range(cursor.y * W, cursor.y * W + cursor.x);
clear_range_utf(cursor.y * W, cursor.y * W + cursor.x);
break;
}
NOTIFY_DONE();
@ -654,14 +759,13 @@ screen_clear_line(ClearMode mode)
void ICACHE_FLASH_ATTR
screen_clear_in_line(unsigned int count)
{
NOTIFY_LOCK();
if (cursor.x + count >= W) {
screen_clear_line(CLEAR_FROM_CURSOR);
} else {
clear_range_utf(cursor.y * W + cursor.x, cursor.y * W + cursor.x + count - 1);
}
else {
NOTIFY_LOCK();
clear_range(cursor.y * W + cursor.x, cursor.y * W + cursor.x + count - 1);
NOTIFY_DONE();
}
NOTIFY_DONE();
}
void ICACHE_FLASH_ATTR
@ -670,24 +774,24 @@ screen_insert_lines(unsigned int lines)
if (!cursor_inside_region()) return; // can't insert if not inside region
NOTIFY_LOCK();
// FIXME remove cleared & overwritten cells from unicode cache!
// shift the following lines
int targetStart = cursor.y + lines;
if (targetStart > R1) {
targetStart = R1-1;
if (targetStart > BTM) {
clear_range_utf(cursor.y*W, (BTM+1)*W-1);
} else {
// do the moving
for (int i = R1; i >= targetStart; i--) {
memcpy(screen+i*W, screen+(i-lines)*W, sizeof(Cell)*W);
for (int i = BTM; i >= targetStart; i--) {
utf_free_row(i); // release old characters
copy_row(i, i - lines);
if (i != targetStart) utf_backup_row(i);
}
}
// clear the first line
screen_clear_line(CLEAR_ALL);
// copy it to the rest of the cleared region
for (int i = cursor.y+1; i < targetStart; i++) {
memcpy(screen+i*W, screen+cursor.y*W, sizeof(Cell)*W);
// clear the first line
clear_row_noutf(cursor.y);
// copy it to the rest of the cleared region
for (int i = cursor.y+1; i < targetStart; i++) {
copy_row(i, cursor.y);
}
}
NOTIFY_DONE();
}
@ -698,21 +802,22 @@ screen_delete_lines(unsigned int lines)
if (!cursor_inside_region()) return; // can't delete if not inside region
NOTIFY_LOCK();
// FIXME remove cleared & overwritten cells from unicode cache!
// shift lines up
int targetEnd = R1 - lines - 1;
if (targetEnd <= cursor.y) {
targetEnd = cursor.y;
int movedBlockEnd = BTM - lines ;
if (movedBlockEnd <= cursor.y) {
// clear the entire rest of the screen
movedBlockEnd = cursor.y;
clear_range_utf(movedBlockEnd*W, (BTM+1)*W-1);
} else {
// do the moving
for (int i = cursor.y; i <= targetEnd; i++) {
memcpy(screen+i*W, screen+(i+lines)*W, sizeof(Cell)*W);
// move some lines up, clear the rest
for (int i = cursor.y; i <= movedBlockEnd; i++) {
utf_free_row(i);
copy_row(i, i+lines);
if (i != movedBlockEnd) utf_backup_row(i);
}
clear_range_noutf((movedBlockEnd+1)*W, (BTM+1)*W-1);
}
// clear the rest
clear_range(targetEnd*W, W*R1);
NOTIFY_DONE();
}
@ -721,19 +826,21 @@ screen_insert_characters(unsigned int count)
{
NOTIFY_LOCK();
// FIXME remove cleared & overwritten cells from unicode cache!
// shove rest of the line to the right
int targetStart = cursor.x + count;
if (targetStart >= W) {
targetStart = W-1;
// all rest of line was cleared
clear_range_utf(cursor.y * W + cursor.x, (cursor.y + 1) * W - 1);
} else {
// do the moving
for (int i = W-1; i >= targetStart; i--) {
memcpy(screen+cursor.y*W+i, screen+cursor.y*W+(i-count), sizeof(Cell));
utf_free_cell(cursor.y, i);
copy_cell(cursor.y, i, i - count);
utf_backup_cell(cursor.y, i);
}
clear_range_utf(cursor.y * W + cursor.x, cursor.y * W + targetStart - 1);
}
clear_range(cursor.y*W+cursor.x, cursor.y*W+targetStart-1);
NOTIFY_DONE();
}
@ -742,21 +849,24 @@ screen_delete_characters(unsigned int count)
{
NOTIFY_LOCK();
// FIXME remove cleared & overwritten cells from unicode cache!
int targetEnd = W - count;
if (targetEnd > cursor.x) {
// do the moving
for (int i = cursor.x; i <= targetEnd; i++) {
memcpy(screen+cursor.y*W+i, screen+cursor.y*W+(i+count), sizeof(Cell));
}
}
// pull rest of the line to the left
if (targetEnd <= cursor.x) {
int movedBlockEnd = W - count;
if (movedBlockEnd > cursor.x) {
// partial line delete / move
for (int i = cursor.x; i <= movedBlockEnd; i++) {
utf_free_cell(cursor.y, i);
copy_cell(cursor.y, i, i + count);
utf_backup_cell(cursor.y, i);
}
// clear original positions of the moved characters
clear_range_noutf(cursor.y * W + (W - count), (cursor.y + 1) * W - 1);
} else {
// all rest was cleared
screen_clear_line(CLEAR_FROM_CURSOR);
}
else {
clear_range(cursor.y * W + (W - count), (cursor.y + 1) * W - 1);
}
NOTIFY_DONE();
}
//endregion
@ -767,12 +877,12 @@ void ICACHE_FLASH_ATTR
screen_fill_with_E(void)
{
NOTIFY_LOCK();
screen_reset(); // based on observation from xterm
screen_reset_do(false, false); // based on observation from xterm
Cell sample;
sample.symbol = 'E';
sample.fg = termconf->default_fg;
sample.bg = termconf->default_bg;
sample.fg = 0;
sample.bg = 0;
sample.attrs = 0;
for (unsigned int i = 0; i <= W*H-1; i++) {
@ -837,7 +947,8 @@ screen_scroll_up(unsigned int lines)
{
NOTIFY_LOCK();
if (lines >= RH) {
clear_range(R0*W, (R1+1)*W-1);
// clear entire region
clear_range_utf(TOP * W, (BTM + 1) * W - 1);
goto done;
}
@ -846,16 +957,16 @@ screen_scroll_up(unsigned int lines)
goto done;
}
// FIXME remove cleared & overwritten cells from unicode cache!
int y;
for (y = R0; y <= R1 - lines; y++) {
memcpy(screen + y * W, screen + (y + lines) * W, W * sizeof(Cell));
for (y = TOP; y <= BTM - lines; y++) {
utf_free_row(y);
copy_row(y, y+lines);
if (y < BTM - lines) utf_backup_row(y);
}
clear_range(y * W, (R1+1)*W-1);
clear_range_noutf(y * W, (BTM + 1) * W - 1);
done:
done:
NOTIFY_DONE();
}
@ -867,24 +978,25 @@ screen_scroll_down(unsigned int lines)
{
NOTIFY_LOCK();
if (lines >= RH) {
clear_range(R0*W, (R1+1)*W-1);
// clear entire region
clear_range_utf(TOP * W, (BTM + 1) * W - 1);
goto done;
}
// FIXME remove cleared & overwritten cells from unicode cache!
// bad cmd
if (lines == 0) {
goto done;
}
int y;
for (y = R1; y >= R0+lines; y--) {
memcpy(screen + y * W, screen + (y - lines) * W, W * sizeof(Cell));
for (y = BTM; y >= TOP+lines; y--) {
utf_free_row(y);
copy_row(y, y-lines);
if (y > TOP + lines) utf_backup_row(y);
}
clear_range(R0*W, R0*W+ lines * W-1);
done:
clear_range_noutf(TOP * W, TOP * W + lines * W - 1);
done:
NOTIFY_DONE();
}
@ -960,7 +1072,7 @@ screen_cursor_get(int *y, int *x)
*y = cursor.y;
if (cursor.origin_mode) {
*y -= R0;
*y -= TOP;
}
}
@ -989,9 +1101,9 @@ screen_cursor_set_y(int y)
{
NOTIFY_LOCK();
if (cursor.origin_mode) {
y += R0;
if (y > R1) y = R1;
if (y < R0) y = R0;
y += TOP;
if (y > BTM) y = BTM;
if (y < TOP) y = TOP;
} else {
if (y > H-1) y = H-1;
if (y < 0) y = 0;
@ -1012,7 +1124,7 @@ screen_cursor_move(int dy, int dx, bool scroll)
clear_invalid_hanging();
if (cursor.hanging && dx < 0) {
dx += 1; // consume one step on the removal of "xenl"
//dx += 1; // consume one step on the removal of "xenl"
cursor.hanging = false;
}
@ -1040,7 +1152,7 @@ screen_cursor_move(int dy, int dx, bool scroll)
}
else {
// end of screen, end of line (wrap around)
cursor.y = R1;
cursor.y = BTM;
cursor.x = W - 1;
}
}
@ -1050,10 +1162,10 @@ screen_cursor_move(int dy, int dx, bool scroll)
}
}
if (cursor.y < R0) {
if (cursor.y < TOP) {
if (was_inside) {
move = -(cursor.y - R0);
cursor.y = R0;
move = -(cursor.y - TOP);
cursor.y = TOP;
if (scroll) screen_scroll_down((unsigned int) move);
}
else {
@ -1065,10 +1177,10 @@ screen_cursor_move(int dy, int dx, bool scroll)
}
}
if (cursor.y > R1) {
if (cursor.y > BTM) {
if (was_inside) {
move = cursor.y - R1;
cursor.y = R1;
move = cursor.y - BTM;
cursor.y = BTM;
if (scroll) screen_scroll_up((unsigned int) move);
}
else {
@ -1174,6 +1286,7 @@ void ICACHE_FLASH_ATTR
screen_set_fg(Color color)
{
cursor.fg = color;
cursor.attrs |= ATTR_FG;
}
/**
@ -1183,10 +1296,31 @@ void ICACHE_FLASH_ATTR
screen_set_bg(Color color)
{
cursor.bg = color;
cursor.attrs |= ATTR_BG;
}
/**
* Set cursor foreground color to default
*/
void ICACHE_FLASH_ATTR
screen_set_default_fg(void)
{
cursor.fg = 0;
cursor.attrs &= ~ATTR_FG;
}
/**
* Set cursor background color to default
*/
void ICACHE_FLASH_ATTR
screen_set_default_bg(void)
{
cursor.bg = 0;
cursor.attrs &= ~ATTR_BG;
}
void ICACHE_FLASH_ATTR
screen_set_sgr(u8 attrs, bool ena)
screen_set_sgr(CellAttrs attrs, bool ena)
{
if (ena) {
cursor.attrs |= attrs;
@ -1196,12 +1330,6 @@ screen_set_sgr(u8 attrs, bool ena)
}
}
void ICACHE_FLASH_ATTR
screen_set_sgr_inverse(bool ena)
{
cursor.inverse = ena;
}
void ICACHE_FLASH_ATTR
screen_set_sgr_conceal(bool ena)
{
@ -1359,9 +1487,11 @@ screen_report_sgr(char *buffer)
if (cursor.attrs & ATTR_BLINK) buffer += sprintf(buffer, ";%d", SGR_BLINK);
if (cursor.attrs & ATTR_FRAKTUR) buffer += sprintf(buffer, ";%d", SGR_FRAKTUR);
if (cursor.attrs & ATTR_STRIKE) buffer += sprintf(buffer, ";%d", SGR_STRIKE);
if (cursor.inverse) buffer += sprintf(buffer, ";%d", SGR_INVERSE);
if (cursor.fg != termconf->default_fg) buffer += sprintf(buffer, ";%d", ((cursor.fg > 7) ? SGR_FG_BRT_START : SGR_FG_START) + (cursor.fg&7));
if (cursor.bg != termconf->default_bg) buffer += sprintf(buffer, ";%d", ((cursor.bg > 7) ? SGR_BG_BRT_START : SGR_BG_START) + (cursor.bg&7));
if (cursor.attrs & ATTR_INVERSE) buffer += sprintf(buffer, ";%d", SGR_INVERSE);
if (cursor.attrs & ATTR_FG)
buffer += sprintf(buffer, ";%d", ((cursor.fg > 7) ? SGR_FG_BRT_START : SGR_FG_START) + (cursor.fg&7));
if (cursor.attrs & ATTR_BG)
buffer += sprintf(buffer, ";%d", ((cursor.bg > 7) ? SGR_BG_BRT_START : SGR_BG_START) + (cursor.bg&7));
(void)buffer;
}
@ -1381,10 +1511,10 @@ putchar_graphic(const char *ch)
cursor.x = 0;
cursor.y++;
// Y wrap
if (cursor.y > R1) {
if (cursor.y > BTM) {
// Scroll up, so we have space for writing
screen_scroll_up(1);
cursor.y = R1;
cursor.y = BTM;
}
cursor.hanging = false;
@ -1403,14 +1533,8 @@ putchar_graphic(const char *ch)
}
unicode_cache_remove(c->symbol);
c->symbol = unicode_cache_add((const u8 *)ch);
if (cursor.inverse) {
c->fg = cursor.bg;
c->bg = cursor.fg;
} else {
c->fg = cursor.fg;
c->bg = cursor.bg;
}
c->fg = cursor.fg;
c->bg = cursor.bg;
c->attrs = cursor.attrs;
cursor.x++;
@ -1543,7 +1667,7 @@ utf8_remap(char *out, char g, char charset)
struct ScreenSerializeState {
Color lastFg;
Color lastBg;
bool lastAttrs;
CellAttrs lastAttrs;
UnicodeCacheRef lastSymbol;
char lastChar[4];
u8 lastCharLen;
@ -1593,58 +1717,46 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, void **data)
}
Cell *cell, *cell0;
WordB2 w1;
WordB3 lw1;
u8 nbytes;
size_t remain = buf_len;
char *bb = buffer;
#define bufput_c(c) do { \
*bb = (char)c; bb++; \
*bb = (char)(c); \
bb++; \
remain--; \
} while(0)
#define bufput_2B(n) do { \
encode2B((u16) n, &w1); \
bufput_c(w1.lsb); \
bufput_c(w1.msb); \
} while(0)
#define bufput_utf8(num) do { \
nbytes = utf8_encode(bb, (num)+1); \
bb += nbytes; \
remain -= nbytes; \
} while(0)
#define bufput_3B(n) do { \
encode3B((u32) n, &lw1); \
bufput_c(lw1.lsb); \
bufput_c(lw1.msb); \
bufput_c(lw1.xsb); \
} while(0)
#define bufput_t2B(t, n) do { \
bufput_c(t); \
bufput_2B(n); \
} while(0)
#define bufput_t3B(t, n) do { \
bufput_c(t); \
bufput_3B(n); \
} while(0)
#define bufput_t_utf8(t, num) do { \
bufput_c((t)); \
bufput_utf8((num)); \
} while(0)
if (ss == NULL) {
*data = ss = malloc(sizeof(struct ScreenSerializeState));
ss->index = 0;
ss->lastBg = 0;
ss->lastFg = 0;
ss->lastAttrs = 0;
ss->lastBg = 0xFF;
ss->lastFg = 0xFF;
ss->lastAttrs = 0xFFFF;
ss->lastCharLen = 0;
ss->lastSymbol = 32;
ss->lastSymbol = 0;
strncpy(ss->lastChar, " ", 4);
bufput_c('S');
// H W X Y Attribs
bufput_2B(H);
bufput_2B(W);
bufput_2B(cursor.y);
bufput_2B(cursor.x);
bufput_utf8(H);
bufput_utf8(W);
bufput_utf8(cursor.y);
bufput_utf8(cursor.x);
// 3B has 18 free bits
bufput_3B(
bufput_utf8(
(scr.cursor_visible << 0) |
(cursor.hanging << 1) |
(scr.cursors_alt_mode << 2) |
@ -1656,7 +1768,8 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, void **data)
(termconf_live.show_config_links << 8) |
((termconf_live.cursor_shape&0x07) << 9) | // 9,10,11 - cursor shape based on DECSCUSR
(termconf_live.crlf_mode << 12) |
(scr.bracketed_paste << 13)
(scr.bracketed_paste << 13) |
(scr.reverse_video << 14)
);
}
@ -1683,35 +1796,29 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, void **data)
}
if (repCnt == 0) {
// No repeat
bool changeAttrs = cell0->attrs != ss->lastAttrs || i==0;
// No repeat - first occurrence
bool changeAttrs = cell0->attrs != ss->lastAttrs;
bool changeFg = cell0->fg != ss->lastFg;
bool changeBg = cell0->bg != ss->lastBg;
bool changeColors = (changeFg && changeBg) || i==0;
bool changeColors = changeFg && changeBg;
Color fg, bg;
// Reverse fg and bg if we're in global reverse mode
if (! scr.reverse_video) {
fg = cell0->fg;
bg = cell0->bg;
}
else {
fg = cell0->bg;
bg = cell0->fg;
}
fg = cell0->fg;
bg = cell0->bg;
if (changeColors) {
bufput_t3B(SEQ_TAG_COLORS, bg<<8 | fg);
bufput_t_utf8(SEQ_TAG_COLORS, bg<<8 | fg);
}
else if (changeFg) {
bufput_t2B(SEQ_TAG_FG, fg);
bufput_t_utf8(SEQ_TAG_FG, fg);
}
else if (changeBg) {
bufput_t2B(SEQ_TAG_BG, bg);
bufput_t_utf8(SEQ_TAG_BG, bg);
}
if (changeAttrs) {
bufput_t2B(SEQ_TAG_ATTRS, cell0->attrs);
bufput_t_utf8(SEQ_TAG_ATTRS, cell0->attrs);
}
// copy the symbol, until first 0 or reached 4 bytes
@ -1733,24 +1840,21 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, void **data)
i++;
} else {
// last character was repeated repCnt times
int savings = ss->lastCharLen*repCnt;
if (savings > 3) {
// Repeat count
bufput_t2B(SEQ_TAG_REPEAT, repCnt);
} else {
// repeat it manually
for(int k = 0; k < repCnt; k++) {
for (int j = 0; j < ss->lastCharLen; j++) {
bufput_c(ss->lastChar[j]);
}
}
}
bufput_t_utf8(SEQ_TAG_REPEAT, repCnt);
}
}
ss->index = i;
bufput_c('\0'); // terminate the string
#if 0
printf("MSG: ");
for (int j=0;j<bb-buffer;j++) {
printf("%02X ", buffer[j]);
}
printf("\n");
#endif
if (i < W*H-1) {
return HTTPD_CGI_MORE;
}

@ -37,7 +37,7 @@
// Size designed for the terminal config structure
// Must be constant to avoid corrupting user config after upgrade
#define TERMCONF_SIZE 300
#define TERMCONF_VERSION 5
#define TERMCONF_VERSION 0
#define TERM_BTN_LEN 10
#define TERM_BTN_MSG_LEN 10
@ -65,11 +65,11 @@ enum CursorShape {
CURSOR_BAR = 6,
};
#define SCR_DEF_SHOW_BUTTONS 0
#define SCR_DEF_SHOW_BUTTONS 1
#define SCR_DEF_SHOW_MENU 1
#define SCR_DEF_CURSOR_SHAPE CURSOR_BLOCK_BL
#define SCR_DEF_CRLF 0
#define SCR_DEF_ALLFN 0
#define SCR_DEF_CRLF 0 // enter sends CRLF
#define SCR_DEF_ALLFN 0 // capture F5 etc
// --- Persistent Settings ---
#define CURSOR_BLINKS(shape) ((shape)==CURSOR_BLOCK_BL||(shape)==CURSOR_UNDERLINE_BL||(shape)==CURSOR_BAR_BL)
@ -77,8 +77,8 @@ enum CursorShape {
typedef struct {
u32 width;
u32 height;
u8 default_bg; // should be the Color typedef, but this way the size is more explicit
u8 default_fg;
u32 default_bg; // 00-FFh - ANSI colors, (00:00:00-FF:FF:FF)+256 - True Color
u32 default_fg;
char title[TERM_TITLE_LEN];
char btn[TERM_BTN_COUNT][TERM_BTN_LEN];
u8 theme;
@ -228,25 +228,32 @@ void screen_set_origin_mode(bool region_origin);
// --- Graphic rendition setting ---
typedef uint8_t Color; // 0-16
#define ATTR_BOLD (1<<0)
#define ATTR_FAINT (1<<1)
#define ATTR_ITALIC (1<<2)
#define ATTR_UNDERLINE (1<<3)
#define ATTR_BLINK (1<<4)
#define ATTR_FRAKTUR (1<<5)
#define ATTR_STRIKE (1<<6)
#define ATTR_OVERLINE (1<<7)
typedef uint8_t Color;
typedef uint16_t CellAttrs;
// TODO sort by the expected frequency of being set - so when we switch to utf-8 encoding for data fields, it uses fewer bytes
#define ATTR_BOLD (1<<0) //!< Bold font
#define ATTR_FAINT (1<<1) //!< Faint foreground color (reduced alpha)
#define ATTR_ITALIC (1<<2) //!< Italic font
#define ATTR_UNDERLINE (1<<3) //!< Underline decoration
#define ATTR_BLINK (1<<4) //!< Blinking
#define ATTR_FRAKTUR (1<<5) //!< Fraktur font (unicode substitution)
#define ATTR_STRIKE (1<<6) //!< Strike-through decoration
#define ATTR_OVERLINE (1<<7) //!< Over-line decoration
#define ATTR_FG (1<<8) //!< 1 if not using default background color (ignore cell bg) - color extension bit
#define ATTR_BG (1<<9) //!< 1 if not using default foreground color (ignore cell fg) - color extension bit
#define ATTR_INVERSE (1<<10) //!< Invert colors - this is useful so we can clear then with SGR manipulation commands
/** Set cursor foreground color */
void screen_set_fg(Color color);
/** Set cursor background coloor */
void screen_set_bg(Color color);
/** Set cursor foreground color to default */
void screen_set_default_fg(void);
/** Set cursor background color to default */
void screen_set_default_bg(void);
/** Enable/disable attrs by bitmask */
void screen_set_sgr(u8 attrs, bool ena);
/** Set the inverse attribute */
void screen_set_sgr_inverse(bool ena);
void screen_set_sgr(CellAttrs attrs, bool ena);
/** Conceal style */
void screen_set_sgr_conceal(bool ena);
/** Reset cursor attribs */

@ -4,8 +4,7 @@
#include "ansi_parser.h"
#include "syscfg.h"
#define LOGBUF_SIZE 512
static char logbuf[LOGBUF_SIZE];
static char logbuf[DEBUG_LOGBUF_SIZE];
static u32 lb_nw = 1;
static u32 lb_ls = 0;
static ETSTimer flushLogTimer;
@ -14,7 +13,7 @@ static void buf_putc(char c)
{
if (lb_ls != lb_nw) {
logbuf[lb_nw++] = c;
if (lb_nw >= LOGBUF_SIZE) lb_nw = 0;
if (lb_nw >= DEBUG_LOGBUF_SIZE) lb_nw = 0;
}
}
@ -25,11 +24,11 @@ buf_pop(void *unused)
u32 old_ls;
while (quantity > 0) {
// stop when done
if ((lb_ls == lb_nw-1) || (lb_ls == LOGBUF_SIZE-1 && lb_nw == 0)) break;
if ((lb_ls == lb_nw-1) || (lb_ls == DEBUG_LOGBUF_SIZE-1 && lb_nw == 0)) break;
old_ls = lb_ls;
lb_ls++;
if (lb_ls >= LOGBUF_SIZE) lb_ls = 0;
if (lb_ls >= DEBUG_LOGBUF_SIZE) lb_ls = 0;
if (OK == UART_WriteCharCRLF(UART1, logbuf[lb_ls], 5)) {
quantity--;

@ -12,7 +12,17 @@ SystemConfigBundle * const sysconf = &persist.current.sysconf;
void ICACHE_FLASH_ATTR
sysconf_apply_settings(void)
{
// !!! Update to current version !!!
bool changed = false;
// if (sysconf->config_version < 1) {
// dbg("Upgrading syscfg to v 1");
// changed = true;
// }
sysconf->config_version = SYSCONF_VERSION;
if (changed) {
persist_store();
}
serialInit();
}
@ -23,4 +33,9 @@ sysconf_restore_defaults(void)
sysconf->uart_parity = PARITY_NONE;
sysconf->uart_baudrate = BIT_RATE_115200;
sysconf->uart_stopbits = ONE_STOP_BIT;
sysconf->config_version = SYSCONF_VERSION;
sysconf->access_pw[0] = 0;
sysconf->pwlock = PWLOCK_NONE;
strcpy(sysconf->access_pw, DEF_ACCESS_PW);
strcpy(sysconf->access_name, DEF_ACCESS_NAME);
}

@ -9,14 +9,29 @@
// Size designed for the wifi config structure
// Must be constant to avoid corrupting user config after upgrade
#define SYSCONF_SIZE 200
#define SYSCONF_SIZE 300
#define SYSCONF_VERSION 0
#define DEF_ACCESS_PW "1234"
#define DEF_ACCESS_NAME "espterm"
enum pwlock {
PWLOCK_NONE = 0,
PWLOCK_SETTINGS_NOTERM = 1,
PWLOCK_SETTINGS_ALL = 2,
PWLOCK_MENUS = 3,
PWLOCK_ALL = 4,
PWLOCK_MAX = 5,
};
typedef struct {
u32 uart_baudrate;
u8 uart_parity;
u8 uart_stopbits;
u8 config_version;
enum pwlock pwlock : 8; // page access lock
char access_pw[64]; // access password
char access_name[32]; // access name
} SystemConfigBundle;
extern SystemConfigBundle * const sysconf;

@ -79,7 +79,6 @@ static void ICACHE_FLASH_ATTR prHeapTimerCb(void *arg)
// Deferred init
static void user_start(void *unused);
static void user_start2(void *unused);
static ETSTimer userStartTimer;
static ETSTimer prHeapTimer;
@ -99,7 +98,7 @@ void ICACHE_FLASH_ATTR user_init(void)
banner_info("Firmware (c) Ondrej Hruska, 2017");
banner_info(TERMINAL_GITHUB_REPO);
banner_info("");
banner_info("Version "FIRMWARE_VERSION", built " __DATE__ " at " __TIME__);
banner_info("Version "FIRMWARE_VERSION", built " __DATE__ " at " __TIME__ " " __TIMEZONE__);
printf("\r\n");
ioInit();
@ -121,7 +120,6 @@ void ICACHE_FLASH_ATTR user_init(void)
TIMER_START(&userStartTimer, user_start, 10, 0);
}
static void ICACHE_FLASH_ATTR user_start(void *unused)
{
// Load and apply stored settings, or defaults if stored settings are invalid

@ -20,7 +20,8 @@ static UnicodeCacheSlot cache[UNICODE_CACHE_SIZE];
* @return
*/
void ICACHE_FLASH_ATTR
unicode_cache_clear(void) {
unicode_cache_clear(void)
{
utfc_dbg("utf8 cache clear!");
for (int slot = 0; slot < UNICODE_CACHE_SIZE; slot++) {
cache[slot].count=0;
@ -35,7 +36,8 @@ unicode_cache_clear(void) {
* @return the obtained look-up reference
*/
UnicodeCacheRef ICACHE_FLASH_ATTR
unicode_cache_add(const u8 *bytes) {
unicode_cache_add(const u8 *bytes)
{
if (bytes[0] < 32) {
utfc_warn("utf8 cache bad char '%c'", bytes[0]);
return '?';
@ -69,6 +71,27 @@ unicode_cache_add(const u8 *bytes) {
return ID_TO_REF(slot);
}
/**
* Increment a reference
*
* @param ref - reference
* @return success
*/
bool ICACHE_FLASH_ATTR
unicode_cache_inc(UnicodeCacheRef ref)
{
if (!IS_UNICODE_CACHE_REF(ref)) return true; // ASCII
int slot = REF_TO_ID(ref);
if (cache[slot].count == 0) {
utfc_warn("utf8 cache inc-after-free ref @ %d", ref);
return false;
}
cache[slot].count++;
utfc_dbg("utf8 cache inc '%.4s' @ %d, %d uses", cache[slot].bytes, slot, cache[slot].count);
return true;
}
/**
* Look up a code point in the cache by reference. Do not change the use counter.
*
@ -77,7 +100,8 @@ unicode_cache_add(const u8 *bytes) {
* @return true if the look-up succeeded
*/
bool ICACHE_FLASH_ATTR
unicode_cache_retrieve(UnicodeCacheRef ref, u8 *target) {
unicode_cache_retrieve(UnicodeCacheRef ref, u8 *target)
{
if (!IS_UNICODE_CACHE_REF(ref)) {
// ASCII, bypass
target[0] = ref;
@ -107,7 +131,8 @@ unicode_cache_retrieve(UnicodeCacheRef ref, u8 *target) {
* @return true if the code point was found in the cache
*/
bool ICACHE_FLASH_ATTR
unicode_cache_remove(UnicodeCacheRef ref) {
unicode_cache_remove(UnicodeCacheRef ref)
{
if (!IS_UNICODE_CACHE_REF(ref)) return true; // ASCII, bypass
u8 slot = REF_TO_ID(ref);

@ -8,7 +8,7 @@
#include <c_types.h>
// 160 is maximum possible
#define UNICODE_CACHE_SIZE 100
#define UNICODE_CACHE_SIZE 160
typedef u8 UnicodeCacheRef;
#define IS_UNICODE_CACHE_REF(c) ((c) < 32 || (c) >= 127)
@ -28,6 +28,14 @@ void unicode_cache_clear(void);
*/
UnicodeCacheRef unicode_cache_add(const u8 *bytes);
/**
* Increment a reference
*
* @param ref - reference
* @return success
*/
bool unicode_cache_inc(UnicodeCacheRef ref);
/**
* Look up a code point in the cache by reference. Do not change the use counter.
*

Loading…
Cancel
Save