Compare commits

...
22 Commits
Author SHA1 Message Date
MightyPork 8028f14387 version bump 2017-09-05 02:14:55 +02:00
MightyPork daba5340f7 fixes and tweaks. implemented polling reload on HB loss 2017-09-05 02:13:16 +02:00
MightyPork a82e14961c RAM tuning 2017-09-05 01:48:37 +02:00
MightyPork 6c9ae7e126 remove debug log 2017-09-04 22:54:27 +02:00
MightyPork a3c84f9a8f Merge branch 'better-mouse' 2017-09-04 22:53:38 +02:00
MightyPork 7aa3cd7f75 working mouse 2017-09-04 22:53:29 +02:00
MightyPork 58ed6098c4 untested impl of mouse tracking modes 2017-09-04 22:03:08 +02:00
MightyPork 16a36a3d26 cleaning 2017-09-04 01:49:17 +02:00
MightyPork 9d1cefeaab done client-side mouse reporting 2017-09-04 01:46:00 +02:00
MightyPork 357600de8f expanded Help section and grouped some things; added screen resize command 2017-09-04 01:01:16 +02:00
MightyPork fe699c7918 Temporarily fixed #71 by fixed cell width, rel 7.0 preparations 2017-09-03 23:06:55 +02:00
MightyPork 4edaa0687d Implemented heartbeat 2017-09-03 22:37:54 +02:00
MightyPork f8ebae044a Fixed #63 2017-09-03 22:25:02 +02:00
MightyPork 9e9539472c renamed the project in the cmakelists clion file 2017-09-03 22:05:48 +02:00
MightyPork b57e0ee75b change GitHub URL 2017-09-03 22:02:02 +02:00
MightyPork 65e34a1634 Updated help page 2017-09-03 21:54:37 +02:00
MightyPork 6f32a6ef1a fix messed up insert/delete with region 2017-09-02 01:04:30 +02:00
MightyPork d3468a109c fix bug with hanging flag left set after GoTo col 80 2017-09-02 00:40:08 +02:00
MightyPorkandGitHub 8f82b81434 Update README.md 2017-08-25 15:19:26 +02:00
MightyPorkandGitHub 653dc55d85 Update README.md 2017-08-25 15:18:55 +02:00
MightyPork dda469ff21 Updated the xterm comparison page 2017-08-25 15:07:16 +02:00
MightyPork 3515e178cb Updated readme 2017-08-25 15:06:54 +02:00
30 changed files with 1431 additions and 643 deletions
+4 -3
View File
@@ -5,7 +5,7 @@ cmake_minimum_required(VERSION 3.7)
# Do not attempt to build it with cmake, use make instead. # # Do not attempt to build it with cmake, use make instead. #
################################################################### ###################################################################
project(esp_vt100_firmware) project(ESPTerm)
set(CMAKE_CXX_STANDARD GNU99) set(CMAKE_CXX_STANDARD GNU99)
@@ -141,7 +141,7 @@ set(SOURCE_FILES
user/apars_osc.c user/apars_osc.c
user/apars_osc.h user/apars_osc.h
user/apars_dcs.c 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(include)
include_directories(user) include_directories(user)
@@ -163,6 +163,7 @@ add_definitions(
-DADMIN_PASSWORD="asdf" -DADMIN_PASSWORD="asdf"
-DGIT_HASH="blabla" -DGIT_HASH="blabla"
-DDEBUG_HEAP=1 -DDEBUG_HEAP=1
-DDEBUG_MALLOC=1
-DESPFS_HEATSHRINK) -DESPFS_HEATSHRINK)
add_executable(esp_vt100_firmware ${SOURCE_FILES}) add_executable(ESPTerm ${SOURCE_FILES})
+69 -43
View File
@@ -1,76 +1,100 @@
# ESPTerm # ESPTerm
ESP8266 Wireless Terminal Emulator project ESPTerm is a terminal emulator running on the ESP8266 WiFi chip.
The firmware emulates VT102 with some additional features based on `xterm` and later VT models, and the terminal screen can be accessed using any web browser, even on a phone or tablet. It works with ESP-01, ESP-01S, ESP-12 and likely many other modules (I use ESP-12 on a NodeMCU board for development).
This project is based on SpriteTM's esphttpd and libesphttpd, forked by MightyPork to ESPTerm can add remote access via WiFi to any embeded project, all you need is a serial port.
[MightyPork/esphttpd](https://github.com/MightyPork/esphttpd) and
[MightyPork/libesphttpd](https://github.com/MightyPork/libesphttpd) respectively.
Those forks include improvements not available upstream. ## Screenshots, photos
## Goals - Look at the [GitHub releases page][releases], there are some pics.
The project aims to be a wireless terminal emulator that'll work with the likes of ## ESPTerm features
Arduino, AVR, PIC, STM8, STM32, mbed etc, anything with UART, even your USB-serial dongle will work.
Connect it to the master device via UART and use the terminal on the built-in web page for debug logging, - **Robust WiFi configuration interface**
remote control etc. It works like a simple LCD screen, in a way. - static IP
- DHCP
- AP channel and strength setting
- AP password
- SSID search for finding your existing network
- **Almost complete VT102 emulation** with some extras
- *Sufficient to run most Linux console applications, including ncurses-based ones\**
- All standard SGR (text style attributes) supported
- Full UTF-8 support
- Alternate character sets support
- 16 colors
- Scrolling Region, Origin Mode
- Tab Stops
- Cursor save/restore
- Character/Line insert, delete, clear operations
- Audible BEL (ASCII 7 beep)
- Most standard queries (get cursor position, get SGR, get screen size...)
- All DEC private options either implemented or safely consumed by the parser
- **Other features:**
- Screen size up to 80x25
- Real-time screen update via WebSocket
- Button to open Android software keyboard
- 5 buttons under the screen for quick commands
- Button labels can be changed (by OSC commands or via settings)
- Screen title can be changed (by OSC commands or via settings)
- Built-in help page with basic troubleshooting and command reference
It lets you make simple UI (manipulating the screen with ANSI sequences) and receive input from buttons on \*) Linux console applications run via agetty at ttyUSB0 were used for testing, obviously you'll be better off using SSH for remote shell
the webpage (and keyboard on PC). There is some rudimentary touch input as well.
The screen size is adjustable up to 25x80 (via a special control sequence) and uses 16 standard colors ESPTerm firmware is written in C and is based on SpriteTM's `libesphttpd` http server library, forked by MightyPork to
(8 dark and 8 bright). Both default size and colors can be configured in the settings. [MightyPork/libesphttpd](https://github.com/MightyPork/libesphttpd) respectively. This fork includes various improvements and changes required by the project.
## Project status ## Running ESPTerm
*A little buggy, but mostly okay! There are many features and fixes planned, but it should be fairly usable already.* To run ESPTerm on your ESP8266, either build it yourself from source using `xtensa-lx106-elf-gcc` (and the included Makefile), or download pre-built binaries from the [GitHub releases section][releases]. Flash the binaries using [esptool](https://github.com/espressif/esptool).
- We have a working **2-way terminal** (UART->ESP->Browser and vice versa) with real-time update via websocket. ### Pins
This means that what you type in the browser is sent to UART0 and what's received on UART0 is processed by the
ANSI parser and applied to the internal screen buffer. You'll also immediately see the changes in your browser.
There's a filter in the way that discards garbage characters (like unicode and most ASCII outside 32-126).
For a quick test, try connecting the UART0 Rx and Tx with a piece of wire to make a loopback interface.
*NOTE: Use the bare module, not something like LoLin or NodeMCU with a FTDI, it'll interfere*.
You should then directly see what you type & can even try some ANSI sequences, right from the browser.
- All ANSI sequences that make sense, as well as control codes like Backspace and CR / LF are implemented.
Set colors with your usual `\e[31;1m` etc (see Wikipedia). `\e` is the ASCII code 27 (ESC).
Arrow keys generate ANSI sequences, ESC sends literal ASCII code 27 etc. Almost everything can be input
straight from the browser.
- Buttons pressed in the browser UI send ASCII codes 1..5. Mouse clicks are sent as `\e[<row>;<col>M`. - Pin GPIO2 is used for debug messages at 115200 baud, 8 bit, no parity.
- Pins Rx and Tx are used for the main communication UART. Connect your USB-serial dongle or application microcontroller here.
- There's a built-in WiFi config page and a Help page with a list of all supported ANSI sequences and other details. ### Console commands - escape sequences
- A list of most supported commands can be found here: http://bits.ondrovo.com/espterm-xterm.html
- To set the screen title, use `OSC 0 ; title ST` (`OSC` = `ESC ]`, `ST` = `ESC \` or ASCII 7)
- To set buttons text, use `OSC 8 1 ; text ST` with 81 through 85.
- Mouse clicks (as of v0.6.9) generate `CSI row ; col M` at the Tx pin (`CSI` = `ESC [`)
- For more info, look eg. on Wikipedia or here: http://ascii-table.com/ansi-escape-sequences-vt-100.php
### Setup
- When flashed for the first time, ESPTerm wipes any possible previous WiFi configuration, because it implements its own WiFi config manager with many additional features.
- It should start in AP mode, the default SSID being `TERM-MACADR` with `MACADR` being three unique bytes from the MAC address / Device ID.
- Connect to it via a smartphone or laptop and configure WiFi as desired.
- To re-enable the built-in AP, hold the BOOT (GPIO0) button for about 1 s, until the blue LED starts slowly flashing. Then release the button!
- To reset all settings to defaults, hold the BOOT (GPIO0) button until the blue LED flashes rapidly, then release the button.
- When you accidentally make the blue LED flash, you can cancel the operation by pressing Reset or disconnecting its power supply. The wipe is done on the button's release.
### Config files
ESPTerm has two config "files", one for defaults and one for the currently used settings. In the case of the terminal config, there is also a third, temporary file for changes done via ESC commands.
When you get your settings *just right*, you can store them as defaults, which can then be at any time restored by holding the BOOT (GPIO0) button. You can do this on the System Settings page. This asks for an "admin password", which you can define when building the firmware in the `esphttpdconfig.mk` file. The default password is `19738426`. This password can't presently be changed without re-flashing the firmware.
You can also restore everything (except the saved defaults) to "factory defaults", there is a button for this on the System Settings page. Those are the initial values in the config files.
## Development ## Development
### Installation for development ### Installation for development
- Clone this project with `--recursive`, or afterwards run `git submodule init` and `git submodule update`. - Clone this project with `--recursive`, or afterwards run `git submodule init` and `git submodule update`.
- Install [esp-open-sdk](https://github.com/pfalcon/esp-open-sdk/) and build it with - Install [esp-open-sdk](https://github.com/pfalcon/esp-open-sdk/) and build it with
`make toolchain esptool libhal STANDALONE=n`. `make toolchain esptool libhal STANDALONE=n`.
Make sure the `xtensa-lx106-elf/bin` folder is on $PATH. Make sure the `xtensa-lx106-elf/bin` folder is on $PATH.
- Install [esptool](https://github.com/espressif/esptool) (it's in the Arch community repo and on AUR, too) - Install [esptool](https://github.com/espressif/esptool) (it's in the Arch community repo and on AUR, too)
- Set up udev rules so you have access to ttyUSB0 without root, eg: - Set up udev rules so you have access to ttyUSB0 without root, eg:
``` ```
KERNEL=="tty[A-Z]*[0-9]*", GROUP="uucp", MODE="0666" KERNEL=="tty[A-Z]*[0-9]*", GROUP="uucp", MODE="0666"
``` ```
- Install Ragel if you wish to make modifications to the ANSI sequence parser. - Install Ragel if you wish to make modifications to the ANSI sequence parser.
If not, comment out its call in `build_parser.sh`. The `.rl` file is the actual source, the `.c` is generated. If not, comment out its call in `build_parser.sh`. The `.rl` file is the actual source, the `.c` is generated.
- Install Ruby and then the `sass` package with `gem install sass` (or try some other implementation, such as - Install Ruby and then the `sass` package with `gem install sass` (or try some other implementation, such as
`sassc`) `sassc`)
- Make sure your `esphttpdconfig.mk` is set up properly - link to the SDK etc. - Make sure your `esphttpdconfig.mk` is set up properly - link to the SDK etc.
The IoT SDK is now included in the project due to problems with obtaining the correct version and patching it. The IoT SDK is now included in the project due to problems with obtaining the correct version and patching it.
@@ -80,7 +104,7 @@ than welcome!
### Web resources ### Web resources
The web resources are in `html_orig`. To prepare for a build, run `build_web.sh`, which packs them and The web resources are in `html_orig`. To prepare for a build, run `build_web.sh`, which packs them and
copies over to `html`. The compression and minification is handled by scripts in libesphttpd, specifically copies over to `html`. The compression and minification is handled by scripts in libesphttpd, specifically,
it runs yuicompressor on js and css and gzip or heatshrink on the other files. The `html` folder is it runs yuicompressor on js and css and gzip or heatshrink on the other files. The `html` folder is
then embedded in the firmware image. then embedded in the firmware image.
@@ -97,3 +121,5 @@ Sometimes it does not, particularly with `make -B`. Try just plain `make`. You c
build scripts manually, too. build scripts manually, too.
To flash, just run `make flash`. To flash, just run `make flash`.
[releases]: https://github.com/MightyPork/esp-vt100-firmware/releases
+7 -4
View File
@@ -671,10 +671,11 @@ CSI ? <i>Pm</i> h
<i>Ps</i> = 1 -&gt; Application Cursor Keys (DECCKM). <i>Ps</i> = 1 -&gt; Application Cursor Keys (DECCKM).
<i>Ps</i> = 2 -&gt; Designate USASCII for character sets G0-G3 <i>Ps</i> = 2 -&gt; Designate USASCII for character sets G0-G3
(DECANM), <del>and set VT100 mode.</del> (DECANM), <del>and set VT100 mode.</del>
<del><i>Ps</i> = 3 -&gt; 132 Column Mode (DECCOLM).</del> <i>Ps</i> = 3 -&gt; 132 Column Mode (DECCOLM). <ins>ESPTerm doesn't have enough RAM for 132x25,
but it implements the side effects of clearing the screen and resetting the scolling region.</ins>
<del><i>Ps</i> = 4 -&gt; Smooth (Slow) Scroll (DECSCLM).</del> <del><i>Ps</i> = 4 -&gt; Smooth (Slow) Scroll (DECSCLM).</del>
<i>Ps</i> = 5 -&gt; Reverse Video (DECSCNM). <i>Ps</i> = 5 -&gt; Reverse Video (DECSCNM).
<i>Ps</i> = 6 -&gt; Origin Mode (DECOM). <ins><b>TODO implement</b></ins> <i>Ps</i> = 6 -&gt; Origin Mode (DECOM).
<i>Ps</i> = 7 -&gt; Wraparound Mode (DECAWM). <i>Ps</i> = 7 -&gt; Wraparound Mode (DECAWM).
<del><i>Ps</i> = 8 -&gt; Auto-repeat Keys (DECARM).</del> <del><i>Ps</i> = 8 -&gt; Auto-repeat Keys (DECARM).</del>
<del><i>Ps</i> = 9 -&gt; Send Mouse X &amp; Y on button press. See the sec- <del><i>Ps</i> = 9 -&gt; Send Mouse X &amp; Y on button press. See the sec-
@@ -1078,7 +1079,7 @@ CSI ! p Soft terminal reset (DECSTR).
<i>Ps</i> = 2 -&gt; DECSED and DECSEL can erase.</del> <i>Ps</i> = 2 -&gt; DECSED and DECSEL can erase.</del>
CSI <i>Ps</i> ; <i>Ps</i> r CSI <i>Ps</i> ; <i>Ps</i> r
Set Scrolling Region [top;bottom] (default = full size of win- Set Scrolling Region [top;bottom] (default = full size of win-
dow) (DECSTBM). <ins><b>TODO</b> implement</ins> dow) (DECSTBM).
CSI ? <i>Pm</i> r CSI ? <i>Pm</i> r
Restore DEC Private Mode Values. The value of <i>Ps</i> previously Restore DEC Private Mode Values. The value of <i>Ps</i> previously
saved is restored. <i>Ps</i> values are the same as for DECSET. <ins><b>TODO</b> implement</ins> saved is restored. <i>Ps</i> values are the same as for DECSET. <ins><b>TODO</b> implement</ins>
@@ -1304,10 +1305,12 @@ OSC <i>Ps</i> ; <i>Pt</i> ST
and window title. and window title.
<i>Ps</i> = 0 -&gt; Change <del>Icon Name and</del> Window Title to <i>Pt</i>. <i>Ps</i> = 0 -&gt; Change <del>Icon Name and</del> Window Title to <i>Pt</i>.
<del><i>Ps</i> = 1 -&gt; Change Icon Name to <i>Pt</i>.</del> <del><i>Ps</i> = 1 -&gt; Change Icon Name to <i>Pt</i>.</del>
<i>Ps</i> = 2 -&gt; Change Window Title to <i>Pt</i>. <i>Ps</i> = 2 -&gt; Change Window Title to <i>Pt</i>. <ins>(same as 0)</ins>
<del><i>Ps</i> = 3 -&gt; Set X property on top-level window. <i>Pt</i> should be <del><i>Ps</i> = 3 -&gt; Set X property on top-level window. <i>Pt</i> should be
in the form "<i>prop=value</i>", or just "<i>prop</i>" to delete the prop- in the form "<i>prop=value</i>", or just "<i>prop</i>" to delete the prop-
erty</del> erty</del>
<ins><i>Ps</i> = 81-85 -&gt; Change text of button 1 through 5. Empty string
makes the button appear disabled (grayed out).</ins>
<del><i>Ps</i> = 4 ; <i>c</i>; <i>spec</i> -&gt; Change Color Number <i>c</i> to the color spec- <del><i>Ps</i> = 4 ; <i>c</i>; <i>spec</i> -&gt; Change Color Number <i>c</i> to the color spec-
ified by <i>spec</i>. This can be a name or RGB specification as per ified by <i>spec</i>. This can be a name or RGB specification as per
<i>XParseColor</i>. Any number of <i>c</i>/<i>spec</i> pairs may be given. The <i>XParseColor</i>. Any number of <i>c</i>/<i>spec</i> pairs may be given. The
+4 -1
View File
@@ -50,5 +50,8 @@ GLOBAL_CFLAGS = \
-DDEBUG_ANSI=1 \ -DDEBUG_ANSI=1 \
-DDEBUG_ANSI_NOIMPL=1 \ -DDEBUG_ANSI_NOIMPL=1 \
-DHTTPD_MAX_BACKLOG_SIZE=8192 \ -DHTTPD_MAX_BACKLOG_SIZE=8192 \
-DHTTPD_MAX_HEAD_LEN=1024 \
-DHTTPD_MAX_POST_LEN=512 \
-DDEBUG_INPUT=0 \ -DDEBUG_INPUT=0 \
-DDEBUG_HEAP=1 -DDEBUG_HEAP=1 \
-DDEBUG_MALLOC=0
+3 -1
View File
@@ -14,6 +14,8 @@ function process_html($s) {
return $s; return $s;
} }
$no_tpl_files = ['help', 'cfg_wifi_conn'];
ob_start(); ob_start();
foreach($_pages as $_k => $p) { foreach($_pages as $_k => $p) {
if ($p->bodyclass == 'api') continue; if ($p->bodyclass == 'api') continue;
@@ -30,7 +32,7 @@ foreach($_pages as $_k => $p) {
// $s = process_html($s); // $s = process_html($s);
ob_clean(); // clean up ob_clean(); // clean up
$of = __DIR__ . '/../html/' . $_k . '.tpl'; $of = __DIR__ . '/../html/' . $_k . (in_array($_k, $no_tpl_files) ? '.html' : '.tpl');
file_put_contents($of, $s); // write to a file file_put_contents($of, $s); // write to a file
} }
+60 -17
View File
@@ -416,8 +416,12 @@ a:hover {
cursor: pointer; } cursor: pointer; }
ul > * { ul > * {
padding-top: .2em; padding-top: .3em;
padding-bottom: .2em; } padding-bottom: .3em; }
ul {
margin-top: 0;
margin-bottom: 0; }
/* Main outer container */ /* Main outer container */
#outer { #outer {
@@ -629,7 +633,36 @@ ul > * {
.Box.mobopen .Row.explain { .Box.mobopen .Row.explain {
margin-top: 18px; } } margin-top: 18px; } }
.Box.fold h2 {
position: relative;
cursor: pointer;
padding: 2px 1.3rem 2px 5px;
margin: 0 -5px 0 -5px; }
.Box.fold h2::after {
position: absolute;
right: 4px;
content: '▸';
top: 50%;
font-size: 120%;
font-weight: bold;
transform: translate(0, -50%) rotate(90deg); }
.Box.fold.expanded h2::after {
transform: translate(-25%, -50%) rotate(-90deg);
margin-bottom: 1rem; }
.Box.fold .Row {
display: none; }
.Box.fold.expanded .Row {
display: flex; }
.Box.fold.expanded .Row.v {
display: block; }
@media screen and (max-width: 544px) { @media screen and (max-width: 544px) {
.Box.fold h2, .Box.mobcol h2 {
padding: 2px 1.3rem 2px 5px;
margin: 0 -5px 0 -5px; }
.Box.fold.expanded h2::after, .Box.mobcol.expanded h2::after {
margin-bottom: 1rem; }
.Box.mobcol h2 { .Box.mobcol h2 {
position: relative; position: relative;
cursor: pointer; cursor: pointer;
@@ -648,10 +681,12 @@ ul > * {
margin-bottom: 1rem; } margin-bottom: 1rem; }
.Box.mobcol .Row { .Box.mobcol .Row {
display: none; } display: none; }
.Box.mobcol #ap-box {
display: none; }
.Box.mobcol.expanded .Row { .Box.mobcol.expanded .Row {
display: flex; } display: flex; }
.Box.mobcol.expanded .Row.v {
display: block; }
.Box.mobcol #ap-box {
display: none; }
.Box.mobcol.expanded #ap-box { .Box.mobcol.expanded #ap-box {
display: block; } } display: block; } }
.Modal { .Modal {
@@ -875,6 +910,16 @@ form {
margin-top: 0; } margin-top: 0; }
.Row:last-child { .Row:last-child {
margin-bottom: 0; } margin-bottom: 0; }
.Row.v {
display: block; }
.Row .aside {
float: right;
margin-left: 5px;
margin-bottom: 5px; }
@media screen and (max-width: 544px) {
.Row .aside {
margin: 0;
float: none; } }
.Row .spacer { .Row .spacer {
width: 160px; } width: 160px; }
@media screen and (max-width: 544px) { @media screen and (max-width: 544px) {
@@ -1107,7 +1152,7 @@ body.term #screen {
-ms-user-select: none; -ms-user-select: none;
user-select: none; user-select: none;
font-family: monospace; font-family: monospace;
font-size: 16pt; font-size: 20px;
white-space: nowrap; white-space: nowrap;
background: #111213; background: #111213;
padding: 6px; padding: 6px;
@@ -1115,12 +1160,11 @@ body.term #screen {
border: 2px solid #3983CD; } border: 2px solid #3983CD; }
body.term #screen span { body.term #screen span {
white-space: pre; white-space: pre;
cursor: pointer; } cursor: pointer;
body.term #screen span:hover { display: inline-block;
outline: 1px solid rgba(255, 255, 255, 0.4); } line-height: 1.15em;
@media screen and (max-width: 544px) { width: 0.6em;
body.term #screen span:hover { overflow: visible; }
outline: 0 none; } }
body.term #buttons { body.term #buttons {
margin-top: 10px; margin-top: 10px;
white-space: nowrap; } white-space: nowrap; }
@@ -1605,20 +1649,19 @@ body.term #botnav {
padding: 0.38198rem 0; padding: 0.38198rem 0;
text-align: center; } text-align: center; }
.ansiref, .ansiref td, .ansiref th { .Row table, .Row table td, .Row table th {
border: 1px solid #666; } border: 1px solid #666; }
.ansiref th, .ansiref td { .Row table th, .Row table td {
white-space: normal; } white-space: normal; }
.ansiref th { .Row table th {
background-color: rgba(255, 255, 255, 0.1); } background-color: rgba(255, 255, 255, 0.1); }
.ansiref td:nth-child(1) { .ansiref td:nth-child(1) {
font-family: monospace; } font-family: monospace; }
.ansiref.w100 { .ansiref.w100 {
width: 100%; } width: 100%; }
.ansiref.w100 td:nth-child(1) { .ansiref.w100 td:nth-child(1) {
width: 9em; } width: 6em; }
.ansiref.w100 td:nth-child(2) {
width: 8em; }
@media screen and (min-width: 545px) { @media screen and (min-width: 545px) {
.mq-phone { .mq-phone {
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

+178 -29
View File
@@ -770,6 +770,14 @@
for(k in _mods) _mods[k] = event[modifierMap[k]]; for(k in _mods) _mods[k] = event[modifierMap[k]];
}; };
function isModifierPressed(mod) {
if (mod=='control'||mod=='ctrl') return _mods[17];
if (mod=='shift') return _mods[16];
if (mod=='meta') return _mods[91];
if (mod=='alt') return _mods[18];
return false;
}
// handle keydown event // handle keydown event
function dispatch(event) { function dispatch(event) {
var key, handler, k, i, modifiersMatch, scope; var key, handler, k, i, modifiersMatch, scope;
@@ -995,6 +1003,7 @@
global.key.deleteScope = deleteScope; global.key.deleteScope = deleteScope;
global.key.filter = filter; global.key.filter = filter;
global.key.isPressed = isPressed; global.key.isPressed = isPressed;
global.key.isModifier = isModifierPressed;
global.key.getPressedKeyCodes = getPressedKeyCodes; global.key.getPressedKeyCodes = getPressedKeyCodes;
global.key.noConflict = noConflict; global.key.noConflict = noConflict;
global.key.unbind = unbindKey; global.key.unbind = unbindKey;
@@ -1216,7 +1225,7 @@ $.ready(function () {
}); });
// Expanding boxes on mobile // Expanding boxes on mobile
$('.Box.mobcol').forEach(function(x) { $('.Box.mobcol,.Box.fold').forEach(function(x) {
var h = x.querySelector('h2'); var h = x.querySelector('h2');
var hdl = function() { var hdl = function() {
@@ -1555,6 +1564,41 @@ function tr(key) { return _tr[key] || '?'+key+'?'; }
w.init = wifiInit; w.init = wifiInit;
w.startScanning = startScanning; w.startScanning = startScanning;
})(window.WiFi = {}); })(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 Screen = (function () {
var W = 0, H = 0; // dimensions var W = 0, H = 0; // dimensions
var inited = false; var inited = false;
@@ -1567,12 +1611,14 @@ var Screen = (function () {
bg: 0, bg: 0,
attrs: 0, attrs: 0,
suppress: false, // do not turn on in blink interval (for safe moving) suppress: false, // do not turn on in blink interval (for safe moving)
forceOn: false,
hidden: false, // do not show hidden: false, // do not show
hanging: false, // xenl hanging: false, // xenl
}; };
var screen = []; var screen = [];
var blinkIval; var blinkIval;
var cursorFlashStartIval;
var frakturExceptions = { var frakturExceptions = {
'C': '\u212d', 'C': '\u212d',
@@ -1679,8 +1725,21 @@ var Screen = (function () {
(function() { (function() {
var x = i % W; var x = i % W;
var y = Math.floor(i / W); var y = Math.floor(i / W);
e.addEventListener('click', function () { e.addEventListener('mouseenter', function (evt) {
Input.onTap(y, x); Input.onMouseMove(x, y);
});
e.addEventListener('mousedown', function (evt) {
Input.onMouseDown(x, y, evt.button+1);
});
e.addEventListener('mouseup', function (evt) {
Input.onMouseUp(x, y, evt.button+1);
});
e.addEventListener('contextmenu', function (evt) {
evt.preventDefault();
});
e.addEventListener('mousewheel', function (evt) {
Input.onMouseWheel(x, y, evt.deltaY>0?1:-1);
return false;
}); });
})(); })();
@@ -1717,14 +1776,14 @@ var Screen = (function () {
} }
if (!cursor.suppress) { if (!cursor.suppress) {
_draw(_curCell(), cursor.a); _draw(_curCell(), cursor.forceOn || cursor.a);
} }
}, 500); }, 500);
// blink attribute // blink attribute
setInterval(function () { setInterval(function () {
$('#screen').removeClass('blink-hide'); $('#screen').removeClass('blink-hide');
setTimeout(function() { setTimeout(function () {
$('#screen').addClass('blink-hide'); $('#screen').addClass('blink-hide');
}, 800); // 200 ms ON }, 800); // 200 ms ON
}, 1000); }, 1000);
@@ -1732,16 +1791,6 @@ var Screen = (function () {
inited = true; 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_SET_COLOR_ATTR = 1;
var SEQ_REPEAT = 2; var SEQ_REPEAT = 2;
var SEQ_SET_COLOR = 3; var SEQ_SET_COLOR = 3;
@@ -1752,6 +1801,8 @@ var Screen = (function () {
if (!inited) _init(); if (!inited) _init();
var cursorMoved;
// Set size // Set size
num = parse2B(str, i); i += 2; // height num = parse2B(str, i); i += 2; // height
num2 = parse2B(str, i); i += 2; // width num2 = parse2B(str, i); i += 2; // width
@@ -1763,6 +1814,7 @@ var Screen = (function () {
// Cursor position // Cursor position
num = parse2B(str, i); i += 2; // row num = parse2B(str, i); i += 2; // row
num2 = parse2B(str, i); i += 2; // col num2 = parse2B(str, i); i += 2; // col
cursorMoved = (cursor.x != num2 || cursor.y != num);
cursorSet(num, num2); cursorSet(num, num2);
// console.log("Cursor at ",num, num2); // console.log("Cursor at ",num, num2);
@@ -1826,9 +1878,17 @@ var Screen = (function () {
_drawAll(); _drawAll();
if (!cursor.hidden || cursor.hanging) { // if (!cursor.hidden || cursor.hanging || !cursor.suppress) {
// hide cursor asap // // hide cursor asap
_draw(_curCell(), false); // _draw(_curCell(), false);
// }
if (cursorMoved) {
cursor.forceOn = true;
cursorFlashStartIval = setTimeout(function() {
cursor.forceOn = false;
}, 1200);
_draw(_curCell(), true);
} }
} }
@@ -1897,6 +1957,8 @@ var Screen = (function () {
/** Handle connections */ /** Handle connections */
var Conn = (function() { var Conn = (function() {
var ws; var ws;
var heartbeatTout;
var pingIv;
function onOpen(evt) { function onOpen(evt) {
console.log("CONNECTED"); console.log("CONNECTED");
@@ -1913,9 +1975,13 @@ var Conn = (function() {
function onMessage(evt) { function onMessage(evt) {
try { try {
//console.log("RX: ", evt.data); // . = heartbeat
// Assume all our messages are screen updates if (evt.data != '.') {
Screen.load(evt.data); //console.log("RX: ", evt.data);
// Assume all our messages are screen updates
Screen.load(evt.data);
}
heartbeat();
} catch(e) { } catch(e) {
console.error(e); console.error(e);
} }
@@ -1936,6 +2002,8 @@ var Conn = (function() {
} }
function init() { function init() {
heartbeat();
ws = new WebSocket("ws://"+_root+"/term/update.ws"); ws = new WebSocket("ws://"+_root+"/term/update.ws");
ws.onopen = onOpen; ws.onopen = onOpen;
ws.onclose = onClose; ws.onclose = onClose;
@@ -1948,11 +2016,33 @@ var Conn = (function() {
if (status !== 200) location.reload(true); if (status !== 200) location.reload(true);
console.log("Data received!"); console.log("Data received!");
Screen.load(resp); Screen.load(resp);
heartbeat();
showPage(); showPage();
}); });
} }
function heartbeat() {
clearTimeout(heartbeatTout);
heartbeatTout = setTimeout(heartbeatFail, 2000);
}
function heartbeatFail() {
console.error("Heartbeat lost, probing server...");
pingIv = setInterval(function() {
console.log("> ping");
$.get('http://'+_root+'/system/ping', function(resp, status) {
if (status == 200) {
clearInterval(pingIv);
console.info("Server ready, reloading page...");
location.reload();
}
}, {
timeout: 100,
});
}, 500);
}
return { return {
ws: null, ws: null,
init: init, init: init,
@@ -1960,7 +2050,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 Input = (function() {
var opts = { var opts = {
np_alt: false, np_alt: false,
@@ -1969,15 +2074,11 @@ var Input = (function() {
}; };
function sendStrMsg(str) { function sendStrMsg(str) {
Conn.send("STR:"+str); Conn.send("s"+str);
}
function sendPosMsg(y, x) {
Conn.send("TAP:"+y+','+x);
} }
function sendBtnMsg(n) { function sendBtnMsg(n) {
Conn.send("BTN:"+n); Conn.send("b"+Chr(n));
} }
function fa(alt, normal) { function fa(alt, normal) {
@@ -2098,6 +2199,10 @@ var Input = (function() {
_bindFnKeys(); _bindFnKeys();
} }
var mb1 = 0;
var mb2 = 0;
var mb3 = 0;
function init() { function init() {
_initKeys(); _initKeys();
@@ -2107,11 +2212,30 @@ var Input = (function() {
sendBtnMsg(+this.dataset['n']); sendBtnMsg(+this.dataset['n']);
}); });
}); });
window.addEventListener('mousedown', function(evt) {
if (evt.button == 0) mb1 = 1;
if (evt.button == 1) mb2 = 1;
if (evt.button == 2) mb3 = 1;
});
window.addEventListener('mouseup', function(evt) {
if (evt.button == 0) mb1 = 0;
if (evt.button == 1) mb2 = 0;
if (evt.button == 2) mb3 = 0;
});
}
function packModifiersForMouse() {
return (key.isModifier('ctrl')?1:0) |
(key.isModifier('shift')?2:0) |
(key.isModifier('alt')?4:0) |
(key.isModifier('meta')?8:0);
} }
return { return {
init: init, init: init,
onTap: sendPosMsg, // onTap: sendPosMsg,
sendString: sendStrMsg, sendString: sendStrMsg,
setAlts: function(cu, np, fn) { setAlts: function(cu, np, fn) {
if (opts.cu_alt != cu || opts.np_alt != np || opts.fn_alt != fn) { if (opts.cu_alt != cu || opts.np_alt != np || opts.fn_alt != fn) {
@@ -2123,6 +2247,31 @@ var Input = (function() {
_bindFnKeys(); _bindFnKeys();
} }
}, },
onMouseMove: function (x, y) {
var b = mb1 ? 1 : mb2 ? 2 : mb3 ? 3 : 0;
var m = packModifiersForMouse();
Conn.send("m" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m));
},
onMouseDown: function (x, y, b) {
if (b > 3 || b < 1) return;
var m = packModifiersForMouse();
Conn.send("p" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m));
console.log("B ",b," M ",m);
},
onMouseUp: function (x, y, b) {
if (b > 3 || b < 1) return;
var m = packModifiersForMouse();
Conn.send("r" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m));
console.log("B ",b," M ",m);
},
onMouseWheel: function (x, y, dir) {
// -1 ... btn 4 (away from user)
// +1 ... btn 5 (towards user)
var m = packModifiersForMouse();
var b = (dir < 0 ? 4 : 5);
Conn.send("p" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m));
console.log("B ",b," M ",m);
},
}; };
})(); })();
+1 -1
View File
@@ -16,7 +16,7 @@ $.ready(function () {
}); });
// Expanding boxes on mobile // Expanding boxes on mobile
$('.Box.mobcol').forEach(function(x) { $('.Box.mobcol,.Box.fold').forEach(function(x) {
var h = x.querySelector('h2'); var h = x.querySelector('h2');
var hdl = function() { var hdl = function() {
+9
View File
@@ -67,6 +67,14 @@
for(k in _mods) _mods[k] = event[modifierMap[k]]; for(k in _mods) _mods[k] = event[modifierMap[k]];
}; };
function isModifierPressed(mod) {
if (mod=='control'||mod=='ctrl') return _mods[17];
if (mod=='shift') return _mods[16];
if (mod=='meta') return _mods[91];
if (mod=='alt') return _mods[18];
return false;
}
// handle keydown event // handle keydown event
function dispatch(event) { function dispatch(event) {
var key, handler, k, i, modifiersMatch, scope; var key, handler, k, i, modifiersMatch, scope;
@@ -292,6 +300,7 @@
global.key.deleteScope = deleteScope; global.key.deleteScope = deleteScope;
global.key.filter = filter; global.key.filter = filter;
global.key.isPressed = isPressed; global.key.isPressed = isPressed;
global.key.isModifier = isModifierPressed;
global.key.getPressedKeyCodes = getPressedKeyCodes; global.key.getPressedKeyCodes = getPressedKeyCodes;
global.key.noConflict = noConflict; global.key.noConflict = noConflict;
global.key.unbind = unbindKey; global.key.unbind = unbindKey;
+168 -28
View File
@@ -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 Screen = (function () {
var W = 0, H = 0; // dimensions var W = 0, H = 0; // dimensions
var inited = false; var inited = false;
@@ -10,12 +45,14 @@ var Screen = (function () {
bg: 0, bg: 0,
attrs: 0, attrs: 0,
suppress: false, // do not turn on in blink interval (for safe moving) suppress: false, // do not turn on in blink interval (for safe moving)
forceOn: false,
hidden: false, // do not show hidden: false, // do not show
hanging: false, // xenl hanging: false, // xenl
}; };
var screen = []; var screen = [];
var blinkIval; var blinkIval;
var cursorFlashStartIval;
var frakturExceptions = { var frakturExceptions = {
'C': '\u212d', 'C': '\u212d',
@@ -122,8 +159,21 @@ var Screen = (function () {
(function() { (function() {
var x = i % W; var x = i % W;
var y = Math.floor(i / W); var y = Math.floor(i / W);
e.addEventListener('click', function () { e.addEventListener('mouseenter', function (evt) {
Input.onTap(y, x); Input.onMouseMove(x, y);
});
e.addEventListener('mousedown', function (evt) {
Input.onMouseDown(x, y, evt.button+1);
});
e.addEventListener('mouseup', function (evt) {
Input.onMouseUp(x, y, evt.button+1);
});
e.addEventListener('contextmenu', function (evt) {
evt.preventDefault();
});
e.addEventListener('mousewheel', function (evt) {
Input.onMouseWheel(x, y, evt.deltaY>0?1:-1);
return false;
}); });
})(); })();
@@ -160,14 +210,14 @@ var Screen = (function () {
} }
if (!cursor.suppress) { if (!cursor.suppress) {
_draw(_curCell(), cursor.a); _draw(_curCell(), cursor.forceOn || cursor.a);
} }
}, 500); }, 500);
// blink attribute // blink attribute
setInterval(function () { setInterval(function () {
$('#screen').removeClass('blink-hide'); $('#screen').removeClass('blink-hide');
setTimeout(function() { setTimeout(function () {
$('#screen').addClass('blink-hide'); $('#screen').addClass('blink-hide');
}, 800); // 200 ms ON }, 800); // 200 ms ON
}, 1000); }, 1000);
@@ -175,16 +225,6 @@ var Screen = (function () {
inited = true; 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_SET_COLOR_ATTR = 1;
var SEQ_REPEAT = 2; var SEQ_REPEAT = 2;
var SEQ_SET_COLOR = 3; var SEQ_SET_COLOR = 3;
@@ -195,6 +235,8 @@ var Screen = (function () {
if (!inited) _init(); if (!inited) _init();
var cursorMoved;
// Set size // Set size
num = parse2B(str, i); i += 2; // height num = parse2B(str, i); i += 2; // height
num2 = parse2B(str, i); i += 2; // width num2 = parse2B(str, i); i += 2; // width
@@ -206,6 +248,7 @@ var Screen = (function () {
// Cursor position // Cursor position
num = parse2B(str, i); i += 2; // row num = parse2B(str, i); i += 2; // row
num2 = parse2B(str, i); i += 2; // col num2 = parse2B(str, i); i += 2; // col
cursorMoved = (cursor.x != num2 || cursor.y != num);
cursorSet(num, num2); cursorSet(num, num2);
// console.log("Cursor at ",num, num2); // console.log("Cursor at ",num, num2);
@@ -269,9 +312,17 @@ var Screen = (function () {
_drawAll(); _drawAll();
if (!cursor.hidden || cursor.hanging) { // if (!cursor.hidden || cursor.hanging || !cursor.suppress) {
// hide cursor asap // // hide cursor asap
_draw(_curCell(), false); // _draw(_curCell(), false);
// }
if (cursorMoved) {
cursor.forceOn = true;
cursorFlashStartIval = setTimeout(function() {
cursor.forceOn = false;
}, 1200);
_draw(_curCell(), true);
} }
} }
@@ -340,6 +391,8 @@ var Screen = (function () {
/** Handle connections */ /** Handle connections */
var Conn = (function() { var Conn = (function() {
var ws; var ws;
var heartbeatTout;
var pingIv;
function onOpen(evt) { function onOpen(evt) {
console.log("CONNECTED"); console.log("CONNECTED");
@@ -356,9 +409,13 @@ var Conn = (function() {
function onMessage(evt) { function onMessage(evt) {
try { try {
//console.log("RX: ", evt.data); // . = heartbeat
// Assume all our messages are screen updates if (evt.data != '.') {
Screen.load(evt.data); //console.log("RX: ", evt.data);
// Assume all our messages are screen updates
Screen.load(evt.data);
}
heartbeat();
} catch(e) { } catch(e) {
console.error(e); console.error(e);
} }
@@ -379,6 +436,8 @@ var Conn = (function() {
} }
function init() { function init() {
heartbeat();
ws = new WebSocket("ws://"+_root+"/term/update.ws"); ws = new WebSocket("ws://"+_root+"/term/update.ws");
ws.onopen = onOpen; ws.onopen = onOpen;
ws.onclose = onClose; ws.onclose = onClose;
@@ -391,11 +450,33 @@ var Conn = (function() {
if (status !== 200) location.reload(true); if (status !== 200) location.reload(true);
console.log("Data received!"); console.log("Data received!");
Screen.load(resp); Screen.load(resp);
heartbeat();
showPage(); showPage();
}); });
} }
function heartbeat() {
clearTimeout(heartbeatTout);
heartbeatTout = setTimeout(heartbeatFail, 2000);
}
function heartbeatFail() {
console.error("Heartbeat lost, probing server...");
pingIv = setInterval(function() {
console.log("> ping");
$.get('http://'+_root+'/system/ping', function(resp, status) {
if (status == 200) {
clearInterval(pingIv);
console.info("Server ready, reloading page...");
location.reload();
}
}, {
timeout: 100,
});
}, 500);
}
return { return {
ws: null, ws: null,
init: init, init: init,
@@ -403,7 +484,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 Input = (function() {
var opts = { var opts = {
np_alt: false, np_alt: false,
@@ -412,15 +508,11 @@ var Input = (function() {
}; };
function sendStrMsg(str) { function sendStrMsg(str) {
Conn.send("STR:"+str); Conn.send("s"+str);
}
function sendPosMsg(y, x) {
Conn.send("TAP:"+y+','+x);
} }
function sendBtnMsg(n) { function sendBtnMsg(n) {
Conn.send("BTN:"+n); Conn.send("b"+Chr(n));
} }
function fa(alt, normal) { function fa(alt, normal) {
@@ -541,6 +633,10 @@ var Input = (function() {
_bindFnKeys(); _bindFnKeys();
} }
var mb1 = 0;
var mb2 = 0;
var mb3 = 0;
function init() { function init() {
_initKeys(); _initKeys();
@@ -550,11 +646,30 @@ var Input = (function() {
sendBtnMsg(+this.dataset['n']); sendBtnMsg(+this.dataset['n']);
}); });
}); });
window.addEventListener('mousedown', function(evt) {
if (evt.button == 0) mb1 = 1;
if (evt.button == 1) mb2 = 1;
if (evt.button == 2) mb3 = 1;
});
window.addEventListener('mouseup', function(evt) {
if (evt.button == 0) mb1 = 0;
if (evt.button == 1) mb2 = 0;
if (evt.button == 2) mb3 = 0;
});
}
function packModifiersForMouse() {
return (key.isModifier('ctrl')?1:0) |
(key.isModifier('shift')?2:0) |
(key.isModifier('alt')?4:0) |
(key.isModifier('meta')?8:0);
} }
return { return {
init: init, init: init,
onTap: sendPosMsg, // onTap: sendPosMsg,
sendString: sendStrMsg, sendString: sendStrMsg,
setAlts: function(cu, np, fn) { setAlts: function(cu, np, fn) {
if (opts.cu_alt != cu || opts.np_alt != np || opts.fn_alt != fn) { if (opts.cu_alt != cu || opts.np_alt != np || opts.fn_alt != fn) {
@@ -566,6 +681,31 @@ var Input = (function() {
_bindFnKeys(); _bindFnKeys();
} }
}, },
onMouseMove: function (x, y) {
var b = mb1 ? 1 : mb2 ? 2 : mb3 ? 3 : 0;
var m = packModifiersForMouse();
Conn.send("m" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m));
},
onMouseDown: function (x, y, b) {
if (b > 3 || b < 1) return;
var m = packModifiersForMouse();
Conn.send("p" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m));
console.log("B ",b," M ",m);
},
onMouseUp: function (x, y, b) {
if (b > 3 || b < 1) return;
var m = packModifiersForMouse();
Conn.send("r" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m));
console.log("B ",b," M ",m);
},
onMouseWheel: function (x, y, dir) {
// -1 ... btn 4 (away from user)
// +1 ... btn 5 (towards user)
var m = packModifiersForMouse();
var b = (dir < 0 ? 4 : 5);
Conn.send("p" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m));
console.log("B ",b," M ",m);
},
}; };
})(); })();
+563 -324
View File
@@ -1,361 +1,600 @@
<div class="Box"> <div class="Box fold">
<h2>Troubleshooting</h2> <h2>Tips & Troubleshooting</h2>
<ul> <div class="Row v">
<li>ESPTerm communicates with external µC via UART on pins <b>Rx</b> and <b>Tx</b>. <img src="/img/adapter.jpg" class="aside" alt="ESPTerm v2">
It can be configured in <a href="<?= url('cfg_system') ?>">System Settings</a>. <ul>
Make sure you're using the correct baud rate and other settings on the other side. <li><b>Communication UART (Rx, Tx)</b> can be configured in the <a href="<?= url('cfg_system') ?>">System Settings</a>.
<li>Boot log and debug messages are available on pin <b>GPIO2</b> (P2) at 115200\,bps, 1 stop bit, no parity. <li><b>Boot log and debug messages</b> are available on pin <b>GPIO2</b> (P2) at 115200\,baud, 1 stop bit, no parity.
Those messages may be disabled through compile flags.
<li>ESPTerm uses 3.3\,V logic. For communication with 5\,V logic (eg. Aurdino), 470\,R <li><b>Loopback test</b>: Connect the Rx and Tx pins with a piece of wire. Anything you type in the browser should
protection resistors are recommended. The ESPTerm breakout board already includes those resistors. appear on the screen. Set Parser Timeout = 0 in <a href="<?= url('cfg_term') ?>">Terminal Settings</a>
to be able to manually enter escape sequences.
<li>If the Low Voltage Detector ("LVD") LED on the ESPTerm breakout lights up, your power connections have too high <li><b>For best performance</b>, use the module in Client mode (connected to external network) and minimize the number
resistance or the power supply is insufficient. ESP8266 can consume in short spikes up to 500\,A and of simultaneous connections. Enabling AP consumes extra RAM and strains the processor.
the supply voltage on the module must not drop below about 2.7\,V. Avoid needless breadboard jumpers and too thin wires.
<li>Connect Rx and Tx with a piece of wire to verify that the terminal works correctly; you should then immediately <li>In AP mode, <b>check that the WiFi channel used is clear</b>; interference may cause flaky connection.
see what you type in the browser. <i>This won't work if your ESP8266 board has a built-in USB-serial A good mobile app to use for this is
converter connected to those pins.</i> <a href="https://play.google.com/store/apps/details?id=com.farproc.wifi.analyzer">WiFi Analyzer (Google Play)</a>.
Adjust the hotspot strength and range using the Tx Power setting.
<li>For best performance, use the module in Client mode (connected to external network). Enabling AP mode consumes <li>Hold the BOOT button (GPIO0 to GND) for ~1 second to force enable AP. Hold it for ~6 seconds to restore default settings.
extra RAM and cycles, since the captive portal DNS server and DHCP server are be loaded. (This is indicated by the blue LED rapidly flashing). Default settings can be overwritten in the
<a href="<?= url('cfg_system') ?>">System Settings</a>.
<li>In AP mode, check that the channel used by ESPTerm is clear; interference may cause flaky connection. A good mobile app to use for this is <a href="https://play.google.com/store/apps/details?id=com.farproc.wifi.analyzer">WiFi Analyzer (Google Play)</a> </ul>
</div>
<li>The terminal browser display needs WebSockets for the real-time updating to work.
It will not work with some old browsers (Android < 4.4, IE < 10, Opera Mini)
- see the <a href="https://caniuse.com/websockets/embed/">compatibility chart</a>.
<li>At most 4 clients can be connected to the AP at the same time. This is a limitation of the ESP8266 SDK.
</ul>
</div> </div>
<div class="Box"> <div class="Box fold">
<h2>Screen</h2> <h2>Escape Sequences Intro</h2>
<ul> <div class="Row v">
<li>Most ANSI escape sequences are supported. <img src="/img/vt100.jpg" class="aside" alt="VT102">
<li>The initial screen size and other terminal options can be configured in <p>ESPTerm emulates VT102 (pictured) with some additions from later VT models and <code>xterm</code>.
<a href="<?= url('cfg_term') ?>">Terminal Settings</a> All commonly used attributes and commands are supported.
ESPTerm is capable of displaying ncurses applications such as <i>Midnight Commander</i> using <i>agetty</i>.</p>
<li>Display updates are sent to the browser after 20 ms of inactivity on the communication UART. <p>
This is to avoid hogging the server with tiny updates during large screen repaints. ESPTerm accepts UTF-8 characters received on the communication UART and displays them on the screen,
</ul> interpreting some codes as Control Characters. Those are e.g. Carriage Return (13), Line Feed (10), Tab (9), Backspace (8) and Bell (7).
Escape sequences start with the control character ESC (27), followed by any number of ASCII characters
forming the body of the command. On this page, we'll use <code>\e</code> to indicate ESC.
</p>
<p>
Escape sequences can be divided based on their first character and structure. Most common types are:
</p>
<ul>
<li>CSI commands: <code>\e[</code> followed by 1 optional leading character, multiple numbers divided by
semicolons, and one or two trailing characters. Those control the cursor, set attributes and manipulate screen content. E.g. <code>\e[?7;10h</code>, <code>\e[2J</code></li>
<li>SGR commands: CSI terminated by <code>m</code>. Those set text attributes and colors. E.g. <code>\e[</code></li>
<li>OSC commands: <code>\e]</code> followed by any number of UTF-8 characters (ESPTerm supports up to 64)
terminated by <code>\e\\</code> (ESC and backslash) or Bell (7). Those are used to exchange text strings.</li>
<li>There are a few other commands that don't follow this pattern, such as <code>\e7</code> or
<code>\e#8</code>, mostly for historical reasons.</li>
</ul>
<p>A list of the most important escape sequences is presented in the following sections.</p>
</div>
</div> </div>
<div class="Box theme-0"> <div class="Box fold">
<h2>Color Reference</h2> <h2>Screen Behavior</h2>
<p>Color is set using <code>\e[&lt;c&gt;m</code> where <code>&lt;c&gt;</code> are numbers separated by semicolons <div class="Row v">
(and <code>\e</code> is ESC, code 27). The actual color representation depends on a color theme which <p>The initial screen size, title text and button labels can be configured in <a href="<?= url('cfg_term') ?>">Terminal Settings</a>.</p>
can be selected in <a href="<?= url('cfg_term') ?>">Terminal Settings</a>.</p>
<b>Foreground</b> <p>
Screen updates are sent to the browser through a WebSocket after some time of inactivity on the communication UART
<div class="colorprev"> (called "Redraw Delay"). After an update is sent, at least a time of "Redraw Cooldown" must elapse before the next
<span class="bg7 fg0">30</span> update can be sent. Those delays are used is to avoid burdening the server with tiny updates during a large screen
<span class="bg0 fg1">31</span> repaint. If you experience issues (broken image due to dropped bytes), try adjusting those config options. It may also
<span class="bg0 fg2">32</span> be useful to try different baud rates.
<span class="bg0 fg3">33</span> </p>
<span class="bg0 fg4">34</span>
<span class="bg0 fg5">35</span>
<span class="bg0 fg6">36</span>
<span class="bg0 fg7">37</span>
</div> </div>
<div class="colorprev">
<span class="bg0 fg8">90</span>
<span class="bg0 fg9">91</span>
<span class="bg0 fg10">92</span>
<span class="bg0 fg11">93</span>
<span class="bg0 fg12">94</span>
<span class="bg0 fg13">95</span>
<span class="bg0 fg14">96</span>
<span class="bg0 fg15">97</span>
</div>
<b>Background</b>
<div class="colorprev">
<span class="bg0 fg15">40</span>
<span class="bg1 fg15">41</span>
<span class="bg2 fg15">42</span>
<span class="bg3 fg0">43</span>
<span class="bg4 fg15">44</span>
<span class="bg5 fg15">45</span>
<span class="bg6 fg15">46</span>
<span class="bg7 fg0">47</span>
</div>
<div class="colorprev">
<span class="bg8 fg15">100</span>
<span class="bg9 fg0">101</span>
<span class="bg10 fg0">102</span>
<span class="bg11 fg0">103</span>
<span class="bg12 fg0">104</span>
<span class="bg13 fg0">105</span>
<span class="bg14 fg0">106</span>
<span class="bg15 fg0">107</span>
</div>
<p>To make the text bold, use the "bold" attribute (eg. <code>\e[31;40;1m</code> for
<span style="padding: 2px;" class="bg0 fg1 bold">bold red on black</span>).</p>
<p>The colors are reset to default using <code>\e[0m</code>. For more info, see the tables below.</p>
</div> </div>
<div class="Box"> <div class="Box fold">
<h2>Text Attributes</h2>
<div class="Row v">
<p>
All text attributes are set using SGR commands like <code>\e[10;20;30m</code>, with up to 10 numbers separated by semicolons.
To restore all attributes to their default states, use SGR 0: <code>\e[0m</code> or <code>\e[m</code>.
</p>
<p>Those are the supported text attributes SGR codes:</p>
<table>
<thead><tr><th>Style</th><th>Enable</th><th>Disable</th></tr></thead>
<tbody>
<tr><td><b>Bold</b></td><td>1</td><td>21, 22</td></tr>
<tr><td style="opacity:.6">Faint</td><td>2</td><td>22</td></tr>
<tr><td><i>Italic</i></td><td>3</td><td>23</td></tr>
<tr><td><u>Underlined</u></td><td>4</td><td>24</td></tr>
<tr><td>Blink</td><td>5</td><td>25</td></tr>
<tr><td><span style="color:black;background:#ccc;">Inverse</span></td><td>7</td><td>27</td></tr>
<tr><td><s>Striked</s></td><td>9</td><td>29</td></tr>
<tr><td>𝔉𝔯𝔞𝔨𝔱𝔲𝔯</td><td>20</td><td>23</td></tr>
</tbody>
</table>
</div>
</div>
<div class="Box fold theme-0">
<h2>Colors</h2>
<div class="Row v">
<p>Colors are set using SGR commands (like <code>\e[10;20;30m</code>). The following tables list the SGR codes to use.
Selected colors are used for any new text entered, as well as for empty space when using line and screen clearing commands.
The configured default colors can be restored using SGR 39 for foreground and SGR 49 for background.</p>
<p>The actual color representation depends on a color theme which
can be selected in <a href="<?= url('cfg_term') ?>">Terminal Settings</a>.</p>
<h3>Foreground colors</h3>
<div class="colorprev">
<span class="bg7 fg0">30</span>
<span class="bg0 fg1">31</span>
<span class="bg0 fg2">32</span>
<span class="bg0 fg3">33</span>
<span class="bg0 fg4">34</span>
<span class="bg0 fg5">35</span>
<span class="bg0 fg6">36</span>
<span class="bg0 fg7">37</span>
</div>
<div class="colorprev">
<span class="bg0 fg8">90</span>
<span class="bg0 fg9">91</span>
<span class="bg0 fg10">92</span>
<span class="bg0 fg11">93</span>
<span class="bg0 fg12">94</span>
<span class="bg0 fg13">95</span>
<span class="bg0 fg14">96</span>
<span class="bg0 fg15">97</span>
</div>
<h3>Background colors</h3>
<div class="colorprev">
<span class="bg0 fg15">40</span>
<span class="bg1 fg15">41</span>
<span class="bg2 fg15">42</span>
<span class="bg3 fg0">43</span>
<span class="bg4 fg15">44</span>
<span class="bg5 fg15">45</span>
<span class="bg6 fg15">46</span>
<span class="bg7 fg0">47</span>
</div>
<div class="colorprev">
<span class="bg8 fg15">100</span>
<span class="bg9 fg0">101</span>
<span class="bg10 fg0">102</span>
<span class="bg11 fg0">103</span>
<span class="bg12 fg0">104</span>
<span class="bg13 fg0">105</span>
<span class="bg14 fg0">106</span>
<span class="bg15 fg0">107</span>
</div>
</div>
</div>
<div class="Box fold">
<h2>User Input</h2> <h2>User Input</h2>
<p> <div class="Row v">
All the user types on their keyboard is sent as-is to the UART, including ESC, ANSI sequences for arrow keys and CR-LF for Enter. <h3>Keyboard</h3>
</p>
<p>The buttons under the screen send ASCII codes 1, 2, 3, 4, 5. This choice was made to make their parsing as simple as possible.</p> <p>
The user can input text using their keyboard, or on Android, using the on-screen keyboard which is open using
a button beneath the screen. Supported are all printable characters, as well as many control keys, such as arrows, Ctrl+letters
and function keys. Sequences sent by function keys are based on VT102 and xterm.
</p>
<p> <p>
Mouse input (click/tap) is sent as <code>\e[&lt;y&gt;;&lt;x&gt;M</code>. You can use this for virtual on-screen buttons. The codes sent by Home, End, F1-F4 and cursor keys are affected by various keyboard modes (Application Cursor Keys,
</p> Application Numpad Mode, SS3 Fn Keys Mode).
Some can be set in the <a href="<?= url('cfg_term') ?>">Terminal Settings</a>, others via commands.
</p>
<p>
Here are some examples of control key codes:
</p>
<table>
<thead><tr><th>Key</th><th>Code</th><th>Key</th><th>Code</th></tr></thead>
<tr>
<td>🡑</td>
<td><code>\e[A</code></td>
<td>F1</td>
<td><code>\eOP</code></td>
</tr>
<tr>
<td>🡓</td>
<td><code>\e[B</code></td>
<td>F2</td>
<td><code>\eOQ</code></td>
</tr>
<tr>
<td>🡒</td>
<td><code>\e[C</code></td>
<td>F3</td>
<td><code>\eOR</code></td>
</tr>
<tr>
<td>🡐</td>
<td><code>\e[D</code></td>
<td>F4</td>
<td><code>\eOS</code></td>
</tr>
<tr>
<td>Home</td>
<td><code>\eOH</code></td>
<td>F5</td>
<td><code>\e[15~</code></td>
</tr>
<tr>
<td>End</td>
<td><code>\eOF</code></td>
<td>F6</td>
<td><code>\e[17~</code></td>
</tr>
<tr>
<td>Insert</td>
<td><code>\e[2~</code></td>
<td>F7</td>
<td><code>\e[18~</code></td>
</tr>
<tr>
<td>Delete</td>
<td><code>\e[3~</code></td>
<td>F8</td>
<td><code>\e[19~</code></td>
</tr>
<tr>
<td>Page Up</td>
<td><code>\e[5~</code></td>
<td>F9</td>
<td><code>\e[20~</code></td>
</tr>
<tr>
<td>Page Down</td>
<td><code>\e[6~</code></td>
<td>F10</td>
<td><code>\e[21~</code></td>
</tr>
<tr>
<td>Enter</td>
<td><code>CR (13)</code></td>
<td>F11</td>
<td><code>\e[23~</code></td>
</tr>
<tr>
<td>Ctrl+Enter</td>
<td><code>LF (10)</code></td>
<td>F12</td>
<td><code>\e[24~</code></td>
</tr>
<tr>
<td>Tab</td>
<td><code>TAB (9)</code></td>
<td>ESC</td>
<td><code>ESC (27)</code></td>
</tr>
<tr>
<td>Backspace</td>
<td><code>BS (8)</code></td>
<td>Ctrl+A..Z</td>
<td><code>ASCII 1-26</code></td>
</tr>
</table>
<h3>Action buttons</h3>
<p>
The blue buttons under the screen send ASCII codes 1, 2, 3, 4, 5, which incidentally correspond to Ctrl+A,B,C,D,E.
This choice was made to make button press parsing as simple as possible.
</p>
<h3>Mouse</h3>
<p>
ESPTerm in the current version does not implement standard mouse input. Instead, clicks/taps produce CSI sequences
<code>\e[<i>r</i>;<i>c</i>M</code> (row, column). You can use this for virtual on-screen buttons.
</p>
</div>
</div> </div>
<div class="Box"> <div class="Box fold">
<h2>Supported ANSI Escape Sequences</h2> <h2>Cursor Commands</h2>
<p>Sequences are started by ASCII code 27 (ESC, <code>\e</code>, <code>\x1b</code>, <code>\033</code>)</p> <div class="Row v">
<p>
The coordinates are 1-based, origin is top left. The cursor can move within the entire screen,
or in the active Scrolling Region if Origin Mode is enabled.
</p>
<p>Instead of <code>\a</code> (BELL, ASCII 7) in the commands, you can use `\e\` (ESC + backslash). It's the Text Separator code.</p> <p>After writing a character, the cursor advances to the right. If it has reached the end of the row,
it stays on the same line, but writing the next character makes it jump to the start of the next
line first, scrolling up if needed. If Auto-wrap mode is disabled, the cursor never wraps or scrolls
the screen.
</p>
<h3>Text Attributes</h3> <p>
<b>Legend:</b>
Italic letters such as <i>n</i> are ASCII numbers that serve as arguments, separated with a semicolon.
If an argument is left out, it's treated as 0 or 1, depending on what makes sense for the command.
</p>
<p> <h3>Movement</h3>
All text attributes are set using <code>\e[...m</code> where the dots are numbers separated by semicolons.
You can combine up to 3 attributes in a single command.
</p>
<table class="ansiref"> <table class="ansiref w100">
<thead> <thead><tr><th>Code</th><th>Meaning</th></tr></thead>
<tr> <tbody>
<th>Attribute</th> <tr>
<th>Meaning</th> <td>
</tr> \e[<i>n</i>A<br>
</thead> \e[<i>n</i>B<br>
<tbody> \e[<i>n</i>C<br>
<tr> \e[<i>n</i>D
<td>0</td> </td>
<td>Reset text attributes to default</td> <td>Move cursor up (A), down (B), right (C), left (D)</td>
</tr> </tr>
<tr> <tr>
<td>1</td> <td>
<td>Bold</td> \e[<i>n</i>F<br>
</tr> \e[<i>n</i>E
<tr> </td>
<td>21 or 22</td> <td>Go <i>n</i> lines up (F) or down (E), start of line</td>
<td>Bold off</td> </tr>
</tr> <tr>
<tr> <td>
<td>7</td> \e[<i>r</i>d<br>
<td>Inverse (fg/bg swap when printing)</td> \e[<i>c</i>G<br>
</tr> \e[<i>r</i>;<i>c</i>H
<tr> </td>
<td>27</td> <td>
<td>Inverse OFF</td> Go to absolute position - row (d), column (G), or both (H). Use <code>\e[H</code> to go to 1,1.
</tr> </td>
<tr> </tr>
<td>30-37, 90-97</td> <tr>
<td>Foreground color, normal and bright</td> <td>\e[6n</td>
</tr> <td>Query cursor position. Sent back as <code>\e[<i>r</i>;<i>c</i>R</code>.</td>
<tr> </tr>
<td>40-47, 100-107</td> </tbody>
<td>Background color, normal and bright</td> </table>
</tr>
</tbody>
</table>
<h3>Cursor</h3> <h3>Save / restore</h3>
<p>Movement commands scroll the screen if needed. The coordinates are 1-based, origin top left.</p> <table class="ansiref w100">
<thead><tr><th>Code</th><th>Meaning</th></tr></thead>
<tbody>
<tr>
<td>\e[s<br>\e[u</td>
<td>Save (s) or restore (u) cursor position</td>
</tr>
<tr>
<td>\e7<br>\e8</td>
<td>Save (7) or restore (8) cursor position and attributes</td>
</tr>
</tbody>
</table>
<table class="ansiref w100"> <h3>Scrolling Region</h3>
<thead>
<tr>
<th>Code</th>
<th>Params</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td>\e[&lt;n&gt;A</td>
<td>[count]</td>
<td>Move cursor up</td>
</tr>
<tr>
<td>\e[&lt;n&gt;B</td>
<td>[count]</td>
<td>Move cursor down</td>
</tr>
<tr>
<td>\e[&lt;n&gt;C</td>
<td>[count]</td>
<td>Move cursor forward (right)</td>
</tr>
<tr>
<td>\e[&lt;n&gt;D</td>
<td>[count]</td>
<td>Move cursor backward (left)</td>
</tr>
<tr>
<td>\e[&lt;n&gt;E</td>
<td>[count]</td>
<td>Go N lines down, start of line</td>
</tr>
<tr>
<td>\e[&lt;n&gt;F</td>
<td>[count]</td>
<td>Go N lines up, start of line</td>
</tr>
<tr>
<td>\e[&lt;n&gt;G</td>
<td>column</td>
<td>Go to column</td>
</tr>
<tr>
<td>\e[&lt;y&gt;;&lt;x&gt;G</td>
<td>[row=1];[col=1]</td>
<td>Go to row and column</td>
</tr>
<tr>
<td>\e[6n</td>
<td>--</td>
<td>Query cursor position. Position is sent back as <code>\e[?;?R</code> with row;column.</td>
</tr>
<tr>
<td>\e[s</td>
<td>--</td>
<td>Store position</td>
</tr>
<tr>
<td>\e[u</td>
<td>--</td>
<td>Restore position</td>
</tr>
<tr>
<td>\e7</td>
<td>--</td>
<td>Store position & attributes</td>
</tr>
<tr>
<td>\e8</td>
<td>--</td>
<td>Restore position & attributes</td>
</tr>
<tr>
<td>\e[?25l</td>
<td>--</td>
<td>Hide cursor</td>
</tr>
<tr>
<td>\e[?25h</td>
<td>--</td>
<td>Show cursor</td>
</tr>
<tr>
<td>\e[?7l</td>
<td>--</td>
<td>Disable cursor auto-wrap & auto-scroll at end of screen</td>
</tr>
<tr>
<td>\e[?7h</td>
<td>--</td>
<td>Enable cursor auto-wrap & auto-scroll at end of screen</td>
</tr>
</tbody>
</table>
<h3>Screen</h3> <table class="ansiref w100">
<thead><tr><th>Code</th><th>Meaning</th></tr></thead>
<tbody>
<tr>
<td>\e[<i>a</i>;<i>b</i>r</td>
<td>Set scrolling region to rows <i>a</i> through <i>b</i> and go to 1,1. By default, the
scrolling region spans the entire screen height. The cursor can leave the region using
absolute position commands, unless Origin Mode (see below) is active.</td>
</tr>
<tr>
<td>\e[?6h<br>\e[?6l</td>
<td>Enable (h) or disable (l) Origin Mode and go to 1,1. In Origin Mode, all coordinates
are relative to the Scrolling Region and the cursor can't leave the region.</td>
</tr>
<tr>
<td>
\e[<i>n</i>S<br>
\e[<i>n</i>T
</td>
<td>Move contents of the Scrolling Region up (S) or down (T), pad with empty lines of the current background color.</td>
</tr>
</tbody>
</table>
<table class="ansiref w100"> <h3>Tab stops</h3>
<thead>
<tr>
<th>Code</th>
<th>Params</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td>\e[&lt;m&gt;J</td>
<td>[mode=0]</td>
<td>Clear screen. Mode: 0 - from cursor, 1 - to cursor, 2 - all</td>
</tr>
<tr>
<td>\e[&lt;m&gt;K</td>
<td>[mode=0]</td>
<td>Erase line. Mode: 0 - from cursor, 1 - to cursor, 2 - all</td>
</tr>
<tr>
<td>\e[&lt;n&gt;S</td>
<td>[lines]</td>
<td>Scroll screen content up, add empty line at the bottom</td>
</tr>
<tr>
<td>\e[&lt;n&gt;T</td>
<td>[lines]</td>
<td>Scroll screen content down, add empty line at the top</td>
</tr>
<tr>
<td>\e]W&lt;r&gt;;&lt;c&gt;\a</td>
<td>rows;cols</td>
<td>Set screen size (max ~ 80x30). This also clears the screen. This is a custom ESPTerm OSC command.</td>
</tr>
</tbody>
</table>
<h3>System Commands</h3> <table class="ansiref w100">
<thead><tr><th>Code</th><th>Meaning</th></tr></thead>
<tbody>
<tr>
<td>\eH</td>
<td>Set tab stop at the current column. There are, by default, tabs every 8 columns.</td>
</tr>
<tr>
<td>
\e[<i>n</i>I<br>
\e[<i>n</i>Z
</td>
<td>Advance (I) or go back (Z) <i>n</i> tab stops or end/start of line. ASCII TAB (9) is equivalent to <code>\e[1I</code></td>
</tr>
<tr>
<td>
\e[0g<br>
\e[3g<br>
</td>
<td>Clear tab stop at the current column (0), or all columns (3).</td>
</tr>
</tbody>
</table>
<table class="ansiref w100"> <h3>Other options</h3>
<thead>
<tr> <table class="ansiref w100">
<th>Code</th> <thead><tr><th>Code</th><th>Meaning</th></tr></thead>
<th>Params</th> <tbody>
<th>Meaning</th> <tr>
</tr> <td>\e[?7h<br>\e[?7l</td>
</thead> <td>Enable (h) or disable (l) cursor auto-wrap and screen auto-scroll</td>
<tbody> </tr>
<tr> <tr>
<td>\ec</td> <td>\e[?25h<br>\e[?25l</td>
<td>--</td> <td>Show (h) or hide (l) the cursor</td>
<td> </tr>
"Device Reset" - clear screen, reset attributes, show cursor & move it to 1,1. </tbody>
The screen size and WiFi settings stay unchanged. </table>
</td> </div>
</tr> </div>
<tr>
<td>\e[5n</td> <div class="Box fold">
<td>--</td> <h2>Screen Content Manipulation</h2>
<td>Query device status, replies with <code>\e[0n</code> "device is OK". Can be used to check if the UART works.</td>
</tr> <div class="Row v">
<tr> <p>
<td>\e]TITLE=…\a</td> <b>Legend:</b>
<td>Title text</td> Italic letters such as <i>n</i> are ASCII numbers that serve as arguments, separated with a semicolon.
<td>Set terminal title. This is a custom ESPTerm OSC command.</td> If an argument is left out, it's treated as 0 or 1, depending on what makes sense for the command.
</tr> </p>
<tr>
<td>\e]BTNn=…\a</td> <table class="ansiref w100">
<td>1-5, label</td> <thead><tr><th>Code</th><th>Meaning</th></tr></thead>
<td>Set button label. This is a custom ESPTerm OSC command.</td> <tbody>
</tr> <tr>
<tr> <td>\e[<i>m</i>J</td>
<td>ASCII 24 (18h)</td> <td>Clear part of screen. <i>m</i>: 0 - from cursor, 1 - to cursor, 2 - all</td>
<td></td> </tr>
<td>This symbol is sent by ESPTerm when it becomes ready to receive commands. It can be used to wait before it becomes <tr>
ready on power-up, or to detect a spontaneous ESPTerm restart - that could happen due to RAM exhaustion due <td>\e[<i>m</i>K</td>
to a memory leak, or perhaps a power outage. <td>Erase part of line. <i>m</i>: 0 - from cursor, 1 - to cursor, 2 - all</td>
</td> </tr>
</tr> <tr>
</tbody> <td>
</table> \e[<i>n</i>X
</td>
<td>Erase <i>n</i> characters in line.</td>
</tr>
<tr>
<td>
\e[<i>n</i>L<br>
\e[<i>n</i>M
</td>
<td>Insert (L) or delete (M) <i>n</i> lines. Following lines are pulled up or pushed down.</td>
</tr>
<tr>
<td>
\e[<i>n</i>@<br>
\e[<i>nP
</td>
<td>Insert (@) or delete (P) <i>n</i> characters. The rest of the line is pulled left or pushed right.
Characters going past the end of line are lost.</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="Box fold">
<h2>Alternate Character Sets</h2>
<div class="Row v">
<p>
ESPTerm implements Alternate Character Sets as a way to print box drawing characters
and special symbols. A character set can change what each received ASCII character
is printed as on the screen (eg. "{" is "π" in codepage 0). The implementation is based
on the original VT devices.
Since ESPTerm also fully supports UTF-8, you can probably ignore this feature and use
Unicode directly. It's added for compatibility with some programs that use this.
</p>
<p>The following codepages are implemented:</p>
<ul>
<li>B - US ASCII (default)</li>
<li>A - UK ASCII: # replaced with £</li>
<li>0 - Symbols and basic line drawing (standard DEC alternate character set)</li>
<li>1 - Symbols and advanced line drawing (based on DOS codepage 437)</li>
</ul>
<p>To see what character maps to which symbol, look in the source code or try it. All codepages use codes 32-127, 32 being space.</p>
<p>
There are two character set slots, G0 and G1.
Those slots are selected as active using ASCII codes Shift In and Shift Out (those originally served for shifting
a red-black typewriter tape). Each slot (G0 and G1) can have a different codepage assigned. G0 and G1 and the active slot number are
saved and restored with the cursor and cleared with a screen reset (<code>\ec</code>).
</p>
<p>The following commands are used:</p>
<table class="ansiref w100">
<thead><tr><th>Code</th><th>Meaning</th></tr></thead>
<tbody>
<tr>
<td>\e(<i>x</i></td>
<td>Set G0 = codepage <i>x</i></td>
</tr>
<tr>
<td>\e)<i>x</i></td>
<td>Set G1 = codepage <i>x</i></td>
</tr>
<tr>
<td>SO (14)</td>
<td>Activate G0</td>
</tr>
<tr>
<td>SI (15)</td>
<td>Activate G1</td>
</tr>
</table>
</div>
</div>
<div class="Box fold">
<h2>System Commands</h2>
<div class="Row v">
<p>
It's possible to dynamically change the screen title text and action button labels.
Setting an empty label to a button makes it look disabled. The buttons send ASCII 1-5 when clicked.
Those changes are not retained after restart.
</p>
<table class="ansiref w100">
<thead><tr><th>Code</th><th>Meaning</th></tr></thead>
<tbody>
<tr>
<td>\ec</td>
<td>
Clear screen, reset attributes and cursor.
The screen size, title and button labels remain unchanged.
</td>
</tr>
<tr>
<td>\e[5n</td>
<td>
Query device status, ESPTerm replies with <code>\e[0n</code> "device is OK".
Can be used to check if the terminal has booted up and is ready to receive commands.</td>
</tr>
<tr>
<td>CAN (24)</td>
<td>
This ASCII code is not a command, but is sent by ESPTerm when it becomes ready to receive commands.
When this code is received on the UART, it means ESPTerm has restarted and is ready. Use this to detect
spontaneous restarts which require a full screen repaint.
</td>
</tr>
<tr>
<td>\e]0;<i>title</i>\a</td>
<td>Set screen title (this is a standard OSC command)</td>
</tr>
<tr>
<td>
\e]<i>81</i>;<i>btn1</i>\a<br>
\e]<i>82</i>;<i>btn2</i>\a<br>
\e]<i>83</i>;<i>btn3</i>\a<br>
\e]<i>84</i>;<i>btn4</i>\a<br>
\e]<i>85</i>;<i>btn5</i>\a<br>
</td>
<td>Set button 1-5 label - eg. <code>\e]81;Yes\a</code>
sets the first button text to "Yes".</td>
</tr>
<tr>
<td>\e[8;<i>r</i>;<i>c</i>t</td>
<td>Set screen size (this is a command borrowed from xterm)</td>
</tr>
</tbody>
</table>
</div>
</div> </div>
+14
View File
@@ -29,6 +29,20 @@ form { @include naked(); }
margin-bottom: 0; margin-bottom: 0;
} }
&.v {
display: block;
}
.aside {
float: right;
margin-left: 5px;
margin-bottom: 5px;
@include media($phone) {
margin: 0; float: none;
}
}
.spacer { .spacer {
width: $form-label-w; width: $form-label-w;
+7 -2
View File
@@ -31,6 +31,11 @@ a:hover {
} }
ul > * { ul > * {
padding-top: .2em; padding-top: .3em;
padding-bottom: .2em; padding-bottom: .3em;
}
ul {
margin-top: 0;
margin-bottom: 0;
} }
+47 -22
View File
@@ -103,44 +103,69 @@
} }
} }
@mixin mobcol-base {
h2 {
position: relative;
cursor: pointer;
padding: 2px 1.3rem 2px 5px;
margin: 0 -5px 0 -5px;
&::after {
position: absolute;
right: 4px;
content: '';
top:50%;
font-size: 120%;
font-weight: bold;
transform: translate(0,-50%) rotate(90deg);
}
}
&.expanded h2::after {
transform: translate(-25%,-50%) rotate(-90deg);
margin-bottom: dist(0);
}
.Row {
display: none;
}
&.expanded {
.Row {
display: flex;
&.v {
display: block;
}
}
}
}
.Box.fold {
@include mobcol-base;
}
@include media($phone) { @include media($phone) {
.Box.mobcol { .Box.fold, .Box.mobcol {
h2 { h2 {
position: relative;
cursor: pointer;
padding: 2px 1.3rem 2px 5px; padding: 2px 1.3rem 2px 5px;
margin: 0 -5px 0 -5px; margin: 0 -5px 0 -5px;
&::after {
position: absolute;
right: 4px;
content: '';
top:50%;
font-size: 120%;
font-weight: bold;
transform: translate(0,-50%) rotate(90deg);
}
} }
&.expanded h2::after { &.expanded h2::after {
transform: translate(-25%,-50%) rotate(-90deg);
margin-bottom: dist(0); margin-bottom: dist(0);
} }
}
.Row { .Box.mobcol {
display: none; @include mobcol-base;
}
#ap-box { #ap-box {
display: none; display: none;
} }
&.expanded { &.expanded {
.Row {
display: flex;
}
#ap-box { #ap-box {
display: block; display: block;
} }
+4 -6
View File
@@ -33,7 +33,7 @@
} }
} }
.ansiref { .Row table {
&,td,th{ &,td,th{
border: 1px solid #666; border: 1px solid #666;
} }
@@ -45,7 +45,9 @@
th { th {
background-color: rgba(255,255,255,.1); background-color: rgba(255,255,255,.1);
} }
}
.ansiref {
td:nth-child(1) { td:nth-child(1) {
font-family: monospace; font-family: monospace;
} }
@@ -54,11 +56,7 @@
width: 100%; width: 100%;
td:nth-child(1) { td:nth-child(1) {
width: 9em; width: 6em;
}
td:nth-child(2) {
width: 8em;
} }
} }
} }
+6 -7
View File
@@ -17,7 +17,7 @@ body.term {
#screen { #screen {
@include noselect(); @include noselect();
font-family: monospace; font-family: monospace;
font-size: 16pt; font-size: 20px;//16pt; - 16pt causes vertical breaks in line drawing characters??
white-space: nowrap; white-space: nowrap;
background: #111213; background: #111213;
padding: 6px; padding: 6px;
@@ -27,12 +27,11 @@ body.term {
span { span {
white-space: pre; white-space: pre;
cursor: pointer; cursor: pointer;
&:hover {
outline: 1px solid rgba(#ffffff, 0.4); display: inline-block;
@include media($phone) { line-height: 1.15em; // this adjusts the spacing
outline: 0 none; width: 0.6em; // horizontal spacing, estimated to be about right
} overflow: visible;
}
} }
} }
+14 -6
View File
@@ -451,16 +451,24 @@ static void ICACHE_FLASH_ATTR do_csi_privattr(CSI_Data *opts)
// - discard repeated keypress events between keydown and keyup. // - discard repeated keypress events between keydown and keyup.
ansi_noimpl("Auto-repeat toggle"); ansi_noimpl("Auto-repeat toggle");
} }
else if (n == 9 || (n >= 1000 && n <= 1006)) { else if (n == 9 || (n >= 1000 && n <= 1006) || n == 1015) {
// TODO mouse // 9 - X10 tracking
// 1000 - C11 mouse - Send Mouse X & Y on button press and release. // 1000 - X11 mouse - Send Mouse X & Y on button press and release.
// 1001 - Hilite mouse tracking // 1001 - Hilite mouse tracking - not impl
// 1002 - Cell Motion Mouse Tracking // 1002 - Cell Motion Mouse Tracking
// 1003 - All Motion Mouse Tracking // 1003 - All Motion Mouse Tracking
// 1004 - Send FocusIn/FocusOut events // 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 // 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) { else if (n == 12) {
// TODO Cursor blink on/off // TODO Cursor blink on/off
+1 -13
View File
@@ -15,18 +15,6 @@
#include "screen.h" #include "screen.h"
#include "ansi_parser.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) * Helper function to parse incoming OSC (Operating System Control)
* @param buffer - the OSC body (after OSC and before ST) * @param buffer - the OSC body (after OSC and before ST)
@@ -52,7 +40,7 @@ apars_handle_osc(const char *buffer)
screen_set_title(buffer); screen_set_title(buffer);
} }
else if (n >= 81 && n <= 85) { // numbers chosen to not collide with any xterm supported codes 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 { else {
ansi_noimpl("OSC %d ; %s ST", n, buffer); ansi_noimpl("OSC %d ; %s ST", n, buffer);
+114 -46
View File
@@ -7,11 +7,13 @@
#include "screen.h" #include "screen.h"
#include "uart_buffer.h" #include "uart_buffer.h"
#include "ansi_parser.h" #include "ansi_parser.h"
#include "jstring.h"
#define LOOPBACK 0 #define LOOPBACK 0
#define SOCK_BUF_LEN 1024 #define HB_TIME 1000
static char sock_buff[SOCK_BUF_LEN];
#define SOCK_BUF_LEN 2000
volatile bool notify_available = true; volatile bool notify_available = true;
volatile bool notify_cooldown = false; volatile bool notify_cooldown = false;
@@ -19,6 +21,7 @@ volatile bool notify_cooldown = false;
static ETSTimer notifyContentTim; static ETSTimer notifyContentTim;
static ETSTimer notifyLabelsTim; static ETSTimer notifyLabelsTim;
static ETSTimer notifyCooldownTim; static ETSTimer notifyCooldownTim;
static ETSTimer heartbeatTim;
// we're trying to do a kind of mutex here, without the actual primitives // we're trying to do a kind of mutex here, without the actual primitives
// this might glitch, very rarely. // this might glitch, very rarely.
@@ -39,6 +42,8 @@ notifyContentTimCb(void *arg)
{ {
void *data = NULL; void *data = NULL;
int max_bl, total_bl; int max_bl, total_bl;
char sock_buff[SOCK_BUF_LEN];
cgiWebsockMeasureBacklog(URL_WS_UPDATE, &max_bl, &total_bl); cgiWebsockMeasureBacklog(URL_WS_UPDATE, &max_bl, &total_bl);
if (!notify_available || notify_cooldown || (max_bl > 2048)) { // do not send if we have anything significant backlogged if (!notify_available || notify_cooldown || (max_bl > 2048)) { // do not send if we have anything significant backlogged
@@ -69,6 +74,8 @@ notifyContentTimCb(void *arg)
static void ICACHE_FLASH_ATTR static void ICACHE_FLASH_ATTR
notifyLabelsTimCb(void *arg) notifyLabelsTimCb(void *arg)
{ {
char sock_buff[SOCK_BUF_LEN];
if (!notify_available || notify_cooldown) { if (!notify_available || notify_cooldown) {
// postpone a little // postpone a little
TIMER_START(&notifyLabelsTim, notifyLabelsTimCb, 1, 0); TIMER_START(&notifyLabelsTim, notifyLabelsTimCb, 1, 0);
@@ -117,69 +124,128 @@ void ICACHE_FLASH_ATTR screen_notifyChange(ScreenNotifyChangeTopic topic)
} }
} }
void ICACHE_FLASH_ATTR sendMouseAction(char evt, int y, int x, int button, u8 mods)
{
// one-based
x++;
y++;
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 || evt == 'r')) {
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-1;
}
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 */ /** Socket received a message */
void ICACHE_FLASH_ATTR updateSockRx(Websock *ws, char *data, int len, int flags) void ICACHE_FLASH_ATTR updateSockRx(Websock *ws, char *data, int len, int flags)
{ {
char buf[20];
// Add terminator if missing (seems to randomly happen) // Add terminator if missing (seems to randomly happen)
data[len] = 0; data[len] = 0;
// TODO re-implement those, use single byte markers and B2 encoding
ws_dbg("Sock RX str: %s, len %d", data, len); 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 // pass string verbatim
#if LOOPBACK #if LOOPBACK
for(int i=4;i<strlen(data); i++) { for(int i=4;i<strlen(data); i++) {
ansi_parser(data[i]); ansi_parser(data[i]);
} }
#else #else
UART_SendAsync(data+4, -1); UART_SendAsync(data+1, -1);
#endif #endif
} break;
else if (strstarts(data, "BTN:")) { case 'b':
// send button as low ASCII value 1-9 // action button press
u8 btnNum = (u8) (data[4] - '0'); btnNum = (u8) (data[1]);
if (btnNum > 0 && btnNum < 10) { if (btnNum > 0 && btnNum < 10) {
UART_SendAsync((const char *) &btnNum, 1); UART_SendAsync((const char *) &btnNum, 1); // TODO this is where we use user-configured codes
}
}
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') { break;
if (phase==0) { case 'm':
y=y*10+(c-'0'); case 'p':
} else { case 'r':
x=x*10+(c-'0'); if (mouse_tracking.mode == MTM_NONE) break; // no need to parse, not enabled
}
}
}
if (!screen_isCoordValid(y, x)) { // mouse move
ws_warn("Mouse input at invalid coordinates"); y = parse2B(data+1); // row, 0-based
return; 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
ws_dbg("Screen clicked at row %d, col %d", y+1, x+1); sendMouseAction(c,y,x,b,m);
break;
// Send as 1-based to user default:
sprintf(buf, "\033[%d;%dM", y+1, x+1); ws_warn("Bad command.");
UART_SendAsync(buf, -1);
} }
else { }
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);
} }
} }
@@ -188,4 +254,6 @@ void ICACHE_FLASH_ATTR updateSockConnect(Websock *ws)
{ {
ws_info("Socket connected to "URL_WS_UPDATE); ws_info("Socket connected to "URL_WS_UPDATE);
ws->recvCb = updateSockRx; ws->recvCb = updateSockRx;
TIMER_START(&heartbeatTim, heartbeatTimCb, HB_TIME, 1);
} }
+41
View File
@@ -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);
}
+29
View File
@@ -0,0 +1,29 @@
//
// Created by MightyPork on 2017/09/04.
//
#ifndef ESPTERM_JSTRING_H
#define ESPTERM_JSTRING_H
#include <esp8266.h>
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
+2 -2
View File
@@ -29,7 +29,7 @@ HttpdBuiltInUrl routes[] = {
// --- Web pages --- // --- Web pages ---
ROUTE_TPL_FILE("/", tplScreen, "/term.tpl"), ROUTE_TPL_FILE("/", tplScreen, "/term.tpl"),
ROUTE_TPL_FILE("/about/?", tplAbout, "/about.tpl"), ROUTE_TPL_FILE("/about/?", tplAbout, "/about.tpl"),
ROUTE_FILE("/help/?", "/help.tpl"), ROUTE_FILE("/help/?", "/help.html"),
// --- Sockets --- // --- Sockets ---
ROUTE_CGI("/term/init", cgiTermInitialImage), ROUTE_CGI("/term/init", cgiTermInitialImage),
@@ -46,7 +46,7 @@ HttpdBuiltInUrl routes[] = {
ROUTE_REDIRECT("/cfg/?", "/cfg/wifi"), ROUTE_REDIRECT("/cfg/?", "/cfg/wifi"),
ROUTE_TPL_FILE("/cfg/wifi/?", tplWlan, "/cfg_wifi.tpl"), ROUTE_TPL_FILE("/cfg/wifi/?", tplWlan, "/cfg_wifi.tpl"),
ROUTE_FILE("/cfg/wifi/connecting/?", "/cfg_wifi_conn.tpl"), ROUTE_FILE("/cfg/wifi/connecting/?", "/cfg_wifi_conn.html"),
ROUTE_CGI("/cfg/wifi/scan", cgiWiFiScan), ROUTE_CGI("/cfg/wifi/scan", cgiWiFiScan),
ROUTE_CGI("/cfg/wifi/connstatus", cgiWiFiConnStatus), ROUTE_CGI("/cfg/wifi/connstatus", cgiWiFiConnStatus),
ROUTE_CGI("/cfg/wifi/set", cgiWiFiSetParams), ROUTE_CGI("/cfg/wifi/set", cgiWiFiSetParams),
+46 -57
View File
@@ -5,10 +5,13 @@
#include "sgr.h" #include "sgr.h"
#include "ascii.h" #include "ascii.h"
#include "apars_logging.h" #include "apars_logging.h"
#include "jstring.h"
TerminalConfigBundle * const termconf = &persist.current.termconf; TerminalConfigBundle * const termconf = &persist.current.termconf;
TerminalConfigBundle termconf_scratch; TerminalConfigBundle termconf_scratch;
MouseTrackingConfig mouse_tracking;
// forward declare // forward declare
static void utf8_remap(char* out, char g, char charset); static void utf8_remap(char* out, char g, char charset);
@@ -123,7 +126,8 @@ static volatile int notifyLock = 0;
/** /**
* Restore hard defaults * Restore hard defaults
*/ */
void terminal_restore_defaults(void) void ICACHE_FLASH_ATTR
terminal_restore_defaults(void)
{ {
termconf->default_bg = 0; termconf->default_bg = 0;
termconf->default_fg = 7; termconf->default_fg = 7;
@@ -141,14 +145,16 @@ void terminal_restore_defaults(void)
/** /**
* Apply settings after eg. restore from defaults * Apply settings after eg. restore from defaults
*/ */
void terminal_apply_settings(void) void ICACHE_FLASH_ATTR
terminal_apply_settings(void)
{ {
terminal_apply_settings_noclear(); terminal_apply_settings_noclear();
screen_init(); screen_init();
} }
/** this is used when changing terminal settings that do not affect the screen size */ /** this is used when changing terminal settings that do not affect the screen size */
void terminal_apply_settings_noclear(void) void ICACHE_FLASH_ATTR
terminal_apply_settings_noclear(void)
{ {
bool changed = false; bool changed = false;
@@ -195,6 +201,8 @@ void terminal_apply_settings_noclear(void)
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
screen_init(void) screen_init(void)
{ {
dbg("Screen buffer size = %d bytes", sizeof(screen));
NOTIFY_LOCK(); NOTIFY_LOCK();
screen_reset(); screen_reset();
NOTIFY_DONE(); NOTIFY_DONE();
@@ -252,6 +260,10 @@ screen_reset(void)
scr.vm0 = 0; scr.vm0 = 0;
scr.vm1 = H-1; scr.vm1 = H-1;
mouse_tracking.encoding = MTE_SIMPLE;
mouse_tracking.focus_tracking = false;
mouse_tracking.mode = MTM_NONE;
// size is left unchanged // size is left unchanged
screen_clear(CLEAR_ALL); screen_clear(CLEAR_ALL);
@@ -513,7 +525,7 @@ void screen_delete_lines(unsigned int lines)
NOTIFY_LOCK(); NOTIFY_LOCK();
// shift lines up // shift lines up
int targetEnd = H - 1 - lines; int targetEnd = R1 - lines - 1;
if (targetEnd <= cursor.y) { if (targetEnd <= cursor.y) {
targetEnd = cursor.y; targetEnd = cursor.y;
} else { } else {
@@ -624,6 +636,18 @@ screen_set_title(const char *title)
screen_notifyChange(CHANGE_LABELS); 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 * Shift screen upwards
*/ */
@@ -632,7 +656,7 @@ screen_scroll_up(unsigned int lines)
{ {
NOTIFY_LOCK(); NOTIFY_LOCK();
if (lines >= RH) { if (lines >= RH) {
clear_range(R0*W, (R1+1)*H-1); clear_range(R0*W, (R1+1)*W-1);
goto done; goto done;
} }
@@ -660,7 +684,7 @@ screen_scroll_down(unsigned int lines)
{ {
NOTIFY_LOCK(); NOTIFY_LOCK();
if (lines >= RH) { if (lines >= RH) {
clear_range(R0*W, (R1+1)*H-1); clear_range(R0*W, (R1+1)*W-1);
goto done; goto done;
} }
@@ -709,21 +733,8 @@ void ICACHE_FLASH_ATTR
screen_cursor_set(int y, int x) screen_cursor_set(int y, int x)
{ {
NOTIFY_LOCK(); NOTIFY_LOCK();
if (cursor.origin_mode) { screen_cursor_set_x(x);
y += R0; screen_cursor_set_y(y);
if (y > R1) y = R1;
if (y < R0) y = R0;
}
else {
if (y > H-1) y = H-1;
if (y < 0) y = 0;
}
if (x >= W) x = W - 1;
if (x < 0) x = 0;
cursor.x = x;
cursor.y = y;
clear_invalid_hanging();
NOTIFY_DONE(); NOTIFY_DONE();
} }
@@ -745,8 +756,12 @@ screen_cursor_set_x(int x)
{ {
NOTIFY_LOCK(); NOTIFY_LOCK();
if (x >= W) x = W - 1; if (x >= W) x = W - 1;
if (x < 0) x = 0;
cursor.x = x; cursor.x = x;
clear_invalid_hanging(); // Always clear hanging on cursor set
// hanging happens when the cursor is virtually at col=81, which
// cannot be set using the cursor-set commands.
cursor.hanging = false;
NOTIFY_DONE(); NOTIFY_DONE();
} }
@@ -1039,9 +1054,7 @@ screen_putchar(const char *ch)
case BS: case BS:
if (cursor.x > 0) { if (cursor.x > 0) {
// backspace should go to col 79 if "hanging" after 80 (as if it never actually left the 80th col) // backspace should go to col 79 if "hanging" after 80 (as if it never actually left the 80th col)
if (cursor.hanging) { cursor.hanging = false;
cursor.hanging = false;
}
cursor.x--; cursor.x--;
} }
// we should not wrap around, and backspace should not even clear the cell (verified in xterm) // we should not wrap around, and backspace should not even clear the cell (verified in xterm)
@@ -1154,14 +1167,14 @@ static const u16 codepage_1[] =
0x2663, // 37 % ♣ (5) 0x2663, // 37 % ♣ (5)
0x2660, // 38 & ♠ (6) 0x2660, // 38 & ♠ (6)
0x2022, // 39 ' • (7) - inverse dot and circle left out, can be done with SGR 0x2022, // 39 ' • (7) - inverse dot and circle left out, can be done with SGR
0x231B, // 40 ( ⌛ - hourglass (timer icon) 0x0,//0x231B, // 40 ( ⌛ - hourglass (timer icon)
0x25CB, // 41 ) ○ (9) 0x25CB, // 41 ) ○ (9)
0x21AF, // 42 * ↯ - electricity (lightning monitor...) 0x21AF, // 42 * ↯ - electricity (lightning monitor...)
0x266A, // 43 + ♪ (13) 0x266A, // 43 + ♪ (13)
0x266B, // 44 , ♫ (14) 0x266B, // 44 , ♫ (14)
0x263C, // 45 - ☼ (15) 0x263C, // 45 - ☼ (15)
0x2302, // 46 . ⌂ (127) 0x2302, // 46 . ⌂ (127)
0x2622, // 47 / ☢ - radioactivity (geiger counter...) 0x0,//0x2622, // 47 / ☢ - radioactivity (geiger counter...)
0x2591, // 48 0 ░ (176) - this block is kept aligned and ordered from DOS, moved -128 0x2591, // 48 0 ░ (176) - this block is kept aligned and ordered from DOS, moved -128
0x2592, // 49 1 ▒ (177) 0x2592, // 49 1 ▒ (177)
0x2593, // 50 2 ▓ (178) 0x2593, // 50 2 ▓ (178)
@@ -1220,10 +1233,10 @@ static const u16 codepage_1[] =
0x25BC, // 103 g ▼ (31) 0x25BC, // 103 g ▼ (31)
0x25BA, // 104 h ► (16) 0x25BA, // 104 h ► (16)
0x25C4, // 105 i ◄ (17) 0x25C4, // 105 i ◄ (17)
0x25E2, // 106 j ◢ - added for slanted corners 0x0,//0x25E2, // 106 j ◢ - added for slanted corners
0x25E3, // 107 k ◣ 0x0,//0x25E3, // 107 k ◣
0x25E4, // 108 l ◤ 0x0,//0x25E4, // 108 l ◤
0x25E5, // 109 m ◥ 0x0,//0x25E5, // 109 m ◥
0x256D, // 110 n ╭ - rounded corners 0x256D, // 110 n ╭ - rounded corners
0x256E, // 111 o ╮ 0x256E, // 111 o ╮
0x256F, // 112 p ╯ 0x256F, // 112 p ╯
@@ -1239,8 +1252,8 @@ static const u16 codepage_1[] =
0x0, // 122 z 0x0, // 122 z
0x0, // 123 { 0x0, // 123 {
0x0, // 124 | 0x0, // 124 |
0x2714, // 125 } ✔ - checkboxes or checklist items 0x0,//0x2714, // 125 } ✔ - checkboxes or checklist items
0x2718, // 126 ~ ✘ 0x0,//0x2718, // 126 ~ ✘
}; };
/** /**
@@ -1315,30 +1328,6 @@ struct ScreenSerializeState {
int index; 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 * buffer should be at least 64+5*10+6 long (title + buttons + 6), ie. 120
* @param buffer * @param buffer
+24 -15
View File
@@ -50,7 +50,7 @@
#define SCR_DEF_TITLE "ESPTerm" #define SCR_DEF_TITLE "ESPTerm"
/** Maximum screen size (determines size of the static data array) */ /** Maximum screen size (determines size of the static data array) */
#define MAX_SCREEN_SIZE (80*26) #define MAX_SCREEN_SIZE (80*25)
#define TERMCONF_VERSION 1 #define TERMCONF_VERSION 1
@@ -80,6 +80,29 @@ extern TerminalConfigBundle * const termconf;
*/ */
extern TerminalConfigBundle termconf_scratch; extern TerminalConfigBundle termconf_scratch;
enum MTM {
MTM_NONE = 0,
MTM_X10 = 1,
MTM_NORMAL = 2,
MTM_BUTTON_MOTION = 3,
MTM_ANY_MOTION = 4,
};
enum MTE {
MTE_SIMPLE = 0,
MTE_UTF8 = 1,
MTE_SGR = 2,
MTE_URXVT = 3,
};
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. */ /** Restore default settings to termconf. Does not apply or copy to scratch. */
void terminal_restore_defaults(void); void terminal_restore_defaults(void);
/** Apply settings, redraw (clears the screen) */ /** Apply settings, redraw (clears the screen) */
@@ -97,17 +120,6 @@ void screen_set_button_text(int num, const char *text);
// --- Encoding --- // --- Encoding ---
typedef struct {
u8 lsb;
u8 msb;
} WordB2;
typedef struct {
u8 lsb;
u8 msb;
u8 xsb;
} WordB3;
typedef enum { typedef enum {
CS_USASCII = 'B', CS_USASCII = 'B',
CS_UKASCII = 'A', CS_UKASCII = 'A',
@@ -115,9 +127,6 @@ typedef enum {
CS_DOS_437 = '1', CS_DOS_437 = '1',
} CHARSET; } 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); httpd_cgi_state screenSerializeToBuffer(char *buffer, size_t buf_len, void **data);
void screenSerializeLabelsToBuffer(char *buffer, size_t buf_len); void screenSerializeLabelsToBuffer(char *buffer, size_t buf_len);
+1 -1
View File
@@ -4,7 +4,7 @@
#include "ansi_parser.h" #include "ansi_parser.h"
#include "syscfg.h" #include "syscfg.h"
#define LOGBUF_SIZE 1500 #define LOGBUF_SIZE 2048
static char logbuf[LOGBUF_SIZE]; static char logbuf[LOGBUF_SIZE];
static u32 lb_nw = 1; static u32 lb_nw = 1;
static u32 lb_ls = 0; static u32 lb_ls = 0;
+11 -11
View File
@@ -45,9 +45,9 @@ UART_AsyncBufferInit(uint32 buf_size)
return NULL; return NULL;
} }
else { else {
struct UartBuffer *pBuff = (struct UartBuffer *) os_malloc(sizeof(struct UartBuffer)); struct UartBuffer *pBuff = (struct UartBuffer *) malloc(sizeof(struct UartBuffer));
pBuff->UartBuffSize = buf_size; pBuff->UartBuffSize = buf_size;
pBuff->pUartBuff = (uint8 *) os_malloc(pBuff->UartBuffSize); pBuff->pUartBuff = (uint8 *) malloc(pBuff->UartBuffSize);
pBuff->pInPos = pBuff->pUartBuff; pBuff->pInPos = pBuff->pUartBuff;
pBuff->pOutPos = pBuff->pUartBuff; pBuff->pOutPos = pBuff->pUartBuff;
pBuff->Space = (uint16) pBuff->UartBuffSize; pBuff->Space = (uint16) pBuff->UartBuffSize;
@@ -68,17 +68,17 @@ static void UART_WriteToAsyncBuffer(struct UartBuffer *pCur, const char *pdata,
uint16 tail_len = (uint16) (pCur->pUartBuff + pCur->UartBuffSize - pCur->pInPos); uint16 tail_len = (uint16) (pCur->pUartBuff + pCur->UartBuffSize - pCur->pInPos);
if (tail_len >= data_len) { //do not need to loop back the queue if (tail_len >= data_len) { //do not need to loop back the queue
os_memcpy(pCur->pInPos, pdata, data_len); memcpy(pCur->pInPos, pdata, data_len);
pCur->pInPos += (data_len); pCur->pInPos += (data_len);
pCur->pInPos = (pCur->pUartBuff + (pCur->pInPos - pCur->pUartBuff) % pCur->UartBuffSize); pCur->pInPos = (pCur->pUartBuff + (pCur->pInPos - pCur->pUartBuff) % pCur->UartBuffSize);
pCur->Space -= data_len; pCur->Space -= data_len;
} }
else { else {
os_memcpy(pCur->pInPos, pdata, tail_len); memcpy(pCur->pInPos, pdata, tail_len);
pCur->pInPos += (tail_len); pCur->pInPos += (tail_len);
pCur->pInPos = (pCur->pUartBuff + (pCur->pInPos - pCur->pUartBuff) % pCur->UartBuffSize); pCur->pInPos = (pCur->pUartBuff + (pCur->pInPos - pCur->pUartBuff) % pCur->UartBuffSize);
pCur->Space -= tail_len; pCur->Space -= tail_len;
os_memcpy(pCur->pInPos, pdata + tail_len, data_len - tail_len); memcpy(pCur->pInPos, pdata + tail_len, data_len - tail_len);
pCur->pInPos += (data_len - tail_len); pCur->pInPos += (data_len - tail_len);
pCur->pInPos = (pCur->pUartBuff + (pCur->pInPos - pCur->pUartBuff) % pCur->UartBuffSize); pCur->pInPos = (pCur->pUartBuff + (pCur->pInPos - pCur->pUartBuff) % pCur->UartBuffSize);
pCur->Space -= (data_len - tail_len); pCur->Space -= (data_len - tail_len);
@@ -93,8 +93,8 @@ static void UART_WriteToAsyncBuffer(struct UartBuffer *pCur, const char *pdata,
*******************************************************************************/ *******************************************************************************/
void ICACHE_FLASH_ATTR UART_FreeAsyncBuffer(struct UartBuffer *pBuff) void ICACHE_FLASH_ATTR UART_FreeAsyncBuffer(struct UartBuffer *pBuff)
{ {
os_free(pBuff->pUartBuff); free(pBuff->pUartBuff);
os_free(pBuff); free(pBuff);
} }
u16 ICACHE_FLASH_ATTR UART_AsyncRxCount(void) u16 ICACHE_FLASH_ATTR UART_AsyncRxCount(void)
@@ -116,19 +116,19 @@ UART_ReadAsync(char *pdata, uint16 data_len)
uint16 len_tmp = 0; uint16 len_tmp = 0;
len_tmp = ((data_len > buf_len) ? buf_len : data_len); len_tmp = ((data_len > buf_len) ? buf_len : data_len);
if (pRxBuffer->pOutPos <= pRxBuffer->pInPos) { if (pRxBuffer->pOutPos <= pRxBuffer->pInPos) {
os_memcpy(pdata, pRxBuffer->pOutPos, len_tmp); memcpy(pdata, pRxBuffer->pOutPos, len_tmp);
pRxBuffer->pOutPos += len_tmp; pRxBuffer->pOutPos += len_tmp;
pRxBuffer->Space += len_tmp; pRxBuffer->Space += len_tmp;
} }
else { else {
if (len_tmp > tail_len) { if (len_tmp > tail_len) {
os_memcpy(pdata, pRxBuffer->pOutPos, tail_len); memcpy(pdata, pRxBuffer->pOutPos, tail_len);
pRxBuffer->pOutPos += tail_len; pRxBuffer->pOutPos += tail_len;
pRxBuffer->pOutPos = (pRxBuffer->pUartBuff + pRxBuffer->pOutPos = (pRxBuffer->pUartBuff +
(pRxBuffer->pOutPos - pRxBuffer->pUartBuff) % pRxBuffer->UartBuffSize); (pRxBuffer->pOutPos - pRxBuffer->pUartBuff) % pRxBuffer->UartBuffSize);
pRxBuffer->Space += tail_len; pRxBuffer->Space += tail_len;
os_memcpy(pdata + tail_len, pRxBuffer->pOutPos, len_tmp - tail_len); memcpy(pdata + tail_len, pRxBuffer->pOutPos, len_tmp - tail_len);
pRxBuffer->pOutPos += (len_tmp - tail_len); pRxBuffer->pOutPos += (len_tmp - tail_len);
pRxBuffer->pOutPos = (pRxBuffer->pUartBuff + pRxBuffer->pOutPos = (pRxBuffer->pUartBuff +
(pRxBuffer->pOutPos - pRxBuffer->pUartBuff) % pRxBuffer->UartBuffSize); (pRxBuffer->pOutPos - pRxBuffer->pUartBuff) % pRxBuffer->UartBuffSize);
@@ -136,7 +136,7 @@ UART_ReadAsync(char *pdata, uint16 data_len)
} }
else { else {
//os_printf("case 3 in rx deq\n\r"); //os_printf("case 3 in rx deq\n\r");
os_memcpy(pdata, pRxBuffer->pOutPos, len_tmp); memcpy(pdata, pRxBuffer->pOutPos, len_tmp);
pRxBuffer->pOutPos += len_tmp; pRxBuffer->pOutPos += len_tmp;
pRxBuffer->pOutPos = (pRxBuffer->pUartBuff + pRxBuffer->pOutPos = (pRxBuffer->pUartBuff +
(pRxBuffer->pOutPos - pRxBuffer->pUartBuff) % pRxBuffer->UartBuffSize); (pRxBuffer->pOutPos - pRxBuffer->pUartBuff) % pRxBuffer->UartBuffSize);
+3 -3
View File
@@ -6,11 +6,11 @@
#define ESP_VT100_FIRMWARE_VERSION_H #define ESP_VT100_FIRMWARE_VERSION_H
#define FW_V_MAJOR 0 #define FW_V_MAJOR 0
#define FW_V_MINOR 6 #define FW_V_MINOR 7
#define FW_V_PATCH 9 #define FW_V_PATCH 1
#define FIRMWARE_VERSION STR(FW_V_MAJOR) "." STR(FW_V_MINOR) "." STR(FW_V_PATCH) "+" GIT_HASH #define FIRMWARE_VERSION STR(FW_V_MAJOR) "." STR(FW_V_MINOR) "." STR(FW_V_PATCH) "+" GIT_HASH
#define FIRMWARE_VERSION_NUM (FW_V_MAJOR*10000 + FW_V_MINOR*100 + FW_V_PATCH) // this is used in ID queries #define FIRMWARE_VERSION_NUM (FW_V_MAJOR*10000 + FW_V_MINOR*100 + FW_V_PATCH) // this is used in ID queries
#define TERMINAL_GITHUB_REPO "https://github.com/MightyPork/esp-vt100-firmware" #define TERMINAL_GITHUB_REPO "https://github.com/MightyPork/ESPTerm"
#endif //ESP_VT100_FIRMWARE_VERSION_H #endif //ESP_VT100_FIRMWARE_VERSION_H