diff --git a/esphttpdconfig.mk b/esphttpdconfig.mk index 617b057..e9eaebf 100644 --- a/esphttpdconfig.mk +++ b/esphttpdconfig.mk @@ -58,4 +58,5 @@ GLOBAL_CFLAGS = \ -DHTTPD_MAX_POST_LEN=512 \ -DDEBUG_LOGBUF_SIZE=1024 \ -mforce-l32 \ - -DUSE_OPTIMIZE_PRINTF=1 + -DUSE_OPTIMIZE_PRINTF=1 \ + -DHTTPD_CONN_TIMEOUT=3 diff --git a/libesphttpd b/libesphttpd index 24f9a37..5772d3c 160000 --- a/libesphttpd +++ b/libesphttpd @@ -1 +1 @@ -Subproject commit 24f9a371eb5c0804dcc6657f99449ef07788140c +Subproject commit 5772d3c9a99460dad79be730453982ecd35ed461 diff --git a/user/cgi_sockets.c b/user/cgi_sockets.c index 44accb2..4824615 100644 --- a/user/cgi_sockets.c +++ b/user/cgi_sockets.c @@ -278,7 +278,7 @@ static void ICACHE_FLASH_ATTR updateSockRx(Websock *ws, char *data, int len, int case 'i': // requests initial load inp_dbg("Client requests initial load"); - updateNotify_do(ws, TOPIC_INITIAL); + updateNotify_do(ws, TOPIC_INITIAL|TOPIC_FLAG_NOCLEAN); break; case 'm': diff --git a/user/screen.c b/user/screen.c index cf6f5f0..617456a 100644 --- a/user/screen.c +++ b/user/screen.c @@ -139,6 +139,25 @@ static struct { static volatile int notifyLock = 0; static volatile ScreenNotifyTopics lockTopics = 0; +static struct { + int x_min, y_min, x_max, y_max; +} scr_dirty; + +#define reset_screen_dirty() do { \ + scr_dirty.x_min = W; \ + scr_dirty.x_max = -1; \ + scr_dirty.y_min = H; \ + scr_dirty.y_max = -1; \ + } while(0) + +#define expand_dirty(y0, y1, x0, x1) do { \ + seri_dbg("Expand: X: (%d..%d) -> %d..%d, Y: (%d..%d) -> %d..%d", scr_dirty.x_min, scr_dirty.x_max, x0, x1, scr_dirty.y_min, scr_dirty.y_max, y0, y1); \ + if ((int)(y0) < scr_dirty.y_min) scr_dirty.y_min = (y0); \ + if ((int)(x0) < scr_dirty.x_min) scr_dirty.x_min = (x0); \ + if ((int)(y1) > scr_dirty.y_max) scr_dirty.y_max = (y1); \ + if ((int)(x1) > scr_dirty.x_max) scr_dirty.x_max = (x1); \ + } while(0) + #define NOTIFY_LOCK() do { \ notifyLock++; \ } while(0) @@ -154,7 +173,10 @@ static volatile ScreenNotifyTopics lockTopics = 0; /** Clear the hanging attribute if the cursor is no longer >= W */ #define clear_invalid_hanging() do { \ - if (cursor.hanging && cursor.x != W-1) cursor.hanging = false; \ + if (cursor.hanging && cursor.x != W-1) { \ + cursor.hanging = false; \ + screen_notifyChange(TOPIC_CHANGE_CURSOR); \ + } \ } while(false) #define cursor_inside_region() (cursor.y >= TOP && cursor.y <= BTM) @@ -252,6 +274,7 @@ screen_init(void) { if(DEBUG_HEAP) dbg("Screen buffer size = %d bytes", sizeof(screen)); + reset_screen_dirty(); screen_reset(); } @@ -746,10 +769,12 @@ screen_clear(ClearMode mode) case CLEAR_FROM_CURSOR: clear_range_utf((cursor.y * W) + cursor.x, W * H - 1); + expand_dirty(cursor.y, H-1, 0, W-1); break; case CLEAR_TO_CURSOR: clear_range_utf(0, (cursor.y * W) + cursor.x); + expand_dirty(0, cursor.y, 0, W-1); break; } NOTIFY_DONE(mode == CLEAR_ALL ? TOPIC_CHANGE_CONTENT_ALL : TOPIC_CHANGE_CONTENT_PART); @@ -765,14 +790,17 @@ screen_clear_line(ClearMode mode) switch (mode) { case CLEAR_ALL: clear_row_utf(cursor.y); + expand_dirty(cursor.y, cursor.y, 0, W-1); break; case CLEAR_FROM_CURSOR: clear_range_utf(cursor.y * W + cursor.x, (cursor.y + 1) * W - 1); + expand_dirty(cursor.y, cursor.y, cursor.x, W-1); break; case CLEAR_TO_CURSOR: clear_range_utf(cursor.y * W, cursor.y * W + cursor.x); + expand_dirty(cursor.y, cursor.y, 0, cursor.x); break; } NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART); @@ -786,6 +814,7 @@ screen_clear_in_line(unsigned int count) screen_clear_line(CLEAR_FROM_CURSOR); } else { clear_range_utf(cursor.y * W + cursor.x, cursor.y * W + cursor.x + count - 1); + expand_dirty(cursor.y, cursor.y, cursor.x, cursor.x + count - 1); } NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART); } @@ -815,6 +844,7 @@ screen_insert_lines(unsigned int lines) copy_row(i, cursor.y); } } + expand_dirty(cursor.y, BTM, 0, W - 1); NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART); } @@ -840,6 +870,7 @@ screen_delete_lines(unsigned int lines) clear_range_noutf((movedBlockEnd+1)*W, (BTM+1)*W-1); } + expand_dirty(cursor.y, BTM, 0, W - 1); NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART); } @@ -863,6 +894,7 @@ screen_insert_characters(unsigned int count) } clear_range_utf(cursor.y * W + cursor.x, cursor.y * W + targetStart - 1); } + expand_dirty(cursor.y, cursor.y, cursor.x, W - 1); NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART); } @@ -889,6 +921,7 @@ screen_delete_characters(unsigned int count) screen_clear_line(CLEAR_FROM_CURSOR); } + expand_dirty(cursor.y, cursor.y, cursor.x, W - 1); NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART); } //endregion @@ -991,6 +1024,7 @@ screen_scroll_up(unsigned int lines) clear_range_noutf(y * W, (BTM + 1) * W - 1); done: + expand_dirty(TOP, BTM, 0, W - 1); NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART); } @@ -1021,6 +1055,7 @@ screen_scroll_down(unsigned int lines) clear_range_noutf(TOP * W, TOP * W + lines * W - 1); done: + expand_dirty(TOP, BTM, 0, W - 1); NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART); } @@ -1146,6 +1181,7 @@ screen_cursor_move(int dy, int dx, bool scroll) { NOTIFY_LOCK(); int move; + bool scrolled = 0; clear_invalid_hanging(); @@ -1192,7 +1228,10 @@ screen_cursor_move(int dy, int dx, bool scroll) if (was_inside) { move = -(cursor.y - TOP); cursor.y = TOP; - if (scroll) screen_scroll_down((unsigned int) move); + if (scroll) { + screen_scroll_down((unsigned int) move); + scrolled = true; + } } else { // outside the region, just validate that we're not going offscreen @@ -1207,7 +1246,10 @@ screen_cursor_move(int dy, int dx, bool scroll) if (was_inside) { move = cursor.y - BTM; cursor.y = BTM; - if (scroll) screen_scroll_up((unsigned int) move); + if (scroll) { + screen_scroll_up((unsigned int) move); + scrolled = true; + } } else { // outside the region, just validate that we're not going offscreen @@ -1218,7 +1260,11 @@ screen_cursor_move(int dy, int dx, bool scroll) } } - NOTIFY_DONE(TOPIC_CHANGE_CURSOR | (scroll*TOPIC_CHANGE_CONTENT_PART)); + if (scrolled) { + expand_dirty(TOP, BTM, 0, W-1); + } + + NOTIFY_DONE(TOPIC_CHANGE_CURSOR | (scrolled*TOPIC_CHANGE_CONTENT_PART)); } /** @@ -1569,6 +1615,9 @@ putchar_graphic(const char *ch) { static char buf[4]; + NOTIFY_LOCK(); + ScreenNotifyTopics topics = TOPIC_CHANGE_CURSOR; + if (cursor.hanging) { // perform the scheduled wrap if hanging // if auto-wrap = off, it overwrites the last char @@ -1591,17 +1640,29 @@ putchar_graphic(const char *ch) // move the rest of the line if we're in Insert Mode if (cursor.x < W-1 && scr.insert_mode) screen_insert_characters(1); - if (ch[1] == 0 && ch[0] <= 0x7f) { + char chs = (cursor.charsetN == 0) ? cursor.charset0 : cursor.charset1; + if (chs != 'B' && ch[1] == 0 && ch[0] <= 0x7f) { // we have len=1 and ASCII, can be re-mapped using a table - utf8_remap(buf, ch[0], (cursor.charsetN == 0) ? cursor.charset0 : cursor.charset1); + utf8_remap(buf, ch[0], chs); ch = buf; } + + UnicodeCacheRef oldSymbol = c->symbol; + Color oldFg = c->fg; + Color oldBg = c->bg; + CellAttrs oldAttrs = c->attrs; + unicode_cache_remove(c->symbol); c->symbol = unicode_cache_add((const u8 *)ch); c->fg = cursor.fg; c->bg = cursor.bg; c->attrs = cursor.attrs; + if (c->symbol != oldSymbol || c->fg != oldFg || c->bg != oldBg || c->attrs != oldAttrs) { + expand_dirty(cursor.y, cursor.y, cursor.x, cursor.x); + topics |= TOPIC_CHANGE_CONTENT_PART; + } + cursor.x++; // X wrap if (cursor.x >= W) { @@ -1609,6 +1670,7 @@ putchar_graphic(const char *ch) cursor.x = W - 1; } + NOTIFY_DONE(topics); return ch; } @@ -1618,8 +1680,6 @@ putchar_graphic(const char *ch) void ICACHE_FLASH_ATTR screen_putchar(const char *ch) { - NOTIFY_LOCK(); - // clear "hanging" flag if not possible clear_invalid_hanging(); @@ -1660,8 +1720,8 @@ screen_putchar(const char *ch) // not have to call the remap function repeatedly. strncpy(scr.last_char, result, 4); - done: - NOTIFY_DONE(TOPIC_WRITE); +done: + return; } /** @@ -1671,7 +1731,6 @@ screen_putchar(const char *ch) void ICACHE_FLASH_ATTR screen_repeat_last_character(int count) { - NOTIFY_LOCK(); if (scr.last_char[0]==0) { scr.last_char[0] = ' '; scr.last_char[1] = 0; @@ -1683,7 +1742,6 @@ screen_repeat_last_character(int count) putchar_graphic(scr.last_char); count--; } - NOTIFY_DONE(TOPIC_WRITE); } /** @@ -1740,6 +1798,10 @@ struct ScreenSerializeState { ScreenNotifyTopics topics; ScreenNotifyTopics last_topic; ScreenNotifyTopics current_topic; + bool partial; + int x_min, x_max, y_min, y_max; + int i_max; + int i_start; }; /** @@ -1795,6 +1857,7 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, ScreenNotifyTopics topics, #define SEQ_TAG_ATTRS '\x04' #define SEQ_TAG_FG '\x05' #define SEQ_TAG_BG '\x06' +#define SEQ_TAG_ATTRS_0 '\x07' #define TOPICMARK_SCREEN_OPTS 'O' #define TOPICMARK_TITLE 'T' @@ -1813,7 +1876,48 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, ScreenNotifyTopics topics, topics |= TOPIC_INTERNAL; } - ss->index = 0; + if (topics & TOPIC_CHANGE_CONTENT_PART) { + // reset dirty extents + ss->partial = true; + + ss->x_min = scr_dirty.x_min; + ss->x_max = scr_dirty.x_max; + ss->y_min = scr_dirty.y_min; + ss->y_max = scr_dirty.y_max; + + if (ss->x_min > ss->x_max || ss->y_min > ss->y_max) { + seri_warn("Partial redraw, but bad bounds! X %d..%d, Y %d..%d", ss->x_min, ss->x_max, ss->y_min, ss->y_max); + // use full redraw + reset_screen_dirty(); + + topics ^= TOPIC_CHANGE_CONTENT_PART; + topics |= TOPIC_CHANGE_CONTENT_ALL; + } else { + // is OK + ss->i_max = ss->y_max * W + ss->x_max; + ss->index = W*ss->y_min + ss->x_min; + seri_dbg("Partial! X %d..%d, Y %d..%d, i_max %d", ss->x_min, ss->x_max, ss->y_min, ss->y_max, ss->i_max); + } + } + + if (topics & TOPIC_CHANGE_CONTENT_ALL) { + // this is a no-clean request, do not purge + // it's also always a full-screen repaint + ss->partial = false; + ss->index = 0; + ss->i_max = W*H-1; + ss->x_min = 0; + ss->x_max = W-1; + ss->y_min = 0; + ss->y_max = H-1; + seri_dbg("Full redraw!"); + } + + ss->i_start = ss->index; + + if ((topics & (TOPIC_CHANGE_CONTENT_ALL | TOPIC_CHANGE_CONTENT_PART)) && !(topics & TOPIC_FLAG_NOCLEAN)) { + reset_screen_dirty(); + } ss->topics = topics; ss->last_topic = 0; // to be filled @@ -1823,11 +1927,22 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, ScreenNotifyTopics topics, bufput_c('U'); // - stands for "update" bufput_utf8(topics); + + if (ss->partial) { + // advance to the first char we want to send + } } int begun_topic = 0; int prev_topic = 0; +#define INC_I() do { \ + i++; \ + if (ss->partial) {\ + if (i%W > ss->x_max) i += (W - ss->x_max + ss->x_min - 1);\ + } \ + } while (0) + #define BEGIN_TOPIC(topic, size) \ if (ss->last_topic == prev_topic) { \ begun_topic = (topic); \ @@ -1945,20 +2060,19 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, ScreenNotifyTopics topics, // no screen mode - wrap it up goto ser_done; } + + // start the screen section } } // screen contents int i = ss->index; - if (i == 0) { + if (i == ss->i_start) { bufput_c(TOPICMARK_SCREEN); // desired update mode is in `ss->current_topic` - - // TODO implement partial - bufput_utf8(0); // Y0 - bufput_utf8(0); // X0 - bufput_utf8(H); // height - bufput_utf8(W); // width - + bufput_utf8(ss->y_min); // Y0 + bufput_utf8(ss->x_min); // X0 + bufput_utf8(ss->y_max - ss->y_min + 1); // height + bufput_utf8(ss->x_max - ss->x_min + 1); // width ss->index = 0; ss->lastBg = 0xFF; ss->lastFg = 0xFF; @@ -1966,26 +2080,27 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, ScreenNotifyTopics topics, ss->lastCharLen = 0; ss->lastSymbol = 0; } - while(i < W*H && remain > 12) { + while(i <= ss->i_max && remain > 12) { cell = cell0 = &screen[i]; // Count how many times same as previous int repCnt = 0; - while (i < W*H + while (i <= ss->i_max && cell->fg == ss->lastFg && cell->bg == ss->lastBg && cell->attrs == ss->lastAttrs && cell->symbol == ss->lastSymbol) { // Repeat repCnt++; - cell = &screen[++i]; + INC_I(); + cell = &screen[i]; // it can go outside the allocated memory here if we went over the top } if (repCnt == 0) { // No repeat - first occurrence bool changeAttrs = cell0->attrs != ss->lastAttrs; - bool changeFg = cell0->fg != ss->lastFg; - bool changeBg = cell0->bg != ss->lastBg; + bool changeFg = (cell0->fg != ss->lastFg) && (cell0->attrs & ATTR_FG); + bool changeBg = (cell0->bg != ss->lastBg) && (cell0->attrs & ATTR_BG); bool changeColors = changeFg && changeBg; Color fg, bg; @@ -2004,7 +2119,11 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, ScreenNotifyTopics topics, } if (changeAttrs) { - bufput_t_utf8(SEQ_TAG_ATTRS, cell0->attrs); + if (cell0->attrs) { + bufput_t_utf8(SEQ_TAG_ATTRS, cell0->attrs); + } else { + bufput_c(SEQ_TAG_ATTRS_0); + } } // copy the symbol, until first 0 or reached 4 bytes @@ -2023,15 +2142,26 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, ScreenNotifyTopics topics, ss->lastAttrs = cell0->attrs; ss->lastSymbol = cell0->symbol; - i++; + INC_I(); } else { // last character was repeated repCnt times - bufput_t_utf8(SEQ_TAG_REPEAT, repCnt); + int savings = ss->lastCharLen*repCnt; + if (savings > 2) { + // Repeat count + bufput_t_utf8(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]); + } + } + } } } ss->index = i; - if (i >= W*H-1) goto ser_done; + if (i >= ss->i_max) goto ser_done; // MORE TO WRITE... bufput_c('\0'); // terminate the string diff --git a/user/screen.h b/user/screen.h index fc496d1..3f6540e 100644 --- a/user/screen.h +++ b/user/screen.h @@ -163,6 +163,8 @@ enum ScreenSerializeTopic { TOPIC_CHANGE_CURSOR = (1<<5), TOPIC_INTERNAL = (1<<6), // debugging internal state TOPIC_BELL = (1<<7), // beep + TOPIC_FLAG_NOCLEAN = (1<<15), // do not clean dirty extents + // combos TOPIC_INITIAL = TOPIC_CHANGE_SCREEN_OPTS | @@ -170,8 +172,6 @@ enum ScreenSerializeTopic { TOPIC_CHANGE_CURSOR | TOPIC_CHANGE_TITLE | TOPIC_CHANGE_BUTTONS, - - TOPIC_WRITE = TOPIC_CHANGE_CURSOR | TOPIC_CHANGE_CONTENT_PART, }; typedef u16 ScreenNotifyTopics; @@ -353,4 +353,7 @@ void screen_report_sgr(char *buffer); */ extern void screen_notifyChange(ScreenNotifyTopics topics); +#define seri_dbg(...) +#define seri_warn(...) warn(__VA_ARGS__) + #endif // SCREEN_H