diff --git a/CMakeLists.txt b/CMakeLists.txt index ddd05cf..cd28108 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,7 +141,7 @@ set(SOURCE_FILES user/apars_osc.c user/apars_osc.h user/apars_dcs.c - user/apars_dcs.h user/uart_buffer.c user/uart_buffer.h) + user/apars_dcs.h user/uart_buffer.c user/uart_buffer.h user/jstring.c user/jstring.h) include_directories(include) include_directories(user) diff --git a/html_orig/js/app.js b/html_orig/js/app.js index bc27b37..3c64eb3 100644 --- a/html_orig/js/app.js +++ b/html_orig/js/app.js @@ -1564,6 +1564,41 @@ function tr(key) { return _tr[key] || '?'+key+'?'; } w.init = wifiInit; w.startScanning = startScanning; })(window.WiFi = {}); +/** Decode two-byte number */ +function parse2B(s, i) { + return (s.charCodeAt(i++) - 1) + (s.charCodeAt(i) - 1) * 127; +} + +/** Decode three-byte number */ +function parse3B(s, i) { + return (s.charCodeAt(i) - 1) + (s.charCodeAt(i+1) - 1) * 127 + (s.charCodeAt(i+2) - 1) * 127 * 127; +} + +function Chr(n) { + return String.fromCharCode(n); +} + +function encode2B(n) { + var lsb, msb; + lsb = (n % 127); + n = ((n - lsb) / 127); + lsb += 1; + msb = (n + 1); + return Chr(lsb) + Chr(msb); +} + +function encode3B(n) { + var lsb, msb, xsb; + lsb = (n % 127); + n = (n - lsb) / 127; + lsb += 1; + msb = (n % 127); + n = (n - msb) / 127; + msb += 1; + xsb = (n + 1); + return Chr(lsb) + Chr(msb) + Chr(xsb); +} + var Screen = (function () { var W = 0, H = 0; // dimensions var inited = false; @@ -1756,16 +1791,6 @@ var Screen = (function () { inited = true; } - /** Decode two-byte number */ - function parse2B(s, i) { - return (s.charCodeAt(i++) - 1) + (s.charCodeAt(i) - 1) * 127; - } - - /** Decode three-byte number */ - function parse3B(s, i) { - return (s.charCodeAt(i) - 1) + (s.charCodeAt(i+1) - 1) * 127 + (s.charCodeAt(i+2) - 1) * 127 * 127; - } - var SEQ_SET_COLOR_ATTR = 1; var SEQ_REPEAT = 2; var SEQ_SET_COLOR = 3; @@ -2013,7 +2038,22 @@ var Conn = (function() { }; })(); -/** User input */ +/** + * User input + * + * --- Rx messages: --- + * S - screen content (binary encoding of the entire screen with simple compression) + * T - text labels - Title and buttons, \0x01-separated + * B - beep + * . - heartbeat + * + * --- Tx messages --- + * s - string + * b - action button + * p - mb press + * r - mb release + * m - mouse move + */ var Input = (function() { var opts = { np_alt: false, @@ -2022,11 +2062,11 @@ var Input = (function() { }; function sendStrMsg(str) { - Conn.send("STR:"+str); + Conn.send("s"+str); } function sendBtnMsg(n) { - Conn.send("BTN:"+n); + Conn.send("b"+Chr(n)); } function fa(alt, normal) { @@ -2195,26 +2235,27 @@ var Input = (function() { _bindFnKeys(); } }, - onMouseMove: function(x, y) { - var b = (mb1?1:0) | (mb2?2:0) | (mb3?4:0); + onMouseMove: function (x, y) { + var b = mb1 ? 1 : mb2 ? 2 : mb3 ? 3 : 0; var m = packModifiersForMouse(); - Conn.send("MM:"+y+','+x+','+b+','+m); + Conn.send("m" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m)); }, - onMouseDown: function(x, y, b) { - if(b>3) return; + onMouseDown: function (x, y, b) { + if (b > 3 || b < 1) return; var m = packModifiersForMouse(); - Conn.send("MP:"+y+','+x+','+b+','+m); + Conn.send("p" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m)); }, - onMouseUp: function(x, y, b) { - if(b>3) return; + onMouseUp: function (x, y, b) { + if (b > 3 || b < 1) return; var m = packModifiersForMouse(); - Conn.send("MR:"+y+','+x+','+b+','+m); + Conn.send("r" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m)); }, - onMouseWheel: function(x, y, dir) { + onMouseWheel: function (x, y, dir) { // -1 ... btn 4 (away from user) // +1 ... btn 5 (towards user) var m = packModifiersForMouse(); - Conn.send("MP:"+y+','+x+','+(dir<0?4:5)+','+m); + var b = (dir < 0 ? 4 : 5); + Conn.send("p" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m)); }, }; })(); diff --git a/html_orig/jssrc/term.js b/html_orig/jssrc/term.js index 4722c2b..14d1bfa 100644 --- a/html_orig/jssrc/term.js +++ b/html_orig/jssrc/term.js @@ -1,3 +1,38 @@ +/** Decode two-byte number */ +function parse2B(s, i) { + return (s.charCodeAt(i++) - 1) + (s.charCodeAt(i) - 1) * 127; +} + +/** Decode three-byte number */ +function parse3B(s, i) { + return (s.charCodeAt(i) - 1) + (s.charCodeAt(i+1) - 1) * 127 + (s.charCodeAt(i+2) - 1) * 127 * 127; +} + +function Chr(n) { + return String.fromCharCode(n); +} + +function encode2B(n) { + var lsb, msb; + lsb = (n % 127); + n = ((n - lsb) / 127); + lsb += 1; + msb = (n + 1); + return Chr(lsb) + Chr(msb); +} + +function encode3B(n) { + var lsb, msb, xsb; + lsb = (n % 127); + n = (n - lsb) / 127; + lsb += 1; + msb = (n % 127); + n = (n - msb) / 127; + msb += 1; + xsb = (n + 1); + return Chr(lsb) + Chr(msb) + Chr(xsb); +} + var Screen = (function () { var W = 0, H = 0; // dimensions var inited = false; @@ -190,16 +225,6 @@ var Screen = (function () { inited = true; } - /** Decode two-byte number */ - function parse2B(s, i) { - return (s.charCodeAt(i++) - 1) + (s.charCodeAt(i) - 1) * 127; - } - - /** Decode three-byte number */ - function parse3B(s, i) { - return (s.charCodeAt(i) - 1) + (s.charCodeAt(i+1) - 1) * 127 + (s.charCodeAt(i+2) - 1) * 127 * 127; - } - var SEQ_SET_COLOR_ATTR = 1; var SEQ_REPEAT = 2; var SEQ_SET_COLOR = 3; @@ -447,7 +472,22 @@ var Conn = (function() { }; })(); -/** User input */ +/** + * User input + * + * --- Rx messages: --- + * S - screen content (binary encoding of the entire screen with simple compression) + * T - text labels - Title and buttons, \0x01-separated + * B - beep + * . - heartbeat + * + * --- Tx messages --- + * s - string + * b - action button + * p - mb press + * r - mb release + * m - mouse move + */ var Input = (function() { var opts = { np_alt: false, @@ -456,11 +496,11 @@ var Input = (function() { }; function sendStrMsg(str) { - Conn.send("STR:"+str); + Conn.send("s"+str); } function sendBtnMsg(n) { - Conn.send("BTN:"+n); + Conn.send("b"+Chr(n)); } function fa(alt, normal) { @@ -629,26 +669,27 @@ var Input = (function() { _bindFnKeys(); } }, - onMouseMove: function(x, y) { - var b = (mb1?1:0) | (mb2?2:0) | (mb3?4:0); + onMouseMove: function (x, y) { + var b = mb1 ? 1 : mb2 ? 2 : mb3 ? 3 : 0; var m = packModifiersForMouse(); - Conn.send("MM:"+y+','+x+','+b+','+m); + Conn.send("m" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m)); }, - onMouseDown: function(x, y, b) { - if(b>3) return; + onMouseDown: function (x, y, b) { + if (b > 3 || b < 1) return; var m = packModifiersForMouse(); - Conn.send("MP:"+y+','+x+','+b+','+m); + Conn.send("p" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m)); }, - onMouseUp: function(x, y, b) { - if(b>3) return; + onMouseUp: function (x, y, b) { + if (b > 3 || b < 1) return; var m = packModifiersForMouse(); - Conn.send("MR:"+y+','+x+','+b+','+m); + Conn.send("r" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m)); }, - onMouseWheel: function(x, y, dir) { + onMouseWheel: function (x, y, dir) { // -1 ... btn 4 (away from user) // +1 ... btn 5 (towards user) var m = packModifiersForMouse(); - Conn.send("MP:"+y+','+x+','+(dir<0?4:5)+','+m); + var b = (dir < 0 ? 4 : 5); + Conn.send("p" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m)); }, }; })(); diff --git a/user/apars_csi.c b/user/apars_csi.c index c1ce4f5..1ad43c1 100644 --- a/user/apars_csi.c +++ b/user/apars_csi.c @@ -451,16 +451,24 @@ static void ICACHE_FLASH_ATTR do_csi_privattr(CSI_Data *opts) // - discard repeated keypress events between keydown and keyup. ansi_noimpl("Auto-repeat toggle"); } - else if (n == 9 || (n >= 1000 && n <= 1006)) { - // TODO mouse - // 1000 - C11 mouse - Send Mouse X & Y on button press and release. - // 1001 - Hilite mouse tracking + else if (n == 9 || (n >= 1000 && n <= 1006) || n == 1015) { + // 9 - X10 tracking + // 1000 - X11 mouse - Send Mouse X & Y on button press and release. + // 1001 - Hilite mouse tracking - not impl // 1002 - Cell Motion Mouse Tracking // 1003 - All Motion Mouse Tracking // 1004 - Send FocusIn/FocusOut events - // 1005 - Enable UTF-8 Mouse Mode + // 1005 - Enable UTF-8 Mouse Mode - we implement this as an alias to X10 mode // 1006 - SGR mouse mode - ansi_noimpl("Mouse tracking"); + + if (n == 9) mouse_tracking.mode = yn ? MTM_X10 : MTM_NONE; + else if (n == 1000) mouse_tracking.mode = yn ? MTM_NORMAL : MTM_NONE; + else if (n == 1002) mouse_tracking.mode = yn ? MTM_BUTTON_MOTION : MTM_NONE; + else if (n == 1003) mouse_tracking.mode = yn ? MTM_ANY_MOTION : MTM_NONE; + else if (n == 1004) mouse_tracking.focus_tracking = yn; + else if (n == 1005) mouse_tracking.encoding = yn ? MTE_UTF8 : MTE_SIMPLE; + else if (n == 1006) mouse_tracking.encoding = yn ? MTE_SGR : MTE_SIMPLE; + else if (n == 1015) mouse_tracking.encoding = yn ? MTE_URXVT : MTE_SIMPLE; } else if (n == 12) { // TODO Cursor blink on/off diff --git a/user/apars_osc.c b/user/apars_osc.c index 18f9855..fc2b27b 100644 --- a/user/apars_osc.c +++ b/user/apars_osc.c @@ -15,18 +15,6 @@ #include "screen.h" #include "ansi_parser.h" -/** - * Helper function to set terminal button label - * @param num - button number 1-5 - * @param str - button text - */ -static void ICACHE_FLASH_ATTR -set_button_text(int num, const char *str) -{ - strncpy(termconf_scratch.btn[num-1], str, TERM_BTN_LEN); - screen_notifyChange(CHANGE_LABELS); -} - /** * Helper function to parse incoming OSC (Operating System Control) * @param buffer - the OSC body (after OSC and before ST) @@ -52,7 +40,7 @@ apars_handle_osc(const char *buffer) screen_set_title(buffer); } else if (n >= 81 && n <= 85) { // numbers chosen to not collide with any xterm supported codes - set_button_text(n - 80, buffer); + screen_set_button_text(n - 80, buffer); } else { ansi_noimpl("OSC %d ; %s ST", n, buffer); diff --git a/user/cgi_sockets.c b/user/cgi_sockets.c index c3a164a..ded74f7 100644 --- a/user/cgi_sockets.c +++ b/user/cgi_sockets.c @@ -7,6 +7,7 @@ #include "screen.h" #include "uart_buffer.h" #include "ansi_parser.h" +#include "jstring.h" #define LOOPBACK 0 @@ -118,75 +119,123 @@ void ICACHE_FLASH_ATTR screen_notifyChange(ScreenNotifyChangeTopic topic) } } +void ICACHE_FLASH_ATTR sendMouseAction(char evt, int y, int x, int button, u8 mods) +{ + bool ctrl = (mods & 1) > 0; + bool shift = (mods & 2) > 0; + bool alt = (mods & 4) > 0; + bool meta = (mods & 8) > 0; + enum MTM mtm = mouse_tracking.mode; + enum MTE mte = mouse_tracking.encoding; + + // No message on release in X10 mode + if (mtm == MTM_X10 && button == 0) { + return; + } + + if (evt == 'm' && mtm != MTM_BUTTON_MOTION && mtm != MTM_ANY_MOTION) { + return; + } + + if (evt == 'm' && mtm == MTM_BUTTON_MOTION && button == 0) { + return; + } + + int eventcode = 0; + + if (mtm == MTM_X10) { + eventcode = button; + } + else { + if (button == 0 || (evt == 'r' && mte != MTE_SGR)) eventcode = 3; // release + else if (button == 1) eventcode = 0; + else if (button == 2) eventcode = 1; + else if (button == 3) eventcode = 2; + else if (button == 4) eventcode = 64; + else if (button == 5) eventcode = 65; + + if (shift) eventcode |= 4; + if (alt || meta) eventcode |= 8; + if (ctrl) eventcode |= 16; + + if (mtm == MTM_BUTTON_MOTION || mtm == MTM_ANY_MOTION) { + if (evt == 'm') { + eventcode |= 32; + } + } + } + + // Encode + char buf[20]; + buf[0] = 0; + if (mte == MTE_SIMPLE || mte == MTE_UTF8) { + // strictly, for UTF8 this will break if any coord is over 127, + // but that is unlikely due to screen size limitations in ESPTerm + sprintf(buf, "\x1b[M%c%c%c", (u8)(32+eventcode), (u8)(32+x), (u8)(32+y)); + } + else if (mte == MTE_SGR) { + sprintf(buf, "\x1b[%d;%d;%d%c", eventcode, x, y, evt == 'p' ? 'M' : 'm'); + } + else if (mte == MTE_URXVT) { + sprintf(buf, "\x1b[%d;%d;%dM", (u8)(32+eventcode), (u8)(32+x), (u8)(32+y)); + } + + UART_SendAsync(buf, -1); +} + /** Socket received a message */ void ICACHE_FLASH_ATTR updateSockRx(Websock *ws, char *data, int len, int flags) { - char buf[20]; // Add terminator if missing (seems to randomly happen) data[len] = 0; - // TODO re-implement those, use single byte markers and B2 encoding - ws_dbg("Sock RX str: %s, len %d", data, len); - if (strstarts(data, "STR:")) { + int y, x, m, b; + u8 btnNum; + + char c = data[0]; + switch (c) { + case 's': // pass string verbatim #if LOOPBACK for(int i=4;i 0 && btnNum < 10) { - UART_SendAsync((const char *) &btnNum, 1); - } - } - else if (strstarts(data, "TAP:")) { - // this comes in as 0-based - - int y=0, x=0; - - char *pc=data+4; - char c; - int phase=0; - - while((c=*pc++) != '\0') { - if (c==','||c==';') { - phase++; - } - else if (c>='0' && c<='9') { - if (phase==0) { - y=y*10+(c-'0'); - } else { - x=x*10+(c-'0'); - } + break; + case 'b': + // action button press + btnNum = (u8) (data[1]); + if (btnNum > 0 && btnNum < 10) { + UART_SendAsync((const char *) &btnNum, 1); // TODO this is where we use user-configured codes } - } - - if (!screen_isCoordValid(y, x)) { - ws_warn("Mouse input at invalid coordinates"); - return; - } - - ws_dbg("Screen clicked at row %d, col %d", y+1, x+1); - - // Send as 1-based to user - sprintf(buf, "\033[%d;%dM", y+1, x+1); - UART_SendAsync(buf, -1); - } - else { - ws_warn("Bad command."); + break; + case 'm': + case 'p': + case 'r': + if (mouse_tracking.mode == MTM_NONE) break; // no need to parse, not enabled + + // mouse move + y = parse2B(data+1); // row, 0-based + x = parse2B(data+3); // column, 0-based + b = parse2B(data+5); // mouse button, 0 = none, 1-5 = button number + m = parse2B(data+7); // modifier keys held + + sendMouseAction(c,y,x,b,m); + break; + default: + ws_warn("Bad command."); } } void ICACHE_FLASH_ATTR heartbeatTimCb(void *unused) { 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); } } diff --git a/user/jstring.c b/user/jstring.c new file mode 100644 index 0000000..f3057dc --- /dev/null +++ b/user/jstring.c @@ -0,0 +1,41 @@ +// +// Created by MightyPork on 2017/09/04. +// + +#include "jstring.h" + +void ICACHE_FLASH_ATTR +encode2B(u16 number, WordB2 *stru) +{ + stru->lsb = (u8) (number % 127); + number = (u16) ((number - stru->lsb) / 127); + stru->lsb += 1; + + stru->msb = (u8) (number + 1); +} + +void ICACHE_FLASH_ATTR +encode3B(u32 number, WordB3 *stru) +{ + stru->lsb = (u8) (number % 127); + number = (number - stru->lsb) / 127; + stru->lsb += 1; + + stru->msb = (u8) (number % 127); + number = (number - stru->msb) / 127; + stru->msb += 1; + + stru->xsb = (u8) (number + 1); +} + +u16 ICACHE_FLASH_ATTR +parse2B(const char *str) +{ + return (u16) ((str[0] - 1) + (str[1] - 1) * 127); +} + +u32 ICACHE_FLASH_ATTR +parse3B(const char *str) +{ + return (u32) ((str[0] - 1) + (str[1] - 1) * 127 + (str[2] - 1) * 127 * 127); +} diff --git a/user/jstring.h b/user/jstring.h new file mode 100644 index 0000000..8d99286 --- /dev/null +++ b/user/jstring.h @@ -0,0 +1,29 @@ +// +// Created by MightyPork on 2017/09/04. +// + +#ifndef ESPTERM_JSTRING_H +#define ESPTERM_JSTRING_H + +#include + +typedef struct { + u8 lsb; + u8 msb; +} WordB2; + +typedef struct { + u8 lsb; + u8 msb; + u8 xsb; +} WordB3; + +void encode2B(u16 number, WordB2 *stru); + +void encode3B(u32 number, WordB3 *stru); + +u16 parse2B(const char *str); + +u32 parse3B(const char *str); + +#endif //ESPTERM_JSTRING_H diff --git a/user/screen.c b/user/screen.c index 5c29a53..7ea042f 100644 --- a/user/screen.c +++ b/user/screen.c @@ -5,10 +5,13 @@ #include "sgr.h" #include "ascii.h" #include "apars_logging.h" +#include "jstring.h" TerminalConfigBundle * const termconf = &persist.current.termconf; TerminalConfigBundle termconf_scratch; +MouseTrackingConfig mouse_tracking; + // forward declare static void utf8_remap(char* out, char g, char charset); @@ -255,6 +258,10 @@ screen_reset(void) scr.vm0 = 0; scr.vm1 = H-1; + mouse_tracking.encoding = MTE_SIMPLE; + mouse_tracking.focus_tracking = false; + mouse_tracking.mode = MTM_NONE; + // size is left unchanged screen_clear(CLEAR_ALL); @@ -627,6 +634,18 @@ screen_set_title(const char *title) screen_notifyChange(CHANGE_LABELS); } +/** + * Helper function to set terminal button label + * @param num - button number 1-5 + * @param str - button text + */ +void ICACHE_FLASH_ATTR +screen_set_button_text(int num, const char *text) +{ + strncpy(termconf_scratch.btn[num-1], text, TERM_BTN_LEN); + screen_notifyChange(CHANGE_LABELS); +} + /** * Shift screen upwards */ @@ -1307,30 +1326,6 @@ struct ScreenSerializeState { int index; }; -void ICACHE_FLASH_ATTR -encode2B(u16 number, WordB2 *stru) -{ - stru->lsb = (u8) (number % 127); - number = (u16) ((number - stru->lsb) / 127); - stru->lsb += 1; - - stru->msb = (u8) (number + 1); -} - -void ICACHE_FLASH_ATTR -encode3B(u32 number, WordB3 *stru) -{ - stru->lsb = (u8) (number % 127); - number = (number - stru->lsb) / 127; - stru->lsb += 1; - - stru->msb = (u8) (number % 127); - number = (number - stru->msb) / 127; - stru->msb += 1; - - stru->xsb = (u8) (number + 1); -} - /** * buffer should be at least 64+5*10+6 long (title + buttons + 6), ie. 120 * @param buffer diff --git a/user/screen.h b/user/screen.h index 66b97fe..7c23c94 100644 --- a/user/screen.h +++ b/user/screen.h @@ -80,6 +80,29 @@ extern TerminalConfigBundle * const termconf; */ extern TerminalConfigBundle termconf_scratch; +enum MTM { + MTM_NONE, + MTM_X10, + MTM_NORMAL, + MTM_BUTTON_MOTION, + MTM_ANY_MOTION, +}; + +enum MTE { + MTE_SIMPLE, + MTE_UTF8, + MTE_SGR, + MTE_URXVT, +}; + +typedef struct { + enum MTM mode; + bool focus_tracking; + enum MTE encoding; +} MouseTrackingConfig; + +extern MouseTrackingConfig mouse_tracking; + /** Restore default settings to termconf. Does not apply or copy to scratch. */ void terminal_restore_defaults(void); /** Apply settings, redraw (clears the screen) */ @@ -97,17 +120,6 @@ void screen_set_button_text(int num, const char *text); // --- Encoding --- -typedef struct { - u8 lsb; - u8 msb; -} WordB2; - -typedef struct { - u8 lsb; - u8 msb; - u8 xsb; -} WordB3; - typedef enum { CS_USASCII = 'B', CS_UKASCII = 'A', @@ -115,9 +127,6 @@ typedef enum { CS_DOS_437 = '1', } CHARSET; -/** Encode number to two nice ASCII bytes */ -void encode2B(u16 number, WordB2 *stru); - httpd_cgi_state screenSerializeToBuffer(char *buffer, size_t buf_len, void **data); void screenSerializeLabelsToBuffer(char *buffer, size_t buf_len);