Compare commits
117
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
184ef1ec36 | ||
|
|
99b07399b4 | ||
|
|
e46abe175f | ||
|
|
48cd9c1a2a | ||
|
|
07d1a3d3b4 | ||
|
|
44ea1e5b49 | ||
|
|
6fdb23531b | ||
|
|
116201e232 | ||
|
|
a78830f805 | ||
|
|
3f198f8360 | ||
|
|
f693373a34 | ||
|
|
0af62f47e6 | ||
|
|
a673843a63 | ||
|
|
023a2db01a | ||
|
|
979f956e7e | ||
|
|
5f6566a6fd | ||
|
|
6d98d2c6da | ||
|
|
9fa9cb0ca2 | ||
|
|
ba9c757cdc | ||
|
|
d2b2b89593 | ||
|
|
6aa4747d3e | ||
|
|
5eec7d2bf6 | ||
|
|
8028f14387 | ||
|
|
daba5340f7 | ||
|
|
a82e14961c | ||
|
|
6c9ae7e126 | ||
|
|
a3c84f9a8f | ||
|
|
7aa3cd7f75 | ||
|
|
58ed6098c4 | ||
|
|
16a36a3d26 | ||
|
|
9d1cefeaab | ||
|
|
357600de8f | ||
|
|
fe699c7918 | ||
|
|
4edaa0687d | ||
|
|
f8ebae044a | ||
|
|
9e9539472c | ||
|
|
b57e0ee75b | ||
|
|
65e34a1634 | ||
|
|
6f32a6ef1a | ||
|
|
d3468a109c | ||
|
|
8f82b81434 | ||
|
|
653dc55d85 | ||
|
|
dda469ff21 | ||
|
|
3515e178cb | ||
|
|
5fd5c92390 | ||
|
|
e3ae34a932 | ||
|
|
423022b8bc | ||
|
|
fce3995800 | ||
|
|
67625a9317 | ||
|
|
35e1a788f0 | ||
|
|
dd68c112b0 | ||
|
|
2c7c496862 | ||
|
|
73c943bb97 | ||
|
|
beda5e67c0 | ||
|
|
357c81ba07 | ||
|
|
acd8801d33 | ||
|
|
5c57ea3b28 | ||
|
|
0e73f57da1 | ||
|
|
1f18c7d1f6 | ||
|
|
2a76eaee8e | ||
|
|
3b8cfd41e3 | ||
|
|
09cc66c75c | ||
|
|
7947b3a88c | ||
|
|
3355295aa3 | ||
|
|
87e0030be8 | ||
|
|
6d9d068268 | ||
|
|
276af04945 | ||
|
|
8d9c5bb246 | ||
|
|
59f26adf68 | ||
|
|
5995988a9b | ||
|
|
fe714bf473 | ||
|
|
68747d493c | ||
|
|
184b186b85 | ||
|
|
af47249492 | ||
|
|
e22ae1e66a | ||
|
|
bf1507b624 | ||
|
|
b5378a6626 | ||
|
|
9cac029e58 | ||
|
|
8117229926 | ||
|
|
04b75b8218 | ||
|
|
89fca5c40a | ||
|
|
ca410736f7 | ||
|
|
2ca8bf1483 | ||
|
|
35393827a9 | ||
|
|
ea84d1889d | ||
|
|
bc41a21f23 | ||
|
|
e48180ac10 | ||
|
|
e47a5477f9 | ||
|
|
e944a994a7 | ||
|
|
6535b49551 | ||
|
|
2cb8586c94 | ||
|
|
ec1cebdd4c | ||
|
|
af5a3a64ed | ||
|
|
7b94eef448 | ||
|
|
c8067122d5 | ||
|
|
12385b7a5f | ||
|
|
5564147f67 | ||
|
|
32ba7dc0d7 | ||
|
|
80721c1715 | ||
|
|
e22d3f26d9 | ||
|
|
24a6a0dfd3 | ||
|
|
8a182f072d | ||
|
|
76b447d03e | ||
|
|
319427ba2e | ||
|
|
f8c60b0668 | ||
|
|
4eaee9389d | ||
|
|
0a731bd35e | ||
|
|
0b3cfe0e74 | ||
|
|
4551ad6dbe | ||
|
|
3f4b8a94f1 | ||
|
|
373affb582 | ||
|
|
b48bb12067 | ||
|
|
110f461cf5 | ||
|
|
3ae1451821 | ||
|
|
d711909812 | ||
|
|
76167b8014 | ||
|
|
e42407ad62 |
+30
-9
@@ -5,7 +5,7 @@ cmake_minimum_required(VERSION 3.7)
|
||||
# Do not attempt to build it with cmake, use make instead. #
|
||||
###################################################################
|
||||
|
||||
project(esp_vt100_firmware)
|
||||
project(ESPTerm)
|
||||
|
||||
set(CMAKE_CXX_STANDARD GNU99)
|
||||
|
||||
@@ -86,7 +86,6 @@ set(SOURCE_FILES
|
||||
esp_iot_sdk_v1.5.2/include/json/jsonparse.h
|
||||
esp_iot_sdk_v1.5.2/include/json/json.h
|
||||
|
||||
include/uart_hw.h
|
||||
include/user_config.h
|
||||
include/ets_sys_extra.h
|
||||
user/io.c
|
||||
@@ -97,10 +96,9 @@ set(SOURCE_FILES
|
||||
user/cgi_persist.h
|
||||
user/cgi_network.c
|
||||
user/cgi_network.h
|
||||
user/cgi_appcfg.c
|
||||
user/cgi_appcfg.h
|
||||
user/cgi_ping.c
|
||||
user/cgi_reset.c
|
||||
user/cgi_term_cfg.c
|
||||
user/cgi_term_cfg.h
|
||||
user/cgi_system.c
|
||||
user/uart_driver.c
|
||||
user/uart_handler.c
|
||||
user/ansi_parser.c
|
||||
@@ -112,6 +110,8 @@ set(SOURCE_FILES
|
||||
user/serial.h
|
||||
user/routes.c
|
||||
user/routes.h
|
||||
user/uart_asyncdrv.c-
|
||||
user/uart_asyncdrv.h-
|
||||
user/cgi_main.c
|
||||
user/cgi_main.h
|
||||
user/cgi_sockets.c
|
||||
@@ -119,11 +119,29 @@ set(SOURCE_FILES
|
||||
user/cgi_sockets.h
|
||||
user/ansi_parser_callbacks.c
|
||||
user/ansi_parser_callbacks.h
|
||||
user/user_main.h
|
||||
user/wifimgr.c
|
||||
user/wifimgr.h
|
||||
user/persist.c
|
||||
user/persist.h include/helpers.h)
|
||||
user/persist.h
|
||||
include/helpers.h
|
||||
user/syscfg.c
|
||||
user/syscfg.h
|
||||
user/ascii.h
|
||||
user/sgr.h
|
||||
user/apars_utf8.c
|
||||
user/apars_utf8.h
|
||||
user/apars_logging.h
|
||||
user/version.h
|
||||
user/apars_csi.c
|
||||
user/apars_csi.h
|
||||
user/apars_short.c
|
||||
user/apars_short.h
|
||||
user/apars_string.c
|
||||
user/apars_string.h
|
||||
user/apars_osc.c
|
||||
user/apars_osc.h
|
||||
user/apars_dcs.c
|
||||
user/apars_dcs.h user/uart_buffer.c user/uart_buffer.h user/jstring.c user/jstring.h user/character_sets.h)
|
||||
|
||||
include_directories(include)
|
||||
include_directories(user)
|
||||
@@ -143,6 +161,9 @@ add_definitions(
|
||||
-DICACHE_RODATA_ATTR=
|
||||
-DFLAG_GZIP=2
|
||||
-DADMIN_PASSWORD="asdf"
|
||||
-DGIT_HASH="blabla"
|
||||
-DDEBUG_HEAP=1
|
||||
-DDEBUG_MALLOC=1
|
||||
-DESPFS_HEATSHRINK)
|
||||
|
||||
add_executable(esp_vt100_firmware ${SOURCE_FILES})
|
||||
add_executable(ESPTerm ${SOURCE_FILES})
|
||||
|
||||
@@ -65,7 +65,14 @@ LIBS += esphttpd
|
||||
# compiler flags using during compilation of source files
|
||||
CFLAGS = -Os -ggdb -std=gnu99 -Werror -Wpointer-arith -Wundef -Wall -Wl,-EL -fno-inline-functions \
|
||||
-nostdlib -mlongcalls -mtext-section-literals -D__ets__ -DICACHE_FLASH \
|
||||
-Wno-address -Wno-unused -DHTTPD_MAX_BACKLOG_SIZE=8192 -DADMIN_PASSWORD=$(ADMIN_PASSWORD)
|
||||
-Wno-address -Wno-unused
|
||||
|
||||
CFLAGS += -DGIT_HASH='"$(shell git rev-parse --short HEAD)"'
|
||||
CFLAGS += -DADMIN_PASSWORD=$(ADMIN_PASSWORD)
|
||||
|
||||
ifdef GLOBAL_CFLAGS
|
||||
CFLAGS += $(GLOBAL_CFLAGS)
|
||||
endif
|
||||
|
||||
# linker flags used to generate the main object file
|
||||
LDFLAGS = -nostdlib -Wl,--no-check-sections -u call_user_start -Wl,-static
|
||||
@@ -196,7 +203,7 @@ libesphttpd/Makefile:
|
||||
$(Q) [[ -e "libesphttpd/Makefile" ]] || echo -e "\e[31mlibesphttpd submodule missing.\nIf build fails, run \"git submodule init\" and \"git submodule update\".\e[0m"
|
||||
|
||||
libesphttpd: libesphttpd/Makefile
|
||||
$(Q) make -C libesphttpd USE_OPENSDK=$(USE_OPENSDK)
|
||||
$(Q) make -C libesphttpd USE_OPENSDK=$(USE_OPENSDK) SERVERNAME_PREFIX="ESPTerm "
|
||||
|
||||
$(APP_AR): libesphttpd $(OBJ)
|
||||
$(vecho) "AR $@"
|
||||
|
||||
@@ -1,76 +1,100 @@
|
||||
# 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
|
||||
[MightyPork/esphttpd](https://github.com/MightyPork/esphttpd) and
|
||||
[MightyPork/libesphttpd](https://github.com/MightyPork/libesphttpd) respectively.
|
||||
ESPTerm can add remote access via WiFi to any embeded project, all you need is a serial port.
|
||||
|
||||
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
|
||||
Arduino, AVR, PIC, STM8, STM32, mbed etc, anything with UART, even your USB-serial dongle will work.
|
||||
## ESPTerm features
|
||||
|
||||
Connect it to the master device via UART and use the terminal on the built-in web page for debug logging,
|
||||
remote control etc. It works like a simple LCD screen, in a way.
|
||||
- **Robust WiFi configuration interface**
|
||||
- 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
|
||||
the webpage (and keyboard on PC). There is some rudimentary touch input as well.
|
||||
\*) Linux console applications run via agetty at ttyUSB0 were used for testing, obviously you'll be better off using SSH for remote shell
|
||||
|
||||
The screen size is adjustable up to 25x80 (via a special control sequence) and uses 16 standard colors
|
||||
(8 dark and 8 bright). Both default size and colors can be configured in the settings.
|
||||
ESPTerm firmware is written in C and is based on SpriteTM's `libesphttpd` http server library, forked by MightyPork to
|
||||
[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.
|
||||
|
||||
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.
|
||||
### Pins
|
||||
|
||||
- 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
|
||||
|
||||
### Installation for development
|
||||
|
||||
- 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
|
||||
`make toolchain esptool libhal STANDALONE=n`.
|
||||
|
||||
`make toolchain esptool libhal STANDALONE=n`.
|
||||
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)
|
||||
|
||||
- Set up udev rules so you have access to ttyUSB0 without root, eg:
|
||||
|
||||
```
|
||||
KERNEL=="tty[A-Z]*[0-9]*", GROUP="uucp", MODE="0666"
|
||||
```
|
||||
|
||||
- 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.
|
||||
|
||||
- Install Ruby and then the `sass` package with `gem install sass` (or try some other implementation, such as
|
||||
`sassc`)
|
||||
|
||||
- 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.
|
||||
@@ -80,7 +104,7 @@ than welcome!
|
||||
### Web resources
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
To flash, just run `make flash`.
|
||||
|
||||
[releases]: https://github.com/MightyPork/esp-vt100-firmware/releases
|
||||
|
||||
@@ -19,3 +19,8 @@ rm html/css/app.css.map
|
||||
|
||||
cp html_orig/img/* html/img/
|
||||
cp html_orig/favicon.ico html/favicon.ico
|
||||
|
||||
# cleanup
|
||||
find html/ -name "*.orig" -delete
|
||||
find html/ -name "*.xcf" -delete
|
||||
find html/ -name "*~" -delete
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -40,3 +40,18 @@ ESP_SPI_FLASH_SIZE_K = 1024
|
||||
|
||||
# Admin password, used to store settings to flash as defaults
|
||||
ADMIN_PASSWORD = "19738426"
|
||||
|
||||
GLOBAL_CFLAGS = \
|
||||
-DDEBUG_ROUTER=0 \
|
||||
-DDEBUG_CAPTDNS=0 \
|
||||
-DDEBUG_HTTP=1 \
|
||||
-DDEBUG_ESPFS=0 \
|
||||
-DDEBUG_WS=0 \
|
||||
-DDEBUG_ANSI=1 \
|
||||
-DDEBUG_ANSI_NOIMPL=1 \
|
||||
-DHTTPD_MAX_BACKLOG_SIZE=8192 \
|
||||
-DHTTPD_MAX_HEAD_LEN=1024 \
|
||||
-DHTTPD_MAX_POST_LEN=512 \
|
||||
-DDEBUG_INPUT=0 \
|
||||
-DDEBUG_HEAP=1 \
|
||||
-DDEBUG_MALLOC=0
|
||||
|
||||
@@ -3,60 +3,69 @@
|
||||
/**
|
||||
* Those replacements are done by the development server to test it locally
|
||||
* without esphttpd. This is needed mainly for places where the replacements
|
||||
* are given to JavaScript, to avoid syntax errors with %%
|
||||
* are given to JavaScript, to avoid syntax errors with
|
||||
*/
|
||||
return [
|
||||
'%term_title%' => 'ESP8266 Wireless Terminal',
|
||||
'term_title' => 'ESPTerm local debug',
|
||||
|
||||
'%btn1%' => '1',
|
||||
'%btn2%' => '2',
|
||||
'%btn3%' => '3',
|
||||
'%btn4%' => '4',
|
||||
'%btn5%' => '5',
|
||||
'btn1' => '1',
|
||||
'btn2' => '2',
|
||||
'btn3' => '3',
|
||||
'btn4' => '',
|
||||
'btn5' => '5',
|
||||
'labels_seq' => 'TESPTerm local debug1235',
|
||||
|
||||
'%screenData%' => '{
|
||||
"w": 26, "h": 10,
|
||||
"x": 0, "y": 0,
|
||||
"cv": 1,
|
||||
"screen": "70 t259"
|
||||
}',
|
||||
'parser_tout_ms' => 10,
|
||||
'display_tout_ms' => 15,
|
||||
'display_cooldown_ms' => 35,
|
||||
'fn_alt_mode' => '1',
|
||||
|
||||
'%ap_enable%' => '1',
|
||||
'%tpw%' => '60',
|
||||
'%ap_channel%' => '7',
|
||||
'%ap_ssid%' => 'ESP-123456',
|
||||
'%ap_password%' => 'Passw0rd!',
|
||||
'%ap_hidden%' => '0',
|
||||
'%sta_ssid%' => 'Chlivek',
|
||||
'%sta_password%' => 'windows XP is The Best',
|
||||
'%sta_active_ip%' => '1.2.3.4',
|
||||
'%sta_active_ssid%' => 'Chlivek',
|
||||
'opmode' => '2',
|
||||
'sta_enable' => '0',
|
||||
'ap_enable' => '1',
|
||||
|
||||
'%sta_enable%' => '1',
|
||||
'%opmode%' => '3',
|
||||
'%vers_fw%' => '1.2.3',
|
||||
'%date%' => date('Y-m-d'),
|
||||
'%time%' => date('G:i'),
|
||||
'%vers_httpd%' => '4.5.6',
|
||||
'%vers_sdk%' => '1.52',
|
||||
'%githubrepo%' => 'https://github.com/MightyPork/esp-vt100-firmware',
|
||||
'tpw' => '60',
|
||||
'ap_channel' => '7',
|
||||
'ap_ssid' => 'ESP-123456',
|
||||
'ap_password' => 'Passw0rd!',
|
||||
'ap_hidden' => '0',
|
||||
|
||||
'%ap_dhcp_time%' => '120',
|
||||
'%ap_dhcp_start%' => '192.168.4.100',
|
||||
'%ap_dhcp_end%' => '192.168.4.200',
|
||||
'%ap_addr_ip%' => '192.168.4.1',
|
||||
'%ap_addr_mask%' => '255.255.255.0',
|
||||
'sta_ssid' => 'Chlivek',
|
||||
'sta_password' => 'windows XP is The Best',
|
||||
'sta_active_ip' => '1.2.3.4',
|
||||
'sta_active_ssid' => 'Chlivek',
|
||||
|
||||
'%sta_dhcp_enable%' => '1',
|
||||
'%sta_addr_ip%' => '192.168.0.33',
|
||||
'%sta_addr_mask%' => '255.255.255.0',
|
||||
'%sta_addr_gw%' => '192.168.0.1',
|
||||
'vers_fw' => '1.2.3',
|
||||
'date' => date('Y-m-d'),
|
||||
'time' => date('G:i'),
|
||||
'vers_httpd' => '4.5.6',
|
||||
'vers_sdk' => '1.52',
|
||||
'githubrepo' => 'https://github.com/MightyPork/esp-vt100-firmware',
|
||||
|
||||
'%sta_mac%' => 'ab:cd:ef:01:23:45',
|
||||
'%ap_mac%' => '01:23:45:ab:cd:ef',
|
||||
'ap_dhcp_time' => '120',
|
||||
'ap_dhcp_start' => '192.168.4.100',
|
||||
'ap_dhcp_end' => '192.168.4.200',
|
||||
'ap_addr_ip' => '192.168.4.1',
|
||||
'ap_addr_mask' => '255.255.255.0',
|
||||
|
||||
'%term_width%' => '26',
|
||||
'%term_height%' => '10',
|
||||
'%default_bg%' => '0',
|
||||
'%default_fg%' => '7',
|
||||
'sta_dhcp_enable' => '1',
|
||||
'sta_addr_ip' => '192.168.0.33',
|
||||
'sta_addr_mask' => '255.255.255.0',
|
||||
'sta_addr_gw' => '192.168.0.1',
|
||||
|
||||
'sta_mac' => 'ab:cd:ef:01:23:45',
|
||||
'ap_mac' => '01:23:45:ab:cd:ef',
|
||||
|
||||
'term_width' => '26',
|
||||
'term_height' => '10',
|
||||
'default_bg' => '0',
|
||||
'default_fg' => '7',
|
||||
'show_buttons' => '1',
|
||||
'show_config_links' => '1',
|
||||
|
||||
'uart_baud' => 115200,
|
||||
'uart_stopbits' => 1,
|
||||
'uart_parity' => 2,
|
||||
|
||||
'theme' => 5,
|
||||
];
|
||||
|
||||
+10
-6
@@ -18,6 +18,9 @@ if (! function_exists('pg')) {
|
||||
}
|
||||
}
|
||||
|
||||
pg('cfg_term', 'cfg', 'terminal', '/cfg/term');
|
||||
pg('term_set', 'api', '', '/cfg/term/set');
|
||||
|
||||
pg('cfg_wifi', 'cfg', 'wifi', '/cfg/wifi');
|
||||
pg('cfg_wifi_conn', '', '', '/cfg/wifi/connecting');
|
||||
pg('wifi_connstatus', 'api', '', '/cfg/wifi/connstatus');
|
||||
@@ -27,18 +30,19 @@ pg('wifi_scan', 'api', '', '/cfg/wifi/scan');
|
||||
pg('cfg_network', 'cfg', 'network', '/cfg/network');
|
||||
pg('network_set', 'api', '', '/cfg/network/set');
|
||||
|
||||
pg('cfg_app', 'cfg', 'terminal', '/cfg/app');
|
||||
pg('app_set', 'api', '', '/cfg/app/set');
|
||||
pg('cfg_system', 'cfg', 'configure', '/cfg/system');
|
||||
pg('system_set', 'api', '', '/cfg/system/set');
|
||||
|
||||
pg('cfg_admin', 'cfg', 'persist', '/cfg/admin');
|
||||
pg('write_defaults', 'api', '', '/cfg/admin/write_defaults');
|
||||
pg('restore_defaults', 'api', '', '/cfg/admin/restore_defaults');
|
||||
pg('restore_hard', 'api', '', '/cfg/admin/restore_hard');
|
||||
pg('write_defaults', 'api', '', '/cfg/system/write_defaults');
|
||||
pg('restore_defaults', 'api', '', '/cfg/system/restore_defaults');
|
||||
pg('restore_hard', 'api', '', '/cfg/system/restore_hard');
|
||||
|
||||
pg('help', 'cfg page-help', 'help', '/help');
|
||||
pg('about', 'cfg page-about', 'about', '/about');
|
||||
pg('term', 'term', '', '/', 'title.term');
|
||||
|
||||
pg('reset_screen', 'api', '', '/system/cls', 'title.term');
|
||||
|
||||
// ajax API
|
||||
|
||||
return $pages;
|
||||
|
||||
@@ -52,6 +52,12 @@ function e($s)
|
||||
return htmlspecialchars($s, ENT_HTML5 | ENT_QUOTES);
|
||||
}
|
||||
|
||||
function je($s)
|
||||
{
|
||||
return htmlspecialchars(json_encode($s), ENT_HTML5);
|
||||
}
|
||||
|
||||
|
||||
function tr($key)
|
||||
{
|
||||
global $_messages;
|
||||
@@ -69,3 +75,78 @@ function include_str($code)
|
||||
fclose($tmp);
|
||||
return $ret;
|
||||
}
|
||||
|
||||
if (!function_exists('utf8')) {
|
||||
function utf8($num)
|
||||
{
|
||||
if($num<=0x7F) return chr($num);
|
||||
if($num<=0x7FF) return chr(($num>>6)+192).chr(($num&63)+128);
|
||||
if($num<=0xFFFF) return chr(($num>>12)+224).chr((($num>>6)&63)+128).chr(($num&63)+128);
|
||||
if($num<=0x1FFFFF) return chr(($num>>18)+240).chr((($num>>12)&63)+128).chr((($num>>6)&63)+128).chr(($num&63)+128);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('load_esp_charsets')) {
|
||||
function load_esp_charsets() {
|
||||
$chsf = __DIR__ . '/../user/character_sets.h';
|
||||
$re_table = '/\/\/ %%BEGIN:(.)%%\s*(.*?)\s*\/\/ %%END:\1%%/s';
|
||||
preg_match_all($re_table, file_get_contents($chsf), $m_tbl);
|
||||
|
||||
$re_bounds = '/#define CODEPAGE_(.)_BEGIN\s+(\d+)\n#define CODEPAGE_\1_END\s+(\d+)/';
|
||||
preg_match_all($re_bounds, file_get_contents($chsf), $m_bounds);
|
||||
|
||||
$cps = [];
|
||||
|
||||
foreach ($m_tbl[2] as $i => $str) {
|
||||
$name = $m_tbl[1][$i];
|
||||
$start = intval($m_bounds[2][$i]);
|
||||
$table = [];
|
||||
$str = preg_replace('/,\s*\/\/[^\n]*/', '', $str);
|
||||
$rows = explode("\n", $str);
|
||||
$rows = array_map('trim', $rows);
|
||||
|
||||
foreach($rows as $j => $v) {
|
||||
if (strpos($v, '0x') === 0) {
|
||||
$v = substr($v, 2);
|
||||
$v = hexdec($v);
|
||||
} else {
|
||||
$v = intval($v);
|
||||
}
|
||||
$ascii = $start+$j;
|
||||
$table[] = [
|
||||
$ascii,
|
||||
chr($ascii),
|
||||
utf8($v==0? $ascii :$v),
|
||||
];
|
||||
}
|
||||
$cps[$name] = $table;
|
||||
}
|
||||
return $cps;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('tplSubs')) {
|
||||
function tplSubs($str, $reps)
|
||||
{
|
||||
return preg_replace_callback('/%(j:|js:|h:|html:)?([a-z0-9-_.]+)%/i', function ($m) use ($reps) {
|
||||
$key = $m[2];
|
||||
if (array_key_exists($key, $reps)) {
|
||||
$val = $reps[$key];
|
||||
} else {
|
||||
$val = '';
|
||||
}
|
||||
switch ($m[1]) {
|
||||
case 'j:':
|
||||
case 'js:':
|
||||
$v = json_encode($val);
|
||||
return substr($v, 1, strlen($v) - 2);
|
||||
case 'h:':
|
||||
case 'html:':
|
||||
return htmlspecialchars($val);
|
||||
default:
|
||||
return $val;
|
||||
}
|
||||
}, $str);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ function process_html($s) {
|
||||
return $s;
|
||||
}
|
||||
|
||||
$no_tpl_files = ['help', 'cfg_wifi_conn'];
|
||||
|
||||
ob_start();
|
||||
foreach($_pages as $_k => $p) {
|
||||
if ($p->bodyclass == 'api') continue;
|
||||
@@ -30,7 +32,7 @@ foreach($_pages as $_k => $p) {
|
||||
// $s = process_html($s);
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
+731
-211
File diff suppressed because one or more lines are too long
@@ -7,6 +7,8 @@ require_once __DIR__ . '/base.php';
|
||||
$selected = [
|
||||
'wifi.connected_ip_is',
|
||||
'wifi.not_conn',
|
||||
'wifi.enter_passwd',
|
||||
'wifi.passwd_saved',
|
||||
];
|
||||
|
||||
$out = [];
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/out/
|
||||
Binary file not shown.
Binary file not shown.
Executable
+93
@@ -0,0 +1,93 @@
|
||||
#!/bin/bash
|
||||
|
||||
# resolve current file's directory
|
||||
DIR=$(dirname $(realpath $0))
|
||||
|
||||
OUTPUT_DIR="$DIR/out"
|
||||
SASS_DIR="$DIR/../sass"
|
||||
|
||||
ICON_PREFIX='icn'
|
||||
|
||||
# list with full paths, sort from newest
|
||||
NEWEST=$(ls -dt1 "$DIR"/*.zip | head -1)
|
||||
|
||||
if [[ -z "$NEWEST" ]]; then
|
||||
echo "Fontello zip not found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Clean the output folder
|
||||
rm -rf "$OUTPUT_DIR"
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
echo "Unpacking fontello..."
|
||||
|
||||
unzip -ju "$NEWEST" -d "$OUTPUT_DIR"
|
||||
|
||||
echo "Patching paths in the fontello CSS..."
|
||||
|
||||
# Fix bad relative paths in the CSS
|
||||
sed -i "s|\.\./font/|/fonts/|g" "$OUTPUT_DIR/"*.css
|
||||
|
||||
echo "Generating SASS file with icon codes..."
|
||||
|
||||
SASSFILE="$SASS_DIR/_fontello.scss"
|
||||
|
||||
echo -e "@charset \"UTF-8\";\n\n/* Fontello data, processed by the unpack script. */\n" > "$SASSFILE"
|
||||
|
||||
# Extract the base font-face style
|
||||
#grep -Pazo "(?s)@font-face.*?normal;\n\}" "$OUTPUT_DIR/fontello.css" \
|
||||
# | sed 's/\x0//g' >> "$SASSFILE"
|
||||
|
||||
grep -Pazo "(?s)@font-face \{\n\s*font-family: 'fontello';\n\s*src: url\('data.*?woff'\)" "$OUTPUT_DIR/fontello-embedded.css" \
|
||||
| sed 's/\x0//g' >> "$SASSFILE"
|
||||
|
||||
echo -e ";\n}" >> "$SASSFILE"
|
||||
|
||||
|
||||
grep -Pazo "(?s)$ICON_PREFIX-\"\]:before .*?\}" "$OUTPUT_DIR/fontello.css" \
|
||||
| sed 's/\x0//g' \
|
||||
| sed "s/$ICON_PREFIX-\"\]:before/\n\n%fontello-icon-base \{\n\&::before /g" \
|
||||
>> "$SASSFILE"
|
||||
echo -e "\n}" >> "$SASSFILE"
|
||||
|
||||
echo -e "\n\n/* Fontello icon codes */" >> "$SASSFILE"
|
||||
echo -n "\$icon-codes: (" >> "$SASSFILE"
|
||||
sed -r "s|\.$ICON_PREFIX-([a-z0-9-]+):before \{ content: ('.*?');.*?$|\t\1: \2,|g" "$OUTPUT_DIR/fontello-codes.css" \
|
||||
| sed -r "s|@.*||g" >> "$SASSFILE"
|
||||
|
||||
echo -ne "\n);\n" >> "$SASSFILE"
|
||||
|
||||
echo -ne "\n/* Fontello classes */" >> "$SASSFILE"
|
||||
cat "$OUTPUT_DIR/fontello-codes.css" \
|
||||
| sed -r 's/\/\*.+\*\///g' \
|
||||
| sed -r "s|@.*||g" \
|
||||
| sed 's/:before/::before/g' >> "$SASSFILE"
|
||||
|
||||
TAIL=$(cat <<ASDF
|
||||
|
||||
|
||||
[class^="$ICON_PREFIX-"], [class*=" $ICON_PREFIX-"] {
|
||||
@extend %fontello-icon-base;
|
||||
}
|
||||
|
||||
@mixin icon-base() {
|
||||
@extend %fontello-icon-base;
|
||||
}
|
||||
|
||||
@mixin icon-content(\$icon-name) {
|
||||
&::before {
|
||||
content: map-get(\$icon-codes, \$icon-name);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin icon(\$icon-name) {
|
||||
@include icon-base();
|
||||
@include icon-content(\icon-name);
|
||||
}
|
||||
ASDF
|
||||
)
|
||||
|
||||
echo "$TAIL" >> "$SASSFILE"
|
||||
|
||||
echo -e "\e[32mFontello ready\e[0m"
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
+27
-2
@@ -9,10 +9,35 @@ $_GET['BODYCLASS'] = $_pages[$_GET['page']]->bodyclass;
|
||||
|
||||
require __DIR__ . '/pages/_head.php';
|
||||
$_pf = __DIR__ . '/pages/'.$_GET['page'].'.php';
|
||||
|
||||
$include_re = '/<\?php\s*(require|include)\s*\(?\s*?(?:__DIR__\s*\.)?\s*(["\'])(.*?)\2\s*\)?;\s*\?>/';
|
||||
|
||||
if (file_exists($_pf)) {
|
||||
$f = file_get_contents($_pf);
|
||||
$reps = DEBUG ? require(__DIR__ . '/_debug_replacements.php') : [];
|
||||
$str = str_replace(array_keys($reps), array_values($reps), $f);
|
||||
|
||||
// Resolve requires inline - they wont work after dumping the resulting file to /tmp for eval
|
||||
$f = preg_replace_callback($include_re, function ($m) use ($_pf) {
|
||||
$n = dirname($_pf).'/'.$m[3];
|
||||
if (file_exists($n)) {
|
||||
return file_get_contents($n);
|
||||
} else {
|
||||
return "<p>NOT FOUND: $n</p>";
|
||||
}
|
||||
}, $f);
|
||||
|
||||
if (DEBUG)
|
||||
$str = tplSubs($f, require(__DIR__ . '/_debug_replacements.php'));
|
||||
else $str = $f;
|
||||
|
||||
// special symbols
|
||||
$str = str_replace('\,', ' ', $str);
|
||||
$str = preg_replace('/(?<=[^ \\\\])~(?=[^ ])/', ' ', $str);
|
||||
$str = str_replace('\~', '~', $str);
|
||||
$str = preg_replace('/(?<![\w\\\\])`([^ `][^`]*?[^ `]|[^ `])`(?!\w)/', '<code>$1</code>', $str);
|
||||
$str = preg_replace('/(?<![\w\\\\])_([^ _][^_]*?[^ _]|[^ _])_(?!\w)/', '<i>$1</i>', $str);
|
||||
$str = preg_replace('/(?<![\w\\\\])\*([^ *][^*]*?[^ *]|[^ *])\*(?!\w)/', '<b>$1</b>', $str);
|
||||
|
||||
$str = preg_replace("/\s*(\\\\\\\\)[\n \t]+/", '<br>', $str);
|
||||
include_str($str);
|
||||
} else {
|
||||
echo "404";
|
||||
|
||||
+1356
-338
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,7 @@ $.ready(function () {
|
||||
});
|
||||
|
||||
// Expanding boxes on mobile
|
||||
$('.Box.mobcol').forEach(function(x) {
|
||||
$('.Box.mobcol,.Box.fold').forEach(function(x) {
|
||||
var h = x.querySelector('h2');
|
||||
|
||||
var hdl = function() {
|
||||
@@ -100,11 +100,90 @@ $.ready(function () {
|
||||
x.removeAttribute('tabindex');
|
||||
});
|
||||
|
||||
// brand works as a link back to term in widescreen mode
|
||||
var br = qs('#brand');
|
||||
br && br.removeAttribute('tabindex');
|
||||
br && br.addEventListener('click', function() {
|
||||
location.href='/'; // go to terminal
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$._loader = function(vis) {
|
||||
$('#loader').toggleClass('show', vis);
|
||||
};
|
||||
|
||||
function showPage() {
|
||||
$('#content').addClass('load');
|
||||
}
|
||||
|
||||
$.ready(function() {
|
||||
if (window.noAutoShow !== true) {
|
||||
setTimeout(function () {
|
||||
showPage();
|
||||
}, 1);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/*! http://mths.be/fromcodepoint v0.1.0 by @mathias */
|
||||
if (!String.fromCodePoint) {
|
||||
(function() {
|
||||
var defineProperty = (function() {
|
||||
// IE 8 only supports `Object.defineProperty` on DOM elements
|
||||
try {
|
||||
var object = {};
|
||||
var $defineProperty = Object.defineProperty;
|
||||
var result = $defineProperty(object, object, object) && $defineProperty;
|
||||
} catch(error) {}
|
||||
return result;
|
||||
}());
|
||||
var stringFromCharCode = String.fromCharCode;
|
||||
var floor = Math.floor;
|
||||
var fromCodePoint = function() {
|
||||
var MAX_SIZE = 0x4000;
|
||||
var codeUnits = [];
|
||||
var highSurrogate;
|
||||
var lowSurrogate;
|
||||
var index = -1;
|
||||
var length = arguments.length;
|
||||
if (!length) {
|
||||
return '';
|
||||
}
|
||||
var result = '';
|
||||
while (++index < length) {
|
||||
var codePoint = Number(arguments[index]);
|
||||
if (
|
||||
!isFinite(codePoint) || // `NaN`, `+Infinity`, or `-Infinity`
|
||||
codePoint < 0 || // not a valid Unicode code point
|
||||
codePoint > 0x10FFFF || // not a valid Unicode code point
|
||||
floor(codePoint) != codePoint // not an integer
|
||||
) {
|
||||
throw RangeError('Invalid code point: ' + codePoint);
|
||||
}
|
||||
if (codePoint <= 0xFFFF) { // BMP code point
|
||||
codeUnits.push(codePoint);
|
||||
} else { // Astral code point; split in surrogate halves
|
||||
// http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
|
||||
codePoint -= 0x10000;
|
||||
highSurrogate = (codePoint >> 10) + 0xD800;
|
||||
lowSurrogate = (codePoint % 0x400) + 0xDC00;
|
||||
codeUnits.push(highSurrogate, lowSurrogate);
|
||||
}
|
||||
if (index + 1 == length || codeUnits.length > MAX_SIZE) {
|
||||
result += stringFromCharCode.apply(null, codeUnits);
|
||||
codeUnits.length = 0;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
if (defineProperty) {
|
||||
defineProperty(String, 'fromCodePoint', {
|
||||
'value': fromCodePoint,
|
||||
'configurable': true,
|
||||
'writable': true
|
||||
});
|
||||
} else {
|
||||
String.fromCodePoint = fromCodePoint;
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
@@ -488,7 +488,7 @@
|
||||
// Multiple select
|
||||
for (j = 0; j < value.length; j += 1) {
|
||||
elm[i].selected = '';
|
||||
if (elm[i].value === value[j]) {
|
||||
if (elm[i].value === ""+value[j]) {
|
||||
elm[i].selected = 'selected';
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,310 @@
|
||||
// keymaster.js
|
||||
// (c) 2011-2013 Thomas Fuchs
|
||||
// keymaster.js may be freely distributed under the MIT license.
|
||||
|
||||
;(function(global){
|
||||
var k,
|
||||
_handlers = {},
|
||||
_mods = { 16: false, 18: false, 17: false, 91: false },
|
||||
_scope = 'all',
|
||||
// modifier keys
|
||||
_MODIFIERS = {
|
||||
'⇧': 16, shift: 16,
|
||||
'⌥': 18, alt: 18, option: 18,
|
||||
'⌃': 17, ctrl: 17, control: 17,
|
||||
'⌘': 91, command: 91
|
||||
},
|
||||
// special keys
|
||||
_MAP = {
|
||||
backspace: 8, tab: 9, clear: 12,
|
||||
enter: 13, 'return': 13,
|
||||
esc: 27, escape: 27, space: 32,
|
||||
left: 37, up: 38,
|
||||
right: 39, down: 40,
|
||||
del: 46, 'delete': 46,
|
||||
home: 36, end: 35,
|
||||
pageup: 33, pagedown: 34,
|
||||
',': 188, '.': 190, '/': 191,
|
||||
'`': 192, '-': 189, '=': 187,
|
||||
';': 186, '\'': 222,
|
||||
'[': 219, ']': 221, '\\': 220,
|
||||
// added:
|
||||
insert: 45,
|
||||
np_0: 96, np_1: 97, np_2: 98, np_3: 99, np_4: 100, np_5: 101,
|
||||
np_6: 102, np_7: 103, np_8: 104, np_9: 105, np_mul: 106,
|
||||
np_add: 107, np_sub: 109, np_point: 110, np_div: 111, numlock: 144,
|
||||
},
|
||||
code = function(x){
|
||||
return _MAP[x] || x.toUpperCase().charCodeAt(0);
|
||||
},
|
||||
_downKeys = [];
|
||||
|
||||
for(k=1;k<20;k++) _MAP['f'+k] = 111+k;
|
||||
|
||||
// IE doesn't support Array#indexOf, so have a simple replacement
|
||||
function index(array, item){
|
||||
var i = array.length;
|
||||
while(i--) if(array[i]===item) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// for comparing mods before unassignment
|
||||
function compareArray(a1, a2) {
|
||||
if (a1.length != a2.length) return false;
|
||||
for (var i = 0; i < a1.length; i++) {
|
||||
if (a1[i] !== a2[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
var modifierMap = {
|
||||
16:'shiftKey',
|
||||
18:'altKey',
|
||||
17:'ctrlKey',
|
||||
91:'metaKey'
|
||||
};
|
||||
function updateModifierKey(event) {
|
||||
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
|
||||
function dispatch(event) {
|
||||
var key, handler, k, i, modifiersMatch, scope;
|
||||
key = event.keyCode;
|
||||
|
||||
if (index(_downKeys, key) == -1) {
|
||||
_downKeys.push(key);
|
||||
}
|
||||
|
||||
// if a modifier key, set the key.<modifierkeyname> property to true and return
|
||||
if(key == 93 || key == 224) key = 91; // right command on webkit, command on Gecko
|
||||
if(key in _mods) {
|
||||
_mods[key] = true;
|
||||
// 'assignKey' from inside this closure is exported to window.key
|
||||
for(k in _MODIFIERS) if(_MODIFIERS[k] == key) assignKey[k] = true;
|
||||
return;
|
||||
}
|
||||
updateModifierKey(event);
|
||||
|
||||
// see if we need to ignore the keypress (filter() can can be overridden)
|
||||
// by default ignore key presses if a select, textarea, or input is focused
|
||||
if(!assignKey.filter.call(this, event)) return;
|
||||
|
||||
// abort if no potentially matching shortcuts found
|
||||
if (!(key in _handlers)) return;
|
||||
|
||||
scope = getScope();
|
||||
|
||||
// for each potential shortcut
|
||||
for (i = 0; i < _handlers[key].length; i++) {
|
||||
handler = _handlers[key][i];
|
||||
|
||||
// see if it's in the current scope
|
||||
if(handler.scope == scope || handler.scope == 'all'){
|
||||
// check if modifiers match if any
|
||||
modifiersMatch = handler.mods.length > 0;
|
||||
for(k in _mods)
|
||||
if((!_mods[k] && index(handler.mods, +k) > -1) ||
|
||||
(_mods[k] && index(handler.mods, +k) == -1)) modifiersMatch = false;
|
||||
// call the handler and stop the event if neccessary
|
||||
if((handler.mods.length == 0 && !_mods[16] && !_mods[18] && !_mods[17] && !_mods[91]) || modifiersMatch){
|
||||
if(handler.method(event, handler)===false){
|
||||
if(event.preventDefault) event.preventDefault();
|
||||
else event.returnValue = false;
|
||||
if(event.stopPropagation) event.stopPropagation();
|
||||
if(event.cancelBubble) event.cancelBubble = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// unset modifier keys on keyup
|
||||
function clearModifier(event){
|
||||
var key = event.keyCode, k,
|
||||
i = index(_downKeys, key);
|
||||
|
||||
// remove key from _downKeys
|
||||
if (i >= 0) {
|
||||
_downKeys.splice(i, 1);
|
||||
}
|
||||
|
||||
if(key == 93 || key == 224) key = 91;
|
||||
if(key in _mods) {
|
||||
_mods[key] = false;
|
||||
for(k in _MODIFIERS) if(_MODIFIERS[k] == key) assignKey[k] = false;
|
||||
}
|
||||
};
|
||||
|
||||
function resetModifiers() {
|
||||
for(k in _mods) _mods[k] = false;
|
||||
for(k in _MODIFIERS) assignKey[k] = false;
|
||||
};
|
||||
|
||||
// parse and assign shortcut
|
||||
function assignKey(key, scope, method){
|
||||
var keys, mods;
|
||||
keys = getKeys(key);
|
||||
if (method === undefined) {
|
||||
method = scope;
|
||||
scope = 'all';
|
||||
}
|
||||
|
||||
// for each shortcut
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
// set modifier keys if any
|
||||
mods = [];
|
||||
key = keys[i].split('+');
|
||||
if (key.length > 1){
|
||||
mods = getMods(key);
|
||||
key = [key[key.length-1]];
|
||||
}
|
||||
// convert to keycode and...
|
||||
key = key[0]
|
||||
key = code(key);
|
||||
// ...store handler
|
||||
if (!(key in _handlers)) _handlers[key] = [];
|
||||
_handlers[key].push({ shortcut: keys[i], scope: scope, method: method, key: keys[i], mods: mods });
|
||||
}
|
||||
};
|
||||
|
||||
// unbind all handlers for given key in current scope
|
||||
function unbindKey(key, scope) {
|
||||
var multipleKeys, keys,
|
||||
mods = [],
|
||||
i, j, obj;
|
||||
|
||||
multipleKeys = getKeys(key);
|
||||
|
||||
for (j = 0; j < multipleKeys.length; j++) {
|
||||
keys = multipleKeys[j].split('+');
|
||||
|
||||
if (keys.length > 1) {
|
||||
mods = getMods(keys);
|
||||
}
|
||||
|
||||
key = keys[keys.length - 1];
|
||||
key = code(key);
|
||||
|
||||
if (scope === undefined) {
|
||||
scope = getScope();
|
||||
}
|
||||
if (!_handlers[key]) {
|
||||
return;
|
||||
}
|
||||
for (i = 0; i < _handlers[key].length; i++) {
|
||||
obj = _handlers[key][i];
|
||||
// only clear handlers if correct scope and mods match
|
||||
if (obj.scope === scope && compareArray(obj.mods, mods)) {
|
||||
_handlers[key][i] = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Returns true if the key with code 'keyCode' is currently down
|
||||
// Converts strings into key codes.
|
||||
function isPressed(keyCode) {
|
||||
if (typeof(keyCode)=='string') {
|
||||
keyCode = code(keyCode);
|
||||
}
|
||||
return index(_downKeys, keyCode) != -1;
|
||||
}
|
||||
|
||||
function getPressedKeyCodes() {
|
||||
return _downKeys.slice(0);
|
||||
}
|
||||
|
||||
function filter(event){
|
||||
var tagName = (event.target || event.srcElement).tagName;
|
||||
// ignore keypressed in any elements that support keyboard data input
|
||||
return !(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA');
|
||||
}
|
||||
|
||||
// initialize key.<modifier> to false
|
||||
for(k in _MODIFIERS) assignKey[k] = false;
|
||||
|
||||
// set current scope (default 'all')
|
||||
function setScope(scope){ _scope = scope || 'all' };
|
||||
function getScope(){ return _scope || 'all' };
|
||||
|
||||
// delete all handlers for a given scope
|
||||
function deleteScope(scope){
|
||||
var key, handlers, i;
|
||||
|
||||
for (key in _handlers) {
|
||||
handlers = _handlers[key];
|
||||
for (i = 0; i < handlers.length; ) {
|
||||
if (handlers[i].scope === scope) handlers.splice(i, 1);
|
||||
else i++;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// abstract key logic for assign and unassign
|
||||
function getKeys(key) {
|
||||
var keys;
|
||||
key = key.replace(/\s/g, '');
|
||||
keys = key.split(',');
|
||||
if ((keys[keys.length - 1]) == '') {
|
||||
keys[keys.length - 2] += ',';
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
// abstract mods logic for assign and unassign
|
||||
function getMods(key) {
|
||||
var mods = key.slice(0, key.length - 1);
|
||||
for (var mi = 0; mi < mods.length; mi++)
|
||||
mods[mi] = _MODIFIERS[mods[mi]];
|
||||
return mods;
|
||||
}
|
||||
|
||||
// cross-browser events
|
||||
function addEvent(object, event, method) {
|
||||
if (object.addEventListener)
|
||||
object.addEventListener(event, method, false);
|
||||
else if(object.attachEvent)
|
||||
object.attachEvent('on'+event, function(){ method(window.event) });
|
||||
};
|
||||
|
||||
// set the handlers globally on document
|
||||
addEvent(document, 'keydown', function(event) { dispatch(event) }); // Passing _scope to a callback to ensure it remains the same by execution. Fixes #48
|
||||
addEvent(document, 'keyup', clearModifier);
|
||||
|
||||
// reset modifiers to false whenever the window is (re)focused.
|
||||
addEvent(window, 'focus', resetModifiers);
|
||||
|
||||
// store previously defined key
|
||||
var previousKey = global.key;
|
||||
|
||||
// restore previously defined key and return reference to our key object
|
||||
function noConflict() {
|
||||
var k = global.key;
|
||||
global.key = previousKey;
|
||||
return k;
|
||||
}
|
||||
|
||||
// set window.key and window.key.set/get/deleteScope, and the default filter
|
||||
global.key = assignKey;
|
||||
global.key.setScope = setScope;
|
||||
global.key.getScope = getScope;
|
||||
global.key.deleteScope = deleteScope;
|
||||
global.key.filter = filter;
|
||||
global.key.isPressed = isPressed;
|
||||
global.key.isModifier = isModifierPressed;
|
||||
global.key.getPressedKeyCodes = getPressedKeyCodes;
|
||||
global.key.noConflict = noConflict;
|
||||
global.key.unbind = unbindKey;
|
||||
|
||||
if(typeof module !== 'undefined') module.exports = assignKey;
|
||||
|
||||
})(this);
|
||||
@@ -1,7 +1,8 @@
|
||||
// Generated from PHP locale file
|
||||
var _tr = {
|
||||
"wifi.connected_ip_is": "Connected, IP is ",
|
||||
"wifi.not_conn": "Not connected."
|
||||
"wifi.not_conn": "Not connected.",
|
||||
"wifi.enter_passwd": "Enter password for \":ssid:\""
|
||||
};
|
||||
|
||||
function tr(key) { return _tr[key] || '?'+key+'?'; }
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
/** Module for toggling a modal overlay */
|
||||
(function () {
|
||||
var modal = {};
|
||||
var curCloseCb = null;
|
||||
|
||||
modal.show = function (sel) {
|
||||
modal.show = function (sel, closeCb) {
|
||||
var $m = $(sel);
|
||||
$m.removeClass('hidden visible');
|
||||
setTimeout(function () {
|
||||
$m.addClass('visible');
|
||||
}, 1);
|
||||
curCloseCb = closeCb;
|
||||
};
|
||||
|
||||
modal.hide = function (sel) {
|
||||
@@ -15,6 +17,7 @@
|
||||
$m.removeClass('visible');
|
||||
setTimeout(function () {
|
||||
$m.addClass('hidden');
|
||||
if (curCloseCb) curCloseCb();
|
||||
}, 500); // transition time
|
||||
};
|
||||
|
||||
|
||||
+6
-303
@@ -1,303 +1,6 @@
|
||||
(function() {
|
||||
/**
|
||||
* Terminal module
|
||||
*/
|
||||
var Term = (function () {
|
||||
var W, H;
|
||||
var cursor = {a: false, x: 0, y: 0, suppress: false, hidden: false};
|
||||
var screen = [];
|
||||
var blinkIval;
|
||||
|
||||
/*
|
||||
/!** Clear screen *!/
|
||||
function cls() {
|
||||
screen.forEach(function(cell, i) {
|
||||
cell.t = ' ';
|
||||
cell.fg = 7;
|
||||
cell.bg = 0;
|
||||
blit(cell);
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
/** Set text and color at XY */
|
||||
function cellAt(y, x) {
|
||||
return screen[y*W+x];
|
||||
}
|
||||
|
||||
/** Get cell under cursor */
|
||||
function cursorCell() {
|
||||
return cellAt(cursor.y, cursor.x);
|
||||
}
|
||||
|
||||
/*
|
||||
/!** Enable or disable cursor visibility *!/
|
||||
function cursorEnable(enable) {
|
||||
cursor.hidden = !enable;
|
||||
cursor.a &= enable;
|
||||
blit(cursorCell(), cursor.a);
|
||||
}
|
||||
|
||||
/!** Safely move cursor *!/
|
||||
function cursorSet(y, x) {
|
||||
// Hide and prevent from showing up during the move
|
||||
cursor.suppress = true;
|
||||
blit(cursorCell(), false);
|
||||
|
||||
cursor.x = x;
|
||||
cursor.y = y;
|
||||
|
||||
// Show again
|
||||
cursor.suppress = false;
|
||||
blit(cursorCell(), cursor.a);
|
||||
}
|
||||
*/
|
||||
|
||||
/** Update cell on display. inv = invert (for cursor) */
|
||||
function blit(cell, inv) {
|
||||
var e = cell.e, fg, bg;
|
||||
// Colors
|
||||
fg = inv ? cell.bg : cell.fg;
|
||||
bg = inv ? cell.fg : cell.bg;
|
||||
// Update
|
||||
e.innerText = (cell.t+' ')[0];
|
||||
e.className = 'fg'+fg+' bg'+bg;
|
||||
}
|
||||
|
||||
/** Show entire screen */
|
||||
function blitAll() {
|
||||
screen.forEach(function(cell, i) {
|
||||
/* Invert if under cursor & cursor active */
|
||||
var inv = cursor.a && (i == cursor.y*W+cursor.x);
|
||||
blit(cell, inv);
|
||||
});
|
||||
}
|
||||
|
||||
/** Load screen content from a 'binary' sequence */
|
||||
function load(obj) {
|
||||
cursor.x = obj.x;
|
||||
cursor.y = obj.y;
|
||||
cursor.hidden = !obj.cv;
|
||||
|
||||
// full re-init if size changed
|
||||
if (obj.w != W || obj.h != H) {
|
||||
Term.init(obj);
|
||||
return;
|
||||
}
|
||||
|
||||
// Simple compression - hexFG hexBG 'ASCII' (r/s/t/u NUM{1,2,3,4})?
|
||||
// comma instead of both colors = same as before
|
||||
|
||||
var i = 0, ci = 0, str = obj.screen;
|
||||
var fg = 7, bg = 0;
|
||||
while(i < str.length && ci<W*H) {
|
||||
var cell = screen[ci++];
|
||||
|
||||
var j = str[i];
|
||||
if (j != ',') { // comma = repeat last colors
|
||||
fg = cell.fg = parseInt(str[i++], 16);
|
||||
bg = cell.bg = parseInt(str[i++], 16);
|
||||
} else {
|
||||
i++;
|
||||
cell.fg = fg;
|
||||
cell.bg = bg;
|
||||
}
|
||||
|
||||
var t = cell.t = str[i++];
|
||||
|
||||
var repchars = 0;
|
||||
switch(str[i]) {
|
||||
case 'r': repchars = 1; break;
|
||||
case 's': repchars = 2; break;
|
||||
case 't': repchars = 3; break;
|
||||
case 'u': repchars = 4; break;
|
||||
default: repchars = 0;
|
||||
}
|
||||
|
||||
if (repchars > 0) {
|
||||
var rep = parseInt(str.substr(i+1,repchars));
|
||||
i = i + repchars + 1;
|
||||
for (; rep>0 && ci<W*H; rep--) {
|
||||
cell = screen[ci++];
|
||||
cell.fg = fg;
|
||||
cell.bg = bg;
|
||||
cell.t = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blitAll();
|
||||
}
|
||||
|
||||
/** Init the terminal */
|
||||
function init(obj) {
|
||||
W = obj.w;
|
||||
H = obj.h;
|
||||
|
||||
/* Build screen & show */
|
||||
var e, cell, scr = qs('#screen');
|
||||
|
||||
// Empty the screen node
|
||||
while (scr.firstChild) scr.removeChild(scr.firstChild);
|
||||
|
||||
screen = [];
|
||||
|
||||
for(var i = 0; i < W*H; i++) {
|
||||
e = mk('span');
|
||||
|
||||
(function() {
|
||||
var x = i % W;
|
||||
var y = Math.floor(i / W);
|
||||
e.addEventListener('click', function () {
|
||||
Input.onTap(y, x);
|
||||
});
|
||||
})();
|
||||
|
||||
/* End of line */
|
||||
if ((i > 0) && (i % W == 0)) {
|
||||
scr.appendChild(mk('br'));
|
||||
}
|
||||
/* The cell */
|
||||
scr.appendChild(e);
|
||||
|
||||
cell = {t: ' ', fg: 7, bg: 0, e: e};
|
||||
screen.push(cell);
|
||||
blit(cell);
|
||||
}
|
||||
|
||||
/* Cursor blinking */
|
||||
clearInterval(blinkIval);
|
||||
blinkIval = setInterval(function() {
|
||||
cursor.a = !cursor.a;
|
||||
if (cursor.hidden) {
|
||||
cursor.a = false;
|
||||
}
|
||||
|
||||
if (!cursor.suppress) {
|
||||
blit(cursorCell(), cursor.a);
|
||||
}
|
||||
}, 500);
|
||||
|
||||
load(obj);
|
||||
}
|
||||
|
||||
// publish
|
||||
return {
|
||||
init: init,
|
||||
load: load
|
||||
};
|
||||
})();
|
||||
|
||||
/** Handle connections */
|
||||
var Conn = (function() {
|
||||
var ws;
|
||||
|
||||
function onOpen(evt) {
|
||||
console.log("CONNECTED");
|
||||
}
|
||||
|
||||
function onClose(evt) {
|
||||
console.warn("SOCKET CLOSED, code "+evt.code+". Reconnecting...");
|
||||
setTimeout(function() {
|
||||
init();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function onMessage(evt) {
|
||||
try {
|
||||
console.log("RX: ", evt.data);
|
||||
// Assume all our messages are screen updates
|
||||
Term.load(JSON.parse(evt.data));
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
function doSend(message) {
|
||||
console.log("TX: ", message);
|
||||
if (ws.readyState != 1) {
|
||||
console.error("Socket not ready");
|
||||
return;
|
||||
}
|
||||
if (typeof message != "string") {
|
||||
message = JSON.stringify(message);
|
||||
}
|
||||
ws.send(message);
|
||||
}
|
||||
|
||||
function init() {
|
||||
ws = new WebSocket("ws://"+_root+"/ws/update.cgi");
|
||||
ws.onopen = onOpen;
|
||||
ws.onclose = onClose;
|
||||
ws.onmessage = onMessage;
|
||||
|
||||
console.log("Opening socket.");
|
||||
}
|
||||
|
||||
return {
|
||||
ws: null,
|
||||
init: init,
|
||||
send: doSend
|
||||
};
|
||||
})();
|
||||
|
||||
//
|
||||
// Keyboard (& mouse) input
|
||||
//
|
||||
var Input = (function() {
|
||||
function sendStrMsg(str) {
|
||||
Conn.send("STR:"+str);
|
||||
}
|
||||
|
||||
function sendPosMsg(y, x) {
|
||||
Conn.send("TAP:"+y+','+x);
|
||||
}
|
||||
|
||||
function sendBtnMsg(n) {
|
||||
Conn.send("BTN:"+n);
|
||||
}
|
||||
|
||||
function init() {
|
||||
window.addEventListener('keypress', function(e) {
|
||||
var code = +e.which;
|
||||
if (code >= 32 && code < 127) {
|
||||
var ch = String.fromCharCode(code);
|
||||
//console.log("Typed ", ch, "code", code, e);
|
||||
sendStrMsg(ch);
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('keydown', function(e) {
|
||||
var code = e.keyCode;
|
||||
//console.log("Down ", code, e);
|
||||
switch(code) {
|
||||
case 8: sendStrMsg('\x08'); break;
|
||||
case 13: sendStrMsg('\x0d\x0a'); break;
|
||||
case 27: sendStrMsg('\x1b'); break; // this allows to directly enter control sequences
|
||||
case 37: sendStrMsg('\x1b[D'); break;
|
||||
case 38: sendStrMsg('\x1b[A'); break;
|
||||
case 39: sendStrMsg('\x1b[C'); break;
|
||||
case 40: sendStrMsg('\x1b[B'); break;
|
||||
}
|
||||
});
|
||||
|
||||
qsa('#buttons button').forEach(function(s) {
|
||||
s.addEventListener('click', function() {
|
||||
sendBtnMsg(+this.dataset['n']);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
init: init,
|
||||
onTap: sendPosMsg
|
||||
};
|
||||
})();
|
||||
|
||||
|
||||
window.termInit = function (obj) {
|
||||
Term.init(obj);
|
||||
Conn.init();
|
||||
Input.init();
|
||||
};
|
||||
})();
|
||||
/** Init the terminal sub-module - called from HTML */
|
||||
window.termInit = function () {
|
||||
Conn.init();
|
||||
Input.init();
|
||||
TermUpl.init();
|
||||
};
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
/** Handle connections */
|
||||
var Conn = (function() {
|
||||
var ws;
|
||||
var heartbeatTout;
|
||||
var pingIv;
|
||||
var xoff = false;
|
||||
var autoXoffTout;
|
||||
|
||||
function onOpen(evt) {
|
||||
console.log("CONNECTED");
|
||||
}
|
||||
|
||||
function onClose(evt) {
|
||||
console.warn("SOCKET CLOSED, code "+evt.code+". Reconnecting...");
|
||||
setTimeout(function() {
|
||||
init();
|
||||
}, 200);
|
||||
// this happens when the buffer gets fucked up via invalid unicode.
|
||||
// we basically use polling instead of socket then
|
||||
}
|
||||
|
||||
function onMessage(evt) {
|
||||
try {
|
||||
// . = heartbeat
|
||||
switch (evt.data.charAt(0)) {
|
||||
case 'B':
|
||||
case 'T':
|
||||
case 'S':
|
||||
Screen.load(evt.data);
|
||||
break;
|
||||
|
||||
case '-':
|
||||
//console.log('xoff');
|
||||
xoff = true;
|
||||
autoXoffTout = setTimeout(function(){xoff=false;}, 250);
|
||||
break;
|
||||
|
||||
case '+':
|
||||
//console.log('xon');
|
||||
xoff = false;
|
||||
clearTimeout(autoXoffTout);
|
||||
break;
|
||||
}
|
||||
heartbeat();
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
function canSend() {
|
||||
return !xoff;
|
||||
}
|
||||
|
||||
function doSend(message) {
|
||||
//console.log("TX: ", message);
|
||||
if (xoff) {
|
||||
// TODO queue
|
||||
console.log("Can't send, flood control.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ws) return false; // for dry testing
|
||||
if (ws.readyState != 1) {
|
||||
console.error("Socket not ready");
|
||||
return false;
|
||||
}
|
||||
if (typeof message != "string") {
|
||||
message = JSON.stringify(message);
|
||||
}
|
||||
ws.send(message);
|
||||
return true;
|
||||
}
|
||||
|
||||
function init() {
|
||||
heartbeat();
|
||||
|
||||
ws = new WebSocket("ws://"+_root+"/term/update.ws");
|
||||
ws.onopen = onOpen;
|
||||
ws.onclose = onClose;
|
||||
ws.onmessage = onMessage;
|
||||
|
||||
console.log("Opening socket.");
|
||||
|
||||
// Ask for initial data
|
||||
$.get('http://'+_root+'/term/init', function(resp, status) {
|
||||
if (status !== 200) location.reload(true);
|
||||
console.log("Data received!");
|
||||
Screen.load(resp);
|
||||
heartbeat();
|
||||
|
||||
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 {
|
||||
ws: null,
|
||||
init: init,
|
||||
send: doSend,
|
||||
canSend: canSend, // check flood control
|
||||
};
|
||||
})();
|
||||
@@ -0,0 +1,262 @@
|
||||
/**
|
||||
* User input
|
||||
*
|
||||
* --- Rx messages: ---
|
||||
* S - screen content (binary encoding of the entire screen with simple compression)
|
||||
* T - text labels - Title and buttons, \0x01-separated
|
||||
* B - beep
|
||||
* . - heartbeat
|
||||
*
|
||||
* --- Tx messages ---
|
||||
* s - string
|
||||
* b - action button
|
||||
* p - mb press
|
||||
* r - mb release
|
||||
* m - mouse move
|
||||
*/
|
||||
var Input = (function() {
|
||||
var opts = {
|
||||
np_alt: false,
|
||||
cu_alt: false,
|
||||
fn_alt: false,
|
||||
mt_click: false,
|
||||
mt_move: false,
|
||||
no_keys: false,
|
||||
};
|
||||
|
||||
/** Send a literal message */
|
||||
function sendStrMsg(str) {
|
||||
return Conn.send("s"+str);
|
||||
}
|
||||
|
||||
/** Send a button event */
|
||||
function sendBtnMsg(n) {
|
||||
Conn.send("b"+Chr(n));
|
||||
}
|
||||
|
||||
/** Fn alt choice for key message */
|
||||
function fa(alt, normal) {
|
||||
return opts.fn_alt ? alt : normal;
|
||||
}
|
||||
|
||||
/** Cursor alt choice for key message */
|
||||
function ca(alt, normal) {
|
||||
return opts.cu_alt ? alt : normal;
|
||||
}
|
||||
|
||||
/** Numpad alt choice for key message */
|
||||
function na(alt, normal) {
|
||||
return opts.np_alt ? alt : normal;
|
||||
}
|
||||
|
||||
function _bindFnKeys() {
|
||||
var keymap = {
|
||||
'tab': '\x09',
|
||||
'backspace': '\x08',
|
||||
'enter': '\x0d',
|
||||
'ctrl+enter': '\x0a',
|
||||
'esc': '\x1b',
|
||||
'up': ca('\x1bOA', '\x1b[A'),
|
||||
'down': ca('\x1bOB', '\x1b[B'),
|
||||
'right': ca('\x1bOC', '\x1b[C'),
|
||||
'left': ca('\x1bOD', '\x1b[D'),
|
||||
'home': ca('\x1bOH', fa('\x1b[H', '\x1b[1~')),
|
||||
'insert': '\x1b[2~',
|
||||
'delete': '\x1b[3~',
|
||||
'end': ca('\x1bOF', fa('\x1b[F', '\x1b[4~')),
|
||||
'pageup': '\x1b[5~',
|
||||
'pagedown': '\x1b[6~',
|
||||
'f1': fa('\x1bOP', '\x1b[11~'),
|
||||
'f2': fa('\x1bOQ', '\x1b[12~'),
|
||||
'f3': fa('\x1bOR', '\x1b[13~'),
|
||||
'f4': fa('\x1bOS', '\x1b[14~'),
|
||||
'f5': '\x1b[15~', // note the disconnect
|
||||
'f6': '\x1b[17~',
|
||||
'f7': '\x1b[18~',
|
||||
'f8': '\x1b[19~',
|
||||
'f9': '\x1b[20~',
|
||||
'f10': '\x1b[21~', // note the disconnect
|
||||
'f11': '\x1b[23~',
|
||||
'f12': '\x1b[24~',
|
||||
'shift+f1': fa('\x1bO1;2P', '\x1b[25~'),
|
||||
'shift+f2': fa('\x1bO1;2Q', '\x1b[26~'), // note the disconnect
|
||||
'shift+f3': fa('\x1bO1;2R', '\x1b[28~'),
|
||||
'shift+f4': fa('\x1bO1;2S', '\x1b[29~'), // note the disconnect
|
||||
'shift+f5': fa('\x1b[15;2~', '\x1b[31~'),
|
||||
'shift+f6': fa('\x1b[17;2~', '\x1b[32~'),
|
||||
'shift+f7': fa('\x1b[18;2~', '\x1b[33~'),
|
||||
'shift+f8': fa('\x1b[19;2~', '\x1b[34~'),
|
||||
'shift+f9': fa('\x1b[20;2~', '\x1b[35~'), // 35-38 are not standard - but what is?
|
||||
'shift+f10': fa('\x1b[21;2~', '\x1b[36~'),
|
||||
'shift+f11': fa('\x1b[22;2~', '\x1b[37~'),
|
||||
'shift+f12': fa('\x1b[23;2~', '\x1b[38~'),
|
||||
'np_0': na('\x1bOp', '0'),
|
||||
'np_1': na('\x1bOq', '1'),
|
||||
'np_2': na('\x1bOr', '2'),
|
||||
'np_3': na('\x1bOs', '3'),
|
||||
'np_4': na('\x1bOt', '4'),
|
||||
'np_5': na('\x1bOu', '5'),
|
||||
'np_6': na('\x1bOv', '6'),
|
||||
'np_7': na('\x1bOw', '7'),
|
||||
'np_8': na('\x1bOx', '8'),
|
||||
'np_9': na('\x1bOy', '9'),
|
||||
'np_mul': na('\x1bOR', '*'),
|
||||
'np_add': na('\x1bOl', '+'),
|
||||
'np_sub': na('\x1bOS', '-'),
|
||||
'np_point': na('\x1bOn', '.'),
|
||||
'np_div': na('\x1bOQ', '/'),
|
||||
// we don't implement numlock key (should change in numpad_alt mode, but it's even more useless than the rest)
|
||||
};
|
||||
|
||||
for (var k in keymap) {
|
||||
if (keymap.hasOwnProperty(k)) {
|
||||
bind(k, keymap[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Bind a keystroke to message */
|
||||
function bind(combo, str) {
|
||||
// mac fix - allow also cmd
|
||||
if (combo.indexOf('ctrl+') !== -1) {
|
||||
combo += ',' + combo.replace('ctrl', 'command');
|
||||
}
|
||||
|
||||
// unbind possible old binding
|
||||
key.unbind(combo);
|
||||
|
||||
key(combo, function (e) {
|
||||
if (opts.no_keys) return;
|
||||
e.preventDefault();
|
||||
sendStrMsg(str)
|
||||
});
|
||||
}
|
||||
|
||||
/** Bind/rebind key messages */
|
||||
function _initKeys() {
|
||||
// This takes care of text characters typed
|
||||
window.addEventListener('keypress', function(evt) {
|
||||
if (opts.no_keys) return;
|
||||
var str = '';
|
||||
if (evt.key) str = evt.key;
|
||||
else if (evt.which) str = String.fromCodePoint(evt.which);
|
||||
if (str.length>0 && str.charCodeAt(0) >= 32) {
|
||||
// console.log("Typed ", str);
|
||||
sendStrMsg(str);
|
||||
}
|
||||
});
|
||||
|
||||
// ctrl-letter codes are sent as simple low ASCII codes
|
||||
for (var i = 1; i<=26;i++) {
|
||||
bind('ctrl+' + String.fromCharCode(96+i), String.fromCharCode(i));
|
||||
}
|
||||
bind('ctrl+]', '\x1b'); // alternate way to enter ESC
|
||||
bind('ctrl+\\', '\x1c');
|
||||
bind('ctrl+[', '\x1d');
|
||||
bind('ctrl+^', '\x1e');
|
||||
bind('ctrl+_', '\x1f');
|
||||
|
||||
_bindFnKeys();
|
||||
}
|
||||
|
||||
// mouse button states
|
||||
var mb1 = 0;
|
||||
var mb2 = 0;
|
||||
var mb3 = 0;
|
||||
|
||||
/** Init the Input module */
|
||||
function init() {
|
||||
_initKeys();
|
||||
|
||||
// Button presses
|
||||
qsa('#action-buttons button').forEach(function(s) {
|
||||
s.addEventListener('click', function() {
|
||||
sendBtnMsg(+this.dataset['n']);
|
||||
});
|
||||
});
|
||||
|
||||
// global mouse state tracking - for motion reporting
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
/** Prepare modifiers byte for mouse message */
|
||||
function packModifiersForMouse() {
|
||||
return (key.isModifier('ctrl')?1:0) |
|
||||
(key.isModifier('shift')?2:0) |
|
||||
(key.isModifier('alt')?4:0) |
|
||||
(key.isModifier('meta')?8:0);
|
||||
}
|
||||
|
||||
return {
|
||||
/** Init the Input module */
|
||||
init: init,
|
||||
|
||||
/** Send a literal string message */
|
||||
sendString: sendStrMsg,
|
||||
|
||||
/** Enable alternate key modes (cursors, numpad, fn) */
|
||||
setAlts: function(cu, np, fn) {
|
||||
if (opts.cu_alt != cu || opts.np_alt != np || opts.fn_alt != fn) {
|
||||
opts.cu_alt = cu;
|
||||
opts.np_alt = np;
|
||||
opts.fn_alt = fn;
|
||||
|
||||
// rebind keys - codes have changed
|
||||
_bindFnKeys();
|
||||
}
|
||||
},
|
||||
|
||||
setMouseMode: function(click, move) {
|
||||
opts.mt_click = click;
|
||||
opts.mt_move = move;
|
||||
},
|
||||
|
||||
// Mouse events
|
||||
onMouseMove: function (x, y) {
|
||||
if (!opts.mt_move) return;
|
||||
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 (!opts.mt_click) return;
|
||||
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 (!opts.mt_click) return;
|
||||
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) {
|
||||
if (!opts.mt_click) return;
|
||||
// -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);
|
||||
},
|
||||
mouseTracksClicks: function() {
|
||||
return opts.mt_click;
|
||||
},
|
||||
blockKeys: function(yes) {
|
||||
opts.no_keys = yes;
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
@@ -0,0 +1,377 @@
|
||||
var Screen = (function () {
|
||||
var W = 0, H = 0; // dimensions
|
||||
var inited = false;
|
||||
|
||||
var cursor = {
|
||||
a: false, // active (blink state)
|
||||
x: 0, // 0-based coordinates
|
||||
y: 0,
|
||||
fg: 7, // colors 0-15
|
||||
bg: 0,
|
||||
attrs: 0,
|
||||
suppress: false, // do not turn on in blink interval (for safe moving)
|
||||
forceOn: false, // force on unless hanging: used to keep cursor visible during move
|
||||
hidden: false, // do not show (DEC opt)
|
||||
hanging: false, // cursor at column "W+1" - not visible
|
||||
};
|
||||
|
||||
var screen = [];
|
||||
var blinkIval;
|
||||
var cursorFlashStartIval;
|
||||
|
||||
// Some non-bold Fraktur symbols are outside the contiguous block
|
||||
var frakturExceptions = {
|
||||
'C': '\u212d',
|
||||
'H': '\u210c',
|
||||
'I': '\u2111',
|
||||
'R': '\u211c',
|
||||
'Z': '\u2128',
|
||||
};
|
||||
|
||||
// for BEL
|
||||
var audioCtx = null;
|
||||
try {
|
||||
audioCtx = new (window.AudioContext || window.audioContext || window.webkitAudioContext)();
|
||||
} catch (er) {
|
||||
console.error("No AudioContext!", er);
|
||||
}
|
||||
|
||||
/** Get cell under cursor */
|
||||
function _curCell() {
|
||||
return screen[cursor.y*W + cursor.x];
|
||||
}
|
||||
|
||||
/** Safely move cursor */
|
||||
function cursorSet(y, x) {
|
||||
// Hide and prevent from showing up during the move
|
||||
cursor.suppress = true;
|
||||
_draw(_curCell(), false);
|
||||
cursor.x = x;
|
||||
cursor.y = y;
|
||||
// Show again
|
||||
cursor.suppress = false;
|
||||
_draw(_curCell());
|
||||
}
|
||||
|
||||
function alpha2fraktur(t) {
|
||||
// perform substitution
|
||||
if (t >= 'a' && t <= 'z') {
|
||||
t = String.fromCodePoint(0x1d51e - 97 + t.charCodeAt(0));
|
||||
}
|
||||
else if (t >= 'A' && t <= 'Z') {
|
||||
// this set is incomplete, some exceptions are needed
|
||||
if (frakturExceptions.hasOwnProperty(t)) {
|
||||
t = frakturExceptions[t];
|
||||
} else {
|
||||
t = String.fromCodePoint(0x1d504 - 65 + t.charCodeAt(0));
|
||||
}
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
/** Update cell on display. inv = invert (for cursor) */
|
||||
function _draw(cell, inv) {
|
||||
if (!cell) return;
|
||||
if (typeof inv == 'undefined') {
|
||||
inv = cursor.a && cursor.x == cell.x && cursor.y == cell.y;
|
||||
}
|
||||
|
||||
var fg, bg, cn, t;
|
||||
|
||||
fg = inv ? cell.bg : cell.fg;
|
||||
bg = inv ? cell.fg : cell.bg;
|
||||
|
||||
t = cell.t;
|
||||
if (!t.length) t = ' ';
|
||||
|
||||
cn = 'fg' + fg + ' bg' + bg;
|
||||
if (cell.attrs & (1<<0)) cn += ' bold';
|
||||
if (cell.attrs & (1<<1)) cn += ' faint';
|
||||
if (cell.attrs & (1<<2)) cn += ' italic';
|
||||
if (cell.attrs & (1<<3)) cn += ' under';
|
||||
if (cell.attrs & (1<<4)) cn += ' blink';
|
||||
if (cell.attrs & (1<<5)) {
|
||||
cn += ' fraktur';
|
||||
t = alpha2fraktur(t);
|
||||
}
|
||||
if (cell.attrs & (1<<6)) cn += ' strike';
|
||||
|
||||
cell.slot.textContent = t;
|
||||
cell.elem.className = cn;
|
||||
}
|
||||
|
||||
/** Show entire screen */
|
||||
function _drawAll() {
|
||||
for (var i = W*H-1; i>=0; i--) {
|
||||
_draw(screen[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function _rebuild(rows, cols) {
|
||||
W = cols;
|
||||
H = rows;
|
||||
|
||||
/* Build screen & show */
|
||||
var cOuter, cInner, cell, screenDiv = qs('#screen');
|
||||
|
||||
// Empty the screen node
|
||||
while (screenDiv.firstChild) screenDiv.removeChild(screenDiv.firstChild);
|
||||
|
||||
screen = [];
|
||||
|
||||
for(var i = 0; i < W*H; i++) {
|
||||
cOuter = mk('span');
|
||||
cInner = mk('span');
|
||||
|
||||
/* Mouse tracking */
|
||||
(function() {
|
||||
var x = i % W;
|
||||
var y = Math.floor(i / W);
|
||||
cOuter.addEventListener('mouseenter', function (evt) {
|
||||
Input.onMouseMove(x, y);
|
||||
});
|
||||
cOuter.addEventListener('mousedown', function (evt) {
|
||||
Input.onMouseDown(x, y, evt.button+1);
|
||||
});
|
||||
cOuter.addEventListener('mouseup', function (evt) {
|
||||
Input.onMouseUp(x, y, evt.button+1);
|
||||
});
|
||||
cOuter.addEventListener('contextmenu', function (evt) {
|
||||
if (Input.mouseTracksClicks()) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
});
|
||||
cOuter.addEventListener('mousewheel', function (evt) {
|
||||
Input.onMouseWheel(x, y, evt.deltaY>0?1:-1);
|
||||
return false;
|
||||
});
|
||||
})();
|
||||
|
||||
/* End of line */
|
||||
if ((i > 0) && (i % W == 0)) {
|
||||
screenDiv.appendChild(mk('br'));
|
||||
}
|
||||
/* The cell */
|
||||
cOuter.appendChild(cInner);
|
||||
screenDiv.appendChild(cOuter);
|
||||
|
||||
cell = {
|
||||
t: ' ',
|
||||
fg: 7,
|
||||
bg: 0, // the colors will be replaced immediately as we receive data (user won't see this)
|
||||
attrs: 0,
|
||||
elem: cOuter,
|
||||
slot: cInner,
|
||||
x: i % W,
|
||||
y: Math.floor(i / W),
|
||||
};
|
||||
screen.push(cell);
|
||||
_draw(cell);
|
||||
}
|
||||
}
|
||||
|
||||
/** Init the terminal */
|
||||
function _init() {
|
||||
/* Cursor blinking */
|
||||
clearInterval(blinkIval);
|
||||
blinkIval = setInterval(function () {
|
||||
cursor.a = !cursor.a;
|
||||
if (cursor.hidden || cursor.hanging) {
|
||||
cursor.a = false;
|
||||
}
|
||||
|
||||
if (!cursor.suppress) {
|
||||
_draw(_curCell(), cursor.forceOn || cursor.a);
|
||||
}
|
||||
}, 500);
|
||||
|
||||
/* blink attribute animation */
|
||||
setInterval(function () {
|
||||
$('#screen').removeClass('blink-hide');
|
||||
setTimeout(function () {
|
||||
$('#screen').addClass('blink-hide');
|
||||
}, 800); // 200 ms ON
|
||||
}, 1000);
|
||||
|
||||
inited = true;
|
||||
}
|
||||
|
||||
// constants for decoding the update blob
|
||||
var SEQ_SET_COLOR_ATTR = 1;
|
||||
var SEQ_REPEAT = 2;
|
||||
var SEQ_SET_COLOR = 3;
|
||||
var SEQ_SET_ATTR = 4;
|
||||
|
||||
/** Parse received screen update object (leading S removed already) */
|
||||
function _load_content(str) {
|
||||
var i = 0, ci = 0, j, jc, num, num2, t = ' ', fg, bg, attrs, cell;
|
||||
|
||||
if (!inited) _init();
|
||||
|
||||
var cursorMoved;
|
||||
|
||||
// Set size
|
||||
num = parse2B(str, i); i += 2; // height
|
||||
num2 = parse2B(str, i); i += 2; // width
|
||||
if (num != H || num2 != W) {
|
||||
_rebuild(num, num2);
|
||||
}
|
||||
// console.log("Size ",num, num2);
|
||||
|
||||
// Cursor position
|
||||
num = parse2B(str, i); i += 2; // row
|
||||
num2 = parse2B(str, i); i += 2; // col
|
||||
cursorMoved = (cursor.x != num2 || cursor.y != num);
|
||||
cursorSet(num, num2);
|
||||
// console.log("Cursor at ",num, num2);
|
||||
|
||||
// Attributes
|
||||
num = parse2B(str, i); i += 2; // fg bg attribs
|
||||
cursor.hidden = !(num & (1<<0)); // DEC opt "visible"
|
||||
cursor.hanging = !!(num & (1<<1));
|
||||
// console.log("Attributes word ",num.toString(16)+'h');
|
||||
|
||||
Input.setAlts(
|
||||
!!(num & (1<<2)), // cursors alt
|
||||
!!(num & (1<<3)), // numpad alt
|
||||
!!(num & (1<<4)) // fn keys alt
|
||||
);
|
||||
|
||||
var mt_click = !!(num & (1<<5));
|
||||
var mt_move = !!(num & (1<<6));
|
||||
Input.setMouseMode(
|
||||
mt_click,
|
||||
mt_move
|
||||
);
|
||||
$('#screen').toggleClass('noselect', mt_move);
|
||||
|
||||
var show_buttons = !!(num & (1<<7));
|
||||
var show_config_links = !!(num & (1<<8));
|
||||
$('.x-term-conf-btn').toggleClass('hidden', !show_config_links);
|
||||
$('#action-buttons').toggleClass('hidden', !show_buttons);
|
||||
|
||||
fg = 7;
|
||||
bg = 0;
|
||||
attrs = 0;
|
||||
|
||||
// Here come the content
|
||||
while(i < str.length && ci<W*H) {
|
||||
|
||||
j = str[i++];
|
||||
jc = j.charCodeAt(0);
|
||||
if (jc == SEQ_SET_COLOR_ATTR) {
|
||||
num = parse3B(str, i); i += 3;
|
||||
fg = num & 0x0F;
|
||||
bg = (num & 0xF0) >> 4;
|
||||
attrs = (num & 0xFF00)>>8;
|
||||
}
|
||||
else if (jc == SEQ_SET_COLOR) {
|
||||
num = parse2B(str, i); i += 2;
|
||||
fg = num & 0x0F;
|
||||
bg = (num & 0xF0) >> 4;
|
||||
}
|
||||
else if (jc == SEQ_SET_ATTR) {
|
||||
num = parse2B(str, i); i += 2;
|
||||
attrs = num & 0xFF;
|
||||
}
|
||||
else if (jc == SEQ_REPEAT) {
|
||||
num = parse2B(str, i); i += 2;
|
||||
// console.log("Repeat x ",num);
|
||||
for (; num>0 && ci<W*H; num--) {
|
||||
cell = screen[ci++];
|
||||
cell.fg = fg;
|
||||
cell.bg = bg;
|
||||
cell.t = t;
|
||||
cell.attrs = attrs;
|
||||
}
|
||||
}
|
||||
else {
|
||||
cell = screen[ci++];
|
||||
// Unique cell character
|
||||
t = cell.t = j;
|
||||
cell.fg = fg;
|
||||
cell.bg = bg;
|
||||
cell.attrs = attrs;
|
||||
// console.log("Symbol ", j);
|
||||
}
|
||||
}
|
||||
|
||||
_drawAll();
|
||||
|
||||
// if (!cursor.hidden || cursor.hanging || !cursor.suppress) {
|
||||
// // hide cursor asap
|
||||
// _draw(_curCell(), false);
|
||||
// }
|
||||
|
||||
if (cursorMoved) {
|
||||
cursor.forceOn = true;
|
||||
cursorFlashStartIval = setTimeout(function() {
|
||||
cursor.forceOn = false;
|
||||
}, 1200);
|
||||
_draw(_curCell(), true);
|
||||
}
|
||||
}
|
||||
|
||||
/** Apply labels to buttons and screen title (leading T removed already) */
|
||||
function _load_labels(str) {
|
||||
var pieces = str.split('\x01');
|
||||
qs('h1').textContent = pieces[0];
|
||||
qsa('#action-buttons button').forEach(function(x, i) {
|
||||
var s = pieces[i+1].trim();
|
||||
// if empty string, use the "dim" effect and put nbsp instead to stretch the btn vertically
|
||||
x.innerHTML = s.length > 0 ? e(s) : " ";
|
||||
x.style.opacity = s.length > 0 ? 1 : 0.2;
|
||||
});
|
||||
}
|
||||
|
||||
/** Audible beep for ASCII 7 */
|
||||
function _beep() {
|
||||
var osc, gain;
|
||||
if (!audioCtx) return;
|
||||
|
||||
// Main beep
|
||||
osc = audioCtx.createOscillator();
|
||||
gain = audioCtx.createGain();
|
||||
osc.connect(gain);
|
||||
gain.connect(audioCtx.destination);
|
||||
gain.gain.value = 0.5;
|
||||
osc.frequency.value = 750;
|
||||
osc.type = 'sine';
|
||||
osc.start();
|
||||
osc.stop(audioCtx.currentTime+0.05);
|
||||
|
||||
// Surrogate beep (making it sound like 'oops')
|
||||
osc = audioCtx.createOscillator();
|
||||
gain = audioCtx.createGain();
|
||||
osc.connect(gain);
|
||||
gain.connect(audioCtx.destination);
|
||||
gain.gain.value = 0.2;
|
||||
osc.frequency.value = 400;
|
||||
osc.type = 'sine';
|
||||
osc.start(audioCtx.currentTime+0.05);
|
||||
osc.stop(audioCtx.currentTime+0.08);
|
||||
}
|
||||
|
||||
/** Load screen content from a binary sequence (new) */
|
||||
function load(str) {
|
||||
var content = str.substr(1);
|
||||
switch(str.charAt(0)) {
|
||||
case 'S':
|
||||
_load_content(content);
|
||||
break;
|
||||
case 'T':
|
||||
_load_labels(content);
|
||||
break;
|
||||
case 'B':
|
||||
_beep();
|
||||
break;
|
||||
default:
|
||||
console.warn("Bad data message type, ignoring.");
|
||||
console.log(str);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
load: load, // full load (string)
|
||||
};
|
||||
})();
|
||||
@@ -0,0 +1,146 @@
|
||||
/** File upload utility */
|
||||
var TermUpl = (function() {
|
||||
var lines, // array of lines without newlines
|
||||
line_i, // current line index
|
||||
fuTout, // timeout handle for line sending
|
||||
send_delay_ms, // delay between lines (ms)
|
||||
nl_str, // newline string to use
|
||||
curLine, // current line (when using fuOil)
|
||||
inline_pos; // Offset in line (for long lines)
|
||||
|
||||
// lines longer than this are split to chunks
|
||||
// sending a super-ling string through the socket is not a good idea
|
||||
var MAX_LINE_LEN = 128;
|
||||
|
||||
function fuOpen() {
|
||||
fuStatus("Ready...");
|
||||
Modal.show('#fu_modal', onClose);
|
||||
$('#fu_form').toggleClass('busy', false);
|
||||
Input.blockKeys(true);
|
||||
}
|
||||
|
||||
function onClose() {
|
||||
console.log("Upload modal closed.");
|
||||
clearTimeout(fuTout);
|
||||
line_i = 0;
|
||||
Input.blockKeys(false);
|
||||
}
|
||||
|
||||
function fuStatus(msg) {
|
||||
qs('#fu_prog').textContent = msg;
|
||||
}
|
||||
|
||||
function fuSend() {
|
||||
var v = qs('#fu_text').value;
|
||||
if (!v.length) {
|
||||
fuClose();
|
||||
return;
|
||||
}
|
||||
|
||||
lines = v.split('\n');
|
||||
line_i = 0;
|
||||
inline_pos = 0; // offset in line
|
||||
send_delay_ms = qs('#fu_delay').value;
|
||||
|
||||
// sanitize - 0 causes overflows
|
||||
if (send_delay_ms < 0) {
|
||||
send_delay_ms = 0;
|
||||
qs('#fu_delay').value = send_delay_ms;
|
||||
}
|
||||
|
||||
nl_str = {
|
||||
'CR': '\r',
|
||||
'LF': '\n',
|
||||
'CRLF': '\r\n',
|
||||
}[qs('#fu_crlf').value];
|
||||
|
||||
$('#fu_form').toggleClass('busy', true);
|
||||
fuStatus("Starting...");
|
||||
fuSendLine();
|
||||
}
|
||||
|
||||
function fuSendLine() {
|
||||
if (!$('#fu_modal').hasClass('visible')) {
|
||||
// Modal is closed, cancel
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Conn.canSend()) {
|
||||
// postpone
|
||||
fuTout = setTimeout(fuSendLine, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (inline_pos == 0) {
|
||||
curLine = lines[line_i++] + nl_str;
|
||||
}
|
||||
|
||||
var chunk;
|
||||
if ((curLine.length - inline_pos) <= MAX_LINE_LEN) {
|
||||
chunk = curLine.substr(inline_pos, MAX_LINE_LEN);
|
||||
inline_pos = 0;
|
||||
} else {
|
||||
chunk = curLine.substr(inline_pos, MAX_LINE_LEN);
|
||||
inline_pos += MAX_LINE_LEN;
|
||||
}
|
||||
|
||||
if (!Input.sendString(chunk)) {
|
||||
fuStatus("FAILED!");
|
||||
return;
|
||||
}
|
||||
|
||||
var all = lines.length;
|
||||
|
||||
fuStatus(line_i+" / "+all+ " ("+(Math.round((line_i/all)*1000)/10)+"%)");
|
||||
|
||||
if (lines.length > line_i || inline_pos > 0) {
|
||||
fuTout = setTimeout(fuSendLine, send_delay_ms);
|
||||
} else {
|
||||
closeWhenReady();
|
||||
}
|
||||
}
|
||||
|
||||
function closeWhenReady() {
|
||||
if (!Conn.canSend()) {
|
||||
// stuck in XOFF still, wait to process...
|
||||
fuStatus("Waiting for Tx buffer...");
|
||||
setTimeout(closeWhenReady, 100);
|
||||
} else {
|
||||
fuStatus("Done.");
|
||||
// delay to show it
|
||||
setTimeout(function() {
|
||||
fuClose();
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
function fuClose() {
|
||||
Modal.hide('#fu_modal');
|
||||
}
|
||||
|
||||
return {
|
||||
init: function() {
|
||||
qs('#fu_file').addEventListener('change', function (evt) {
|
||||
var reader = new FileReader();
|
||||
var file = evt.target.files[0];
|
||||
console.log("Selected file type: "+file.type);
|
||||
if (!file.type.match(/text\/.*|application\/(json|csv|.*xml.*|.*script.*)/)) {
|
||||
// Deny load of blobs like img - can crash browser and will get corrupted anyway
|
||||
if (!confirm("This does not look like a text file: "+file.type+"\nReally load?")) {
|
||||
qs('#fu_file').value = '';
|
||||
return;
|
||||
}
|
||||
}
|
||||
reader.onload = function(e) {
|
||||
var txt = e.target.result.replace(/[\r\n]+/,'\n');
|
||||
qs('#fu_text').value = txt;
|
||||
};
|
||||
console.log("Loading file...");
|
||||
reader.readAsText(file);
|
||||
}, false);
|
||||
},
|
||||
close: fuClose,
|
||||
start: fuSend,
|
||||
open: fuOpen,
|
||||
}
|
||||
})();
|
||||
@@ -113,11 +113,49 @@ function undef(x) {
|
||||
}
|
||||
|
||||
/** Safe json parse */
|
||||
function jsp() {
|
||||
function jsp(str) {
|
||||
try {
|
||||
return JSON.parse(e);
|
||||
return JSON.parse(str);
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Create a character from ASCII code */
|
||||
function Chr(n) {
|
||||
return String.fromCharCode(n);
|
||||
}
|
||||
|
||||
/** Decode number from 2B encoding */
|
||||
function parse2B(s, i) {
|
||||
return (s.charCodeAt(i++) - 1) + (s.charCodeAt(i) - 1) * 127;
|
||||
}
|
||||
|
||||
/** Decode number from 3B encoding */
|
||||
function parse3B(s, i) {
|
||||
return (s.charCodeAt(i) - 1) + (s.charCodeAt(i+1) - 1) * 127 + (s.charCodeAt(i+2) - 1) * 127 * 127;
|
||||
}
|
||||
|
||||
/** Encode using 2B encoding, returns string. */
|
||||
function encode2B(n) {
|
||||
var lsb, msb;
|
||||
lsb = (n % 127);
|
||||
n = ((n - lsb) / 127);
|
||||
lsb += 1;
|
||||
msb = (n + 1);
|
||||
return Chr(lsb) + Chr(msb);
|
||||
}
|
||||
|
||||
/** Encode using 3B encoding, returns string. */
|
||||
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);
|
||||
}
|
||||
|
||||
+10
-33
@@ -17,27 +17,11 @@
|
||||
|
||||
$('#sta-nw .essid').html(e(name));
|
||||
var nopw = undef(password) || password.length == 0;
|
||||
$('#sta-nw .x-passwd').html(e(password));
|
||||
$('#sta-nw .passwd').toggleClass('hidden', nopw);
|
||||
$('#sta-nw .nopasswd').toggleClass('hidden', !nopw);
|
||||
$('#sta-nw .ip').html(ip.length>0 ? tr('wifi.connected_ip_is')+ip : tr('wifi.not_conn'));
|
||||
}
|
||||
|
||||
function submitPskModal(e, open) {
|
||||
var passwd = $('#conn-passwd').val();
|
||||
var ssid = $('#conn-ssid').val();
|
||||
|
||||
if (open || passwd.length) {
|
||||
$('#sta_password').val(passwd);
|
||||
$('#sta_ssid').val(ssid);
|
||||
selectSta(ssid, passwd, '');
|
||||
}
|
||||
|
||||
if (e) e.preventDefault();
|
||||
Modal.hide('#psk-modal');
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Update display for received response */
|
||||
function onScan(resp, status) {
|
||||
//var ap_json = {
|
||||
@@ -83,7 +67,6 @@
|
||||
ap.enc = parseInt(ap.enc);
|
||||
|
||||
if (ap.enc > 4) return; // hide unsupported auths
|
||||
WiFi.scan_url = '/cfg/wifi/scan';
|
||||
|
||||
var item = mk('div');
|
||||
|
||||
@@ -107,19 +90,18 @@
|
||||
$item.on('click', function () {
|
||||
var $th = $(this);
|
||||
|
||||
var ssid = $th.data('ssid');
|
||||
|
||||
$('#conn-ssid').val(ssid);
|
||||
$('#conn-passwd').val('');
|
||||
var conn_ssid = $th.data('ssid');
|
||||
var conn_pass = '';
|
||||
|
||||
if (+$th.data('pwd')) {
|
||||
// this AP needs a password
|
||||
Modal.show('#psk-modal');
|
||||
$('#conn-passwd')[0].focus();
|
||||
} else {
|
||||
//Modal.show('#reset-modal');
|
||||
submitPskModal(null, true);
|
||||
conn_pass = prompt(tr("wifi.enter_passwd").replace(":ssid:", conn_ssid));
|
||||
if (!conn_pass) return;
|
||||
}
|
||||
|
||||
$('#sta_password').val(conn_pass);
|
||||
$('#sta_ssid').val(conn_ssid);
|
||||
selectSta(conn_ssid, conn_pass, '');
|
||||
});
|
||||
|
||||
|
||||
@@ -132,12 +114,13 @@
|
||||
$('#ap-loader').removeClass('hidden');
|
||||
$('#ap-scan').addClass('hidden');
|
||||
$('#ap-loader .anim-dots').html('.');
|
||||
|
||||
scanAPs();
|
||||
}
|
||||
|
||||
/** Ask the CGI what APs are visible (async) */
|
||||
function scanAPs() {
|
||||
$.get('http://'+_root+w.scan_url, onScan);
|
||||
$.get('http://'+_root+'/cfg/wifi/scan', onScan);
|
||||
}
|
||||
|
||||
function rescan(time) {
|
||||
@@ -146,12 +129,6 @@
|
||||
|
||||
/** Set up the WiFi page */
|
||||
function wifiInit(cfg) {
|
||||
// Hide what should be hidden in this mode
|
||||
cfg.mode = +cfg.mode;
|
||||
|
||||
$('#ap-noscan').toggleClass('hidden', cfg.mode != 2);
|
||||
$('#ap-scan').toggleClass('hidden', cfg.mode == 2);
|
||||
|
||||
// Update slider value displays
|
||||
$('.Row.range').forEach(function(x) {
|
||||
var inp = x.querySelector('input');
|
||||
|
||||
+90
-51
@@ -4,70 +4,84 @@ return [
|
||||
'appname' => 'ESPTerm',
|
||||
|
||||
'menu.cfg_wifi' => 'WiFi Settings',
|
||||
'menu.cfg_network' => 'Network Configuration',
|
||||
'menu.cfg_app' => 'Terminal Settings',
|
||||
'menu.cfg_network' => 'Network Settings',
|
||||
'menu.cfg_term' => 'Terminal Settings',
|
||||
'menu.about' => 'About ESPTerm',
|
||||
'menu.help' => 'Quick Reference',
|
||||
'menu.term' => 'Back to Terminal',
|
||||
'menu.cfg_admin' => 'Reset & Restore',
|
||||
'menu.cfg_wifi_conn' => 'Connecting to External Network',
|
||||
'menu.cfg_system' => 'System Settings',
|
||||
'menu.cfg_wifi_conn' => 'Connecting to Network',
|
||||
|
||||
'menu.settings' => 'Settings',
|
||||
|
||||
'title.term' => 'Terminal',
|
||||
|
||||
'term_nav.config' => 'Config',
|
||||
'term_nav.wifi' => 'WiFi',
|
||||
'term_nav.help' => 'Help',
|
||||
'term_nav.about' => 'About',
|
||||
'term_nav.paste' => 'Paste',
|
||||
'term_nav.upload' => 'Upload',
|
||||
'term_nav.keybd' => 'Keyboard',
|
||||
'term_nav.paste_prompt' => 'Paste text to send:',
|
||||
|
||||
'net.ap' => 'DHCP Server (AP)',
|
||||
'net.sta' => 'DHCP Client (Station)',
|
||||
'net.sta_mac' => 'Station MAC',
|
||||
'net.ap_mac' => 'AP MAC',
|
||||
'net.details' => 'MAC addresses',
|
||||
|
||||
'app.defaults' => 'Initial settings',
|
||||
'app.explain_initials' => '
|
||||
Those are the initial settings used after ESPTerm restarts, and they
|
||||
will also be applied immediately after you submit this form.
|
||||
They can be subsequently changed by ESC commands, but those changes
|
||||
aren\'t persistent and will be lost when the device powers off.',
|
||||
'term.defaults' => 'Initial Settings',
|
||||
'term.expert' => 'Expert Options',
|
||||
'term.explain_initials' => '
|
||||
Those are the initial settings used after ESPTerm powers on or when the screen
|
||||
reset command is received. Some options can be changed by the application via escape sequences,
|
||||
those changes won\'t be saved in Flash.
|
||||
',
|
||||
'term.explain_expert' => '
|
||||
Those are advanced config options that usually don\'t need to be changed.
|
||||
Edit them only if you know what you\'re doing.',
|
||||
|
||||
'app.term_title' => 'Header text',
|
||||
'app.term_width' => 'Screen width',
|
||||
'app.term_height' => 'Screen height',
|
||||
'app.default_fg' => 'Base text color',
|
||||
'app.default_bg' => 'Base background',
|
||||
'app.btn1' => 'Button 1 text',
|
||||
'app.btn2' => 'Button 2 text',
|
||||
'app.btn3' => 'Button 3 text',
|
||||
'app.btn4' => 'Button 4 text',
|
||||
'app.btn5' => 'Button 5 text',
|
||||
'term.example' => 'Default colors preview',
|
||||
|
||||
'term.reset_screen' => 'Reset screen & parser',
|
||||
'term.term_title' => 'Header text',
|
||||
'term.term_width' => 'Width / height',
|
||||
'term.default_fg_bg' => 'Text / background',
|
||||
'term.buttons' => 'Button labels',
|
||||
'term.theme' => 'Color scheme',
|
||||
'term.parser_tout_ms' => 'Parser timeout',
|
||||
'term.display_tout_ms' => 'Redraw delay',
|
||||
'term.display_cooldown_ms' => 'Redraw cooldown',
|
||||
'term.fn_alt_mode' => 'SS3 Fn keys',
|
||||
'term.show_config_links' => 'Show nav links',
|
||||
'term.show_buttons' => 'Show buttons',
|
||||
'term.loopback' => 'Local Echo',
|
||||
'term.button_msgs' => 'Button codes<br>(ASCII, dec, CSV)',
|
||||
|
||||
// terminal color labels
|
||||
'color.0' => 'Black',
|
||||
'color.1' => 'Dark Red',
|
||||
'color.2' => 'Dark Green',
|
||||
'color.3' => 'Dim Yellow',
|
||||
'color.4' => 'Deep Blue',
|
||||
'color.5' => 'Dark Violet',
|
||||
'color.6' => 'Dark Cyan',
|
||||
'color.1' => 'Red',
|
||||
'color.2' => 'Green',
|
||||
'color.3' => 'Yellow',
|
||||
'color.4' => 'Blue',
|
||||
'color.5' => 'Purple',
|
||||
'color.6' => 'Cyan',
|
||||
'color.7' => 'Silver',
|
||||
'color.8' => 'Gray',
|
||||
'color.9' => 'Light Red',
|
||||
'color.10' => 'Light Green',
|
||||
'color.11' => 'Light Yellow',
|
||||
'color.12' => 'Light Blue',
|
||||
'color.13' => 'Light Violet',
|
||||
'color.13' => 'Light Purple',
|
||||
'color.14' => 'Light Cyan',
|
||||
'color.15' => 'White',
|
||||
|
||||
'net.explain_sta' => '
|
||||
Those settings affect the built-in DHCP client used for
|
||||
connecting to an external network. Switching DHCP (dynamic IP) off
|
||||
makes ESPTerm use the configured static IP. Please double-check
|
||||
those settings before submitting, setting them incorrectly may
|
||||
make it hard to access ESPTerm via the external network.',
|
||||
Switch off Dynamic IP to configure the static IP address.',
|
||||
|
||||
'net.explain_ap' => '
|
||||
Those settings affect the built-in DHCP server in AP mode.
|
||||
Please double-check those settings before submitting, setting them
|
||||
incorrectly may render ESPTerm inaccessible via the AP.',
|
||||
Those settings affect the built-in DHCP server in AP mode.',
|
||||
|
||||
'net.ap_dhcp_time' => 'Lease time',
|
||||
'net.ap_dhcp_start' => 'Pool start IP',
|
||||
@@ -81,7 +95,7 @@ return [
|
||||
'net.sta_addr_gw' => 'Gateway IP',
|
||||
|
||||
'wifi.ap' => 'Built-in Access Point',
|
||||
'wifi.sta' => 'Connect to External Network',
|
||||
'wifi.sta' => 'Join Existing Network',
|
||||
|
||||
'wifi.enable' => 'Enabled',
|
||||
'wifi.tpw' => 'Transmit power',
|
||||
@@ -93,42 +107,67 @@ return [
|
||||
|
||||
'wifi.not_conn' => 'Not connected.',
|
||||
'wifi.sta_none' => 'None',
|
||||
'wifi.sta_active_pw' => '🔒',
|
||||
'wifi.sta_active_pw' => '🔒 Password saved',
|
||||
'wifi.sta_active_nopw' => '🔓 Open access',
|
||||
'wifi.connected_ip_is' => 'Connected, IP is ',
|
||||
'wifi.sta_password' => 'Password:',
|
||||
|
||||
'wifi.scanning' => 'Scanning',
|
||||
'wifi.scan_now' => 'Start scanning!',
|
||||
'wifi.cant_scan_no_sta' => 'Can\'t scan with Client mode disabled.',
|
||||
'wifi.scan_now' => 'Click here to start scanning!',
|
||||
'wifi.cant_scan_no_sta' => 'Click here to enable client mode and start scanning!',
|
||||
'wifi.select_ssid' => 'Available networks:',
|
||||
'wifi.enter_passwd' => 'Enter password for ":ssid:"',
|
||||
'wifi.sta_explain' => 'After selecting a network, press Apply to connect.',
|
||||
|
||||
'wifi.conn.status' => 'Status:',
|
||||
'wifi.conn.back_to_config' => 'Back to WiFi config',
|
||||
'wifi.conn.telemetry_lost' => 'Telemetry lost, something went wrong. Try again...',
|
||||
'wifi.conn.telemetry_lost' => 'Telemetry lost; something went wrong, or your device disconnected.',
|
||||
'wifi.conn.explain_android_sucks' => '
|
||||
If you\'re configuring ESPTerm via a smartphone, or were connected
|
||||
from another external network, your device may lose connection and this
|
||||
progress indicator won\'t work. Please wait a while (~ 15 seconds),
|
||||
then check if the connection succeeded.',
|
||||
|
||||
'wifi.conn.explain_reset' => '
|
||||
To force enable the built-in AP, hold the BOOT
|
||||
button until the blue LED starts flashing. Hold the button longer (until the LED
|
||||
flashes rapidly) for a "factory reset".',
|
||||
|
||||
'wifi.conn.disabled' =>"Station mode is disabled.",
|
||||
'wifi.conn.idle' =>"Idle, not connected and with no IP.",
|
||||
'wifi.conn.idle' =>"Idle, not connected and has no IP.",
|
||||
'wifi.conn.success' => "Connected! Received IP ",
|
||||
'wifi.conn.working' => "Connecting to selected AP",
|
||||
'wifi.conn.fail' => "Connection failed, check settings & try again. Cause: ",
|
||||
|
||||
'admin.confirm_restore' => 'Restore all settings to their default values?',
|
||||
'admin.confirm_restore_hard' =>
|
||||
'system.save_restore' => 'Save & Restore',
|
||||
'system.confirm_restore' => 'Restore all settings to their default values?',
|
||||
'system.confirm_restore_hard' =>
|
||||
'Restore to firmware default settings? This will reset ' .
|
||||
'all active settings and switch to AP mode with the default SSID.',
|
||||
'admin.confirm_store_defaults' =>
|
||||
'system.confirm_store_defaults' =>
|
||||
'Enter admin password to confirm you want to store the current settings as defaults.',
|
||||
'admin.password' => 'Admin password:',
|
||||
'admin.restore_defaults' => 'Reset to default settings',
|
||||
'admin.write_defaults' => 'Save current settings as default',
|
||||
'admin.restore_hard' => 'Reset to firmware default settings',
|
||||
'admin.explain' => '
|
||||
'system.password' => 'Admin password:',
|
||||
'system.restore_defaults' => 'Reset active settings to defaults',
|
||||
'system.write_defaults' => 'Save active settings as defaults',
|
||||
'system.restore_hard' => 'Reset active settings to firmware defaults',
|
||||
'system.explain_persist' => '
|
||||
ESPTerm contains two persistent memory banks, one for default and
|
||||
one for active settings. Active settings can be stored as defaults
|
||||
by the administrator. Use the following button to revert all
|
||||
active settings to their stored default values.
|
||||
by the administrator (password required).
|
||||
',
|
||||
'system.uart' => 'Serial Port',
|
||||
'system.explain_uart' => '
|
||||
This form controls the primary, communication UART. The debug UART is fixed at 115.200 baud, one stop-bit and no parity.
|
||||
',
|
||||
'uart.baud' => 'Baud rate',
|
||||
'uart.parity' => 'Parity',
|
||||
'uart.parity.none' => 'None',
|
||||
'uart.parity.odd' => 'Odd',
|
||||
'uart.parity.even' => 'Even',
|
||||
'uart.stop_bits' => 'Stop-bits',
|
||||
'uart.stop_bits.one' => 'One',
|
||||
'uart.stop_bits.one_and_half' => 'One and half',
|
||||
'uart.stop_bits.two' => 'Two',
|
||||
|
||||
'apply' => 'Apply!',
|
||||
'enabled' => 'Enabled',
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
echo "Packing js..."
|
||||
|
||||
cat jssrc/chibi.js \
|
||||
jssrc/keymaster.js \
|
||||
jssrc/utils.js \
|
||||
jssrc/modal.js \
|
||||
jssrc/notif.js \
|
||||
jssrc/appcommon.js \
|
||||
jssrc/lang.js \
|
||||
jssrc/wifi.js \
|
||||
jssrc/term_* \
|
||||
jssrc/term.js > js/app.js
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
|
||||
<?php if ($_GET['BODYCLASS'] !== 'page-term'): ?>
|
||||
<div class="botpad"></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="NotifyMsg hidden" id="notif"></div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<table>
|
||||
<tr>
|
||||
<th>ESPTerm</th>
|
||||
<td>v%vers_fw%, build %date% at %time%</td>
|
||||
<td>v%vers_fw%, built %date% at %time%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>libesphttpd</th>
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
<div class="Box">
|
||||
<div class="Row explain">
|
||||
<?= tr('admin.explain') ?>
|
||||
</div>
|
||||
|
||||
<div class="Row buttons">
|
||||
<a class="button icn-restore"
|
||||
onclick="return confirm('<?= tr('admin.confirm_restore') ?>');"
|
||||
href="<?= e(url('restore_defaults')) ?>"
|
||||
>
|
||||
<?= tr('admin.restore_defaults') ?>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="Row buttons">
|
||||
<a onclick="writeDefaults(); return false;" href="#"><?= tr('admin.write_defaults') ?></a>
|
||||
</div>
|
||||
|
||||
<div class="Row buttons">
|
||||
<a onclick="return confirm('<?= tr('admin.confirm_restore_hard') ?>');"
|
||||
href="<?= e(url('restore_hard')) ?>"
|
||||
>
|
||||
<?= tr('admin.restore_hard') ?>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function writeDefaults() {
|
||||
var pw = prompt('<?= tr('admin.confirm_store_defaults') ?>');
|
||||
if (!pw) return;
|
||||
location.href = <?=json_encode(url('write_defaults')) ?> + '?pw=' + pw;
|
||||
}
|
||||
</script>
|
||||
@@ -1,71 +0,0 @@
|
||||
<form class="Box mobopen str" action="<?= e(url('app_set')) ?>" method="GET" id='form-1'>
|
||||
<h2><?= tr('app.defaults') ?></h2>
|
||||
|
||||
<div class="Row explain">
|
||||
<?= tr('app.explain_initials') ?>
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="term_width"><?= tr('app.term_width') ?></label>
|
||||
<input type="number" step=1 min=1 max=255 name="term_width" id="term_width" value="%term_width%" required>
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="term_height"><?= tr('app.term_height') ?></label>
|
||||
<input type="number" step=1 min=1 max=255 name="term_height" id="term_height" value="%term_height%" required>
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="default_fg"><?= tr("app.default_fg") ?></label>
|
||||
<select name="default_fg" id="default_fg">
|
||||
<?php for($i=0; $i<16; $i++): ?>
|
||||
<option value="<?=$i?>"><?= tr("color.$i") ?></option>
|
||||
<?php endfor; ?>
|
||||
</select>
|
||||
<script>qs('#default_fg').selectedIndex = %default_fg%;</script>
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="default_bg"><?= tr("app.default_bg") ?></label>
|
||||
<select name="default_bg" id="default_bg">
|
||||
<?php for($i=0; $i<16; $i++): ?>
|
||||
<option value="<?=$i?>"><?= tr("color.$i") ?></option>
|
||||
<?php endfor; ?>
|
||||
</select>
|
||||
<script>qs('#default_bg').selectedIndex = %default_bg%;</script>
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="term_title"><?= tr('app.term_title') ?></label>
|
||||
<input type="text" name="term_title" id="term_title" value="%term_title%" required>
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="btn1"><?= tr("app.btn1") ?></label>
|
||||
<input class="short" type="text" name="btn1" id="btn1" value="%btn1%">
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="btn2"><?= tr("app.btn2") ?></label>
|
||||
<input class="short" type="text" name="btn2" id="btn2" value="%btn2%">
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="btn3"><?= tr("app.btn3") ?></label>
|
||||
<input class="short" type="text" name="btn3" id="btn3" value="%btn3%">
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="btn4"><?= tr("app.btn4") ?></label>
|
||||
<input class="short" type="text" name="btn4" id="btn4" value="%btn4%">
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="btn5"><?= tr("app.btn5") ?></label>
|
||||
<input class="short" type="text" name="btn5" id="btn5" value="%btn5%">
|
||||
</div>
|
||||
|
||||
<div class="Row buttons">
|
||||
<a class="button icn-ok" href="#" onclick="qs('#form-1').submit()"><?= tr('apply') ?></a>
|
||||
</div>
|
||||
</form>
|
||||
@@ -12,22 +12,22 @@ $ipmask='pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}$"';
|
||||
<div class="Row checkbox x-static-toggle" >
|
||||
<label><?= tr('net.sta_dhcp_enable') ?></label><!--
|
||||
--><span class="box" tabindex=0 role=checkbox></span>
|
||||
<input type="hidden" name="sta_dhcp_enable" value="%sta_dhcp_enable%">
|
||||
<input type="hidden" id="sta_dhcp_enable" name="sta_dhcp_enable" value="%sta_dhcp_enable%">
|
||||
</div>
|
||||
|
||||
<div class="Row x-static">
|
||||
<label for="sta_addr_ip"><?= tr('net.sta_addr_ip') ?></label>
|
||||
<input type="text" name="sta_addr_ip" id="sta_addr_ip" value="%sta_addr_ip%" <?=$ipmask?> required>
|
||||
<input type="text" name="sta_addr_ip" id="sta_addr_ip" value="%h:sta_addr_ip%" <?=$ipmask?> required>
|
||||
</div>
|
||||
|
||||
<div class="Row x-static">
|
||||
<label for="sta_addr_mask"><?= tr('net.sta_addr_mask') ?></label>
|
||||
<input type="text" name="sta_addr_mask" id="sta_addr_mask" value="%sta_addr_mask%" <?=$ipmask?> required>
|
||||
<input type="text" name="sta_addr_mask" id="sta_addr_mask" value="%h:sta_addr_mask%" <?=$ipmask?> required>
|
||||
</div>
|
||||
|
||||
<div class="Row x-static">
|
||||
<label for="sta_addr_gw"><?= tr('net.sta_addr_gw') ?></label>
|
||||
<input type="text" name="sta_addr_gw" id="sta_addr_gw" value="%sta_addr_gw%" <?=$ipmask?> required>
|
||||
<input type="text" name="sta_addr_gw" id="sta_addr_gw" value="%h:sta_addr_gw%" <?=$ipmask?> required>
|
||||
</div>
|
||||
|
||||
<div class="Row buttons">
|
||||
@@ -43,29 +43,29 @@ $ipmask='pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}$"';
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="ap_dhcp_time"><?= tr('net.ap_dhcp_time') ?><span class="mq-phone"> (min)</span></label>
|
||||
<input type="number" step=1 min=1 max=2880 name="ap_dhcp_time" id="ap_dhcp_time" value="%ap_dhcp_time%" required>
|
||||
<span class="mq-no-phone"> (min)</span>
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="ap_dhcp_start"><?= tr('net.ap_dhcp_start') ?></label>
|
||||
<input type="text" name="ap_dhcp_start" id="ap_dhcp_start" value="%ap_dhcp_start%" <?=$ipmask?> required>
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="ap_dhcp_end"><?= tr('net.ap_dhcp_end') ?></label>
|
||||
<input type="text" name="ap_dhcp_end" id="ap_dhcp_end" value="%ap_dhcp_end%" <?=$ipmask?> required>
|
||||
<label for="ap_addr_mask"><?= tr('net.ap_addr_mask') ?></label>
|
||||
<input type="text" name="ap_addr_mask" id="ap_addr_mask" value="%h:ap_addr_mask%" <?=$ipmask?> required>
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="ap_addr_ip"><?= tr('net.ap_addr_ip') ?></label>
|
||||
<input type="text" name="ap_addr_ip" id="ap_addr_ip" value="%ap_addr_ip%" <?=$ipmask?> required>
|
||||
<input type="text" name="ap_addr_ip" id="ap_addr_ip" value="%h:ap_addr_ip%" <?=$ipmask?> required>
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="ap_addr_mask"><?= tr('net.ap_addr_mask') ?></label>
|
||||
<input type="text" name="ap_addr_mask" id="ap_addr_mask" value="%ap_addr_mask%" <?=$ipmask?> required>
|
||||
<label for="ap_dhcp_start"><?= tr('net.ap_dhcp_start') ?></label>
|
||||
<input type="text" name="ap_dhcp_start" id="ap_dhcp_start" value="%h:ap_dhcp_start%" <?=$ipmask?> required>
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="ap_dhcp_end"><?= tr('net.ap_dhcp_end') ?></label>
|
||||
<input type="text" name="ap_dhcp_end" id="ap_dhcp_end" value="%h:ap_dhcp_end%" <?=$ipmask?> required>
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="ap_dhcp_time"><?= tr('net.ap_dhcp_time') ?><span class="mq-phone"> (min)</span></label>
|
||||
<input type="number" step=1 min=1 max=2880 name="ap_dhcp_time" id="ap_dhcp_time" value="%ap_dhcp_time%" required>
|
||||
<span class="mq-no-phone"> min</span>
|
||||
</div>
|
||||
|
||||
<div class="Row buttons">
|
||||
@@ -86,10 +86,13 @@ $ipmask='pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}$"';
|
||||
|
||||
<script>
|
||||
function updateStaticDisp() {
|
||||
$('.x-static').toggleClass('hidden', $('#sta_dhcp_enable').val());
|
||||
var sttc = !parseInt($('#sta_dhcp_enable').val());
|
||||
$('.x-static').toggleClass('hidden', !sttc);
|
||||
}
|
||||
$('.x-static-toggle').on('click', function() {
|
||||
updateStaticDisp();
|
||||
setTimeout(function() {
|
||||
updateStaticDisp();
|
||||
}, 0)
|
||||
});
|
||||
updateStaticDisp();
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
<div class="Box str mobcol">
|
||||
<h2 tabindex=0><?= tr('system.save_restore') ?></h2>
|
||||
|
||||
<div class="Row explain nomargintop">
|
||||
<?= tr('system.explain_persist') ?>
|
||||
</div>
|
||||
|
||||
<div class="Row buttons2">
|
||||
<a class="button icn-restore"
|
||||
onclick="return confirm('<?= tr('system.confirm_restore') ?>');"
|
||||
href="<?= e(url('restore_defaults')) ?>">
|
||||
<?= tr('system.restore_defaults') ?>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="Row buttons2">
|
||||
<a onclick="writeDefaults(); return false;" href="#"><?= tr('system.write_defaults') ?></a>
|
||||
</div>
|
||||
|
||||
<div class="Row buttons2">
|
||||
<a onclick="return confirm('<?= tr('system.confirm_restore_hard') ?>');"
|
||||
href="<?= e(url('restore_hard')) ?>">
|
||||
<?= tr('system.restore_hard') ?>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<form class="Box str mobcol" action="<?= e(url('system_set')) ?>" method="GET" id="form-1">
|
||||
<h2 tabindex=0><?= tr('system.uart') ?></h2>
|
||||
|
||||
<div class="Row explain">
|
||||
<?= tr('system.explain_uart') ?>
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="uart_baud"><?= tr('uart.baud') ?><span class="mq-phone"> (bps)</span></label>
|
||||
<select name="uart_baud" id="uart_baud" class="short">
|
||||
<?php foreach([
|
||||
300, 600, 1200, 2400, 4800, 9600, 19200, 38400,
|
||||
57600, 74880, 115200, 230400, 460800, 921600, 1843200, 3686400,
|
||||
] as $b):
|
||||
?><option value="<?=$b?>"><?= number_format($b, 0, ',', '.') ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<span class="mq-no-phone"> bps</span>
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="uart_parity"><?= tr('uart.parity') ?></label>
|
||||
<select name="uart_parity" id="uart_parity" class="short">
|
||||
<?php foreach([
|
||||
2 => tr('uart.parity.none'),
|
||||
1 => tr('uart.parity.odd'),
|
||||
0 => tr('uart.parity.even'),
|
||||
] as $k => $label):
|
||||
?><option value="<?=$k?>"><?=$label?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="uart_stopbits"><?= tr('uart.stop_bits') ?></label>
|
||||
<select name="uart_stopbits" id="uart_stopbits" class="short">
|
||||
<?php foreach([
|
||||
1 => tr('uart.stop_bits.one'),
|
||||
2 => tr('uart.stop_bits.one_and_half'),
|
||||
3 => tr('uart.stop_bits.two'),
|
||||
] as $k => $label):
|
||||
?><option value="<?=$k?>"><?=$label?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="Row buttons">
|
||||
<a class="button icn-ok" href="#" onclick="qs('#form-1').submit()"><?= tr('apply') ?></a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
function writeDefaults() {
|
||||
var pw = prompt('<?= tr('system.confirm_store_defaults') ?>');
|
||||
if (!pw) return;
|
||||
location.href = <?=json_encode(url('write_defaults')) ?> + '?pw=' + pw;
|
||||
}
|
||||
|
||||
$('#uart_baud').val(%uart_baud%);
|
||||
$('#uart_parity').val(%uart_parity%);
|
||||
$('#uart_stopbits').val(%uart_stopbits%);
|
||||
</script>
|
||||
@@ -0,0 +1,203 @@
|
||||
<div class="Box">
|
||||
<a href="<?= e(url('reset_screen')) ?>"><?= tr('term.reset_screen') ?></a>
|
||||
</div>
|
||||
|
||||
<form class="Box mobopen str" action="<?= e(url('term_set')) ?>" method="GET" id='form-1'>
|
||||
<h2><?= tr('term.defaults') ?></h2>
|
||||
|
||||
<div class="Row explain">
|
||||
<?= tr('term.explain_initials') ?>
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="theme"><?= tr("term.theme") ?></label>
|
||||
<select name="theme" id="theme" class="short" onchange="showColor()">
|
||||
<option value="0">Tango</option>
|
||||
<option value="1">Linux</option>
|
||||
<option value="2">XTerm</option>
|
||||
<option value="3">Rxvt</option>
|
||||
<option value="4">Ambience</option>
|
||||
<option value="5">Solarized</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="Row color-preview">
|
||||
<div class="colorprev">
|
||||
<span data-fg=0 class="bg0 fg0">30</span><!--
|
||||
--><span data-fg=1 class="bg0 fg1">31</span><!--
|
||||
--><span data-fg=2 class="bg0 fg2">32</span><!--
|
||||
--><span data-fg=3 class="bg0 fg3">33</span><!--
|
||||
--><span data-fg=4 class="bg0 fg4">34</span><!--
|
||||
--><span data-fg=5 class="bg0 fg5">35</span><!--
|
||||
--><span data-fg=6 class="bg0 fg6">36</span><!--
|
||||
--><span data-fg=7 class="bg0 fg7">37</span>
|
||||
</div>
|
||||
|
||||
<div class="colorprev">
|
||||
<span data-fg=8 class="bg0 fg8">90</span><!--
|
||||
--><span data-fg=9 class="bg0 fg9">91</span><!--
|
||||
--><span data-fg=10 class="bg0 fg10">92</span><!--
|
||||
--><span data-fg=11 class="bg0 fg11">93</span><!--
|
||||
--><span data-fg=12 class="bg0 fg12">94</span><!--
|
||||
--><span data-fg=13 class="bg0 fg13">95</span><!--
|
||||
--><span data-fg=14 class="bg0 fg14">96</span><!--
|
||||
--><span data-fg=15 class="bg0 fg15">97</span>
|
||||
</div>
|
||||
|
||||
<div class="colorprev">
|
||||
<span data-bg=0 class="bg0 fg15">40</span><!--
|
||||
--><span data-bg=1 class="bg1 fg15">41</span><!--
|
||||
--><span data-bg=2 class="bg2 fg15">42</span><!--
|
||||
--><span data-bg=3 class="bg3 fg0">43</span><!--
|
||||
--><span data-bg=4 class="bg4 fg15">44</span><!--
|
||||
--><span data-bg=5 class="bg5 fg15">45</span><!--
|
||||
--><span data-bg=6 class="bg6 fg15">46</span><!--
|
||||
--><span data-bg=7 class="bg7 fg0">47</span>
|
||||
</div>
|
||||
|
||||
<div class="colorprev">
|
||||
<span data-bg=8 class="bg8 fg15">100</span><!--
|
||||
--><span data-bg=9 class="bg9 fg0">101</span><!--
|
||||
--><span data-bg=10 class="bg10 fg0">102</span><!--
|
||||
--><span data-bg=11 class="bg11 fg0">103</span><!--
|
||||
--><span data-bg=12 class="bg12 fg0">104</span><!--
|
||||
--><span data-bg=13 class="bg13 fg0">105</span><!--
|
||||
--><span data-bg=14 class="bg14 fg0">106</span><!--
|
||||
--><span data-bg=15 class="bg15 fg0">107</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="Row color-preview">
|
||||
<div style="
|
||||
" id="color-example">
|
||||
<?= tr("term.example") ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label><?= tr("term.default_fg_bg") ?></label>
|
||||
<select name="default_fg" id="default_fg" class="short" onchange="showColor()">
|
||||
<?php for($i=0; $i<16; $i++): ?>
|
||||
<option value="<?=$i?>"><?= tr("color.$i") ?></option>
|
||||
<?php endfor; ?>
|
||||
</select> <!--
|
||||
--><select name="default_bg" id="default_bg" class="short" onchange="showColor()">
|
||||
<?php for($i=0; $i<16; $i++): ?>
|
||||
<option value="<?=$i?>"><?= tr("color.$i") ?></option>
|
||||
<?php endfor; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="term_width"><?= tr('term.term_width') ?></label>
|
||||
<input type="number" step=1 min=1 max=255 name="term_width" id="term_width" value="%term_width%" required> <!--
|
||||
--><input type="number" step=1 min=1 max=255 name="term_height" id="term_height" value="%term_height%" required>
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="term_title"><?= tr('term.term_title') ?></label>
|
||||
<input type="text" name="term_title" id="term_title" value="%h:term_title%" required>
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label><?= tr("term.buttons") ?></label>
|
||||
<input class="short" type="text" name="btn1" id="btn1" value="%h:btn1%">
|
||||
<input class="short" type="text" name="btn2" id="btn2" value="%h:btn2%">
|
||||
<input class="short" type="text" name="btn3" id="btn3" value="%h:btn3%">
|
||||
<input class="short" type="text" name="btn4" id="btn4" value="%h:btn4%">
|
||||
<input class="short" type="text" name="btn5" id="btn5" value="%h:btn5%">
|
||||
</div>
|
||||
|
||||
<div class="Row buttons">
|
||||
<a class="button icn-ok" href="#" onclick="qs('#form-1').submit()"><?= tr('apply') ?></a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form class="Box fold str" action="<?= e(url('term_set')) ?>" method="GET" id='form-2'>
|
||||
<h2><?= tr('term.expert') ?></h2>
|
||||
|
||||
<div class="Row explain">
|
||||
<?= tr('term.explain_expert') ?>
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="parser_tout_ms"><?= tr('term.parser_tout_ms') ?><span class="mq-phone"> (ms)</span></label>
|
||||
<input type="number" step=1 min=0 name="parser_tout_ms" id="parser_tout_ms" value="%parser_tout_ms%" required>
|
||||
<span class="mq-no-phone"> ms</span>
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="display_tout_ms"><?= tr('term.display_tout_ms') ?><span class="mq-phone"> (ms)</span></label>
|
||||
<input type="number" step=1 min=0 name="display_tout_ms" id="display_tout_ms" value="%display_tout_ms%" required>
|
||||
<span class="mq-no-phone"> ms</span>
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="display_cooldown_ms"><?= tr('term.display_cooldown_ms') ?><span class="mq-phone"> (ms)</span></label>
|
||||
<input type="number" step=1 min=0 name="display_cooldown_ms" id="display_cooldown_ms" value="%display_cooldown_ms%" required>
|
||||
<span class="mq-no-phone"> ms</span>
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label><?= tr("term.button_msgs") ?></label>
|
||||
<input class="short" type="text" name="bm1" id="bm1" value="%h:bm1%">
|
||||
<input class="short" type="text" name="bm2" id="bm2" value="%h:bm2%">
|
||||
<input class="short" type="text" name="bm3" id="bm3" value="%h:bm3%">
|
||||
<input class="short" type="text" name="bm4" id="bm4" value="%h:bm4%">
|
||||
<input class="short" type="text" name="bm5" id="bm5" value="%h:bm5%">
|
||||
</div>
|
||||
|
||||
<div class="Row checkbox" >
|
||||
<label><?= tr('term.fn_alt_mode') ?></label><!--
|
||||
--><span class="box" tabindex=0 role=checkbox></span>
|
||||
<input type="hidden" id="fn_alt_mode" name="fn_alt_mode" value="%fn_alt_mode%">
|
||||
</div>
|
||||
|
||||
<div class="Row checkbox" >
|
||||
<label><?= tr('term.show_buttons') ?></label><!--
|
||||
--><span class="box" tabindex=0 role=checkbox></span>
|
||||
<input type="hidden" id="show_buttons" name="show_buttons" value="%show_buttons%">
|
||||
</div>
|
||||
|
||||
<div class="Row checkbox" >
|
||||
<label><?= tr('term.show_config_links') ?></label><!--
|
||||
--><span class="box" tabindex=0 role=checkbox></span>
|
||||
<input type="hidden" id="show_config_links" name="show_config_links" value="%show_config_links%">
|
||||
</div>
|
||||
|
||||
<div class="Row checkbox" >
|
||||
<label><?= tr('term.loopback') ?></label><!--
|
||||
--><span class="box" tabindex=0 role=checkbox></span>
|
||||
<input type="hidden" id="loopback" name="loopback" value="%loopback%">
|
||||
</div>
|
||||
|
||||
<div class="Row buttons">
|
||||
<a class="button icn-ok" href="#" onclick="qs('#form-2').submit()"><?= tr('apply') ?></a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
$('#default_fg').val(%default_fg%);
|
||||
$('#default_bg').val(%default_bg%);
|
||||
$('#theme').val(%theme%);
|
||||
|
||||
function showColor() {
|
||||
var ex = qs('#color-example');
|
||||
ex.className = '';
|
||||
ex.classList.add('fg'+$('#default_fg').val());
|
||||
ex.classList.add('bg'+$('#default_bg').val());
|
||||
var th = $('#theme').val();
|
||||
$('.color-preview').forEach(function(e) {
|
||||
e.className = 'Row color-preview theme-'+th;
|
||||
});
|
||||
}
|
||||
showColor();
|
||||
|
||||
$('.colorprev span').on('click', function() {
|
||||
var fg = this.dataset.fg;
|
||||
var bg = this.dataset.bg;
|
||||
if (typeof fg != 'undefined') $('#default_fg').val(fg);
|
||||
if (typeof bg != 'undefined') $('#default_bg').val(bg);
|
||||
showColor()
|
||||
});
|
||||
</script>
|
||||
@@ -1,28 +1,28 @@
|
||||
<form class="Box str mobcol" action="<?= e(url('wifi_set')) ?>" method="GET" id="form-1">
|
||||
<h2 tabindex=0><?= tr('wifi.ap') ?></h2>
|
||||
|
||||
<div class="Row checkbox">
|
||||
<div class="Row checkbox x-ap-toggle">
|
||||
<label><?= tr('wifi.enable') ?></label><!--
|
||||
--><span class="box" tabindex=0></span>
|
||||
<input type="hidden" name="ap_enable" value="%ap_enable%">
|
||||
<input type="hidden" id="ap_enabled" name="ap_enable" value="%ap_enable%">
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<div class="Row x-ap-on">
|
||||
<label for="ap_ssid"><?= tr('wifi.ap_ssid') ?></label>
|
||||
<input type="text" name="ap_ssid" id="ap_ssid" value="%ap_ssid%" required>
|
||||
<input type="text" name="ap_ssid" id="ap_ssid" value="%h:ap_ssid%" required>
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<div class="Row x-ap-on">
|
||||
<label for="ap_password"><?= tr('wifi.ap_password') ?></label>
|
||||
<input type="text" name="ap_password" id="ap_password" value="%ap_password%">
|
||||
<input type="text" name="ap_password" id="ap_password" value="%h:ap_password%">
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<div class="Row x-ap-on">
|
||||
<label for="ap_channel"><?= tr('wifi.ap_channel') ?></label>
|
||||
<input type="number" name="ap_channel" id="ap_channel" min=1 max=14 value="%ap_channel%" required>
|
||||
</div>
|
||||
|
||||
<div class="Row range">
|
||||
<div class="Row range x-ap-on">
|
||||
<label for="tpw">
|
||||
<?= tr('wifi.tpw') ?>
|
||||
<span class="display x-disp1 mq-phone"></span>
|
||||
@@ -31,7 +31,7 @@
|
||||
<span class="display x-disp2 mq-no-phone"></span>
|
||||
</div>
|
||||
|
||||
<div class="Row checkbox">
|
||||
<div class="Row checkbox x-ap-on">
|
||||
<label><?= tr('wifi.ap_hidden') ?></label><!--
|
||||
--><span class="box" tabindex=0></span>
|
||||
<input type="hidden" name="ap_hidden" value="%ap_hidden%">
|
||||
@@ -42,25 +42,30 @@
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form class="Box str mobcol" action="<?= e(url('wifi_set')) ?>" method="GET" id="form-2">
|
||||
<form class="Box str mobcol expanded" action="<?= e(url('wifi_set')) ?>" method="GET" id="form-2">
|
||||
<h2 tabindex=0><?= tr('wifi.sta') ?></h2>
|
||||
|
||||
<div class="Row checkbox">
|
||||
<div class="Row checkbox x-sta-toggle">
|
||||
<label><?= tr('wifi.enable') ?></label><!--
|
||||
--><span class="box" tabindex=0></span>
|
||||
<input type="hidden" name="sta_enable" value="%sta_enable%">
|
||||
<input type="hidden" id="sta_enabled" name="sta_enable" value="%sta_enable%">
|
||||
</div>
|
||||
|
||||
<div class="Row explain nomargintop x-sta-on">
|
||||
<span class="spacer"></span>
|
||||
<?= tr("wifi.sta_explain") ?>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="sta_ssid" id="sta_ssid" value="">
|
||||
<input type="hidden" name="sta_password" id="sta_password" value="">
|
||||
|
||||
<div class="Row sta-info">
|
||||
<div class="Row sta-info x-sta-on">
|
||||
<label><?= tr('wifi.sta_info') ?></label>
|
||||
<div class="AP-preview hidden" id="sta-nw">
|
||||
<div class="wrap">
|
||||
<div class="inner">
|
||||
<div class="essid"></div>
|
||||
<div class="passwd"><?= tr('wifi.sta_active_pw') ?> <span class="x-passwd"></span></div>
|
||||
<div class="passwd"><?= tr('wifi.sta_active_pw') ?></div>
|
||||
<div class="nopasswd"><?= tr('wifi.sta_active_nopw') ?></div>
|
||||
<div class="ip"></div>
|
||||
</div>
|
||||
@@ -72,11 +77,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="ap-box">
|
||||
<div id="ap-box" class="x-sta-on">
|
||||
<label><?= tr('wifi.select_ssid') ?></label>
|
||||
<div id="ap-scan"><a href="#" onclick="WiFi.startScanning(); return false"><?= tr('wifi.scan_now') ?></a></div>
|
||||
<div id="ap-loader" class="hidden"><?= tr('wifi.scanning') ?><span class="anim-dots">.</span></div>
|
||||
<div id="ap-noscan" class="hidden"><?= tr('wifi.cant_scan_no_sta') ?></div>
|
||||
<div id="ap-list" class="hidden"></div>
|
||||
</div>
|
||||
|
||||
@@ -85,24 +89,35 @@
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="Modal hidden" id="psk-modal">
|
||||
<div class="Dialog">
|
||||
<form id="conn-form" onsubmit="submitPskModal(); return false;">
|
||||
<input type="hidden" id="conn-ssid"><!--
|
||||
--><label for="conn-passwd"><?= tr('wifi.sta_password') ?></label><!--
|
||||
--><input type="text" id="conn-passwd"><!--
|
||||
--><input type="submit" value="<?= tr('confirm') ?>">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
WiFi.scan_url = '<?= url('wifi_scan', true) ?>';
|
||||
WiFi.init({
|
||||
mode: '%opmode%',
|
||||
sta_ssid: '%sta_ssid%',
|
||||
sta_password: '%sta_password%',
|
||||
sta_active_ip: '%sta_active_ip%',
|
||||
sta_active_ssid: '%sta_active_ssid%',
|
||||
sta_ssid: '%j:sta_ssid%',
|
||||
sta_password: '%j:sta_password%',
|
||||
sta_active_ip: '%j:sta_active_ip%',
|
||||
sta_active_ssid: '%j:sta_active_ssid%',
|
||||
});
|
||||
|
||||
function updateApDisp() {
|
||||
var a = !!parseInt($('#ap_enabled').val());
|
||||
$('.x-ap-on').toggleClass('hidden', !a);
|
||||
}
|
||||
$('.x-ap-toggle').on('click', function() {
|
||||
setTimeout(function() {
|
||||
updateApDisp();
|
||||
}, 0)
|
||||
});
|
||||
|
||||
function updateStaDisp() {
|
||||
var a = !!parseInt($('#sta_enabled').val());
|
||||
$('.x-sta-on').toggleClass('hidden', !a);
|
||||
}
|
||||
$('.x-sta-toggle').on('click', function() {
|
||||
setTimeout(function() {
|
||||
updateStaDisp();
|
||||
}, 0)
|
||||
});
|
||||
|
||||
updateApDisp();
|
||||
updateStaDisp();
|
||||
</script>
|
||||
|
||||
@@ -5,9 +5,15 @@
|
||||
<a href="<?= e(url('cfg_wifi')) ?>" id="backbtn" class="button"><?= tr('wifi.conn.back_to_config') ?></a>
|
||||
</div>
|
||||
|
||||
<div class="Box">
|
||||
<p><?= tr('wifi.conn.explain_android_sucks') ?></p>
|
||||
<p><?= tr('wifi.conn.explain_reset') ?></p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var xhr = new XMLHttpRequest();
|
||||
var abortTmeo;
|
||||
var failCounter = 0;
|
||||
|
||||
var messages = <?= json_encode([
|
||||
'disabled' => tr('wifi.conn.disabled'),
|
||||
@@ -17,33 +23,63 @@
|
||||
'fail' => tr('wifi.conn.fail'),
|
||||
]) ?>;
|
||||
|
||||
function onFail() {
|
||||
$("#status").html(<?= json_encode(tr('wifi.conn.telemetry_lost')) ?>);
|
||||
$('.anim-dots').addClass('hidden');
|
||||
}
|
||||
|
||||
function getStatus() {
|
||||
xhr.open("GET", 'http://'+_root+'<?= url('wifi_connstatus', true) ?>');
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState == 4 && xhr.status >= 200 && xhr.status < 300) {
|
||||
clearTimeout(abortTmeo);
|
||||
var data = JSON.parse(xhr.responseText);
|
||||
var done = false;
|
||||
var msg = messages[data.status] || '...';
|
||||
if (data.status == 'success') msg += data.ip;
|
||||
if (data.status == 'fail') msg += data.cause;
|
||||
if (xhr.readyState == 4) {
|
||||
if (xhr.status == 200) {
|
||||
clearTimeout(abortTmeo);
|
||||
|
||||
$("#status").html(msg);
|
||||
try {
|
||||
var data = JSON.parse(xhr.responseText);
|
||||
var done = false;
|
||||
var msg = messages[data.status] || '...';
|
||||
|
||||
if (done) {
|
||||
// $('#backbtn').removeClass('hidden');
|
||||
$('.anim-dots').addClass('hidden');
|
||||
if (data.status == 'success') {
|
||||
msg += data.ip;
|
||||
done = true;
|
||||
}
|
||||
|
||||
if (data.status == 'fail') {
|
||||
msg += data.cause;
|
||||
done = true;
|
||||
}
|
||||
|
||||
$("#status").html(msg);
|
||||
|
||||
if (done) {
|
||||
// $('#backbtn').removeClass('hidden');
|
||||
$('.anim-dots').addClass('hidden');
|
||||
} else {
|
||||
// ask again after a short delay
|
||||
window.setTimeout(getStatus, 1000);
|
||||
}
|
||||
} catch(e) {
|
||||
failCounter++;
|
||||
console.log(e);
|
||||
// repeat
|
||||
if (failCounter > 5) {
|
||||
onFail();
|
||||
}
|
||||
else {
|
||||
window.setTimeout(getStatus, 1000);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
window.setTimeout(getStatus, 1000);
|
||||
onFail();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// XHR timeout
|
||||
abortTmeo = setTimeout(function () {
|
||||
xhr.abort();
|
||||
$("#status").html(<?= json_encode(tr('wifi.conn.telemetry_lost')) ?>);
|
||||
// $('#backbtn').removeClass('hidden');
|
||||
$('.anim-dots').addClass('hidden');
|
||||
onFail();
|
||||
}, 4000);
|
||||
|
||||
xhr.send();
|
||||
|
||||
+16
-312
@@ -1,316 +1,20 @@
|
||||
<div class="Box">
|
||||
<h2>Wiring</h2>
|
||||
|
||||
<ul>
|
||||
<li>Communication UART is on pins <b>Rx, Tx</b> at 115200-8-1-N. The baud rate can be changed in Terminal Settings.
|
||||
<li>Debug log is on pin <b>GPIO2</b> (P2) at 115200-8-1-N. This baud rate is fixed.
|
||||
<li>Compatible with 3.3 V and 5 V logic. For 5 V, 470 R protection resistors are recommended.
|
||||
<li>If the "LVD" LED on the ESPTerm module lights up, it doesn't get enough power to run correctly. Check your connections.
|
||||
<li>Connect Rx and Tx with a piece of wire to test the terminal alone, you should see what you type in the browser.
|
||||
<i>NOTE: This won't work if your ESP8266 board has a built-in USB-serial converter (like NodeMCU).</i>
|
||||
<li>For best performance, use the module in Client mode (connected to external network).
|
||||
<li>In AP mode, check that the channel used is clear; interference may cause a flaky connection.
|
||||
</ul>
|
||||
<a href="#" onclick="hpfold(1);return false">Expand all</a> | <a href="#" onclick="hpfold(0);return false">Collapse all</a>
|
||||
</div>
|
||||
|
||||
<div class="Box">
|
||||
<h2>Screen</h2>
|
||||
<?php require __DIR__ . "/help/troubleshooting.php"; ?>
|
||||
<?php require __DIR__ . "/help/nomenclature.php"; ?>
|
||||
<?php require __DIR__ . "/help/screen_behavior.php"; ?>
|
||||
<?php require __DIR__ . "/help/input.php"; ?>
|
||||
<?php require __DIR__ . "/help/charsets.php"; ?>
|
||||
<?php require __DIR__ . "/help/sgr_styles.php"; ?>
|
||||
<?php require __DIR__ . "/help/sgr_colors.php"; ?>
|
||||
<?php require __DIR__ . "/help/cmd_cursor.php"; ?>
|
||||
<?php require __DIR__ . "/help/cmd_screen.php"; ?>
|
||||
<?php require __DIR__ . "/help/cmd_system.php"; ?>
|
||||
|
||||
<ul>
|
||||
<li>Most ANSI escape sequences are supported.</li>
|
||||
<li>The max screen size is 2000 characters (eg. <b>25x80</b>), default is <b>10x26</b>. Set using <code>\e]W<rows>;<cols>\a</code>.</li>
|
||||
<li>The screen will automatically scroll, can be used for log output.</li>
|
||||
<li>Display update is sent <b>after 20 ms of inactivity</b>.</li>
|
||||
<li>At most 4 clients can be connected at the same time.</li>
|
||||
<li>The browser display needs WebSockets for the real-time updating. It may not work on really old phones / browsers.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="Box">
|
||||
<h2>Color Reference</h2>
|
||||
|
||||
<p>Color is set using <code>\e[<c>m</code> or <code>\e[<c>;<c>m</code> where "<c>" are numbers from the following tables:</p>
|
||||
|
||||
<b>Foreground</b>
|
||||
|
||||
<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="bg7 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 nb">40</span>
|
||||
<span class="bg1 fg15 nb">41</span>
|
||||
<span class="bg2 fg15 nb">42</span>
|
||||
<span class="bg3 fg0">43</span>
|
||||
<span class="bg4 fg15 nb">44</span>
|
||||
<span class="bg5 fg15 nb">45</span>
|
||||
<span class="bg6 fg15 nb">46</span>
|
||||
<span class="bg7 fg0">47</span>
|
||||
</div>
|
||||
|
||||
<div class="colorprev">
|
||||
<span class="bg8 fg15 nb">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>Bright foreground can also be set using the "bold" attribute 1 (eg. <code>\e[31;1m</code>). For more details, see the ANSI code reference below.</p>
|
||||
</div>
|
||||
|
||||
<div class="Box">
|
||||
<h2>User Input</h2>
|
||||
|
||||
<p>
|
||||
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.
|
||||
The input is limited to ASCII codes 32-126, backspace 8 and tab 9.
|
||||
</p>
|
||||
|
||||
<p>The buttons under the screen send ASCII codes 1, 2, 3, 4, 5.</p>
|
||||
|
||||
<p>
|
||||
<b>Mouse input</b> (click/tap) is sent as <code>\e[<y>;<x>M</code>. You can use this for on-screen buttons, menu navigation etc.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="Box">
|
||||
<h2>Supported ANSI Escape Sequences</h2>
|
||||
|
||||
<p>Sequences are started by ASCII code 27 (ESC, <code>\e</code>, <code>\x1b</code>, <code>\033</code>)</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>
|
||||
|
||||
<h3>Text Attributes</h3>
|
||||
|
||||
<p>
|
||||
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">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Attribute</th>
|
||||
<th>Meaning</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>0</td>
|
||||
<td>Reset text attributes to default</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>7</td>
|
||||
<td>Inverse (fg/bg swap when printing)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>27</td>
|
||||
<td>Inverse OFF</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>30-37, 90-97</td>
|
||||
<td>Foreground color, normal and bright</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>40-47, 100-107</td>
|
||||
<td>Background color, normal and bright</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Cursor</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>Params</th>
|
||||
<th>Meaning</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>\e[<n>A</td>
|
||||
<td>[count]</td>
|
||||
<td>Move cursor up</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[<n>B</td>
|
||||
<td>[count]</td>
|
||||
<td>Move cursor down</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[<n>C</td>
|
||||
<td>[count]</td>
|
||||
<td>Move cursor forward (right)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[<n>D</td>
|
||||
<td>[count]</td>
|
||||
<td>Move cursor backward (left)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[<n>E</td>
|
||||
<td>[count]</td>
|
||||
<td>Go N line down, start of line</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[<n>F</td>
|
||||
<td>[count]</td>
|
||||
<td>Go N lines up, start of line</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[<n>G</td>
|
||||
<td>column</td>
|
||||
<td>Go to column</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[<y>;<x>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>Params</th>
|
||||
<th>Meaning</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>\e[<m>J</td>
|
||||
<td>[mode=0]</td>
|
||||
<td>Clear screen. Mode: 0 - from cursor, 1 - to cursor, 2 - all</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[<m>K</td>
|
||||
<td>[mode=0]</td>
|
||||
<td>Erase line. Mode: 0 - from cursor, 1 - to cursor, 2 - all</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[<n>S</td>
|
||||
<td>[lines]</td>
|
||||
<td>Scroll screen content up, add empty line at the bottom</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[<n>T</td>
|
||||
<td>[lines]</td>
|
||||
<td>Scroll screen content down, add empty line at the top</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e]W<r>;<c>\a</td>
|
||||
<td>rows;cols</td>
|
||||
<td>Set screen size, maximum 25x80 (resp. total 2000 characters). This also clears the screen.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>System Commands</h3>
|
||||
|
||||
<table class="ansiref w100">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Code</th>
|
||||
<th>Params</th>
|
||||
<th>Meaning</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>\ec</td>
|
||||
<td>--</td>
|
||||
<td>
|
||||
"Device Reset" - clear screen, reset attributes, show cursor & move it to 1,1.
|
||||
The screen size and WiFi settings stay unchanged.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e]FR\a</td>
|
||||
<td>--</td>
|
||||
<td>"Factory Reset", emergency code when you mess up the WiFi, restores SSID to unique default, clears stored credentials & enters Client+AP mode.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[5n</td>
|
||||
<td>--</td>
|
||||
<td>Query device status, replies with <code>\e[0n</code> "device is OK". Can be used to check if the UART works.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<script>
|
||||
function hpfold(yes) {
|
||||
$('.fold').toggleClass('expanded', !!yes);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
<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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Since ESPTerm also supports UTF-8, this feature is the most useful for applications
|
||||
which can't print UTF-8 or already use alternate character sets for historical reasons.
|
||||
</p>
|
||||
|
||||
<h3>Supported codepages</h3>
|
||||
|
||||
<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, ESPTerm specific)</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
All codepages use codes 32-127, 32 being space. A character with no entry in the active codepage
|
||||
stays unchanged.
|
||||
</p>
|
||||
|
||||
<?php
|
||||
$codepages = load_esp_charsets();
|
||||
foreach($codepages as $name => $cp) {
|
||||
echo "<h4>Codepage `$name`</h4>\n";
|
||||
echo '<div class="charset">';
|
||||
foreach($cp as $point) {
|
||||
$dis = $point[1]==$point[2]?' class="none"' : '';
|
||||
echo "<div$dis><span>$point[0]</span><span>$point[1]</span><span>$point[2]</span></div>";
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
?>
|
||||
|
||||
<h3>Switching commands</h3>
|
||||
|
||||
<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). Often only G0 is used for simplicity.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
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>
|
||||
@@ -0,0 +1,199 @@
|
||||
<div class="Box fold">
|
||||
<h2>Commands: Cursor Functions</h2>
|
||||
|
||||
<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>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>
|
||||
|
||||
<p>
|
||||
*Legend:*
|
||||
Italic letters such as _n_ 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>
|
||||
|
||||
<h3>Movement</h3>
|
||||
|
||||
<table class="ansiref w100">
|
||||
<thead><tr><th>Code</th><th>Meaning</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>
|
||||
\e[<i>n</i>A \\
|
||||
\e[<i>n</i>B \\
|
||||
\e[<i>n</i>C \\
|
||||
\e[<i>n</i>D
|
||||
</code>
|
||||
</td>
|
||||
<td>Move cursor up (`A`), down (`B`), right (`C`), left (`D`)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>
|
||||
\e[<i>n</i>F \\
|
||||
\e[<i>n</i>E
|
||||
</code>
|
||||
</td>
|
||||
<td>Go _n_ lines up (`F`) or down (`E`), start of line</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>
|
||||
\e[<i>r</i>d \\
|
||||
\e[<i>c</i>G \\
|
||||
\e[<i>r</i>;<i>c</i>H
|
||||
</code>
|
||||
</td>
|
||||
<td>
|
||||
Go to absolute position - row (`d`), column (`G`), or both (`H`). Use `\e[H` to go to 1,1.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
`\e[6n`
|
||||
</td>
|
||||
<td>
|
||||
Query cursor position. Sent back as `\e[<i>r</i>;<i>c</i>R`.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Save / restore</h3>
|
||||
|
||||
<table class="ansiref w100">
|
||||
<thead><tr><th>Code</th><th>Meaning</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>
|
||||
\e[s \\
|
||||
\e[u
|
||||
</code>
|
||||
</td>
|
||||
<td>Save (`s`) or restore (`u`) cursor position</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>
|
||||
\e7 \\
|
||||
\e8
|
||||
</code>
|
||||
</td>
|
||||
<td>Save (`7`) or restore (`8`) cursor position and attributes</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Scrolling Region</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 _a_ through _b_ 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>
|
||||
<code>
|
||||
\e[?6h \\
|
||||
\e[?6l
|
||||
</code>
|
||||
</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>
|
||||
<code>
|
||||
\e[<i>n</i>S \\
|
||||
\e[<i>n</i>T
|
||||
</code>
|
||||
</td>
|
||||
<td>
|
||||
Move contents of the Scrolling Region up (`S`) or down (`T`), pad with empty
|
||||
lines of the current background color. This is similar to what happens when AutoWrap
|
||||
is enabled and some text is printed at the very end of the screen.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Tab stops</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>
|
||||
<code>
|
||||
\e[<i>n</i>I \\
|
||||
\e[<i>n</i>Z
|
||||
</code>
|
||||
</td>
|
||||
<td>Advance (`I`) or go back (`Z`) _n_ tab stops or end/start of line. ASCII _TAB_ (9) is equivalent to <code>\e[1I</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>
|
||||
\e[0g \\
|
||||
\e[3g \\
|
||||
</code>
|
||||
</td>
|
||||
<td>Clear tab stop at the current column (`0`), or all columns (`3`).</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Other options</h3>
|
||||
|
||||
<table class="ansiref w100">
|
||||
<thead><tr><th>Code</th><th>Meaning</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>
|
||||
\e[?7h \\
|
||||
\e[?7l
|
||||
</code>
|
||||
</td>
|
||||
<td>Enable (`h`) or disable (`l`) cursor auto-wrap and screen auto-scroll</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>
|
||||
\e[?25h \\
|
||||
\e[?25l
|
||||
</code>
|
||||
</td>
|
||||
<td>Show (`h`) or hide (`l`) the cursor</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,63 @@
|
||||
<div class="Box fold">
|
||||
<h2>Commands: Screen Functions</h2>
|
||||
|
||||
<div class="Row v">
|
||||
<p>
|
||||
<b>Legend:</b>
|
||||
Italic letters such as _n_ 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>
|
||||
|
||||
<table class="ansiref w100">
|
||||
<thead><tr><th>Code</th><th>Meaning</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
`\e[<i>m</i>J`
|
||||
</td>
|
||||
<td>
|
||||
Clear part of screen. _m_: 0 - from cursor, 1 - to cursor, 2 - all
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
`\e[<i>m</i>K`
|
||||
</td>
|
||||
<td>
|
||||
Erase part of line. _m_: 0 - from cursor, 1 - to cursor, 2 - all
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
`\e[<i>n</i>X`</td>
|
||||
<td>
|
||||
Erase _n_ characters in line.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>
|
||||
\e[<i>n</i>L \\
|
||||
\e[<i>n</i>M
|
||||
</code>
|
||||
</td>
|
||||
<td>
|
||||
Insert (`L`) or delete (`M`) _n_ lines. Following lines are pulled up or pushed down.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>
|
||||
\e[<i>n</i>@ \\
|
||||
\e[<i>n</i>P
|
||||
</code>
|
||||
</td>
|
||||
<td>
|
||||
Insert (`@`) or delete (`P`) _n_ 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>
|
||||
@@ -0,0 +1,103 @@
|
||||
<div class="Box fold">
|
||||
<h2>Commands: System Functions</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 `\e[0n` "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>t</i>\a`</td>
|
||||
<td>Set screen title to _t_ (this is a standard OSC command)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>
|
||||
\e]<i>80+n</i>;<i>t</i>\a
|
||||
</code>
|
||||
</td>
|
||||
<td>
|
||||
Set label for button _n_ = 1-5 (code 81-85) to _t_ - e.g.`\e]81;Yes\a`
|
||||
sets the first button text to "Yes".
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>
|
||||
\e]<i>90+n</i>;<i>m</i>\a
|
||||
</code>
|
||||
</td>
|
||||
<td>
|
||||
Set message for button _n_ = 1-5 (code 81-85) to _m_ - e.g.`\e]94;iv\a`
|
||||
sets the 3rd button to send string "iv" when pressed.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>
|
||||
\e[?800h \\
|
||||
\e[?800l
|
||||
</code>
|
||||
</td>
|
||||
<td>
|
||||
Show (`h`) or hide (`l`) action buttons (the blue buttons under the screen).
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>
|
||||
\e[?801h \\
|
||||
\e[?801l
|
||||
</code>
|
||||
</td>
|
||||
<td>
|
||||
Show (`h`) or hide (`l`) menu/help links under the screen.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>
|
||||
\e[12h \\
|
||||
\e[12l
|
||||
</code>
|
||||
</td>
|
||||
<td>
|
||||
Enable (`h`) or disable (`l`) Send-Receive Mode (SRM).
|
||||
SRM is the opposite of Local Echo, meaning `\e[12h` disables and `\e[12l` enables Local Echo.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`\e[8;<i>r</i>;<i>c</i>t`</td>
|
||||
<td>Set screen size to _r_ rows and _c_ columns (this is a command borrowed from Xterm)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,254 @@
|
||||
|
||||
<div class="Box fold">
|
||||
<h2>User Input: Keyboard, Mouse</h2>
|
||||
|
||||
<div class="Row v">
|
||||
<h3>Keyboard</h3>
|
||||
|
||||
<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>
|
||||
The codes sent by _Home_, _End_, _F1-F4_ and cursor keys are affected by various keyboard modes (_Application Cursor Keys_,
|
||||
_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>Up</td>
|
||||
<td>`\e[A`,~`\eOA`</td>
|
||||
<td>F1</td>
|
||||
<td>`\eOP`,~`\e[11\~`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Down</td>
|
||||
<td>`\e[B`,~`\eOB`</td>
|
||||
<td>F2</td>
|
||||
<td>`\eOQ`,~`\e[12\~`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Right</td>
|
||||
<td>`\e[C`,~`\eOC`</td>
|
||||
<td>F3</td>
|
||||
<td>`\eOR`,~`\e[13\~`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Left</td>
|
||||
<td>`\e[D`,~`\eOD`</td>
|
||||
<td>F4</td>
|
||||
<td>`\eOS`,~`\e[14\~`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Home</td>
|
||||
<td>`\eOH`,~`\e[H`,~`\e[1\~`</td>
|
||||
<td>F5</td>
|
||||
<td>`\e[15~`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>End</td>
|
||||
<td>`\eOF`,~`\e[F`,~`\e[4\~`</td>
|
||||
<td>F6</td>
|
||||
<td>`\e[17\~`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Insert</td>
|
||||
<td>`\e[2\~`</td>
|
||||
<td>F7</td>
|
||||
<td>`\e[18\~`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Delete</td>
|
||||
<td>`\e[3\~`</td>
|
||||
<td>F8</td>
|
||||
<td>`\e[19\~`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Page Up</td>
|
||||
<td>`\e[5\~`</td>
|
||||
<td>F9</td>
|
||||
<td>`\e[20\~`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Page Down</td>
|
||||
<td>`\e[6\~`</td>
|
||||
<td>F10</td>
|
||||
<td>`\e[21\~`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Enter</td>
|
||||
<td>`\r` (13)</td>
|
||||
<td>F11</td>
|
||||
<td>`\e[23\~`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ctrl+Enter</td>
|
||||
<td>`\n` (10)</td>
|
||||
<td>F12</td>
|
||||
<td>`\e[24\~`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Tab</td>
|
||||
<td>`\t` (9)</td>
|
||||
<td>ESC</td>
|
||||
<td>`\e` (27)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Backspace</td>
|
||||
<td>`\b` (8)</td>
|
||||
<td>Ctrl+A..Z</td>
|
||||
<td>ASCII 1-26</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 implements standard mouse tracking modes based on Xterm. Mouse tracking can be used to implement
|
||||
powerful user interactions such as on-screen buttons, draggable sliders or dials, menus etc. ESPTerm's
|
||||
mouse tracking was tested using VTTest and should be compatible with all terminal applications
|
||||
that request mouse tracking.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Mouse can be tracked in different ways; some are easier to parse, others more powerful. The coordinates
|
||||
can also be encoded in different ways. All mouse tracking options are set using option commands:
|
||||
`CSI ? _n_ h` to enable, `CSI ? _n_ l` to disable option _n_.
|
||||
</p>
|
||||
|
||||
<h4>Mouse Tracking Modes</h4>
|
||||
|
||||
<p>
|
||||
All tracking modes produce three numbers which are then encoded and send to the application.
|
||||
First is the _event number_ N, then the _X and Y coordinates_, 1-based.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Mouse buttons are numbered: 1=left, 2=middle, 3=right.
|
||||
Wheel works as two buttons (4 and 5) which generate only press events (no release).
|
||||
</p>
|
||||
|
||||
<div class="tscroll">
|
||||
<table class="nomen">
|
||||
<thead><tr><th>Option</th><th>Name</th><th>Description</th></tr></thead>
|
||||
<tr>
|
||||
<td>`9`</td>
|
||||
<td>*X10~mode*</td>
|
||||
<td>
|
||||
This is the most basic tracking mode, in which <b>only button presses</b> are reported.
|
||||
N = button - 1: (0 left, 1 middle, 2 right, 3, 4 wheel).
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`1000`</td>
|
||||
<td>*Normal~mode*</td>
|
||||
<td>
|
||||
In Normal mode, both button presses and releases are reported.
|
||||
The lower two bits of N indicate the button pressed:
|
||||
`00b` (0) left, `01b` (1) middle, `10b` (2) right, `11b` (3) button release.
|
||||
Wheel buttons are reported as 0 and 1 with added 64 (e.g. 64 and 65).
|
||||
Normal mode also supports tracking of modifier keys, which are added to N as bit masks:
|
||||
4=_Shift_, 8=_Meta/Alt_, 16=_Control/Cmd_. Example: middle button with _Shift_ = 1 + 4 = `101b` (5).
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`1002`</td>
|
||||
<td>*Button-Event tracking*</td>
|
||||
<td>
|
||||
This is similar to Normal mode (`1000`), but mouse motion with a button held is also reported.
|
||||
A motion event is generated when the mouse cursor moves between screen character cells.
|
||||
A motion event has the same N as a press event, but 32 is added.
|
||||
For example, drag-drop event with the middle button will produce N = 1 (press), 33 (dragging) and 3 (release).
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`1003`</td>
|
||||
<td>*Any-Event tracking*</td>
|
||||
<td>
|
||||
This mode is almost identical to Button Event tracking (1002), but motion events
|
||||
are sent even when no mouse buttons are held. This could be used to draw on-screen mouse cursor, for example.
|
||||
Motion events with no buttons will use N = 32 + _11b_ (35).
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`1004`</td>
|
||||
<td>*Focus~tracking*</td>
|
||||
<td>
|
||||
Focus tracking is a separate function from the other mouse tracking modes, therefore they can be enabled together.
|
||||
Focus tracking reports when the terminal window (in Xterm) gets or loses focus, or in ESPTerm's case, when any
|
||||
user is connected. This can be used to pause/resume a game or on-screen animations.
|
||||
Focus tracking mode sends `CSI I` when the terminal receives, and `CSI O` when it loses focus.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h4>Mouse Report Encoding</h4>
|
||||
|
||||
<p>
|
||||
The following encoding schemes can be used with any of the tracking modes (except Focus tracking, which is not affected).
|
||||
</p>
|
||||
|
||||
<div class="tscroll">
|
||||
<table class="nomen">
|
||||
<thead><tr><th>Option</th><th>Name</th><th>Description</th></tr></thead>
|
||||
<tr>
|
||||
<td>--</td>
|
||||
<td>*Normal~encoding*</td>
|
||||
<td>
|
||||
This is the default encoding scheme used when no other option is selected.
|
||||
In this mode, a mouse report has the format `CSI M _n_ _x_ _y_`,
|
||||
where _n_, _x_ and _y_ are characters with ASCII value = 32 (space) + the respective number, e.g.
|
||||
0 becomes 32 (space), 1 becomes 33 (!). The reason for adding 32 is to avoid producing control characters.
|
||||
Example: `\e[M !!` - left button press at coordinates 1,1 when using X10 mode.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`1005`</td>
|
||||
<td>*UTF-8~encoding*</td>
|
||||
<td>
|
||||
This scheme should encode each of the numbers as a UTF-8 code point, expanding the maximum possible value.
|
||||
Since ESPTerm's screen size is limited and this has no practical benefit, this serves simply as an alias
|
||||
to the normal scheme.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`1006`</td>
|
||||
<td>*SGR~encoding*</td>
|
||||
<td>
|
||||
In SGR encoding, the response looks like a SGR sequence with the three numbers as semicolon-separated
|
||||
ASCII values. In this case 32 is not added like in the Normal and UTF-8 schemes, because
|
||||
it would serve nor purpose here. Also, button release is not reported as 11b,
|
||||
but using the normal button code while changing the final SGR character: `M` for button press
|
||||
and `m` for button release. Example: `\e[2;80;24m` - the right button was released
|
||||
at row 80, column 24.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`1015`</td>
|
||||
<td>*URXVT~encoding*</td>
|
||||
<td>
|
||||
This is similar to SGR encoding, but the final character is always `M` and the numbers are
|
||||
like in the Normal scheme, with 32 added. This scheme has no real advantage over the previous schemes and
|
||||
was added solely for completeness.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,84 @@
|
||||
<div class="Box fold">
|
||||
<h2>Basic Intro & Nomenclature</h2>
|
||||
|
||||
<div class="Row v">
|
||||
<img src="/img/vt100.jpg" class="aside" alt="VT102">
|
||||
|
||||
<p>
|
||||
ESPTerm emulates VT102 (pictured) with some additions from later VT models and Xterm.
|
||||
All commonly used attributes and commands are supported.
|
||||
ESPTerm is capable of displaying ncurses applications such as _Midnight Commander_ using _agetty_.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
ESPTerm accepts UTF-8 characters received on the communication UART and displays them on the screen,
|
||||
interpreting some codes as Control Characters. Those are e.g. _Carriage Return_ (13), _Line Feed_ (10),
|
||||
_Tab_ (9), _Backspace_ (8) and _Bell_ (7).
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Escape sequences start with the control character _ESC_ (27),
|
||||
followed by any number of ASCII characters forming the body of the command.
|
||||
</p>
|
||||
|
||||
<h3>Nomenclature & Command Types</h3>
|
||||
|
||||
<p>
|
||||
Examples on this help page use the following symbols for special characters and command types:\\
|
||||
(spaces are for clarity only, _DO NOT_ include them in the commands!)
|
||||
</p>
|
||||
|
||||
<div class="tscroll">
|
||||
<table class="nomen">
|
||||
<thead><tr><th>Name</th><th>Symbol</th><th>ASCII</th><th>C string</th><th>Function</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>*ESC*</td>
|
||||
<td>`\e`</td>
|
||||
<td>`ESC` (27)</td>
|
||||
<td>`"\e"`, `"\x1b"`, `"\033"`</td>
|
||||
<td>Introduces an escape sequence. _(Note: `\e` is a GCC extension)_</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>*Bell*</td>
|
||||
<td>`\a`</td>
|
||||
<td>`BEL`~(7)</td>
|
||||
<td>`"\a"`, `"\x7"`, `"\07"`</td>
|
||||
<td>Audible beep</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>*String Terminator*</td>
|
||||
<td>`ST`</td>
|
||||
<td>`ESC \`~(27~92)<br>_or_~`\a`~(7)</td>
|
||||
<td>`"\x1b\\"`, `"\a"`</td>
|
||||
<td>Terminates a string command (`\a` can be used as an alternative)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>*Control Sequence Introducer*</td>
|
||||
<td>`CSI`</td>
|
||||
<td>`ESC [`</td>
|
||||
<td>`"\x1b["`</td>
|
||||
<td>Starts a CSI command. Examples: `\e[?7;10h`, `\e[2J`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>*Operating System Command*</td>
|
||||
<td>`OSC`</td>
|
||||
<td>`ESC ]`</td>
|
||||
<td>`"\x1b]"`</td>
|
||||
<td>Starts an OSC command. Is followed by a command string terminated by `ST`. Example: `\e]0;My Screen Title\a`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>*Select Graphic Rendition*</td>
|
||||
<td>`SGR`</td>
|
||||
<td>`CSI <i>n</i>;<i>n</i>;<i>n</i>m`</td>
|
||||
<td>`"\x1b[1;2;3m"`</td>
|
||||
<td>Set text attributes, like color or style. 0 to 10 numbers can be used, `\e[m` is treated as `\e[0m`</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p>There are also some other commands that don't follow the CSI, SGR or OSC pattern, such as `\e7` or
|
||||
`\e#8`. A list of the most important escape sequences is presented in the following sections.</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,17 @@
|
||||
<div class="Box fold">
|
||||
<h2>Screen Behavior & Refreshing</h2>
|
||||
|
||||
<div class="Row v">
|
||||
<p>
|
||||
The initial screen size, title text and button labels can be configured in <a href="<?= url('cfg_term') ?>">Terminal Settings</a>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Screen updates are sent to the browser through a WebSocket after some time of inactivity on the communication UART
|
||||
(called "Redraw Delay"). After an update is sent, at least a time of "Redraw Cooldown" must elapse before the next
|
||||
update can be sent. Those delays are used is to avoid burdening the server with tiny updates during a large screen
|
||||
repaint. If you experience issues (broken image due to dropped bytes), try adjusting those config options. It may also
|
||||
be useful to try different baud rates.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,65 @@
|
||||
|
||||
<div class="Box fold theme-0">
|
||||
<h2>Commands: Color SGR</h2>
|
||||
|
||||
<div class="Row v">
|
||||
<p>
|
||||
Colors are set using SGR commands (like `\e[10;20;30m`). 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>
|
||||
@@ -0,0 +1,26 @@
|
||||
<div class="Box fold">
|
||||
<h2>Commands: Style SGR</h2>
|
||||
|
||||
<div class="Row v">
|
||||
<p>
|
||||
All text attributes are set using SGR commands like `\e[10;20;30m`, with up to 10 numbers separated by semicolons.
|
||||
To restore all attributes to their default states, use SGR 0: `\e[0m` or `\e[m`.
|
||||
</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>
|
||||
@@ -0,0 +1,33 @@
|
||||
<div class="Box fold">
|
||||
<h2>Tips & Troubleshooting</h2>
|
||||
|
||||
<div class="Row v">
|
||||
<ul>
|
||||
<li>*Communication UART (Rx, Tx)* can be configured in the <a href="<?= url('cfg_system') ?>">System Settings</a>.
|
||||
|
||||
<li>*Boot log and debug messages* are available on pin *GPIO2* (P2) at 115200\,baud, 1 stop bit, no parity.
|
||||
Those messages may be disabled through compile flags.
|
||||
|
||||
<li>*Loopback test*: Connect the Rx and Tx pins with a piece of wire. Anything you type in the browser should
|
||||
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>There is very little RAM available to the webserver, and it can support at most 4 connections at the same time.
|
||||
Each terminal session (open window with the terminal screen) uses one persistent connection for screen updates.
|
||||
*Avoid leaving unused windows open*, or either the RAM or connections may be exhausted.
|
||||
|
||||
<li>*For best performance*, use the module in Client mode (connected to external network) and minimize the number
|
||||
of simultaneous connections. Enabling AP consumes extra RAM because the DHCP server and Captive Portal
|
||||
DNS server are started.
|
||||
|
||||
<li>In AP mode, *check that the WiFi channel used 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>.
|
||||
Adjust the hotspot strength and range using the _Tx Power setting_.
|
||||
|
||||
<li>Hold the BOOT button (GPIO0 to GND) for ~1 second to force enable AP. Hold it for ~6 seconds to restore default settings.
|
||||
(This is indicated by the blue LED rapidly flashing). Default settings can be overwritten in the
|
||||
<a href="<?= url('cfg_system') ?>">System Settings</a>.
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
+75
-19
@@ -2,36 +2,92 @@
|
||||
// Workaround for badly loaded page
|
||||
setTimeout(function() {
|
||||
if (typeof termInit == 'undefined' || typeof $ == 'undefined') {
|
||||
console.error("Page load failed, refreshing…");
|
||||
location.reload(true);
|
||||
}
|
||||
}, 2000);
|
||||
}, 3000);
|
||||
</script>
|
||||
|
||||
<h1>%term_title%</h1>
|
||||
|
||||
<div id="termwrap">
|
||||
<div id="screen"></div>
|
||||
|
||||
<div id="buttons">
|
||||
<button data-n="1" class="btn-blue">%btn1%</button><!--
|
||||
--><button data-n="2" class="btn-blue">%btn2%</button><!--
|
||||
--><button data-n="3" class="btn-blue">%btn3%</button><!--
|
||||
--><button data-n="4" class="btn-blue">%btn4%</button><!--
|
||||
--><button data-n="5" class="btn-blue">%btn5%</button>
|
||||
<div class="Modal light hidden" id="fu_modal">
|
||||
<div id="fu_form" class="Dialog">
|
||||
<div class="fu-content">
|
||||
<h2>Text Upload</h2>
|
||||
<p>
|
||||
<label for="fu_file">Load a text file:</label>
|
||||
<input type="file" id="fu_file" accept="text/*" /><br>
|
||||
<textarea id="fu_text"></textarea>
|
||||
</p>
|
||||
<p>
|
||||
<label for="fu_crlf">Line Endings:</label>
|
||||
<select id="fu_crlf">
|
||||
<option value="CR">CR (Enter key)</option>
|
||||
<option value="CRLF">CR LF (Windows)</option>
|
||||
<option value="LF">LF (Linux)</option>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<label for="fu_delay">Line Delay (ms):</label>
|
||||
<input id="fu_delay" type="number" value=1 min=0>
|
||||
</p>
|
||||
</div>
|
||||
<div class="fu-buttons">
|
||||
<button onclick="TermUpl.start()" class="icn-ok x-fu-go">Start</button>
|
||||
<button onclick="TermUpl.close()" class="icn-cancel x-fu-cancel">Cancel</button>
|
||||
<i class="fu-prog-box">Upload: <span id="fu_prog"></span></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav id="botnav">
|
||||
<a href="<?= url('cfg_wifi') ?>"><?= tr('menu.settings') ?></a><!--
|
||||
--><a href="<?= url('help') ?>">Help</a><!--
|
||||
--><a href="<?= url('about') ?>">About</a>
|
||||
<h1><!-- Screen title gets loaded here by JS --></h1>
|
||||
|
||||
<div id="term-wrap">
|
||||
<div id="screen" class="theme-%theme%"></div>
|
||||
|
||||
<div id="action-buttons">
|
||||
<button data-n="1"></button><!--
|
||||
--><button data-n="2"></button><!--
|
||||
--><button data-n="3"></button><!--
|
||||
--><button data-n="4"></button><!--
|
||||
--><button data-n="5"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<textarea id="softkb-input" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"></textarea>
|
||||
|
||||
<nav id="term-nav">
|
||||
<a href="#" onclick="kbOpen(true);return false" class="mq-tablet-max"><i class="icn-keyboard"></i><span><?= tr('term_nav.keybd') ?></span></a><!--
|
||||
--><a href="#" onclick="TermUpl.open();return false"><i class="icn-download"></i><span><?= tr('term_nav.upload') ?></a><!--
|
||||
--><a href="<?= url('cfg_term') ?>" class="x-term-conf-btn"><i class="icn-configure"></i><span><?= tr('term_nav.config') ?></span></a><!--
|
||||
--><a href="<?= url('cfg_wifi') ?>" class="x-term-conf-btn"><i class="icn-wifi"></i><span><?= tr('term_nav.wifi') ?></span></a><!--
|
||||
--><a href="<?= url('help') ?>" class="x-term-conf-btn"><i class="icn-help"></i><span><?= tr('term_nav.help') ?></span></a><!--
|
||||
--><a href="<?= url('about') ?>" class="x-term-conf-btn"><i class="icn-about"></i><span><?= tr('term_nav.about') ?></span></a>
|
||||
</nav>
|
||||
|
||||
<script>
|
||||
try {
|
||||
termInit(%screenData%);
|
||||
window.noAutoShow = true;
|
||||
termInit(); // the screen will be loaded via ajax
|
||||
Screen.load('%j:labels_seq%');
|
||||
|
||||
// auto-clear the input box
|
||||
$('#softkb-input').on('input', function(e) {
|
||||
setTimeout(function(){
|
||||
var str = $('#softkb-input').val();
|
||||
$('#softkb-input').val('');
|
||||
Input.sendString(str);
|
||||
}, 1);
|
||||
});
|
||||
} catch(e) {
|
||||
console.error("Fail, reloading...");
|
||||
location.reload(true);
|
||||
console.error(e);
|
||||
console.error("Fail, reloading in 3s…");
|
||||
setTimeout(function() {
|
||||
location.reload(true);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function kbOpen(yes) {
|
||||
var i = qs('#softkb-input');
|
||||
if (yes) i.focus();
|
||||
else i.blur();
|
||||
}
|
||||
</script>
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -245,7 +245,7 @@ code,
|
||||
pre
|
||||
//samp
|
||||
{
|
||||
font-family: monospace;
|
||||
font-family: "DejaVu Sans Mono", "Inconsolata", monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
$term-colors:
|
||||
// 0 black, 1 red, 2 green, 3 yellow
|
||||
// 4 blue, 5 mag, 6 cyan, 7 white
|
||||
#111213, #CC0000, #4E9A06, #C4A000,
|
||||
#3465A4, #75507B, #06989A, #D3D7CF,
|
||||
// BRIGHT
|
||||
// 8 black, 9 red, 10 green, 11 yellow
|
||||
// 12 blue, 13 mag, 14 cyan, 15 white
|
||||
#555753, #EF2929, #8AE234, #FCE94F,
|
||||
#729FCF, #AD7FA8, #34E2E2, #EEEEEC;
|
||||
|
||||
@for $i from 1 through length($term-colors) {
|
||||
$c: nth($term-colors, $i);
|
||||
.fg#{$i - 1} { color: $c; }
|
||||
.bg#{$i - 1} { background-color: $c; }
|
||||
}
|
||||
|
||||
.fg8, .fg9, .fg10, .fg11, .fg12, .fg13, .fg14, .fg15 {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.nb {
|
||||
font-weight: normal !important;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
@import "normalize";
|
||||
@import "fontello-embedded";
|
||||
@import "fontello";
|
||||
@import "lib/bourbon/bourbon";
|
||||
|
||||
@import "grid-settings";
|
||||
@@ -18,6 +18,10 @@ $c-form-field-fg: white;
|
||||
$c-form-highlight: #2972ba;
|
||||
$c-form-highlight-a: #2ea1f9;
|
||||
|
||||
$c-modal-bg: #242426;
|
||||
|
||||
$screen-stack: "DejaVu Sans Mono", "Liberation Mono", "Inconsolata", monospace;
|
||||
|
||||
@function dist($x) {
|
||||
@return modular-scale($x, 1rem, $golden);
|
||||
}
|
||||
@@ -33,7 +37,6 @@ $c-form-highlight-a: #2ea1f9;
|
||||
@import "pages/wifi";
|
||||
@import "pages/term";
|
||||
@import "pages/about";
|
||||
@import "term-colors";
|
||||
|
||||
// media queries
|
||||
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
@import "buttons";
|
||||
|
||||
#{$all-text-inputs}, select, label.select-wrap {
|
||||
width: $form-field-w;
|
||||
}
|
||||
|
||||
input[type="number"], input.short, select.short {
|
||||
width: $form-field-w/2;
|
||||
}
|
||||
|
||||
#{$all-text-inputs}, select {
|
||||
border: 0 none;
|
||||
border-bottom: 2px solid $c-form-highlight;
|
||||
@@ -33,17 +41,20 @@
|
||||
color: $c-form-highlight-a;
|
||||
|
||||
&::before {
|
||||
font-weight: bold;
|
||||
font-family: "fontello";
|
||||
position: absolute;
|
||||
content: '×';
|
||||
//content: '×';
|
||||
//content: '✓';
|
||||
left: 0; top: 0; right: 0; bottom: 0;
|
||||
line-height: $h - 1px;
|
||||
line-height: 25px;
|
||||
text-align: center;
|
||||
font-size: $h;
|
||||
font-size: 20px;
|
||||
vertical-align: middle;
|
||||
display: none;
|
||||
}
|
||||
|
||||
@include icon-content('ok');
|
||||
|
||||
&.checked::before {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
// Unified Form wrapper
|
||||
form { @include naked(); }
|
||||
|
||||
#{$all-text-inputs}, select, label.select-wrap {
|
||||
width: $form-field-w;
|
||||
}
|
||||
|
||||
input[type="number"], input.short {
|
||||
width: $form-field-w/2;
|
||||
}
|
||||
|
||||
.Box.errors {
|
||||
.list {
|
||||
color: crimson;
|
||||
@@ -24,6 +16,7 @@ input[type="number"], input.short {
|
||||
vertical-align: middle;
|
||||
margin: 12px auto;
|
||||
text-align: left;
|
||||
line-height: 1.35em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -37,6 +30,20 @@ input[type="number"], input.short {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&.v {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.aside {
|
||||
float: right;
|
||||
margin-left: 5px;
|
||||
margin-bottom: 5px;
|
||||
|
||||
@include media($phone) {
|
||||
margin: 0; float: none;
|
||||
}
|
||||
}
|
||||
|
||||
.spacer {
|
||||
width: $form-label-w;
|
||||
|
||||
@@ -45,7 +52,8 @@ input[type="number"], input.short {
|
||||
}
|
||||
}
|
||||
|
||||
&.buttons {
|
||||
// buttons2 is the same style, but different selector for use in the admin page
|
||||
&.buttons, &.buttons2 {
|
||||
margin: 16px auto;
|
||||
input, .button {
|
||||
margin-right: dist(-1);
|
||||
|
||||
@@ -31,6 +31,11 @@ a:hover {
|
||||
}
|
||||
|
||||
ul > * {
|
||||
padding-top: .2em;
|
||||
padding-bottom: .2em;
|
||||
padding-top: .3em;
|
||||
padding-bottom: .3em;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
.botpad {
|
||||
display:block;
|
||||
height: 5em;
|
||||
}
|
||||
|
||||
.Box {
|
||||
display: block;
|
||||
max-width: 900px;
|
||||
@@ -78,10 +83,21 @@
|
||||
}
|
||||
|
||||
.Row.explain {
|
||||
display: block; // this stops flex messing up text with inline tags
|
||||
max-width: 600px; margin-left: 0;
|
||||
line-height: 1.2;
|
||||
|
||||
@include media($phone) {
|
||||
margin-top: 60px;
|
||||
}
|
||||
|
||||
&.nomargintop {
|
||||
margin-top: 12px !important;
|
||||
}
|
||||
|
||||
&.padleft {
|
||||
|
||||
}
|
||||
}
|
||||
&.mobopen .Row.explain {
|
||||
margin-top: 12px; // default from .Row
|
||||
@@ -92,43 +108,69 @@
|
||||
}
|
||||
}
|
||||
|
||||
@include media($phone) {
|
||||
.Box.mobcol {
|
||||
h2 {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
padding-right: 1.3rem;
|
||||
@mixin mobcol-base {
|
||||
h2 {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
padding: 2px 1.3rem 2px 5px;
|
||||
margin: 0 -5px 0 -5px;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
content: '▸';
|
||||
&::after {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
content: '▸';
|
||||
|
||||
top:50%;
|
||||
font-size: 120%;
|
||||
font-weight: bold;
|
||||
transform: translate(0,-50%) rotate(90deg);
|
||||
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) {
|
||||
.Box.fold, .Box.mobcol {
|
||||
h2 {
|
||||
padding: 2px 1.3rem 2px 5px;
|
||||
margin: 0 -5px 0 -5px;
|
||||
}
|
||||
|
||||
&.expanded h2::after {
|
||||
transform: translate(-25%,-50%) rotate(-90deg);
|
||||
margin-bottom: dist(0);
|
||||
}
|
||||
}
|
||||
|
||||
.Row {
|
||||
display: none;
|
||||
}
|
||||
.Box.mobcol {
|
||||
@include mobcol-base;
|
||||
|
||||
#ap-box {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.expanded {
|
||||
.Row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#ap-box {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -48,3 +48,13 @@
|
||||
color: $c-form-label-fg;
|
||||
}
|
||||
}
|
||||
|
||||
#content {
|
||||
// fade in effect
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s ease-in;
|
||||
}
|
||||
|
||||
#content.load {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
}
|
||||
|
||||
#brand {
|
||||
cursor: pointer;
|
||||
color: $menu-fg;
|
||||
background: darken($menu-bg, 10%);
|
||||
font-size: 120%;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
.Modal {
|
||||
z-index: 100;
|
||||
position: fixed;
|
||||
width: 100%; height: 100%;
|
||||
left: 0; top: 0; right: 0; bottom: 0;
|
||||
@@ -12,6 +13,21 @@
|
||||
opacity: 0;
|
||||
&.visible { opacity: 1 }
|
||||
&.hidden { display: none }
|
||||
|
||||
&.light {
|
||||
background: rgba(black, .25);
|
||||
}
|
||||
|
||||
@include media($phone) {
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
overflow-y: auto;
|
||||
.Dialog {
|
||||
margin-top: dist(-1) !important;
|
||||
flex-basis: unset;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.Dialog {
|
||||
@@ -19,16 +35,16 @@
|
||||
padding: dist(0);
|
||||
overflow: hidden;
|
||||
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
flex: 0 1 30rem;
|
||||
//max-width: 100%;
|
||||
//max-height: 100%;
|
||||
flex-basis: 35rem;
|
||||
//min-height: 15rem;
|
||||
|
||||
background: #1c1c1e;
|
||||
background: $c-modal-bg;//#1c1c1e;
|
||||
border-left: 6px solid $c-form-highlight;
|
||||
border-right: 6px solid $c-form-highlight;
|
||||
border-top: 1px solid $c-form-highlight;
|
||||
border-bottom: 1px solid $c-form-highlight;
|
||||
//border-top: 1px solid $c-form-highlight;
|
||||
//border-bottom: 1px solid $c-form-highlight;
|
||||
box-shadow: 0 0 6px 0 black;
|
||||
|
||||
border-radius: 6px;
|
||||
|
||||
@@ -22,6 +22,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
.page-help {
|
||||
code {
|
||||
background: rgba(33, 97, 109, 0.31);
|
||||
border-radius: 1px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.colorprev {
|
||||
margin-top: dist(-2);
|
||||
margin-bottom: dist(-2);
|
||||
@@ -33,7 +41,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.ansiref {
|
||||
.Row table {
|
||||
&,td,th{
|
||||
border: 1px solid #666;
|
||||
}
|
||||
@@ -45,20 +53,76 @@
|
||||
th {
|
||||
background-color: rgba(255,255,255,.1);
|
||||
}
|
||||
}
|
||||
|
||||
td:nth-child(1) {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.ansiref {
|
||||
&.w100 {
|
||||
width: 100%;
|
||||
|
||||
td:nth-child(1) {
|
||||
width: 9em;
|
||||
}
|
||||
|
||||
td:nth-child(2) {
|
||||
width: 8em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nomen {
|
||||
width: 100%;
|
||||
|
||||
code {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
td:last-child {
|
||||
min-width: 15em;
|
||||
}
|
||||
}
|
||||
|
||||
.tscroll {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.charset {
|
||||
line-height: 1;
|
||||
|
||||
div {
|
||||
display: inline-block;
|
||||
width: 2.7em;
|
||||
border: 1px solid #666;
|
||||
height: 3em;
|
||||
margin: 1px;
|
||||
position: relative;
|
||||
|
||||
span {
|
||||
display: block;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
span:nth-child(1) {
|
||||
left: .2em;
|
||||
top: .2em;
|
||||
height: 1em;
|
||||
font-size: 85%;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
span:nth-child(2) {
|
||||
right: .2em;
|
||||
top: .2em;
|
||||
height: 1em;
|
||||
font-size: 85%;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
span:nth-child(3) {
|
||||
width: 100%;
|
||||
font-size: 105%;
|
||||
text-align: center;
|
||||
bottom: .4em;
|
||||
font-family: $screen-stack;
|
||||
}
|
||||
|
||||
&.none {
|
||||
opacity: .4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+299
-52
@@ -1,68 +1,315 @@
|
||||
body.term {
|
||||
h1 {
|
||||
font-size: fsize(5);
|
||||
@include media($phone) {
|
||||
font-size: fsize(3);
|
||||
#content {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
|
||||
h1 {
|
||||
font-size: fsize(5);
|
||||
@include media($phone) {
|
||||
font-size: fsize(3);
|
||||
}
|
||||
}
|
||||
|
||||
// longer duration to load everything in background nicely
|
||||
transition: opacity 0.25s ease-in;
|
||||
}
|
||||
}
|
||||
|
||||
#screen {
|
||||
white-space: nowrap;
|
||||
background: #111213;
|
||||
padding: 6px;
|
||||
display: inline-block;
|
||||
border: 2px solid #3983CD;
|
||||
|
||||
font-size: 20px; // some font heights cause visual glitches with some font renderers. This should be configurable.
|
||||
font-family: $screen-stack;
|
||||
|
||||
span {
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
> span {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
&::before {
|
||||
content: " ";
|
||||
}
|
||||
|
||||
> span {
|
||||
position:absolute;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
#screen {
|
||||
&.noselect {
|
||||
@include noselect();
|
||||
font-family: monospace;
|
||||
font-size: 16pt;
|
||||
white-space: nowrap;
|
||||
background: #111213;
|
||||
padding: 6px;
|
||||
display: inline-block;
|
||||
border: 2px solid #3983CD; //#1bc249;
|
||||
}
|
||||
}
|
||||
|
||||
#action-buttons {
|
||||
margin-top: 10px;
|
||||
white-space: nowrap;
|
||||
|
||||
button {
|
||||
margin: 0 3px;
|
||||
padding: 8px 5px;
|
||||
min-width: 62px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
|
||||
&:focus {
|
||||
outline: 0 none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#term-nav {
|
||||
padding-top: 1.5em;
|
||||
text-align: center;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
padding: 5px 5px;
|
||||
border-radius: 2px;
|
||||
position: relative;
|
||||
|
||||
&, &:visited, &:link {
|
||||
color: #336085;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #5abfff;
|
||||
background: #1b273c;
|
||||
}
|
||||
|
||||
&:active {
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
i::before {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
span {
|
||||
white-space: pre;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
outline: 1px solid rgba(#ffffff, 0.4);
|
||||
@include media($phone) {
|
||||
outline: 0 none;
|
||||
}
|
||||
margin-left: .2em;
|
||||
}
|
||||
|
||||
@include media($phone) {
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
font-size: 130%;
|
||||
padding: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
#buttons {
|
||||
margin-top: 10px;
|
||||
white-space: nowrap;
|
||||
|
||||
button {
|
||||
margin: 0 3px;
|
||||
padding: 8px 5px;
|
||||
//width: 18%;
|
||||
min-width: 65px;
|
||||
//max-width: 65px;
|
||||
//min-width: initial;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
#botnav {
|
||||
padding-top: 1.5em;
|
||||
text-align: center;
|
||||
|
||||
a {
|
||||
padding: 0 dist(-2);
|
||||
text-decoration: underline;
|
||||
|
||||
&, &:visited, &:link {
|
||||
color: #2e4d6e;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #5abfff;
|
||||
}
|
||||
}
|
||||
.icn-keyboard {
|
||||
text-decoration: none;
|
||||
font-size: 130%; // make it bigger - icon is too small to be recognizable
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
#termwrap {
|
||||
#term-wrap {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
// Dummy input field used to open android keyboard
|
||||
#softkb-input {
|
||||
position: absolute;
|
||||
top: -9999px;
|
||||
}
|
||||
|
||||
#fu_modal {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
#fu_form {
|
||||
//border: 1px solid #3983CD;
|
||||
//border-radius: 2px;
|
||||
//padding: 0 dist(0);
|
||||
//background: #333e58;
|
||||
padding: dist(0);
|
||||
margin-top: 100px; // offset
|
||||
|
||||
z-index: 1000;
|
||||
|
||||
label {
|
||||
width: 8em;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
input[type="number"], select {
|
||||
width: 10em;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
min-height: 8em;
|
||||
margin-top: dist(-1);
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.fu-buttons {
|
||||
text-align: center;
|
||||
padding: dist(-2);
|
||||
margin-top: dist(-2);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.fu-prog-box {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.busy {
|
||||
background: rgba($c-modal-bg, .3);
|
||||
border-left-color: rgba($c-form-highlight, .3);
|
||||
border-right-color: rgba($c-form-highlight, .3);
|
||||
|
||||
.fu-content {
|
||||
@include click-through;
|
||||
opacity: .3;
|
||||
}
|
||||
|
||||
.fu-buttons {
|
||||
text-align: left;
|
||||
background: $c-modal-bg;
|
||||
border: 1px solid $c-form-highlight;
|
||||
}
|
||||
|
||||
.fu-prog-box {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tango
|
||||
.theme-0 {
|
||||
$term-colors:
|
||||
#111213, #CC0000, #4E9A06, #C4A000, #3465A4, #75507B, #06989A, #D3D7CF,
|
||||
#555753, #EF2929, #8AE234, #FCE94F, #729FCF, #AD7FA8, #34E2E2, #EEEEEC;
|
||||
@for $i from 1 through length($term-colors) {
|
||||
$c: nth($term-colors, $i);
|
||||
.fg#{$i - 1} { color: $c; }
|
||||
.bg#{$i - 1} { background-color: $c; }
|
||||
}
|
||||
}
|
||||
|
||||
// Linux
|
||||
.theme-1 {
|
||||
$term-colors:
|
||||
#000000, #aa0000, #00aa00, #aa5500, #0000aa, #aa00aa, #00aaaa, #aaaaaa,
|
||||
#555555, #ff5555, #55ff55, #ffff55, #5555ff, #ff55ff, #55ffff, #ffffff;
|
||||
@for $i from 1 through length($term-colors) {
|
||||
$c: nth($term-colors, $i);
|
||||
.fg#{$i - 1} { color: $c; }
|
||||
.bg#{$i - 1} { background-color: $c; }
|
||||
}
|
||||
}
|
||||
|
||||
// xterm
|
||||
.theme-2 {
|
||||
$term-colors:
|
||||
#000000, #cd0000, #00cd00, #cdcd00, #0000ee, #cd00cd, #00cdcd, #e5e5e5,
|
||||
#7f7f7f, #ff0000, #00ff00, #ffff00, #5c5cff, #ff00ff, #00ffff, #ffffff;
|
||||
@for $i from 1 through length($term-colors) {
|
||||
$c: nth($term-colors, $i);
|
||||
.fg#{$i - 1} { color: $c; }
|
||||
.bg#{$i - 1} { background-color: $c; }
|
||||
}
|
||||
}
|
||||
|
||||
// rxvt
|
||||
.theme-3 {
|
||||
$term-colors:
|
||||
#000000, #cd0000, #00cd00, #cdcd00, #0000cd, #cd00cd, #00cdcd, #faebd7,
|
||||
#404040, #ff0000, #00ff00, #ffff00, #0000ff, #ff00ff, #00ffff, #ffffff;
|
||||
@for $i from 1 through length($term-colors) {
|
||||
$c: nth($term-colors, $i);
|
||||
.fg#{$i - 1} { color: $c; }
|
||||
.bg#{$i - 1} { background-color: $c; }
|
||||
}
|
||||
}
|
||||
|
||||
// Ambience
|
||||
.theme-4 {
|
||||
$term-colors:
|
||||
#2e3436, #cc0000, #4e9a06, #c4a000, #3465a4, #75507b, #06989a, #d3d7cf,
|
||||
#555753, #ef2929, #8ae234, #fce94f, #729fcf, #ad7fa8, #34e2e2, #eeeeec;
|
||||
@for $i from 1 through length($term-colors) {
|
||||
$c: nth($term-colors, $i);
|
||||
.fg#{$i - 1} { color: $c; }
|
||||
.bg#{$i - 1} { background-color: $c; }
|
||||
}
|
||||
}
|
||||
|
||||
// Solarized
|
||||
.theme-5 {
|
||||
$term-colors:
|
||||
#073642, #dc322f, #859900, #b58900, #268bd2, #d33682, #2aa198, #eee8d5,
|
||||
#002b36, #cb4b16, #586e75, #657b83, #839496, #6c71c4, #93a1a1, #fdf6e3;
|
||||
@for $i from 1 through length($term-colors) {
|
||||
$c: nth($term-colors, $i);
|
||||
.fg#{$i - 1} { color: $c; }
|
||||
.bg#{$i - 1} { background-color: $c; }
|
||||
}
|
||||
}
|
||||
|
||||
// Attributes
|
||||
.bold {
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.faint span { // content of faint is wrapped in span
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.under {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.strike {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.underline.strike {
|
||||
text-decoration: underline line-through;
|
||||
}
|
||||
|
||||
.blink-hide .blink {
|
||||
color: transparent;
|
||||
}
|
||||
//
|
||||
|
||||
.Row.color-preview {
|
||||
font-family: monospace;
|
||||
font-size: 16pt;
|
||||
display: block;
|
||||
margin-bottom: 0;
|
||||
|
||||
padding-left: $form-label-w;
|
||||
|
||||
@include media($phone) {
|
||||
padding-left: 0;
|
||||
font-size: 14pt;
|
||||
}
|
||||
|
||||
.colorprev {
|
||||
display:block;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
#color-example {
|
||||
display: inline-block;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
padding: dist(-2);
|
||||
margin-bottom: dist(-2);
|
||||
margin-top: dist(-2);
|
||||
font-size: 110%;
|
||||
}
|
||||
|
||||
#ap-noscan {
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@
|
||||
/**
|
||||
* Helper that retrieves an arg from `connData->getArgs` and stores it in `buff`. Returns 1 on success
|
||||
*/
|
||||
#define GET_ARG(key) (httpdFindArg(connData->getArgs, key, buff, sizeof(buff)) > 0)
|
||||
#define GET_ARG(key) (httpdFindArg(connData->getArgs, key, buff, sizeof(buff)) >= 0)
|
||||
|
||||
#define STR_HELPER(x) #x
|
||||
#define STR(x) STR_HELPER(x)
|
||||
|
||||
+1
-1
Submodule libesphttpd updated: f3dd1a2599...13fa224963
+394
-210
@@ -2,35 +2,87 @@
|
||||
/* #line 1 "user/ansi_parser.rl" */
|
||||
#include <esp8266.h>
|
||||
#include "ansi_parser.h"
|
||||
#include "ansi_parser_callbacks.h"
|
||||
#include "ascii.h"
|
||||
#include "apars_logging.h"
|
||||
|
||||
/* Ragel constants block */
|
||||
|
||||
/* #line 9 "user/ansi_parser.c" */
|
||||
/* #line 12 "user/ansi_parser.c" */
|
||||
static const char _ansi_actions[] = {
|
||||
0, 1, 0, 1, 1, 1, 2, 1,
|
||||
3, 1, 4, 1, 5, 1, 6, 1,
|
||||
7, 1, 8, 1, 9, 1, 10, 1,
|
||||
11, 1, 12
|
||||
11, 1, 12, 1, 13, 1, 14, 1,
|
||||
15
|
||||
};
|
||||
|
||||
static const char _ansi_eof_actions[] = {
|
||||
0, 13, 13, 13, 13, 13, 13, 13,
|
||||
13, 13, 13, 13, 13, 13, 0, 0,
|
||||
0
|
||||
0, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 0, 0, 0, 0
|
||||
};
|
||||
|
||||
static const int ansi_start = 1;
|
||||
static const int ansi_first_final = 14;
|
||||
static const int ansi_first_final = 11;
|
||||
static const int ansi_error = 0;
|
||||
|
||||
static const int ansi_en_CSI_body = 3;
|
||||
static const int ansi_en_OSC_body = 5;
|
||||
static const int ansi_en_CSI_body = 5;
|
||||
static const int ansi_en_STRCMD_body = 8;
|
||||
static const int ansi_en_charsetcmd_body = 10;
|
||||
static const int ansi_en_main = 1;
|
||||
|
||||
|
||||
/* #line 8 "user/ansi_parser.rl" */
|
||||
/* #line 11 "user/ansi_parser.rl" */
|
||||
|
||||
|
||||
// Max nr of CSI parameters
|
||||
#define CSI_N_MAX 10
|
||||
#define STR_CHAR_MAX 64
|
||||
|
||||
static volatile int cs = -1;
|
||||
static volatile bool inside_string = false;
|
||||
|
||||
// public
|
||||
volatile u32 ansi_parser_char_cnt = 0;
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
ansi_parser_reset(void) {
|
||||
if (cs != ansi_start) {
|
||||
cs = ansi_start;
|
||||
inside_string = false;
|
||||
apars_reset_utf8buffer();
|
||||
ansi_warn("Parser state reset (timeout?)");
|
||||
}
|
||||
}
|
||||
|
||||
#define HISTORY_LEN 10
|
||||
|
||||
#if DEBUG_ANSI
|
||||
static char history[HISTORY_LEN + 1];
|
||||
#endif
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_show_context(void)
|
||||
{
|
||||
#if DEBUG_ANSI
|
||||
char buf1[HISTORY_LEN*3+2];
|
||||
char buf2[HISTORY_LEN*3+2];
|
||||
char *b1 = buf1;
|
||||
char *b2 = buf2;
|
||||
char c;
|
||||
|
||||
for(int i=0;i<HISTORY_LEN;i++) {
|
||||
c = history[i];
|
||||
b1 += sprintf(b1, "%2X ", c);
|
||||
if (c < 32 || c > 127) c = '.';
|
||||
b2 += sprintf(b2, "%c ", c);
|
||||
}
|
||||
|
||||
ansi_dbg("Context: %s", buf2);
|
||||
ansi_dbg(" %s", buf1);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Linear ANSI chars stream parser
|
||||
*
|
||||
@@ -45,37 +97,109 @@ static const int ansi_en_main = 1;
|
||||
* \param len - length of the newdata buffer
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR
|
||||
ansi_parser(const char *newdata, size_t len)
|
||||
ansi_parser(char newchar)
|
||||
{
|
||||
static int cs = -1;
|
||||
|
||||
// The CSI code is built here
|
||||
static char csi_leading; //!< Leading char, 0 if none
|
||||
static int csi_ni; //!< Number of the active digit
|
||||
static int csi_n[CSI_N_MAX]; //!< Param digits
|
||||
static char csi_char; //!< CSI action char (end)
|
||||
static char leadchar;
|
||||
static char interchar; // intermediate CSI char
|
||||
static int arg_ni;
|
||||
static int arg_cnt;
|
||||
static int arg[CSI_N_MAX];
|
||||
static char string_buffer[STR_CHAR_MAX];
|
||||
static int str_ni;
|
||||
|
||||
if (len == 0) len = strlen(newdata);
|
||||
|
||||
// Load new data to Ragel vars
|
||||
const char *p = newdata;
|
||||
const char *eof = NULL;
|
||||
const char *pe = newdata + len;
|
||||
// This is used to detect timeout delay (time since last rx char)
|
||||
ansi_parser_char_cnt++;
|
||||
|
||||
// Init Ragel on the first run
|
||||
if (cs == -1) {
|
||||
|
||||
/* #line 69 "user/ansi_parser.c" */
|
||||
/* #line 118 "user/ansi_parser.c" */
|
||||
{
|
||||
cs = ansi_start;
|
||||
}
|
||||
|
||||
/* #line 44 "user/ansi_parser.rl" */
|
||||
/* #line 92 "user/ansi_parser.rl" */
|
||||
|
||||
#if DEBUG_ANSI
|
||||
memset(history, 0, sizeof(history));
|
||||
#endif
|
||||
}
|
||||
|
||||
#if DEBUG_ANSI
|
||||
for(int i=1; i<HISTORY_LEN; i++) {
|
||||
history[i-1] = history[i];
|
||||
}
|
||||
history[HISTORY_LEN-1] = newchar;
|
||||
#endif
|
||||
|
||||
// Handle simple characters immediately (bypass parser)
|
||||
if (newchar < ' ' && !inside_string) {
|
||||
switch (newchar) {
|
||||
case ESC:
|
||||
// Reset state
|
||||
cs = ansi_start;
|
||||
// now the ESC will be processed by the parser
|
||||
break; // proceed to parser
|
||||
|
||||
// Literally passed
|
||||
case FF:
|
||||
case VT:
|
||||
newchar = LF; // translate to LF, like VT100 / xterm do
|
||||
case CR:
|
||||
case LF:
|
||||
case BS:
|
||||
apars_handle_plainchar(newchar);
|
||||
return;
|
||||
|
||||
case TAB:
|
||||
apars_handle_tab();
|
||||
return;
|
||||
|
||||
// Select G0 or G1
|
||||
case SI:
|
||||
apars_handle_chs_switch(0);
|
||||
return;
|
||||
|
||||
case SO:
|
||||
apars_handle_chs_switch(1);
|
||||
return;
|
||||
|
||||
case BEL:
|
||||
// bel is also used to terminate OSC
|
||||
apars_handle_bel();
|
||||
return;
|
||||
|
||||
case ENQ:
|
||||
apars_handle_enq();
|
||||
return;
|
||||
|
||||
// Cancel the active sequence
|
||||
case CAN:
|
||||
case SUB:
|
||||
cs = ansi_start;
|
||||
return;
|
||||
|
||||
default:
|
||||
// Discard all other control codes
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// bypass the parser for simple characters (speed-up)
|
||||
if (cs == ansi_start && newchar >= ' ') {
|
||||
apars_handle_plainchar(newchar);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Load new data to Ragel vars
|
||||
const char *p = &newchar;
|
||||
const char *eof = NULL;
|
||||
const char *pe = &newchar + 1;
|
||||
|
||||
// The parser
|
||||
|
||||
/* #line 79 "user/ansi_parser.c" */
|
||||
/* #line 203 "user/ansi_parser.c" */
|
||||
{
|
||||
const char *_acts;
|
||||
unsigned int _nacts;
|
||||
@@ -87,257 +211,315 @@ ansi_parser(const char *newdata, size_t len)
|
||||
_resume:
|
||||
switch ( cs ) {
|
||||
case 1:
|
||||
if ( (*p) == 27 )
|
||||
goto tr1;
|
||||
goto tr0;
|
||||
case 2:
|
||||
switch( (*p) ) {
|
||||
case 55: goto tr3;
|
||||
case 56: goto tr4;
|
||||
case 91: goto tr5;
|
||||
case 93: goto tr6;
|
||||
case 99: goto tr7;
|
||||
case 7: goto tr1;
|
||||
case 27: goto tr2;
|
||||
}
|
||||
goto tr2;
|
||||
goto tr0;
|
||||
case 0:
|
||||
goto _out;
|
||||
case 14:
|
||||
if ( (*p) == 27 )
|
||||
goto tr1;
|
||||
goto tr0;
|
||||
case 2:
|
||||
switch( (*p) ) {
|
||||
case 32: goto tr3;
|
||||
case 35: goto tr4;
|
||||
case 37: goto tr5;
|
||||
case 80: goto tr7;
|
||||
case 88: goto tr7;
|
||||
case 91: goto tr8;
|
||||
case 107: goto tr7;
|
||||
}
|
||||
if ( (*p) < 64 ) {
|
||||
if ( (*p) < 48 ) {
|
||||
if ( 40 <= (*p) && (*p) <= 47 )
|
||||
goto tr5;
|
||||
} else if ( (*p) > 57 ) {
|
||||
if ( 60 <= (*p) && (*p) <= 62 )
|
||||
goto tr6;
|
||||
} else
|
||||
goto tr6;
|
||||
} else if ( (*p) > 92 ) {
|
||||
if ( (*p) < 97 ) {
|
||||
if ( 93 <= (*p) && (*p) <= 95 )
|
||||
goto tr7;
|
||||
} else if ( (*p) > 122 ) {
|
||||
if ( 124 <= (*p) && (*p) <= 126 )
|
||||
goto tr6;
|
||||
} else
|
||||
goto tr6;
|
||||
} else
|
||||
goto tr6;
|
||||
goto tr1;
|
||||
case 3:
|
||||
if ( (*p) == 59 )
|
||||
goto tr10;
|
||||
if ( (*p) < 60 ) {
|
||||
if ( (*p) > 47 ) {
|
||||
if ( 48 <= (*p) && (*p) <= 57 )
|
||||
goto tr9;
|
||||
} else if ( (*p) >= 32 )
|
||||
goto tr8;
|
||||
} else if ( (*p) > 64 ) {
|
||||
if ( (*p) > 90 ) {
|
||||
if ( 97 <= (*p) && (*p) <= 122 )
|
||||
goto tr11;
|
||||
} else if ( (*p) >= 65 )
|
||||
goto tr11;
|
||||
} else
|
||||
goto tr8;
|
||||
goto tr2;
|
||||
case 4:
|
||||
if ( (*p) == 59 )
|
||||
goto tr10;
|
||||
if ( (*p) < 65 ) {
|
||||
if ( 48 <= (*p) && (*p) <= 57 )
|
||||
if ( (*p) > 71 ) {
|
||||
if ( 76 <= (*p) && (*p) <= 78 )
|
||||
goto tr9;
|
||||
} else if ( (*p) > 90 ) {
|
||||
if ( 97 <= (*p) && (*p) <= 122 )
|
||||
goto tr11;
|
||||
} else
|
||||
goto tr11;
|
||||
goto tr2;
|
||||
case 15:
|
||||
goto tr2;
|
||||
} else if ( (*p) >= 70 )
|
||||
goto tr9;
|
||||
goto tr1;
|
||||
case 11:
|
||||
switch( (*p) ) {
|
||||
case 7: goto tr1;
|
||||
case 27: goto tr2;
|
||||
}
|
||||
goto tr0;
|
||||
case 4:
|
||||
if ( 48 <= (*p) && (*p) <= 57 )
|
||||
goto tr10;
|
||||
goto tr1;
|
||||
case 5:
|
||||
switch( (*p) ) {
|
||||
case 70: goto tr12;
|
||||
case 87: goto tr13;
|
||||
case 36: goto tr11;
|
||||
case 59: goto tr13;
|
||||
}
|
||||
goto tr2;
|
||||
if ( (*p) < 48 ) {
|
||||
if ( (*p) < 38 ) {
|
||||
if ( 32 <= (*p) && (*p) <= 34 )
|
||||
goto tr11;
|
||||
} else if ( (*p) > 39 ) {
|
||||
if ( 41 <= (*p) && (*p) <= 45 )
|
||||
goto tr11;
|
||||
} else
|
||||
goto tr11;
|
||||
} else if ( (*p) > 57 ) {
|
||||
if ( (*p) < 64 ) {
|
||||
if ( 61 <= (*p) && (*p) <= 63 )
|
||||
goto tr14;
|
||||
} else if ( (*p) > 90 ) {
|
||||
if ( 96 <= (*p) && (*p) <= 126 )
|
||||
goto tr15;
|
||||
} else
|
||||
goto tr15;
|
||||
} else
|
||||
goto tr12;
|
||||
goto tr1;
|
||||
case 6:
|
||||
if ( (*p) == 82 )
|
||||
goto tr14;
|
||||
goto tr2;
|
||||
if ( (*p) > 90 ) {
|
||||
if ( 96 <= (*p) && (*p) <= 126 )
|
||||
goto tr15;
|
||||
} else if ( (*p) >= 64 )
|
||||
goto tr15;
|
||||
goto tr1;
|
||||
case 12:
|
||||
goto tr1;
|
||||
case 7:
|
||||
switch( (*p) ) {
|
||||
case 7: goto tr15;
|
||||
case 27: goto tr16;
|
||||
case 36: goto tr11;
|
||||
case 59: goto tr13;
|
||||
}
|
||||
goto tr2;
|
||||
case 16:
|
||||
goto tr2;
|
||||
if ( (*p) < 41 ) {
|
||||
if ( (*p) > 34 ) {
|
||||
if ( 38 <= (*p) && (*p) <= 39 )
|
||||
goto tr11;
|
||||
} else if ( (*p) >= 32 )
|
||||
goto tr11;
|
||||
} else if ( (*p) > 45 ) {
|
||||
if ( (*p) < 64 ) {
|
||||
if ( 48 <= (*p) && (*p) <= 57 )
|
||||
goto tr12;
|
||||
} else if ( (*p) > 90 ) {
|
||||
if ( 96 <= (*p) && (*p) <= 126 )
|
||||
goto tr15;
|
||||
} else
|
||||
goto tr15;
|
||||
} else
|
||||
goto tr11;
|
||||
goto tr1;
|
||||
case 8:
|
||||
if ( (*p) == 92 )
|
||||
goto tr15;
|
||||
goto tr2;
|
||||
case 9:
|
||||
if ( 48 <= (*p) && (*p) <= 57 )
|
||||
goto tr17;
|
||||
goto tr2;
|
||||
case 10:
|
||||
if ( (*p) == 59 )
|
||||
goto tr18;
|
||||
if ( 48 <= (*p) && (*p) <= 57 )
|
||||
goto tr17;
|
||||
goto tr2;
|
||||
case 11:
|
||||
if ( 48 <= (*p) && (*p) <= 57 )
|
||||
goto tr19;
|
||||
goto tr2;
|
||||
case 12:
|
||||
switch( (*p) ) {
|
||||
case 7: goto tr20;
|
||||
case 27: goto tr21;
|
||||
case 7: goto tr17;
|
||||
case 27: goto tr18;
|
||||
}
|
||||
if ( 48 <= (*p) && (*p) <= 57 )
|
||||
goto tr19;
|
||||
goto tr2;
|
||||
goto tr16;
|
||||
case 13:
|
||||
goto tr1;
|
||||
case 9:
|
||||
if ( (*p) == 92 )
|
||||
goto tr20;
|
||||
goto tr2;
|
||||
goto tr17;
|
||||
goto tr1;
|
||||
case 10:
|
||||
switch( (*p) ) {
|
||||
case 7: goto tr1;
|
||||
case 27: goto tr1;
|
||||
}
|
||||
goto tr19;
|
||||
case 14:
|
||||
goto tr1;
|
||||
}
|
||||
|
||||
tr2: cs = 0; goto f0;
|
||||
tr1: cs = 0; goto f0;
|
||||
tr0: cs = 1; goto f1;
|
||||
tr1: cs = 2; goto _again;
|
||||
tr8: cs = 4; goto f7;
|
||||
tr9: cs = 4; goto f8;
|
||||
tr10: cs = 4; goto f9;
|
||||
tr12: cs = 6; goto _again;
|
||||
tr14: cs = 7; goto _again;
|
||||
tr16: cs = 8; goto _again;
|
||||
tr13: cs = 9; goto _again;
|
||||
tr17: cs = 10; goto f8;
|
||||
tr18: cs = 11; goto f9;
|
||||
tr19: cs = 12; goto f8;
|
||||
tr21: cs = 13; goto _again;
|
||||
tr3: cs = 14; goto f2;
|
||||
tr4: cs = 14; goto f3;
|
||||
tr5: cs = 14; goto f4;
|
||||
tr6: cs = 14; goto f5;
|
||||
tr7: cs = 14; goto f6;
|
||||
tr11: cs = 15; goto f10;
|
||||
tr15: cs = 16; goto f11;
|
||||
tr20: cs = 16; goto f12;
|
||||
tr2: cs = 2; goto _again;
|
||||
tr3: cs = 3; goto _again;
|
||||
tr4: cs = 4; goto _again;
|
||||
tr11: cs = 6; goto f8;
|
||||
tr12: cs = 7; goto f9;
|
||||
tr13: cs = 7; goto f10;
|
||||
tr14: cs = 7; goto f11;
|
||||
tr16: cs = 8; goto f13;
|
||||
tr18: cs = 9; goto _again;
|
||||
tr5: cs = 11; goto f2;
|
||||
tr6: cs = 11; goto f3;
|
||||
tr7: cs = 11; goto f4;
|
||||
tr8: cs = 11; goto f5;
|
||||
tr9: cs = 11; goto f6;
|
||||
tr10: cs = 11; goto f7;
|
||||
tr15: cs = 12; goto f12;
|
||||
tr17: cs = 13; goto f14;
|
||||
tr19: cs = 14; goto f15;
|
||||
|
||||
f1: _acts = _ansi_actions + 1; goto execFuncs;
|
||||
f4: _acts = _ansi_actions + 3; goto execFuncs;
|
||||
f7: _acts = _ansi_actions + 5; goto execFuncs;
|
||||
f8: _acts = _ansi_actions + 7; goto execFuncs;
|
||||
f0: _acts = _ansi_actions + 1; goto execFuncs;
|
||||
f1: _acts = _ansi_actions + 3; goto execFuncs;
|
||||
f5: _acts = _ansi_actions + 5; goto execFuncs;
|
||||
f11: _acts = _ansi_actions + 7; goto execFuncs;
|
||||
f9: _acts = _ansi_actions + 9; goto execFuncs;
|
||||
f10: _acts = _ansi_actions + 11; goto execFuncs;
|
||||
f0: _acts = _ansi_actions + 13; goto execFuncs;
|
||||
f5: _acts = _ansi_actions + 15; goto execFuncs;
|
||||
f11: _acts = _ansi_actions + 17; goto execFuncs;
|
||||
f12: _acts = _ansi_actions + 19; goto execFuncs;
|
||||
f6: _acts = _ansi_actions + 21; goto execFuncs;
|
||||
f2: _acts = _ansi_actions + 23; goto execFuncs;
|
||||
f8: _acts = _ansi_actions + 13; goto execFuncs;
|
||||
f12: _acts = _ansi_actions + 15; goto execFuncs;
|
||||
f4: _acts = _ansi_actions + 17; goto execFuncs;
|
||||
f13: _acts = _ansi_actions + 19; goto execFuncs;
|
||||
f14: _acts = _ansi_actions + 21; goto execFuncs;
|
||||
f7: _acts = _ansi_actions + 23; goto execFuncs;
|
||||
f3: _acts = _ansi_actions + 25; goto execFuncs;
|
||||
f6: _acts = _ansi_actions + 27; goto execFuncs;
|
||||
f2: _acts = _ansi_actions + 29; goto execFuncs;
|
||||
f15: _acts = _ansi_actions + 31; goto execFuncs;
|
||||
|
||||
execFuncs:
|
||||
_nacts = *_acts++;
|
||||
while ( _nacts-- > 0 ) {
|
||||
switch ( *_acts++ ) {
|
||||
case 0:
|
||||
/* #line 54 "user/ansi_parser.rl" */
|
||||
/* #line 179 "user/ansi_parser.rl" */
|
||||
{
|
||||
apars_handle_plainchar((*p));
|
||||
ansi_warn("Parser error.");
|
||||
apars_show_context();
|
||||
inside_string = false; // no longer in string, for sure
|
||||
{cs = 1;goto _again;}
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
/* #line 61 "user/ansi_parser.rl" */
|
||||
/* #line 188 "user/ansi_parser.rl" */
|
||||
{
|
||||
/* Reset the CSI builder */
|
||||
csi_leading = csi_char = 0;
|
||||
csi_ni = 0;
|
||||
|
||||
/* Zero out digits */
|
||||
for(int i = 0; i < CSI_N_MAX; i++) {
|
||||
csi_n[i] = 0;
|
||||
if ((*p) != 0) {
|
||||
apars_handle_plainchar((*p));
|
||||
}
|
||||
|
||||
{cs = 3;goto _again;}
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
/* #line 74 "user/ansi_parser.rl" */
|
||||
/* #line 196 "user/ansi_parser.rl" */
|
||||
{
|
||||
csi_leading = (*p);
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
/* #line 78 "user/ansi_parser.rl" */
|
||||
{
|
||||
/* x10 + digit */
|
||||
if (csi_ni < CSI_N_MAX) {
|
||||
csi_n[csi_ni] = csi_n[csi_ni]*10 + ((*p) - '0');
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
/* #line 85 "user/ansi_parser.rl" */
|
||||
{
|
||||
csi_ni++;
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
/* #line 89 "user/ansi_parser.rl" */
|
||||
{
|
||||
csi_char = (*p);
|
||||
// Reset the CSI builder
|
||||
leadchar = NUL;
|
||||
interchar = NUL;
|
||||
arg_ni = 0;
|
||||
arg_cnt = 0;
|
||||
|
||||
apars_handle_CSI(csi_leading, csi_n, csi_char);
|
||||
|
||||
{cs = 1;goto _again;}
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
/* #line 97 "user/ansi_parser.rl" */
|
||||
{
|
||||
apars_handle_badseq();
|
||||
{cs = 1;goto _again;}
|
||||
}
|
||||
break;
|
||||
case 7:
|
||||
/* #line 114 "user/ansi_parser.rl" */
|
||||
{
|
||||
csi_ni = 0;
|
||||
|
||||
// we reuse the CSI numeric buffer
|
||||
// Zero out digits
|
||||
for(int i = 0; i < CSI_N_MAX; i++) {
|
||||
csi_n[i] = 0;
|
||||
arg[i] = 0;
|
||||
}
|
||||
|
||||
{cs = 5;goto _again;}
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
/* #line 125 "user/ansi_parser.rl" */
|
||||
case 3:
|
||||
/* #line 211 "user/ansi_parser.rl" */
|
||||
{
|
||||
apars_handle_OSC_FactoryReset();
|
||||
leadchar = (*p);
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
/* #line 215 "user/ansi_parser.rl" */
|
||||
{
|
||||
if (arg_cnt == 0) arg_cnt = 1;
|
||||
// x10 + digit
|
||||
if (arg_ni < CSI_N_MAX) {
|
||||
arg[arg_ni] = arg[arg_ni]*10 + ((*p) - '0');
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
/* #line 223 "user/ansi_parser.rl" */
|
||||
{
|
||||
if (arg_cnt == 0) arg_cnt = 1; // handle case when first arg is empty
|
||||
arg_cnt++;
|
||||
arg_ni++;
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
/* #line 229 "user/ansi_parser.rl" */
|
||||
{
|
||||
interchar = (*p);
|
||||
}
|
||||
break;
|
||||
case 7:
|
||||
/* #line 233 "user/ansi_parser.rl" */
|
||||
{
|
||||
apars_handle_csi(leadchar, arg, arg_cnt, interchar, (*p));
|
||||
{cs = 1;goto _again;}
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
/* #line 245 "user/ansi_parser.rl" */
|
||||
{
|
||||
leadchar = (*p);
|
||||
str_ni = 0;
|
||||
string_buffer[0] = '\0';
|
||||
inside_string = true;
|
||||
{cs = 8;goto _again;}
|
||||
}
|
||||
break;
|
||||
case 9:
|
||||
/* #line 130 "user/ansi_parser.rl" */
|
||||
/* #line 253 "user/ansi_parser.rl" */
|
||||
{
|
||||
apars_handle_OSC_SetScreenSize(csi_n[0], csi_n[1]);
|
||||
|
||||
{cs = 1;goto _again;}
|
||||
string_buffer[str_ni++] = (*p);
|
||||
}
|
||||
break;
|
||||
case 10:
|
||||
/* #line 141 "user/ansi_parser.rl" */
|
||||
/* #line 257 "user/ansi_parser.rl" */
|
||||
{
|
||||
// Reset screen
|
||||
apars_handle_RESET_cmd();
|
||||
inside_string = false;
|
||||
string_buffer[str_ni++] = '\0';
|
||||
apars_handle_string_cmd(leadchar, string_buffer);
|
||||
{cs = 1;goto _again;}
|
||||
}
|
||||
break;
|
||||
case 11:
|
||||
/* #line 147 "user/ansi_parser.rl" */
|
||||
/* #line 270 "user/ansi_parser.rl" */
|
||||
{
|
||||
apars_handle_saveCursorAttrs();
|
||||
apars_handle_hash_cmd((*p));
|
||||
{cs = 1;goto _again;}
|
||||
}
|
||||
break;
|
||||
case 12:
|
||||
/* #line 152 "user/ansi_parser.rl" */
|
||||
/* #line 275 "user/ansi_parser.rl" */
|
||||
{
|
||||
apars_handle_restoreCursorAttrs();
|
||||
apars_handle_short_cmd((*p));
|
||||
{cs = 1;goto _again;}
|
||||
}
|
||||
break;
|
||||
/* #line 341 "user/ansi_parser.c" */
|
||||
case 13:
|
||||
/* #line 280 "user/ansi_parser.rl" */
|
||||
{
|
||||
apars_handle_space_cmd((*p));
|
||||
{cs = 1;goto _again;}
|
||||
}
|
||||
break;
|
||||
case 14:
|
||||
/* #line 287 "user/ansi_parser.rl" */
|
||||
{
|
||||
leadchar = (*p);
|
||||
{cs = 10;goto _again;}
|
||||
}
|
||||
break;
|
||||
case 15:
|
||||
/* #line 292 "user/ansi_parser.rl" */
|
||||
{
|
||||
apars_handle_chs_designate(leadchar, (*p));
|
||||
{cs = 1;goto _again;}
|
||||
}
|
||||
break;
|
||||
/* #line 523 "user/ansi_parser.c" */
|
||||
}
|
||||
}
|
||||
goto _again;
|
||||
@@ -354,16 +536,18 @@ _again:
|
||||
unsigned int __nacts = (unsigned int) *__acts++;
|
||||
while ( __nacts-- > 0 ) {
|
||||
switch ( *__acts++ ) {
|
||||
case 6:
|
||||
/* #line 97 "user/ansi_parser.rl" */
|
||||
case 0:
|
||||
/* #line 179 "user/ansi_parser.rl" */
|
||||
{
|
||||
apars_handle_badseq();
|
||||
ansi_warn("Parser error.");
|
||||
apars_show_context();
|
||||
inside_string = false; // no longer in string, for sure
|
||||
{cs = 1; if ( p == pe )
|
||||
goto _test_eof;
|
||||
goto _again;}
|
||||
}
|
||||
break;
|
||||
/* #line 367 "user/ansi_parser.c" */
|
||||
/* #line 551 "user/ansi_parser.c" */
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -371,6 +555,6 @@ goto _again;}
|
||||
_out: {}
|
||||
}
|
||||
|
||||
/* #line 171 "user/ansi_parser.rl" */
|
||||
/* #line 315 "user/ansi_parser.rl" */
|
||||
|
||||
}
|
||||
|
||||
+7
-13
@@ -3,17 +3,9 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
// Max nr of CSI parameters
|
||||
#define CSI_N_MAX 3
|
||||
void ansi_parser_reset(void);
|
||||
|
||||
extern void apars_handle_badseq(void);
|
||||
extern void apars_handle_CSI(char leadchar, int *params, char keychar);
|
||||
extern void apars_handle_RESET_cmd(void);
|
||||
extern void apars_handle_plainchar(char c);
|
||||
extern void apars_handle_OSC_FactoryReset(void);
|
||||
extern void apars_handle_OSC_SetScreenSize(int rows, int cols);
|
||||
extern void apars_handle_saveCursorAttrs(void);
|
||||
extern void apars_handle_restoreCursorAttrs(void);
|
||||
extern volatile u32 ansi_parser_char_cnt;
|
||||
|
||||
/**
|
||||
* \brief Linear ANSI chars stream parser
|
||||
@@ -25,9 +17,11 @@ extern void apars_handle_restoreCursorAttrs(void);
|
||||
* \attention -> but always check the Ragel output for 'p--'
|
||||
* or 'p -=', that means trouble.
|
||||
*
|
||||
* \param newdata - array of new chars to process
|
||||
* \param len - length of the newdata buffer
|
||||
* \param newchar - received char
|
||||
*/
|
||||
void ansi_parser(const char *newdata, size_t len);
|
||||
void ansi_parser(char newchar);
|
||||
|
||||
/** This shows a short error message and prints the history (if any) */
|
||||
void apars_show_context(void);
|
||||
|
||||
#endif // ANSI_PARSER_H
|
||||
|
||||
+221
-77
@@ -1,5 +1,8 @@
|
||||
#include <esp8266.h>
|
||||
#include "ansi_parser.h"
|
||||
#include "ansi_parser_callbacks.h"
|
||||
#include "ascii.h"
|
||||
#include "apars_logging.h"
|
||||
|
||||
/* Ragel constants block */
|
||||
%%{
|
||||
@@ -7,6 +10,54 @@
|
||||
write data;
|
||||
}%%
|
||||
|
||||
// Max nr of CSI parameters
|
||||
#define CSI_N_MAX 10
|
||||
#define STR_CHAR_MAX 64
|
||||
|
||||
static volatile int cs = -1;
|
||||
static volatile bool inside_string = false;
|
||||
|
||||
// public
|
||||
volatile u32 ansi_parser_char_cnt = 0;
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
ansi_parser_reset(void) {
|
||||
if (cs != ansi_start) {
|
||||
cs = ansi_start;
|
||||
inside_string = false;
|
||||
apars_reset_utf8buffer();
|
||||
ansi_warn("Parser state reset (timeout?)");
|
||||
}
|
||||
}
|
||||
|
||||
#define HISTORY_LEN 10
|
||||
|
||||
#if DEBUG_ANSI
|
||||
static char history[HISTORY_LEN + 1];
|
||||
#endif
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_show_context(void)
|
||||
{
|
||||
#if DEBUG_ANSI
|
||||
char buf1[HISTORY_LEN*3+2];
|
||||
char buf2[HISTORY_LEN*3+2];
|
||||
char *b1 = buf1;
|
||||
char *b2 = buf2;
|
||||
char c;
|
||||
|
||||
for(int i=0;i<HISTORY_LEN;i++) {
|
||||
c = history[i];
|
||||
b1 += sprintf(b1, "%2X ", c);
|
||||
if (c < 32 || c > 127) c = '.';
|
||||
b2 += sprintf(b2, "%c ", c);
|
||||
}
|
||||
|
||||
ansi_dbg("Context: %s", buf2);
|
||||
ansi_dbg(" %s", buf1);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Linear ANSI chars stream parser
|
||||
*
|
||||
@@ -21,152 +72,245 @@
|
||||
* \param len - length of the newdata buffer
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR
|
||||
ansi_parser(const char *newdata, size_t len)
|
||||
ansi_parser(char newchar)
|
||||
{
|
||||
static int cs = -1;
|
||||
|
||||
// The CSI code is built here
|
||||
static char csi_leading; //!< Leading char, 0 if none
|
||||
static int csi_ni; //!< Number of the active digit
|
||||
static int csi_n[CSI_N_MAX]; //!< Param digits
|
||||
static char csi_char; //!< CSI action char (end)
|
||||
static char leadchar;
|
||||
static char interchar; // intermediate CSI char
|
||||
static int arg_ni;
|
||||
static int arg_cnt;
|
||||
static int arg[CSI_N_MAX];
|
||||
static char string_buffer[STR_CHAR_MAX];
|
||||
static int str_ni;
|
||||
|
||||
if (len == 0) len = strlen(newdata);
|
||||
|
||||
// Load new data to Ragel vars
|
||||
const char *p = newdata;
|
||||
const char *eof = NULL;
|
||||
const char *pe = newdata + len;
|
||||
// This is used to detect timeout delay (time since last rx char)
|
||||
ansi_parser_char_cnt++;
|
||||
|
||||
// Init Ragel on the first run
|
||||
if (cs == -1) {
|
||||
%% write init;
|
||||
|
||||
#if DEBUG_ANSI
|
||||
memset(history, 0, sizeof(history));
|
||||
#endif
|
||||
}
|
||||
|
||||
#if DEBUG_ANSI
|
||||
for(int i=1; i<HISTORY_LEN; i++) {
|
||||
history[i-1] = history[i];
|
||||
}
|
||||
history[HISTORY_LEN-1] = newchar;
|
||||
#endif
|
||||
|
||||
// Handle simple characters immediately (bypass parser)
|
||||
if (newchar < ' ' && !inside_string) {
|
||||
switch (newchar) {
|
||||
case ESC:
|
||||
// Reset state
|
||||
cs = ansi_start;
|
||||
// now the ESC will be processed by the parser
|
||||
break; // proceed to parser
|
||||
|
||||
// Literally passed
|
||||
case FF:
|
||||
case VT:
|
||||
newchar = LF; // translate to LF, like VT100 / xterm do
|
||||
case CR:
|
||||
case LF:
|
||||
case BS:
|
||||
apars_handle_plainchar(newchar);
|
||||
return;
|
||||
|
||||
case TAB:
|
||||
apars_handle_tab();
|
||||
return;
|
||||
|
||||
// Select G0 or G1
|
||||
case SI:
|
||||
apars_handle_chs_switch(0);
|
||||
return;
|
||||
|
||||
case SO:
|
||||
apars_handle_chs_switch(1);
|
||||
return;
|
||||
|
||||
case BEL:
|
||||
// bel is also used to terminate OSC
|
||||
apars_handle_bel();
|
||||
return;
|
||||
|
||||
case ENQ:
|
||||
apars_handle_enq();
|
||||
return;
|
||||
|
||||
// Cancel the active sequence
|
||||
case CAN:
|
||||
case SUB:
|
||||
cs = ansi_start;
|
||||
return;
|
||||
|
||||
default:
|
||||
// Discard all other control codes
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// bypass the parser for simple characters (speed-up)
|
||||
if (cs == ansi_start && newchar >= ' ') {
|
||||
apars_handle_plainchar(newchar);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Load new data to Ragel vars
|
||||
const char *p = &newchar;
|
||||
const char *eof = NULL;
|
||||
const char *pe = &newchar + 1;
|
||||
|
||||
// The parser
|
||||
%%{
|
||||
#/*
|
||||
ESC = 27;
|
||||
NOESC = (any - ESC);
|
||||
NOESC = (any - ESC - 7);
|
||||
TOK_ST = ESC '\\'; # String terminator - used for OSC commands
|
||||
STR_END = (7 | TOK_ST);
|
||||
|
||||
# --- Error handler ---
|
||||
|
||||
action errBadSeq {
|
||||
ansi_warn("Parser error.");
|
||||
apars_show_context();
|
||||
inside_string = false; // no longer in string, for sure
|
||||
fgoto main;
|
||||
}
|
||||
|
||||
# --- Regular characters to be printed ---
|
||||
|
||||
action plain_char {
|
||||
apars_handle_plainchar(fc);
|
||||
if (fc != 0) {
|
||||
apars_handle_plainchar(fc);
|
||||
}
|
||||
}
|
||||
|
||||
# --- CSI CSI commands (Select Graphic Rendition) ---
|
||||
# Text color & style
|
||||
# --- CSI commands ---
|
||||
|
||||
action CSI_start {
|
||||
/* Reset the CSI builder */
|
||||
csi_leading = csi_char = 0;
|
||||
csi_ni = 0;
|
||||
// Reset the CSI builder
|
||||
leadchar = NUL;
|
||||
interchar = NUL;
|
||||
arg_ni = 0;
|
||||
arg_cnt = 0;
|
||||
|
||||
/* Zero out digits */
|
||||
// Zero out digits
|
||||
for(int i = 0; i < CSI_N_MAX; i++) {
|
||||
csi_n[i] = 0;
|
||||
arg[i] = 0;
|
||||
}
|
||||
|
||||
fgoto CSI_body;
|
||||
}
|
||||
|
||||
action CSI_leading {
|
||||
csi_leading = fc;
|
||||
leadchar = fc;
|
||||
}
|
||||
|
||||
action CSI_digit {
|
||||
/* x10 + digit */
|
||||
if (csi_ni < CSI_N_MAX) {
|
||||
csi_n[csi_ni] = csi_n[csi_ni]*10 + (fc - '0');
|
||||
if (arg_cnt == 0) arg_cnt = 1;
|
||||
// x10 + digit
|
||||
if (arg_ni < CSI_N_MAX) {
|
||||
arg[arg_ni] = arg[arg_ni]*10 + (fc - '0');
|
||||
}
|
||||
}
|
||||
|
||||
action CSI_semi {
|
||||
csi_ni++;
|
||||
if (arg_cnt == 0) arg_cnt = 1; // handle case when first arg is empty
|
||||
arg_cnt++;
|
||||
arg_ni++;
|
||||
}
|
||||
|
||||
action CSI_intermed {
|
||||
interchar = fc;
|
||||
}
|
||||
|
||||
action CSI_end {
|
||||
csi_char = fc;
|
||||
|
||||
apars_handle_CSI(csi_leading, csi_n, csi_char);
|
||||
|
||||
apars_handle_csi(leadchar, arg, arg_cnt, interchar, fc);
|
||||
fgoto main;
|
||||
}
|
||||
|
||||
action errBadSeq {
|
||||
apars_handle_badseq();
|
||||
fgoto main;
|
||||
}
|
||||
|
||||
action back2main {
|
||||
fgoto main;
|
||||
}
|
||||
|
||||
CSI_body := ((32..47|60..64) @CSI_leading)?
|
||||
#(32..47|60..64)
|
||||
CSI_body := ([?>=] @CSI_leading)?
|
||||
((digit @CSI_digit)* ';' @CSI_semi)*
|
||||
(digit @CSI_digit)* alpha @CSI_end $!errBadSeq;
|
||||
(digit @CSI_digit)* ([ $*"+,)'&!\-] @CSI_intermed)? (alpha|[`@{}~|]) @CSI_end $!errBadSeq;
|
||||
|
||||
# --- String commands ---
|
||||
|
||||
# --- OSC commands (Operating System Commands) ---
|
||||
# Module parametrisation
|
||||
|
||||
action OSC_start {
|
||||
csi_ni = 0;
|
||||
|
||||
// we reuse the CSI numeric buffer
|
||||
for(int i = 0; i < CSI_N_MAX; i++) {
|
||||
csi_n[i] = 0;
|
||||
}
|
||||
|
||||
fgoto OSC_body;
|
||||
action StrCmd_start {
|
||||
leadchar = fc;
|
||||
str_ni = 0;
|
||||
string_buffer[0] = '\0';
|
||||
inside_string = true;
|
||||
fgoto STRCMD_body;
|
||||
}
|
||||
|
||||
action OSC_fr {
|
||||
apars_handle_OSC_FactoryReset();
|
||||
action StrCmd_char {
|
||||
string_buffer[str_ni++] = fc;
|
||||
}
|
||||
|
||||
action StrCmd_end {
|
||||
inside_string = false;
|
||||
string_buffer[str_ni++] = '\0';
|
||||
apars_handle_string_cmd(leadchar, string_buffer);
|
||||
fgoto main;
|
||||
}
|
||||
|
||||
action OSC_resize {
|
||||
apars_handle_OSC_SetScreenSize(csi_n[0], csi_n[1]);
|
||||
# According to the spec, ESC should be allowed inside the string sequence.
|
||||
# We disallow ESC for simplicity, as it's hardly ever used.
|
||||
STRCMD_body := ((NOESC @StrCmd_char)* STR_END @StrCmd_end) $!errBadSeq;
|
||||
|
||||
fgoto main;
|
||||
}
|
||||
|
||||
OSC_body := (
|
||||
("FR" ('\a' | ESC '\\') @OSC_fr) |
|
||||
('W' (digit @CSI_digit)+ ';' @CSI_semi (digit @CSI_digit)+ ('\a' | ESC '\\') @OSC_resize)
|
||||
) $!errBadSeq;
|
||||
# --- Single character ESC ---
|
||||
|
||||
action RESET_cmd {
|
||||
// Reset screen
|
||||
apars_handle_RESET_cmd();
|
||||
action HASH_code {
|
||||
apars_handle_hash_cmd(fc);
|
||||
fgoto main;
|
||||
}
|
||||
|
||||
action CSI_SaveCursorAttrs {
|
||||
apars_handle_saveCursorAttrs();
|
||||
action SHORT_code {
|
||||
apars_handle_short_cmd(fc);
|
||||
fgoto main;
|
||||
}
|
||||
|
||||
action CSI_RestoreCursorAttrs {
|
||||
apars_handle_restoreCursorAttrs();
|
||||
action SPACE_cmd {
|
||||
apars_handle_space_cmd(fc);
|
||||
fgoto main;
|
||||
}
|
||||
|
||||
# --- Charset selection ---
|
||||
|
||||
action CharsetCmd_start {
|
||||
leadchar = fc;
|
||||
fgoto charsetcmd_body;
|
||||
}
|
||||
|
||||
action CharsetCmd_end {
|
||||
apars_handle_chs_designate(leadchar, fc);
|
||||
fgoto main;
|
||||
}
|
||||
|
||||
charsetcmd_body := (NOESC @CharsetCmd_end) $!errBadSeq;
|
||||
|
||||
# --- Main parser loop ---
|
||||
|
||||
main :=
|
||||
(
|
||||
(NOESC @plain_char)* ESC (
|
||||
'[' @CSI_start |
|
||||
']' @OSC_start |
|
||||
'c' @RESET_cmd |
|
||||
'7' @CSI_SaveCursorAttrs |
|
||||
'8' @CSI_RestoreCursorAttrs
|
||||
('[' @CSI_start) |
|
||||
([_\]Pk\^X] @StrCmd_start) |
|
||||
('#' digit @HASH_code) |
|
||||
(([a-zA-Z0-9=<>~}|@\\] - [PXk]) @SHORT_code) |
|
||||
([()*+-./%] @CharsetCmd_start) |
|
||||
(' ' [FGLMN] @SPACE_cmd)
|
||||
)
|
||||
)+ $!errBadSeq;
|
||||
|
||||
write exec;
|
||||
#*/
|
||||
}%%
|
||||
}
|
||||
|
||||
+17
-270
@@ -5,297 +5,44 @@
|
||||
*/
|
||||
|
||||
#include <esp8266.h>
|
||||
#include <helpers.h>
|
||||
#include "ansi_parser_callbacks.h"
|
||||
#include "cgi_sockets.h"
|
||||
#include "version.h"
|
||||
#include "uart_buffer.h"
|
||||
#include "screen.h"
|
||||
#include "ansi_parser.h"
|
||||
#include "uart_driver.h"
|
||||
|
||||
/**
|
||||
* Handle a received plain character
|
||||
* Send a response to UART0
|
||||
* @param str
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_handle_plainchar(char c)
|
||||
apars_respond(const char *str)
|
||||
{
|
||||
screen_putchar(c);
|
||||
UART_SendAsync(str, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bad sequence received, show warning
|
||||
*/
|
||||
void apars_handle_badseq(void)
|
||||
{
|
||||
warn("Invalid escape sequence discarded.");
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Handle fully received CSI ANSI sequence
|
||||
* \param leadchar - private range leading character, 0 if none
|
||||
* \param params - array of CSI_N_MAX ints holding the numeric arguments
|
||||
* \param keychar - the char terminating the sequence
|
||||
* Beep at the user.
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_handle_CSI(char leadchar, int *params, char keychar)
|
||||
apars_handle_bel(void)
|
||||
{
|
||||
/*
|
||||
Implemented codes (from Wikipedia)
|
||||
|
||||
CSI n A CUU – Cursor Up
|
||||
CSI n B CUD – Cursor Down
|
||||
CSI n C CUF – Cursor Forward
|
||||
CSI n D CUB – Cursor Back
|
||||
CSI n E CNL – Cursor Next Line
|
||||
CSI n F CPL – Cursor Previous Line
|
||||
CSI n G CHA – Cursor Horizontal Absolute
|
||||
CSI n ; m H CUP – Cursor Position
|
||||
CSI n J ED – Erase Display
|
||||
CSI n K EL – Erase in Line
|
||||
CSI n S SU – Scroll Up
|
||||
CSI n T SD – Scroll Down
|
||||
CSI n ; m f HVP – Horizontal and Vertical Position
|
||||
CSI n m SGR – Select Graphic Rendition (Implemented only some)
|
||||
CSI 6n DSR – Device Status Report
|
||||
CSI s SCP – Save Cursor Position
|
||||
CSI u RCP – Restore Cursor Position
|
||||
CSI ?25l DECTCEM Hides the cursor
|
||||
CSI ?25h DECTCEM Shows the cursor
|
||||
*/
|
||||
|
||||
int n1 = params[0];
|
||||
int n2 = params[1];
|
||||
// int n3 = params[2];
|
||||
|
||||
// defaults
|
||||
switch (keychar) {
|
||||
case 'A':
|
||||
case 'B':
|
||||
case 'C':
|
||||
case 'D':
|
||||
case 'E':
|
||||
case 'F':
|
||||
case 'G':
|
||||
case 'S':
|
||||
case 'T':
|
||||
if (n1 == 0) n1 = 1;
|
||||
break;
|
||||
|
||||
case 'H':
|
||||
case 'f':
|
||||
if (n1 == 0) n1 = 1;
|
||||
if (n2 == 0) n2 = 1;
|
||||
break;
|
||||
|
||||
case 'J':
|
||||
case 'K':
|
||||
if (n1 > 2) n1 = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (keychar) {
|
||||
// CUU CUD CUF CUB
|
||||
case 'A': screen_cursor_move(-n1, 0); break;
|
||||
case 'B': screen_cursor_move(n1, 0); break;
|
||||
case 'C': screen_cursor_move(0, n1); break;
|
||||
case 'D': screen_cursor_move(0, -n1); break;
|
||||
|
||||
case 'E': // CNL
|
||||
screen_cursor_move(n1, 0);
|
||||
screen_cursor_set_x(0);
|
||||
break;
|
||||
|
||||
case 'F': // CPL
|
||||
screen_cursor_move(-n1, 0);
|
||||
screen_cursor_set_x(0);
|
||||
break;
|
||||
|
||||
// CHA
|
||||
case 'G':
|
||||
screen_cursor_set_x(n1 - 1); break; // 1-based
|
||||
|
||||
// SU, SD
|
||||
case 'S': screen_scroll_up(n1); break;
|
||||
case 'T': screen_scroll_down(n1); break;
|
||||
|
||||
// CUP,HVP
|
||||
case 'H':
|
||||
case 'f':
|
||||
screen_cursor_set(n1-1, n2-1); break; // 1-based
|
||||
|
||||
case 'J': // ED
|
||||
if (n1 == 0) {
|
||||
screen_clear(CLEAR_TO_CURSOR);
|
||||
} else if (n1 == 1) {
|
||||
screen_clear(CLEAR_FROM_CURSOR);
|
||||
} else {
|
||||
screen_clear(CLEAR_ALL);
|
||||
screen_cursor_set(0, 0);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'K': // EL
|
||||
if (n1 == 0) {
|
||||
screen_clear_line(CLEAR_TO_CURSOR);
|
||||
} else if (n1 == 1) {
|
||||
screen_clear_line(CLEAR_FROM_CURSOR);
|
||||
} else {
|
||||
screen_clear_line(CLEAR_ALL);
|
||||
}
|
||||
break;
|
||||
|
||||
// SCP, RCP
|
||||
case 's': screen_cursor_save(0); break;
|
||||
case 'u': screen_cursor_restore(0); break;
|
||||
|
||||
case 'n':
|
||||
if (n1 == 6) {
|
||||
// Query cursor position
|
||||
char buf[20];
|
||||
int x, y;
|
||||
screen_cursor_get(&y, &x);
|
||||
sprintf(buf, "\033[%d;%dR", y+1, x+1);
|
||||
UART_WriteString(UART0, buf, UART_TIMEOUT_US);
|
||||
}
|
||||
else if (n1 == 5) {
|
||||
// Query device status - reply "Device is OK"
|
||||
UART_WriteString(UART0, "\033[0n", UART_TIMEOUT_US);
|
||||
}
|
||||
break;
|
||||
|
||||
// DECTCEM feature enable / disable
|
||||
|
||||
case 'h':
|
||||
if (leadchar == '?') {
|
||||
if (n1 == 25) {
|
||||
screen_cursor_enable(1);
|
||||
} else if (n1 == 7) {
|
||||
screen_wrap_enable(1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
if (leadchar == '?') {
|
||||
if (n1 == 25) {
|
||||
screen_cursor_enable(0);
|
||||
} else if (n1 == 7) {
|
||||
screen_wrap_enable(0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'm': // SGR
|
||||
// iterate arguments
|
||||
for (int i = 0; i < CSI_N_MAX; i++) {
|
||||
int n = params[i];
|
||||
|
||||
if (i == 0 && n == 0) { // reset SGR
|
||||
screen_set_fg(7);
|
||||
screen_set_bg(0);
|
||||
break; // cannot combine reset with others
|
||||
}
|
||||
else if (n >= 30 && n <= 37) screen_set_fg(n-30); // ANSI normal fg
|
||||
else if (n >= 40 && n <= 47) screen_set_bg(n-40); // ANSI normal bg
|
||||
else if (n == 39) screen_set_fg(7); // default fg
|
||||
else if (n == 49) screen_set_bg(0); // default bg
|
||||
else if (n == 7) screen_inverse(1); // inverse
|
||||
else if (n == 27) screen_inverse(0); // positive
|
||||
else if (n == 1) screen_set_bright_fg(); // ANSI bold = bright fg
|
||||
else if (n >= 90 && n <= 97) screen_set_fg(n-90+8); // AIX bright fg
|
||||
else if (n >= 100 && n <= 107) screen_set_bg(n-100+8); // AIX bright bg
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR apars_handle_saveCursorAttrs(void)
|
||||
{
|
||||
screen_cursor_save(1);
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR apars_handle_restoreCursorAttrs(void)
|
||||
{
|
||||
screen_cursor_restore(1);
|
||||
send_beep();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Handle a request to reset the display device
|
||||
* Send to uart the answerback message
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_handle_RESET_cmd(void)
|
||||
apars_handle_enq(void)
|
||||
{
|
||||
// XXX maybe user wanted to reset the module instead?
|
||||
screen_reset();
|
||||
// version encased in SOS and ST
|
||||
apars_respond("\x1bXESPTerm " FIRMWARE_VERSION "\x1b\\");
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a factory reset request
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_handle_OSC_FactoryReset(void)
|
||||
apars_handle_tab(void)
|
||||
{
|
||||
warn("-------- Factory reset --------");
|
||||
|
||||
dbg("Switching to Client+AP mode");
|
||||
|
||||
// Send acknowledgement message to UART0
|
||||
// User is performing this manually, so we can just print it as string
|
||||
UART_WriteString(UART0, "\r\nFACTORY RESET\r\n", UART_TIMEOUT_US);
|
||||
|
||||
// Disconnect from AP if connected
|
||||
int opmode = wifi_get_opmode();
|
||||
if (opmode != SOFTAP_MODE) {
|
||||
wifi_station_disconnect();
|
||||
}
|
||||
|
||||
// Both must be enabled so we can manipulate their settings
|
||||
wifi_set_opmode(STATIONAP_MODE);
|
||||
|
||||
// --- AP config ---
|
||||
dbg("AP WiFi channel: 6");
|
||||
|
||||
struct softap_config apconf;
|
||||
wifi_softap_get_config(&apconf);
|
||||
apconf.authmode=AUTH_OPEN; // Disable access protection
|
||||
apconf.channel=6; // Reset channel; user may have set bad channel in the UI
|
||||
|
||||
// generate unique AP name
|
||||
u8 mac[6];
|
||||
wifi_get_macaddr(SOFTAP_IF, mac);
|
||||
sprintf((char*)apconf.ssid, "TERM-%02X%02X%02X", mac[3], mac[4], mac[5]);
|
||||
apconf.ssid_len = (u8)strlen((char*)apconf.ssid);
|
||||
|
||||
info("New AP name: %s", (char*)apconf.ssid);
|
||||
|
||||
// --- Station ---
|
||||
dbg("Erasing stored WiFi credentials...");
|
||||
|
||||
struct station_config staconf;
|
||||
wifi_station_get_config(&staconf);
|
||||
|
||||
// clear info about SSID
|
||||
staconf.ssid[0]=0;
|
||||
staconf.bssid_set=0;
|
||||
staconf.password[0]=0;
|
||||
|
||||
dbg("Commiting changes...");
|
||||
wifi_softap_set_config(&apconf);
|
||||
wifi_station_set_config(&staconf);
|
||||
|
||||
UART_WriteString(UART0, "Factory Reset complete, device reset.\r\n\r\n", UART_TIMEOUT_US);
|
||||
|
||||
info("*** FACTORY RESET COMPLETE ***");
|
||||
dbg("Device reset...");
|
||||
|
||||
// Reboot to clean STA+AP mode with Channel X & reset AP SSID.
|
||||
system_restart();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a screen resize request
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_handle_OSC_SetScreenSize(int rows, int cols)
|
||||
{
|
||||
info("OSC: Set screen size to %d x %d", rows, cols);
|
||||
|
||||
screen_resize(rows, cols);
|
||||
screen_tab_forward(1);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
#ifndef ANSI_PARSER_CALLBACKS_H
|
||||
#define ANSI_PARSER_CALLBACKS_H
|
||||
|
||||
void apars_handle_badseq(void);
|
||||
void apars_handle_CSI(char leadchar, int *params, char keychar);
|
||||
void apars_handle_RESET_cmd(void);
|
||||
void apars_handle_plainchar(char c);
|
||||
void apars_handle_OSC_FactoryReset(void);
|
||||
void apars_handle_OSC_SetScreenSize(int rows, int cols);
|
||||
void apars_handle_saveCursorAttrs(void);
|
||||
void apars_handle_restoreCursorAttrs(void);
|
||||
#include "apars_csi.h"
|
||||
#include "apars_dcs.h"
|
||||
#include "apars_osc.h"
|
||||
#include "apars_string.h"
|
||||
#include "apars_short.h"
|
||||
#include "apars_utf8.h"
|
||||
|
||||
void apars_respond(const char *str);
|
||||
|
||||
void apars_handle_bel(void);
|
||||
void apars_handle_enq(void);
|
||||
void apars_handle_tab(void);
|
||||
|
||||
extern void apars_show_context(void);
|
||||
|
||||
#endif //ESP_VT100_FIRMWARE_ANSI_PARSER_CALLBACKS_H
|
||||
|
||||
@@ -0,0 +1,830 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/20.
|
||||
//
|
||||
// Handle CSI sequences
|
||||
// CSI <symbol?> Pm <symbol?> <char>
|
||||
//
|
||||
// Example of those are cursor manipulation sequences and SGR.
|
||||
//
|
||||
// For details, see:
|
||||
// http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Functions-using-CSI-_-ordered-by-the-final-character_s_
|
||||
//
|
||||
|
||||
#include <esp8266.h>
|
||||
#include "apars_csi.h"
|
||||
#include "screen.h"
|
||||
#include "apars_logging.h"
|
||||
#include "ansi_parser.h"
|
||||
#include "ascii.h"
|
||||
#include "ansi_parser_callbacks.h"
|
||||
#include "uart_driver.h"
|
||||
#include "sgr.h"
|
||||
#include "version.h"
|
||||
#include "syscfg.h"
|
||||
|
||||
/** Struct passed to subroutines */
|
||||
typedef struct {
|
||||
char lead;
|
||||
const int *n;
|
||||
int count;
|
||||
char inter;
|
||||
char key;
|
||||
} CSI_Data;
|
||||
|
||||
// Disambiguations
|
||||
static inline void switch_csi_Plain(CSI_Data *opts);
|
||||
static inline void switch_csi_NoLeadInterBang(CSI_Data *opts);
|
||||
static inline void switch_csi_LeadGreater(CSI_Data *opts);
|
||||
static inline void switch_csi_LeadQuest(CSI_Data *opts);
|
||||
static inline void switch_csi_LeadEquals(CSI_Data *opts);
|
||||
|
||||
// Subroutines
|
||||
static inline void do_csi_sgr(CSI_Data *opts);
|
||||
static inline void do_csi_decreqtparm(CSI_Data *opts);
|
||||
static inline void do_csi_set_option(CSI_Data *opts);
|
||||
static inline void do_csi_xterm_screen_cmd(CSI_Data *opts);
|
||||
static inline void do_csi_set_private_option(CSI_Data *opts);
|
||||
|
||||
/**
|
||||
* Show warning and dump context for invalid CSI
|
||||
*/
|
||||
static void warn_bad_csi(void)
|
||||
{
|
||||
ansi_noimpl_r("Unknown CSI");
|
||||
apars_show_context();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle fully received CSI ANSI sequence
|
||||
*
|
||||
* @param leadchar - leading character
|
||||
* @param params - array of CSI_N_MAX ints holding the numeric arguments
|
||||
* @param count - actual amount of received numeric arguments
|
||||
* @param keychar - intermediate character
|
||||
* @param keychar - final character
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_handle_csi(char leadchar, const int *params, int count, char interchar, char keychar)
|
||||
{
|
||||
CSI_Data opts = {leadchar, params, count, interchar, keychar};
|
||||
|
||||
switch(leadchar) {
|
||||
case '?':
|
||||
switch_csi_LeadQuest(&opts);
|
||||
break;
|
||||
|
||||
case '>':
|
||||
switch_csi_LeadGreater(&opts);
|
||||
break;
|
||||
|
||||
case '=':
|
||||
switch_csi_LeadEquals(&opts);
|
||||
break;
|
||||
|
||||
case NUL:
|
||||
// No leading character, switch by intermediate character
|
||||
switch(interchar) {
|
||||
case NUL:
|
||||
switch_csi_Plain(&opts);
|
||||
break;
|
||||
|
||||
case '!':
|
||||
switch_csi_NoLeadInterBang(&opts);
|
||||
break;
|
||||
|
||||
// case '\'':
|
||||
// switch_csi_NoLeadInterApos(opts);
|
||||
// break;
|
||||
|
||||
// case '*':
|
||||
// switch_csi_NoLeadInterStar(opts);
|
||||
// break;
|
||||
|
||||
// case '+':
|
||||
// switch_csi_NoLeadInterPlus(opts);
|
||||
// break;
|
||||
|
||||
// case '"':
|
||||
// switch_csi_NoLeadInterQuote(opts);
|
||||
// break;
|
||||
|
||||
// case '|':
|
||||
// switch_csi_NoLeadInterDollar(opts);
|
||||
// break;
|
||||
|
||||
// case ' ':
|
||||
// switch_csi_NoLeadInterSpace(opts);
|
||||
// break;
|
||||
|
||||
// case ',':
|
||||
// switch_csi_NoLeadInterComma(opts);
|
||||
// break;
|
||||
|
||||
// case ')':
|
||||
// switch_csi_NoLeadInterRparen(opts);
|
||||
// break;
|
||||
|
||||
// case '&':
|
||||
// switch_csi_NoLeadInterAmpers(opts);
|
||||
// break;
|
||||
|
||||
// case '-':
|
||||
// switch_csi_NoLeadInterDash(opts);
|
||||
// break;
|
||||
|
||||
default:
|
||||
warn_bad_csi();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
warn_bad_csi();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* CSI none Pm none key
|
||||
* @param opts
|
||||
*/
|
||||
static inline void ICACHE_FLASH_ATTR
|
||||
switch_csi_Plain(CSI_Data *opts)
|
||||
{
|
||||
char resp_buf[20];
|
||||
int n1 = opts->n[0];
|
||||
int n2 = opts->n[1];
|
||||
|
||||
// fix arguments (default values etc)
|
||||
switch (opts->key) {
|
||||
// Single argument, 1-based
|
||||
case 'A': // up
|
||||
case 'e': // down (old)
|
||||
case 'B': // down
|
||||
case 'a': // right (old)
|
||||
case 'C': // right
|
||||
case 'D': // left
|
||||
case 'E': // cursor next line
|
||||
case 'F': // cursor prev line
|
||||
case 'b': // repeat last char
|
||||
case 'G': // set X
|
||||
case '`': // set X (alias)
|
||||
case 'd': // set Y
|
||||
case 'X': // clear in line
|
||||
case 'S': // scroll up
|
||||
case 'T': // scroll down
|
||||
case 'L': // Insert lines
|
||||
case 'M': // Delete lines
|
||||
case '@': // Insert in line
|
||||
case 'P': // Delete in line
|
||||
case 'I': // Tab forward
|
||||
case 'Z': // Tab backward
|
||||
if (n1 == 0) n1 = 1;
|
||||
break;
|
||||
|
||||
// Two arguments, 1-based
|
||||
case 'H': // Absolute positioning
|
||||
case 'f':
|
||||
if (n1 == 0) n1 = 1;
|
||||
if (n2 == 0) n2 = 1;
|
||||
break;
|
||||
|
||||
// Erase modes 0,1,2
|
||||
case 'J': // Erase in screen
|
||||
case 'K': // Erase in line
|
||||
if (n1 > 2) n1 = 0;
|
||||
break;
|
||||
|
||||
// No defaults
|
||||
case 't': // Xterm window commands & reports
|
||||
case 's': // Cursor save no attr
|
||||
case 'r': // Set scrolling region
|
||||
case 'u': // Cursor restore no attr
|
||||
case 'h': // Option ON
|
||||
case 'l': // Option OFF
|
||||
case 'm': // SGR
|
||||
case 'g': // Clear tabs
|
||||
case 'n': // Queries 1 - device status
|
||||
case 'c': // Queries 2 - primary DA
|
||||
case 'x': // Queries 3 - DECREQTPARM
|
||||
default:
|
||||
// leave as is
|
||||
break;
|
||||
}
|
||||
|
||||
switch (opts->key) {
|
||||
// CUU CUD CUF CUB
|
||||
case 'A': // Up
|
||||
screen_cursor_move(-n1, 0, false);
|
||||
break;
|
||||
|
||||
case 'e':
|
||||
case 'B': // Down
|
||||
screen_cursor_move(n1, 0, false);
|
||||
break;
|
||||
|
||||
case 'a': // some archaic form of "Go Right"
|
||||
case 'C': // Right (forward)
|
||||
screen_cursor_move(0, n1, false);
|
||||
break;
|
||||
|
||||
case 'D': // Left (backward)
|
||||
screen_cursor_move(0, -n1, false);
|
||||
break;
|
||||
|
||||
case 'E': // CNL - Cursor Next Line
|
||||
screen_cursor_move(n1, 0, false);
|
||||
screen_cursor_set_x(0);
|
||||
break;
|
||||
|
||||
case 'F': // CPL - Cursor Prev Line
|
||||
screen_cursor_move(-n1, 0, false);
|
||||
screen_cursor_set_x(0);
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
// TODO repeat preceding graphic character n1 times
|
||||
ansi_noimpl("Repeat char");
|
||||
return;
|
||||
|
||||
// Set X
|
||||
case 'G':
|
||||
case '`': // alternate code
|
||||
screen_cursor_set_x(n1 - 1);
|
||||
break; // 1-based
|
||||
|
||||
// Set Y
|
||||
case 'd':
|
||||
screen_cursor_set_y(n1 - 1);
|
||||
break; // 1-based
|
||||
|
||||
// Clear in line
|
||||
case 'X':
|
||||
screen_clear_in_line(n1);
|
||||
break; // 1-based
|
||||
|
||||
// SU, SD - scroll up/down
|
||||
case 'S':
|
||||
screen_scroll_up(n1);
|
||||
break;
|
||||
|
||||
case 'T':
|
||||
screen_scroll_down(n1);
|
||||
break;
|
||||
|
||||
case 't': // xterm window commands
|
||||
do_csi_xterm_screen_cmd(opts);
|
||||
break;
|
||||
|
||||
// CUP,HVP - set position
|
||||
case 'H':
|
||||
case 'f':
|
||||
screen_cursor_set(n1-1, n2-1);
|
||||
break; // 1-based
|
||||
|
||||
case 'J': // Erase screen
|
||||
if (n1 == 0) {
|
||||
screen_clear(CLEAR_FROM_CURSOR);
|
||||
} else if (n1 == 1) {
|
||||
screen_clear(CLEAR_TO_CURSOR);
|
||||
} else {
|
||||
screen_clear(CLEAR_ALL);
|
||||
screen_cursor_set(0, 0);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'K': // Erase lines
|
||||
if (n1 == 0) {
|
||||
screen_clear_line(CLEAR_FROM_CURSOR);
|
||||
} else if (n1 == 1) {
|
||||
screen_clear_line(CLEAR_TO_CURSOR);
|
||||
} else {
|
||||
screen_clear_line(CLEAR_ALL);
|
||||
}
|
||||
break;
|
||||
|
||||
// SCP, RCP - save/restore position
|
||||
case 's':
|
||||
screen_cursor_save(0);
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
screen_set_scrolling_region(n1-1, n2-1);
|
||||
break;
|
||||
|
||||
case 'u':
|
||||
screen_cursor_restore(0);
|
||||
break;
|
||||
|
||||
case 'h': // DEC feature enable
|
||||
case 'l': // DEC feature disable
|
||||
// --- DEC standard attributes ---
|
||||
do_csi_set_option(opts);
|
||||
break;
|
||||
|
||||
case 'm': // SGR - set graphics rendition
|
||||
do_csi_sgr(opts);
|
||||
break;
|
||||
|
||||
case 'L': // Insert lines (shove down)
|
||||
screen_insert_lines(n1);
|
||||
break;
|
||||
|
||||
case 'M': // Delete lines (pull up)
|
||||
screen_delete_lines(n1);
|
||||
break;
|
||||
|
||||
case '@': // Insert in line (shove right)
|
||||
screen_insert_characters(n1);
|
||||
break;
|
||||
|
||||
case 'P': // Delete in line (pull left)
|
||||
screen_delete_characters(n1);
|
||||
break;
|
||||
|
||||
case 'g': // Clear tabs
|
||||
if (n1 == 3) {
|
||||
screen_clear_all_tabs();
|
||||
}
|
||||
else if (n1 == 0) {
|
||||
screen_clear_tab();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'Z': // Tab backward
|
||||
screen_tab_reverse(n1);
|
||||
break;
|
||||
|
||||
case 'I': // Tab forward
|
||||
screen_tab_forward(n1);
|
||||
break;
|
||||
|
||||
case 'n': // Queries
|
||||
if (n1 == 6) {
|
||||
// Query cursor position
|
||||
int x, y;
|
||||
screen_cursor_get(&y, &x);
|
||||
sprintf(resp_buf, "\033[%d;%dR", y + 1, x + 1);
|
||||
apars_respond(resp_buf);
|
||||
}
|
||||
else if (n1 == 5) {
|
||||
// Query device status - reply "Device is OK"
|
||||
apars_respond("\033[0n");
|
||||
}
|
||||
else {
|
||||
warn_bad_csi();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'c': // CSI-c - report capabilities
|
||||
// Primary device attributes
|
||||
apars_respond("\033[?64;22;9c"); // pretend we're vt420 with national character sets and colors.
|
||||
break;
|
||||
|
||||
case 'x': // DECREQTPARM -> DECREPTPARM
|
||||
do_csi_decreqtparm(opts);
|
||||
break;
|
||||
|
||||
default:
|
||||
warn_bad_csi();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* CSI none Pm ! key
|
||||
*/
|
||||
static inline void ICACHE_FLASH_ATTR
|
||||
switch_csi_NoLeadInterBang(CSI_Data *opts)
|
||||
{
|
||||
switch(opts->key) {
|
||||
case 'p':
|
||||
// RIS - CSI ! p
|
||||
// On real VT there are differences between soft and hard reset, we treat both equally
|
||||
screen_reset();
|
||||
break;
|
||||
|
||||
default:
|
||||
warn_bad_csi();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* CSI > Pm inter key
|
||||
*/
|
||||
static inline void ICACHE_FLASH_ATTR
|
||||
switch_csi_LeadGreater(CSI_Data *opts)
|
||||
{
|
||||
char resp_buf[20];
|
||||
switch(opts->key) {
|
||||
case 'c': // CSI > c - secondary device attributes query
|
||||
// 41 - we're "VT400", FV wers, 0 - ROM cartridge number
|
||||
sprintf(resp_buf, "\033[>41;%d;0c", FIRMWARE_VERSION_NUM);
|
||||
apars_respond(resp_buf);
|
||||
break;
|
||||
|
||||
default:
|
||||
warn_bad_csi();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* CSI = Pm inter key
|
||||
*/
|
||||
static inline void ICACHE_FLASH_ATTR
|
||||
switch_csi_LeadEquals(CSI_Data *opts)
|
||||
{
|
||||
char resp_buf[20];
|
||||
u8 mac[6];
|
||||
switch(opts->key) {
|
||||
case 'c': // CSI = c - tertiary device attributes query
|
||||
// report our unique ID number
|
||||
wifi_get_macaddr(SOFTAP_IF, mac);
|
||||
sprintf(resp_buf, "\033P!|%02X%02X%02X\033\\", mac[3], mac[4], mac[5]);
|
||||
apars_respond(resp_buf);
|
||||
break;
|
||||
|
||||
default:
|
||||
warn_bad_csi();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI ? Pm inter key
|
||||
*/
|
||||
static inline void ICACHE_FLASH_ATTR
|
||||
switch_csi_LeadQuest(CSI_Data *opts)
|
||||
{
|
||||
switch(opts->key) {
|
||||
case 's':
|
||||
// Save private attributes
|
||||
ansi_noimpl("Save private attrs");
|
||||
apars_show_context(); // TODO priv attr s/r
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
// Restore private attributes
|
||||
ansi_noimpl("Restore private attrs");
|
||||
apars_show_context(); // TODO priv attr s/r
|
||||
break;
|
||||
|
||||
case 'J': // Erase screen selectively
|
||||
// TODO selective erase
|
||||
ansi_noimpl("Selective screen erase");
|
||||
break;
|
||||
|
||||
case 'K': // Erase line selectively
|
||||
// TODO selective erase
|
||||
ansi_noimpl("Selective line erase");
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
case 'h':
|
||||
do_csi_set_private_option(opts);
|
||||
break;
|
||||
|
||||
default:
|
||||
warn_bad_csi();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* CSI Pm m
|
||||
* @param opts
|
||||
*/
|
||||
static inline void ICACHE_FLASH_ATTR
|
||||
do_csi_sgr(CSI_Data *opts)
|
||||
{
|
||||
int count = opts->count;
|
||||
|
||||
if (count == 0) {
|
||||
count = 1; // this makes it work as 0 (reset)
|
||||
}
|
||||
|
||||
// iterate arguments
|
||||
for (int i = 0; i < count; i++) {
|
||||
int n = opts->n[i];
|
||||
|
||||
if (n == SGR_RESET) screen_reset_sgr();
|
||||
// -- set color --
|
||||
else if (n >= SGR_FG_START && n <= SGR_FG_END) screen_set_fg((Color) (n - SGR_FG_START)); // ANSI normal fg
|
||||
else if (n >= SGR_BG_START && n <= SGR_BG_END) screen_set_bg((Color) (n - SGR_BG_START)); // ANSI normal bg
|
||||
else if (n == SGR_FG_DEFAULT) screen_set_fg(termconf_scratch.default_fg); // default fg
|
||||
else if (n == SGR_BG_DEFAULT) screen_set_bg(termconf_scratch.default_bg); // default bg
|
||||
// -- set attr --
|
||||
else if (n == SGR_BOLD) screen_set_sgr(ATTR_BOLD, 1);
|
||||
else if (n == SGR_FAINT) screen_set_sgr(ATTR_FAINT, 1);
|
||||
else if (n == SGR_ITALIC) screen_set_sgr(ATTR_ITALIC, 1);
|
||||
else if (n == SGR_UNDERLINE) screen_set_sgr(ATTR_UNDERLINE, 1);
|
||||
else if (n == SGR_BLINK || n == SGR_BLINK_FAST) screen_set_sgr(ATTR_BLINK, 1); // 6 - rapid blink, not supported
|
||||
else if (n == SGR_STRIKE) screen_set_sgr(ATTR_STRIKE, 1);
|
||||
else if (n == SGR_FRAKTUR) screen_set_sgr(ATTR_FRAKTUR, 1);
|
||||
else if (n == SGR_INVERSE) screen_set_sgr_inverse(1);
|
||||
// -- clear attr --
|
||||
else if (n == SGR_OFF(SGR_BOLD)) screen_set_sgr(ATTR_BOLD, 0); // can also mean "Double Underline"
|
||||
else if (n == SGR_OFF(SGR_FAINT)) screen_set_sgr(ATTR_FAINT | ATTR_BOLD, 0); // "normal"
|
||||
else if (n == SGR_OFF(SGR_ITALIC)) screen_set_sgr(ATTR_ITALIC | ATTR_FRAKTUR, 0); // there is no dedicated OFF code for Fraktur
|
||||
else if (n == SGR_OFF(SGR_UNDERLINE)) screen_set_sgr(ATTR_UNDERLINE, 0);
|
||||
else if (n == SGR_OFF(SGR_BLINK)) screen_set_sgr(ATTR_BLINK, 0);
|
||||
else if (n == SGR_OFF(SGR_STRIKE)) screen_set_sgr(ATTR_STRIKE, 0);
|
||||
else if (n == SGR_OFF(SGR_INVERSE)) screen_set_sgr_inverse(0);
|
||||
// -- AIX bright colors --
|
||||
else if (n >= SGR_FG_BRT_START && n <= SGR_FG_BRT_END) screen_set_fg((Color) ((n - SGR_FG_BRT_START) + 8)); // AIX bright fg
|
||||
else if (n >= SGR_BG_BRT_START && n <= SGR_BG_BRT_END) screen_set_bg((Color) ((n - SGR_BG_BRT_START) + 8)); // AIX bright bg
|
||||
else {
|
||||
ansi_noimpl("SGR %d", n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* CSI Pm h or l
|
||||
* @param opts
|
||||
*/
|
||||
static inline void ICACHE_FLASH_ATTR
|
||||
do_csi_set_option(CSI_Data *opts)
|
||||
{
|
||||
bool yn = (opts->key == 'h');
|
||||
|
||||
for (int i = 0; i < opts->count; i++) {
|
||||
int n = opts->n[i];
|
||||
|
||||
if (n == 4) {
|
||||
screen_set_insert_mode(yn);
|
||||
}
|
||||
else if (n == 12) {
|
||||
// SRM is inverted, according to vt510 manual
|
||||
termconf_scratch.loopback = !yn;
|
||||
}
|
||||
else if (n == 20) {
|
||||
screen_set_newline_mode(yn);
|
||||
}
|
||||
else if (n == 33) {
|
||||
ansi_noimpl("Steady cursor");
|
||||
// reference https://ttssh2.osdn.jp/manual/en/about/ctrlseq.html
|
||||
}
|
||||
else if (n == 34) {
|
||||
ansi_noimpl("Underline cursor");
|
||||
// reference https://ttssh2.osdn.jp/manual/en/about/ctrlseq.html
|
||||
}
|
||||
else {
|
||||
ansi_noimpl("OPTION %d", n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* CSI ? Pm h or l
|
||||
* @param opts
|
||||
*/
|
||||
static inline void ICACHE_FLASH_ATTR
|
||||
do_csi_set_private_option(CSI_Data *opts)
|
||||
{
|
||||
bool yn = (opts->key == 'h');
|
||||
|
||||
// --- DEC private attributes ---
|
||||
for (int i = 0; i < opts->count; i++) {
|
||||
int n = opts->n[i];
|
||||
if (n == 1) {
|
||||
screen_set_cursors_alt_mode(yn);
|
||||
}
|
||||
else if (n == 2) {
|
||||
// should reset all Gx to USASCII and reset to VT100 (which we use always)
|
||||
screen_set_charset(0, 'B');
|
||||
screen_set_charset(1, 'B');
|
||||
}
|
||||
else if (n == 3) {
|
||||
// DECCOLM 132 column mode - not implemented due to RAM demands
|
||||
ansi_noimpl("132col");
|
||||
|
||||
// DECCOLM side effects as per
|
||||
// https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/deccolm-cls.html
|
||||
screen_clear(CLEAR_ALL);
|
||||
screen_set_scrolling_region(0, 0);
|
||||
screen_cursor_set(0, 0);
|
||||
}
|
||||
else if (n == 4) {
|
||||
// Smooth scroll - not implemented
|
||||
}
|
||||
else if (n == 5) {
|
||||
screen_set_reverse_video(yn);
|
||||
}
|
||||
else if (n == 6) {
|
||||
screen_set_origin_mode(yn);
|
||||
}
|
||||
else if (n == 7) {
|
||||
screen_wrap_enable(yn);
|
||||
}
|
||||
else if (n == 8) {
|
||||
// Key auto-repeat
|
||||
// We don't implement this currently, but it could be added
|
||||
// - discard repeated keypress events between keydown and keyup.
|
||||
ansi_noimpl("Auto-repeat toggle");
|
||||
}
|
||||
else if (n == 9 || (n >= 1000 && n <= 1006) || n == 1015) {
|
||||
// 9 - X10 tracking
|
||||
// 1000 - X11 mouse - Send Mouse X & Y on button press and release.
|
||||
// 1001 - Hilite mouse tracking - not impl
|
||||
// 1002 - Cell Motion Mouse Tracking
|
||||
// 1003 - All Motion Mouse Tracking
|
||||
// 1004 - Send FocusIn/FocusOut events
|
||||
// 1005 - Enable UTF-8 Mouse Mode - we implement this as an alias to X10 mode
|
||||
// 1006 - SGR mouse mode
|
||||
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;
|
||||
|
||||
dbg("Mouse mode=%d, enc=%d, foctr=%d",
|
||||
mouse_tracking.mode,
|
||||
mouse_tracking.encoding,
|
||||
mouse_tracking.focus_tracking);
|
||||
}
|
||||
else if (n == 12) {
|
||||
// TODO Cursor blink on/off
|
||||
ansi_noimpl("Cursor blink toggle");
|
||||
}
|
||||
else if (n == 40) {
|
||||
// allow/disallow 80->132 mode
|
||||
// not implemented because of RAM demands
|
||||
ansi_noimpl("132col enable");
|
||||
}
|
||||
else if (n == 45) {
|
||||
// reverse wrap-around
|
||||
ansi_noimpl("Reverse Wraparound");
|
||||
}
|
||||
else if (n == 69) {
|
||||
// horizontal margins
|
||||
ansi_noimpl("Left/right margin");
|
||||
}
|
||||
else if (n == 47 || n == 1047) {
|
||||
// Switch to/from alternate screen
|
||||
// - not implemented fully due to RAM demands
|
||||
screen_swap_state(yn);
|
||||
}
|
||||
else if (n == 1048) {
|
||||
// same as DECSC - save/restore cursor with attributes
|
||||
if (yn) {
|
||||
screen_cursor_save(true);
|
||||
}
|
||||
else {
|
||||
screen_cursor_restore(true);
|
||||
}
|
||||
}
|
||||
else if (n == 1049) {
|
||||
// save/restore cursor and screen and clear it
|
||||
if (yn) {
|
||||
screen_cursor_save(true);
|
||||
screen_swap_state(true); // this should save the screen - can't because of RAM size
|
||||
screen_clear(CLEAR_ALL);
|
||||
}
|
||||
else {
|
||||
screen_clear(CLEAR_ALL);
|
||||
screen_swap_state(false); // this should restore the screen - can't because of RAM size
|
||||
screen_cursor_restore(true);
|
||||
}
|
||||
}
|
||||
else if (n == 2004) {
|
||||
// Bracketed paste mode
|
||||
ansi_noimpl("Bracketed paste");
|
||||
}
|
||||
else if (n == 25) {
|
||||
screen_set_cursor_visible(yn);
|
||||
}
|
||||
else if (n == 800) { // ESPTerm: Toggle display of buttons
|
||||
termconf_scratch.show_buttons = yn;
|
||||
screen_notifyChange(CHANGE_CONTENT); // this info is included in the screen preamble
|
||||
}
|
||||
else if (n == 801) { // ESPTerm: Toggle display of config links
|
||||
termconf_scratch.show_config_links = yn;
|
||||
screen_notifyChange(CHANGE_CONTENT); // this info is included in the screen preamble
|
||||
}
|
||||
else {
|
||||
ansi_noimpl("?OPTION %d", n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* CSI Ps ; Ps ; Ps t
|
||||
* @param opts
|
||||
*/
|
||||
static inline void ICACHE_FLASH_ATTR
|
||||
do_csi_xterm_screen_cmd(CSI_Data *opts)
|
||||
{
|
||||
char resp_buf[20];
|
||||
switch (opts->n[0]) {
|
||||
case 8: // set size
|
||||
screen_resize(opts->n[1], opts->n[2]);
|
||||
break;
|
||||
|
||||
case 18: // Report the size of the text area in characters.
|
||||
case 19: // Report the size of the screen in characters.
|
||||
sprintf(resp_buf, "\033[8;%d;%dt", termconf_scratch.height, termconf_scratch.width);
|
||||
apars_respond(resp_buf);
|
||||
break;
|
||||
|
||||
case 20: // Report icon
|
||||
case 21: // Report title
|
||||
apars_respond("\033]l");
|
||||
apars_respond(termconf_scratch.title);
|
||||
apars_respond("\033\\");
|
||||
break;
|
||||
|
||||
case 22:
|
||||
ansi_noimpl("Push title");
|
||||
break;
|
||||
case 23:
|
||||
ansi_noimpl("Pop title");
|
||||
break;
|
||||
|
||||
case 24: // Set Height only
|
||||
screen_resize(opts->n[1], termconf_scratch.width);
|
||||
break;
|
||||
|
||||
default:
|
||||
ansi_noimpl("Xterm win report %d", opts->n[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// data tables for the DECREPTPARM command response
|
||||
struct DECREPTPARM_parity { int parity; const char * msg; };
|
||||
static const struct DECREPTPARM_parity DECREPTPARM_parity_arr[] = {
|
||||
{PARITY_NONE, "1"},
|
||||
{PARITY_ODD, "4"},
|
||||
{PARITY_EVEN, "5"},
|
||||
{-1, 0}
|
||||
};
|
||||
|
||||
struct DECREPTPARM_baud { int baud; const char * msg; };
|
||||
static const struct DECREPTPARM_baud DECREPTPARM_baud_arr[] = {
|
||||
{BIT_RATE_300, "48"},
|
||||
{BIT_RATE_600, "56"},
|
||||
{BIT_RATE_1200, "64"},
|
||||
{BIT_RATE_2400, "88"},
|
||||
{BIT_RATE_4800, "96"},
|
||||
{BIT_RATE_9600 , "104"},
|
||||
{BIT_RATE_19200 , "112"},
|
||||
{BIT_RATE_38400 , "120"},
|
||||
{BIT_RATE_57600 , "128"}, // this is the last in the spec, follow +8
|
||||
{BIT_RATE_74880 , "136"},
|
||||
{BIT_RATE_115200, "144"},
|
||||
{BIT_RATE_230400, "152"},
|
||||
{BIT_RATE_460800, "160"},
|
||||
{BIT_RATE_921600, "168"},
|
||||
{BIT_RATE_1843200, "176"},
|
||||
{BIT_RATE_3686400, "184"},
|
||||
{-1, 0}
|
||||
};
|
||||
|
||||
/**
|
||||
* CSI Ps x
|
||||
* @param opts
|
||||
*/
|
||||
static inline void ICACHE_FLASH_ATTR
|
||||
do_csi_decreqtparm(CSI_Data *opts)
|
||||
{
|
||||
const int n1 = opts->n[0];
|
||||
|
||||
// reference http://vt100.net/docs/vt100-ug/chapter3.html - search DECREPTPARM
|
||||
if (n1 <= 1) {
|
||||
apars_respond("\033["); // this is a response on request (2 would be gratuitous)
|
||||
|
||||
apars_respond(n1 == 0 ? "2;" : "3;");
|
||||
|
||||
// Parity
|
||||
for(const struct DECREPTPARM_parity *p = DECREPTPARM_parity_arr; p->parity != -1; p++) {
|
||||
if (p->parity == sysconf->uart_parity) {
|
||||
apars_respond(p->msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// bits per character (uart byte) - 1 = 8, 2 = 7
|
||||
apars_respond(";1;");
|
||||
|
||||
// Baud rate
|
||||
for(const struct DECREPTPARM_baud *p = DECREPTPARM_baud_arr; p->baud != -1; p++) {
|
||||
if (p->baud == sysconf->uart_baudrate) {
|
||||
apars_respond(p->msg);
|
||||
apars_respond(";");
|
||||
apars_respond(p->msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// multiplier 1, flags 0
|
||||
apars_respond(";1;0x"); // ROM cartridge number ??
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/20.
|
||||
//
|
||||
|
||||
#ifndef ESP_VT100_FIRMWARE_APARS_CSI_H
|
||||
#define ESP_VT100_FIRMWARE_APARS_CSI_H
|
||||
|
||||
void apars_handle_csi(char leadchar, const int *params, int count, char interchar, char keychar);
|
||||
|
||||
#endif //ESP_VT100_FIRMWARE_APARS_CSI_H
|
||||
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/20.
|
||||
//
|
||||
// Handle DCS sequences (Device Control String)
|
||||
// DCS Pt ST
|
||||
// (DCS = ESC P)
|
||||
//
|
||||
// Those are used for terminal status queries.
|
||||
//
|
||||
// Pt =
|
||||
// $ q " p .... read conformance level
|
||||
// $ q " q .... read character protection attribute
|
||||
// $ q r ...... read scrolling region extents
|
||||
// $ q s ...... read horizontal margins extents
|
||||
// $ q m ...... read SGR in a format that would restore them exactly when run as a CSI Pm m
|
||||
// $ q SP q ... read cursor style
|
||||
//
|
||||
// For details, see
|
||||
// http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Device-Control-functions
|
||||
//
|
||||
|
||||
#include <esp8266.h>
|
||||
#include "apars_dcs.h"
|
||||
#include "ansi_parser_callbacks.h"
|
||||
#include "screen.h"
|
||||
#include "apars_logging.h"
|
||||
|
||||
/**
|
||||
* Helper function to parse incoming DCS (Device Control String)
|
||||
* @param buffer - the DCS body (after DCS and before ST)
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_handle_dcs(const char *buffer)
|
||||
{
|
||||
char buf[64]; // just about big enough for full-house SGR
|
||||
size_t len = strlen(buffer);
|
||||
if ((len == 3 || len == 4) && strneq(buffer, "$q", 2)) {
|
||||
// DECRQSS - Request Status String
|
||||
if (strneq(buffer+2, "\"p", 2)) {
|
||||
// DECSCL - Select Conformance Level
|
||||
apars_respond("\033P1$r64;1\"p\033\\"); // 64;1 - Pretend we are VT400 with 7-bit characters
|
||||
}
|
||||
else if (strneq(buffer+2, "\"q", 2)) {
|
||||
// DECSCA - Select character protection attribute
|
||||
sprintf(buf, "\033P1$r%d\"q\033\\", 0); // 0 - Can erase - TODO real protection status if implemented
|
||||
apars_respond(buf);
|
||||
}
|
||||
else if (buffer[2] == 'r') {
|
||||
// DECSTBM - Query scrolling region
|
||||
sprintf(buf, "\033P1$r%d;%dr\033\\", 1, termconf_scratch.height); // 1-80 TODO real extent of scrolling region
|
||||
apars_respond(buf);
|
||||
}
|
||||
else if (buffer[2] == 's') {
|
||||
// DECSLRM - Query horizontal margins
|
||||
sprintf(buf, "\033P1$r%d;%ds\033\\", 1, termconf_scratch.width); // Can erase - TODO real extent of horiz margins if implemented
|
||||
apars_respond(buf);
|
||||
}
|
||||
else if (buffer[2] == 'm') {
|
||||
// SGR - query SGR
|
||||
apars_respond("\033P1$r");
|
||||
screen_report_sgr(buf);
|
||||
apars_respond(buf);
|
||||
apars_respond("m\033\\");
|
||||
}
|
||||
else if (strneq(buffer+2, " q", 2)) {
|
||||
// DECSCUSR - Query cursor style
|
||||
sprintf(buf, "\033P1$r%d q\033\\", 1);
|
||||
/*
|
||||
Ps = 0 -> blinking block.
|
||||
Ps = 1 -> blinking block (default).
|
||||
Ps = 2 -> steady block.
|
||||
Ps = 3 -> blinking underline.
|
||||
Ps = 4 -> steady underline.
|
||||
Ps = 5 -> blinking bar (xterm).
|
||||
Ps = 6 -> steady bar (xterm).
|
||||
*/
|
||||
apars_respond(buf);
|
||||
}
|
||||
else {
|
||||
// invalid query
|
||||
ansi_noimpl("DCS %s ST", buffer);
|
||||
sprintf(buf, "\033P0$r%s\033\\", buffer+2);
|
||||
}
|
||||
}
|
||||
else {
|
||||
ansi_warn("Bad DCS: %s", buffer);
|
||||
apars_show_context();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/20.
|
||||
//
|
||||
|
||||
#ifndef ESP_VT100_FIRMWARE_APARS_DCS_H
|
||||
#define ESP_VT100_FIRMWARE_APARS_DCS_H
|
||||
|
||||
void apars_handle_dcs(const char *buffer);
|
||||
|
||||
#endif //ESP_VT100_FIRMWARE_APARS_DCS_H
|
||||
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/20.
|
||||
//
|
||||
// Logging functions for the parser, can be switched off with a compile flag
|
||||
//
|
||||
|
||||
#ifndef ESP_VT100_FIRMWARE_APARS_LOGGING_H
|
||||
#define ESP_VT100_FIRMWARE_APARS_LOGGING_H
|
||||
|
||||
#include <esp8266.h>
|
||||
|
||||
#ifndef DEBUG_ANSI
|
||||
#define DEBUG_ANSI 0
|
||||
#endif
|
||||
|
||||
#ifndef DEBUG_ANSI_NOIMPL
|
||||
#define DEBUG_ANSI_NOIMPL 0
|
||||
#endif
|
||||
|
||||
// defined in the makefile
|
||||
#if DEBUG_ANSI
|
||||
#define ansi_warn warn
|
||||
#define ansi_dbg dbg
|
||||
#else
|
||||
#define ansi_warn(fmt, ...)
|
||||
#define ansi_dbg(fmt, ...)
|
||||
#endif
|
||||
|
||||
#if DEBUG_ANSI_NOIMPL
|
||||
#define ansi_noimpl(fmt, ...) warn("NOIMPL: " fmt, ##__VA_ARGS__)
|
||||
#define ansi_noimpl_r(fmt, ...) warn(fmt, ##__VA_ARGS__)
|
||||
#else
|
||||
#define ansi_noimpl(fmt, ...)
|
||||
#define ansi_noimpl_r(fmt, ...)
|
||||
#endif
|
||||
|
||||
#endif //ESP_VT100_FIRMWARE_APARS_LOGGING_H
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/20.
|
||||
//
|
||||
// Handle OSC commands (Operating System Command)
|
||||
// ESC ] Ps ; Pt ST
|
||||
//
|
||||
// Those are used to pass various text variables to the terminal:
|
||||
//
|
||||
// Ps = 0 or 2 ... set screen title to Pt
|
||||
// Ps = 81-85 ... set button label to Pt
|
||||
//
|
||||
|
||||
#include "apars_osc.h"
|
||||
#include "apars_logging.h"
|
||||
#include "screen.h"
|
||||
#include "ansi_parser.h"
|
||||
|
||||
/**
|
||||
* Helper function to parse incoming OSC (Operating System Control)
|
||||
* @param buffer - the OSC body (after OSC and before ST)
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_handle_osc(const char *buffer)
|
||||
{
|
||||
int n = 0;
|
||||
char c = 0;
|
||||
while ((c = *buffer++) != 0) {
|
||||
if (c >= '0' && c <= '9') {
|
||||
n = (n * 10 + (c - '0'));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (c == ';') {
|
||||
// Do something with the data string and number
|
||||
// (based on xterm manpage)
|
||||
if (n == 0 || n == 2) {
|
||||
screen_set_title(buffer);
|
||||
}
|
||||
else if (n >= 81 && n <= 85) { // ESPTerm: action button label
|
||||
screen_set_button_text(n - 80, buffer);
|
||||
}
|
||||
else if (n >= 91 && n <= 95) { // ESPTerm: action button text
|
||||
strncpy(termconf_scratch.btn_msg[n - 91], buffer, TERM_BTN_MSG_LEN);
|
||||
}
|
||||
else {
|
||||
ansi_noimpl("OSC %d ; %s ST", n, buffer);
|
||||
}
|
||||
}
|
||||
else {
|
||||
ansi_warn("BAD OSC: %s", buffer);
|
||||
apars_show_context();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/20.
|
||||
//
|
||||
|
||||
#ifndef ESP_VT100_FIRMWARE_APARS_OSC_H
|
||||
#define ESP_VT100_FIRMWARE_APARS_OSC_H
|
||||
|
||||
void apars_handle_osc(const char *buffer);
|
||||
|
||||
#endif //ESP_VT100_FIRMWARE_APARS_OSC_H
|
||||
@@ -0,0 +1,183 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/20.
|
||||
//
|
||||
// Short sequences: (* = not implemented)
|
||||
//
|
||||
// ESC char
|
||||
// ESC 6 Back Index (DECBI), VT420 and up.
|
||||
// ESC 7 Save Cursor (DECSC).
|
||||
// ESC 8 Restore Cursor (DECRC).
|
||||
// ESC 9 Forward Index (DECFI), VT420 and up.
|
||||
// ESC = Application Keypad (DECKPAM).
|
||||
// ESC > Normal Keypad (DECKPNM).
|
||||
// ESC F Cursor to lower left corner of screen.
|
||||
// ESC c Full Reset (RIS).
|
||||
// *ESC l Memory Lock (per HP terminals). Locks memory above the cursor.
|
||||
// *ESC m Memory Unlock (per HP terminals).
|
||||
// ESC n Invoke the G2 Character Set as GL (LS2).
|
||||
// ESC o Invoke the G3 Character Set as GL (LS3).
|
||||
// *ESC | Invoke the G3 Character Set as GR (LS3R).
|
||||
// *ESC } Invoke the G2 Character Set as GR (LS2R).
|
||||
// *ESC ~ Invoke the G1 Character Set as GR (LS1R).
|
||||
//
|
||||
// ESC # Ps
|
||||
// *ESC # 3 DEC double-height line, top half (DECDHL).
|
||||
// *ESC # 4 DEC double-height line, bottom half (DECDHL).
|
||||
// *ESC # 5 DEC single-width line (DECSWL).
|
||||
// *ESC # 6 DEC double-width line (DECDWL).
|
||||
// ESC # 8 DEC Screen Alignment Test (DECALN).
|
||||
//
|
||||
// ESC SP char
|
||||
// *ESC SP F 7-bit controls (S7C1T).
|
||||
// *ESC SP G 8-bit controls (S8C1T).
|
||||
// *ESC SP L Set ANSI conformance level 1 (dpANS X3.134.1).
|
||||
// *ESC SP M Set ANSI conformance level 2 (dpANS X3.134.1).
|
||||
// *ESC SP N Set ANSI conformance level 3 (dpANS X3.134.1).
|
||||
//
|
||||
// Charset commands
|
||||
// ESC ( char Designate G0 character set ('0' = symbols, 'B' = ASCII-US, 'A' = ASCII-UK)
|
||||
// ESC ) char Designate G1 character set
|
||||
// SO Switch to G0 character set
|
||||
// SI Switch to G1 character set
|
||||
//
|
||||
|
||||
#include <esp8266.h>
|
||||
#include "apars_short.h"
|
||||
#include "apars_logging.h"
|
||||
#include "screen.h"
|
||||
|
||||
// ----- Character Set ---
|
||||
|
||||
/**
|
||||
* Command to assign G0 or G1
|
||||
* @param slot - ( or ) for G0 or G1
|
||||
* @param c - character table ID (0, B etc)
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_handle_chs_designate(char slot, char c)
|
||||
{
|
||||
if (slot == '(') screen_set_charset(0, c); // G0
|
||||
else if (slot == ')') screen_set_charset(1, c); // G1
|
||||
else {
|
||||
ansi_noimpl("ESC %c %c", slot, c);
|
||||
}
|
||||
// other alternatives * + . - / not implemented
|
||||
}
|
||||
|
||||
/** Select charset slot */
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_handle_chs_switch(int Gx)
|
||||
{
|
||||
screen_set_charset_n(Gx);
|
||||
}
|
||||
|
||||
// ----- ESC SP char -----
|
||||
|
||||
/**
|
||||
* ESC SP <c> (this sets 8/7-bit mode and some other archaic options)
|
||||
* @param c - key character
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_handle_space_cmd(char c)
|
||||
{
|
||||
ansi_noimpl("ESC SP %c", c);
|
||||
}
|
||||
|
||||
// ----- ESC # num -----
|
||||
|
||||
/**
|
||||
* Codes in the format ESC # n
|
||||
* @param c - the trailing symbol (numeric ASCII)
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR apars_handle_hash_cmd(char c)
|
||||
{
|
||||
switch(c) {
|
||||
case '3': // Double size, top half
|
||||
case '4': // Single size, bottom half
|
||||
case '5': // Single width, single height
|
||||
case '6': // Double width
|
||||
ansi_noimpl("Double Size Line");
|
||||
break;
|
||||
|
||||
case '8':
|
||||
screen_fill_with_E();
|
||||
break;
|
||||
|
||||
default:
|
||||
ansi_noimpl("ESC # %c", c);
|
||||
}
|
||||
}
|
||||
|
||||
// ----- ESC char -----
|
||||
|
||||
/**
|
||||
* Single-character escape codes (ESC x)
|
||||
* @param c - the trailing symbol (ASCII)
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR apars_handle_short_cmd(char c)
|
||||
{
|
||||
switch(c) {
|
||||
case 'c': // screen reset
|
||||
screen_reset();
|
||||
break;
|
||||
|
||||
case '6': // back index
|
||||
screen_back_index(1);
|
||||
break;
|
||||
|
||||
case '7': // save cursor + attributes
|
||||
screen_cursor_save(true);
|
||||
break;
|
||||
|
||||
case '8': // restore cursor + attributes
|
||||
screen_cursor_restore(true);
|
||||
break;
|
||||
|
||||
case 'E': // same as CR LF
|
||||
screen_cursor_move(1, 0, false);
|
||||
screen_cursor_set_x(0);
|
||||
break;
|
||||
|
||||
case 'F': // bottom left
|
||||
screen_cursor_set(termconf_scratch.height-1, 0);
|
||||
break;
|
||||
|
||||
case 'D': // move cursor down, scroll screen up if needed
|
||||
screen_cursor_move(1, 0, true);
|
||||
break;
|
||||
|
||||
case 'M': // move cursor up, scroll screen down if needed
|
||||
screen_cursor_move(-1, 0, true);
|
||||
break;
|
||||
|
||||
case 'H':
|
||||
screen_set_tab();
|
||||
break;
|
||||
|
||||
case '>':
|
||||
screen_set_numpad_alt_mode(false);
|
||||
break;
|
||||
|
||||
case '<': // "Enter ANSI / VT100 mode" - no-op (we don't support VT52 mode)
|
||||
break;
|
||||
|
||||
case '=':
|
||||
screen_set_numpad_alt_mode(true);
|
||||
break;
|
||||
|
||||
case '|': // Invoke the G3 Character Set as GR (LS3R).
|
||||
case '}': // Invoke the G2 Character Set as GR (LS2R).
|
||||
case '~': // Invoke the G1 Character Set as GR (LS1R).
|
||||
// Those do not seem to do anything TODO investigate
|
||||
break;
|
||||
|
||||
case '@': // no-op padding char (?)
|
||||
break;
|
||||
|
||||
case '\\': // spurious string terminator
|
||||
break;
|
||||
|
||||
default:
|
||||
ansi_noimpl("ESC %c", c);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/20.
|
||||
//
|
||||
|
||||
#ifndef ESP_VT100_FIRMWARE_APARS_SIMPLE_H
|
||||
#define ESP_VT100_FIRMWARE_APARS_SIMPLE_H
|
||||
|
||||
void apars_handle_short_cmd(char c);
|
||||
void apars_handle_hash_cmd(char c);
|
||||
void apars_handle_space_cmd(char c);
|
||||
void apars_handle_chs_designate(char slot, char c);
|
||||
void apars_handle_chs_switch(int Gx);
|
||||
|
||||
#endif //ESP_VT100_FIRMWARE_APARS_SIMPLE_H
|
||||
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/20.
|
||||
//
|
||||
// String based commands.
|
||||
// Those command start with a introducer sequence, followed by arbitrary string,
|
||||
// and end with a String Terminator (`ESC \`, or `BEL`)
|
||||
//
|
||||
// ESC k Pt ST ... set screen title (same as `OSC 0 ; Pt ST`, is shorter)
|
||||
// ESC ] Pt ST ... OSC - Operating System Command (split to its own file)
|
||||
// ESC P Pt ST ... DCS - Device Control String (split to its own file)
|
||||
// ESC ^ Pt ST ... PM - Privacy message (unused)
|
||||
// ESC _ Pt ST ... APC - Application Program Command (unused)
|
||||
// ESC X Pt ST ... SOS - Start Of String (unused; sent back in response to ENQ)
|
||||
|
||||
#include <esp8266.h>
|
||||
#include "apars_string.h"
|
||||
#include "apars_logging.h"
|
||||
#include "ansi_parser_callbacks.h"
|
||||
#include "screen.h"
|
||||
|
||||
// ----- Generic String cmd - disambiguation -----
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_handle_string_cmd(char leadchar, const char *buffer)
|
||||
{
|
||||
switch (leadchar) {
|
||||
case 'k': // ESC k TITLE ST (defined in GNU screen manpage)
|
||||
screen_set_title(buffer);
|
||||
break;
|
||||
|
||||
case ']': // OSC - Operating System Command
|
||||
apars_handle_osc(buffer);
|
||||
break;
|
||||
|
||||
case 'P': // DCS - Device Control String
|
||||
apars_handle_dcs(buffer);
|
||||
break;
|
||||
|
||||
case '^': // PM - Privacy Message
|
||||
break;
|
||||
|
||||
case '_': // APC - Application Program Command
|
||||
break;
|
||||
|
||||
case 'X': // SOS - Start of String (purpose unclear)
|
||||
break;
|
||||
|
||||
default:
|
||||
ansi_warn("Bad str cmd");
|
||||
apars_show_context();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/20.
|
||||
//
|
||||
|
||||
#ifndef ESP_VT100_FIRMWARE_APARS_STRING_H
|
||||
#define ESP_VT100_FIRMWARE_APARS_STRING_H
|
||||
|
||||
void apars_handle_string_cmd(char leadchar, const char *buffer);
|
||||
|
||||
#endif //ESP_VT100_FIRMWARE_APARS_STRING_H
|
||||
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/20.
|
||||
//
|
||||
// UTF-8 parser - collects bytes of a code point before writing them
|
||||
// into a screen cell.
|
||||
//
|
||||
|
||||
#include "apars_utf8.h"
|
||||
#include "apars_logging.h"
|
||||
#include "screen.h"
|
||||
|
||||
static char utf_collect[4];
|
||||
static int utf_i = 0;
|
||||
static int utf_j = 0;
|
||||
|
||||
/**
|
||||
* Clear the buffer where we collect pieces of a code point.
|
||||
* This is used for parser reset.
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_reset_utf8buffer(void)
|
||||
{
|
||||
utf_i = 0;
|
||||
utf_j = 0;
|
||||
memset(utf_collect, 0, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a received plain character
|
||||
* @param c - received character
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_handle_plainchar(char c)
|
||||
{
|
||||
// collecting unicode glyphs...
|
||||
if (c & 0x80) {
|
||||
if (utf_i == 0) {
|
||||
// start
|
||||
if (c == 192 || c == 193 || c >= 245) {
|
||||
// forbidden codes
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if ((c & 0xE0) == 0xC0) {
|
||||
utf_i = 2;
|
||||
}
|
||||
else if ((c & 0xF0) == 0xE0) {
|
||||
utf_i = 3;
|
||||
}
|
||||
else if ((c & 0xF8) == 0xF0) {
|
||||
utf_i = 4;
|
||||
}
|
||||
else {
|
||||
// chars over 127 that don't start unicode sequences
|
||||
goto fail;
|
||||
}
|
||||
|
||||
utf_collect[0] = c;
|
||||
utf_j = 1;
|
||||
}
|
||||
else {
|
||||
if ((c & 0xC0) != 0x80) {
|
||||
goto fail;
|
||||
}
|
||||
else {
|
||||
utf_collect[utf_j++] = c;
|
||||
if (utf_j >= utf_i) {
|
||||
screen_putchar(utf_collect);
|
||||
apars_reset_utf8buffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
utf_collect[0] = c;
|
||||
utf_collect[1] = 0; // just to make sure it's closed...
|
||||
screen_putchar(utf_collect);
|
||||
}
|
||||
|
||||
return;
|
||||
fail:
|
||||
ansi_warn("Bad UTF-8: %0Xh", c);
|
||||
apars_reset_utf8buffer();
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/20.
|
||||
//
|
||||
|
||||
#ifndef ESP_VT100_FIRMWARE_APARS_UTF8_H
|
||||
#define ESP_VT100_FIRMWARE_APARS_UTF8_H
|
||||
|
||||
void apars_handle_plainchar(char c);
|
||||
void apars_reset_utf8buffer(void);
|
||||
|
||||
#endif //ESP_VT100_FIRMWARE_APARS_UTF8_H
|
||||
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/19.
|
||||
//
|
||||
|
||||
#ifndef ESP_VT100_FIRMWARE_ASCII_H
|
||||
#define ESP_VT100_FIRMWARE_ASCII_H
|
||||
|
||||
enum ASCII_CODES {
|
||||
NUL = 0,
|
||||
SOH = 1,
|
||||
STX = 2,
|
||||
ETX = 3,
|
||||
EOT = 4,
|
||||
ENQ = 5,
|
||||
ACK = 6,
|
||||
BEL = 7, /* \a */
|
||||
BS = 8, /* \b */
|
||||
TAB = 9, /* \t */
|
||||
LF = 10, /* \n */
|
||||
VT = 11, /* \v */
|
||||
FF = 12, /* \f */
|
||||
CR = 13, /* \r */
|
||||
SO = 14,
|
||||
SI = 15,
|
||||
DLE = 16,
|
||||
DC1 = 17,
|
||||
DC2 = 18,
|
||||
DC3 = 19,
|
||||
DC4 = 20,
|
||||
NAK = 21,
|
||||
SYN = 22,
|
||||
ETB = 23,
|
||||
CAN = 24,
|
||||
EM = 25,
|
||||
SUB = 26,
|
||||
ESC = 27, /* \033, \x1b, \e */
|
||||
FS = 28,
|
||||
GS = 29,
|
||||
RS = 30,
|
||||
US = 31,
|
||||
SP = 32,
|
||||
DEL = 127,
|
||||
|
||||
// aliases
|
||||
XON = DC1,
|
||||
XOFF = DC3,
|
||||
};
|
||||
|
||||
#endif //ESP_VT100_FIRMWARE_ASCII_H
|
||||
@@ -1,178 +0,0 @@
|
||||
/*
|
||||
Cgi/template routines for configuring non-wifi settings
|
||||
*/
|
||||
|
||||
#include <esp8266.h>
|
||||
#include "cgi_appcfg.h"
|
||||
#include "persist.h"
|
||||
#include "screen.h"
|
||||
#include "helpers.h"
|
||||
|
||||
#define SET_REDIR_SUC "/cfg/app"
|
||||
#define SET_REDIR_ERR SET_REDIR_SUC"?err="
|
||||
|
||||
/**
|
||||
* Universal CGI endpoint to set Terminal params.
|
||||
*/
|
||||
httpd_cgi_state ICACHE_FLASH_ATTR
|
||||
cgiAppCfgSetParams(HttpdConnData *connData)
|
||||
{
|
||||
char buff[50];
|
||||
|
||||
char redir_url_buf[300];
|
||||
char *redir_url = redir_url_buf;
|
||||
redir_url += sprintf(redir_url, SET_REDIR_ERR);
|
||||
// we'll test if anything was printed by looking for \0 in failed_keys_buf
|
||||
|
||||
if (connData->conn == NULL) {
|
||||
//Connection aborted. Clean up.
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
// width and height must always go together so we can do max size validation
|
||||
if (GET_ARG("term_width")) {
|
||||
dbg("Default screen width: %s", buff);
|
||||
int w = atoi(buff);
|
||||
if (w > 1) {
|
||||
if (GET_ARG("term_height")) {
|
||||
dbg("Default screen height: %s", buff);
|
||||
int h = atoi(buff);
|
||||
if (h > 1) {
|
||||
if (w * h <= MAX_SCREEN_SIZE) {
|
||||
termconf->width = w;
|
||||
termconf->height = h;
|
||||
} else {
|
||||
warn("Bad dimensions: %d x %d (total %d)", w, h, w*h);
|
||||
redir_url += sprintf(redir_url, "term_width,term_height,");
|
||||
}
|
||||
} else {
|
||||
warn("Bad height: \"%s\"", buff);
|
||||
redir_url += sprintf(redir_url, "term_width,");
|
||||
}
|
||||
} else {
|
||||
warn("Missing height arg!");
|
||||
// this wont happen normally when the form is used
|
||||
redir_url += sprintf(redir_url, "term_width,term_height,");
|
||||
}
|
||||
} else {
|
||||
warn("Bad width: \"%s\"", buff);
|
||||
redir_url += sprintf(redir_url, "term_width,");
|
||||
}
|
||||
}
|
||||
|
||||
if (GET_ARG("default_bg")) {
|
||||
dbg("Screen default BG: %s", buff);
|
||||
int color = atoi(buff);
|
||||
if (color >= 0 && color < 16) {
|
||||
termconf->default_bg = (u8) color;
|
||||
} else {
|
||||
warn("Bad color %s", buff);
|
||||
redir_url += sprintf(redir_url, "default_bg,");
|
||||
}
|
||||
}
|
||||
|
||||
if (GET_ARG("default_fg")) {
|
||||
dbg("Screen default FG: %s", buff);
|
||||
int color = atoi(buff);
|
||||
if (color >= 0 && color < 16) {
|
||||
termconf->default_fg = (u8) color;
|
||||
} else {
|
||||
warn("Bad color %s", buff);
|
||||
redir_url += sprintf(redir_url, "default_fg,");
|
||||
}
|
||||
}
|
||||
|
||||
if (GET_ARG("term_title")) {
|
||||
dbg("Terminal title default text: \"%s\"", buff);
|
||||
strncpy_safe(termconf->title, buff, 64); // ATTN those must match the values in
|
||||
}
|
||||
|
||||
if (GET_ARG("btn1")) {
|
||||
dbg("Button1 default text: \"%s\"", buff);
|
||||
strncpy_safe(termconf->btn1, buff, 10);
|
||||
}
|
||||
|
||||
if (GET_ARG("btn2")) {
|
||||
dbg("Button1 default text: \"%s\"", buff);
|
||||
strncpy_safe(termconf->btn2, buff, 10);
|
||||
}
|
||||
|
||||
if (GET_ARG("btn3")) {
|
||||
dbg("Button1 default text: \"%s\"", buff);
|
||||
strncpy_safe(termconf->btn3, buff, 10);
|
||||
}
|
||||
|
||||
if (GET_ARG("btn4")) {
|
||||
dbg("Button1 default text: \"%s\"", buff);
|
||||
strncpy_safe(termconf->btn4, buff, 10);
|
||||
}
|
||||
|
||||
if (GET_ARG("btn5")) {
|
||||
dbg("Button1 default text: \"%s\"", buff);
|
||||
strncpy_safe(termconf->btn5, buff, 10);
|
||||
}
|
||||
|
||||
if (redir_url_buf[strlen(SET_REDIR_ERR)] == 0) {
|
||||
// All was OK
|
||||
info("Set app params - success, saving...");
|
||||
|
||||
terminal_apply_settings();
|
||||
persist_store();
|
||||
|
||||
httpdRedirect(connData, SET_REDIR_SUC);
|
||||
} else {
|
||||
warn("Some settings did not validate, asking for correction");
|
||||
// Some errors, appended to the URL as ?err=
|
||||
httpdRedirect(connData, redir_url_buf);
|
||||
}
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
|
||||
httpd_cgi_state ICACHE_FLASH_ATTR
|
||||
tplAppCfg(HttpdConnData *connData, char *token, void **arg)
|
||||
{
|
||||
#define BUFLEN 100
|
||||
char buff[BUFLEN];
|
||||
|
||||
if (token == NULL) {
|
||||
// We're done
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
strcpy(buff, ""); // fallback
|
||||
|
||||
if (streq(token, "term_width")) {
|
||||
sprintf(buff, "%d", termconf->width);
|
||||
}
|
||||
else if (streq(token, "term_height")) {
|
||||
sprintf(buff, "%d", termconf->height);
|
||||
}
|
||||
else if (streq(token, "default_bg")) {
|
||||
sprintf(buff, "%d", termconf->default_bg);
|
||||
}
|
||||
else if (streq(token, "default_fg")) {
|
||||
sprintf(buff, "%d", termconf->default_fg);
|
||||
}
|
||||
else if (streq(token, "term_title")) {
|
||||
strncpy_safe(buff, termconf->title, BUFLEN);
|
||||
}
|
||||
else if (streq(token, "btn1")) {
|
||||
strncpy_safe(buff, termconf->btn1, BUFLEN);
|
||||
}
|
||||
else if (streq(token, "btn2")) {
|
||||
strncpy_safe(buff, termconf->btn2, BUFLEN);
|
||||
}
|
||||
else if (streq(token, "btn3")) {
|
||||
strncpy_safe(buff, termconf->btn3, BUFLEN);
|
||||
}
|
||||
else if (streq(token, "btn4")) {
|
||||
strncpy_safe(buff, termconf->btn4, BUFLEN);
|
||||
}
|
||||
else if (streq(token, "btn5")) {
|
||||
strncpy_safe(buff, termconf->btn5, BUFLEN);
|
||||
}
|
||||
|
||||
httpdSend(connData, buff, -1);
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
#ifndef CGIAPPCFG_H
|
||||
#define CGIAPPCFG_H
|
||||
|
||||
#include "httpd.h"
|
||||
|
||||
httpd_cgi_state cgiAppCfgSetParams(HttpdConnData *connData);
|
||||
httpd_cgi_state tplAppCfg(HttpdConnData *connData, char *token, void **arg);
|
||||
|
||||
#endif
|
||||
+41
-40
@@ -1,10 +1,11 @@
|
||||
#include <esp8266.h>
|
||||
#include <httpd.h>
|
||||
#include <esp_sdk_ver.h>
|
||||
#include <httpdespfs.h>
|
||||
|
||||
#include "cgi_main.h"
|
||||
#include "screen.h"
|
||||
#include "user_main.h"
|
||||
#include "version.h"
|
||||
#include "helpers.h"
|
||||
|
||||
/**
|
||||
@@ -19,70 +20,70 @@ httpd_cgi_state ICACHE_FLASH_ATTR tplScreen(HttpdConnData *connData, char *token
|
||||
{
|
||||
if (token == NULL) {
|
||||
// Release data object
|
||||
screenSerializeToBuffer(NULL, 0, arg);
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
const int bufsiz = 512;
|
||||
char buff[bufsiz];
|
||||
char buff[150];
|
||||
|
||||
if (streq(token, "term_title")) {
|
||||
httpdSend(connData, termconf->title, -1);
|
||||
if (streq(token, "labels_seq")) {
|
||||
screenSerializeLabelsToBuffer(buff, 150);
|
||||
tplSend(connData, buff, -1);
|
||||
}
|
||||
else if (streq(token, "btn1")) {
|
||||
httpdSend(connData, termconf->btn1, -1);
|
||||
}
|
||||
else if (streq(token, "btn2")) {
|
||||
httpdSend(connData, termconf->btn2, -1);
|
||||
}
|
||||
else if (streq(token, "btn3")) {
|
||||
httpdSend(connData, termconf->btn3, -1);
|
||||
}
|
||||
else if (streq(token, "btn4")) {
|
||||
httpdSend(connData, termconf->btn4, -1);
|
||||
}
|
||||
else if (streq(token, "btn5")) {
|
||||
httpdSend(connData, termconf->btn5, -1);
|
||||
}
|
||||
else if (streq(token, "default_bg")) {
|
||||
sprintf(buff, "%d", termconf->default_bg);
|
||||
httpdSend(connData, buff, -1);
|
||||
}
|
||||
else if (streq(token, "default_fg")) {
|
||||
sprintf(buff, "%d", termconf->default_fg);
|
||||
httpdSend(connData, buff, -1);
|
||||
}
|
||||
else if (streq(token, "screenData")) {
|
||||
httpd_cgi_state cont = screenSerializeToBuffer(buff, bufsiz, arg);
|
||||
httpdSend(connData, buff, -1);
|
||||
return cont;
|
||||
else if (streq(token, "theme")) {
|
||||
sprintf(buff, "%d", termconf->theme);
|
||||
tplSend(connData, buff, -1);
|
||||
}
|
||||
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
httpd_cgi_state ICACHE_FLASH_ATTR
|
||||
cgiTermInitialImage(HttpdConnData *connData)
|
||||
{
|
||||
const int bufsiz = 512;
|
||||
char buff[bufsiz];
|
||||
|
||||
if (connData->conn == NULL) {
|
||||
//Connection aborted. Clean up.
|
||||
// Release data object
|
||||
screenSerializeToBuffer(NULL, 0, &connData->cgiData);
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
if (connData->cgiData == NULL) {
|
||||
httpdStartResponse(connData, 200);
|
||||
httpdHeader(connData, "Content-Type", "application/octet-stream");
|
||||
httpdEndHeaders(connData);
|
||||
}
|
||||
|
||||
httpd_cgi_state cont = screenSerializeToBuffer(buff, bufsiz, &connData->cgiData);
|
||||
httpdSend(connData, buff, -1); // no encode
|
||||
return cont;
|
||||
}
|
||||
|
||||
/** "About" page */
|
||||
httpd_cgi_state ICACHE_FLASH_ATTR tplAbout(HttpdConnData *connData, char *token, void **arg)
|
||||
httpd_cgi_state ICACHE_FLASH_ATTR
|
||||
tplAbout(HttpdConnData *connData, char *token, void **arg)
|
||||
{
|
||||
if (token == NULL) return HTTPD_CGI_DONE;
|
||||
|
||||
if (streq(token, "vers_fw")) {
|
||||
httpdSend(connData, FIRMWARE_VERSION, -1);
|
||||
tplSend(connData, FIRMWARE_VERSION, -1);
|
||||
}
|
||||
else if (streq(token, "date")) {
|
||||
httpdSend(connData, __DATE__, -1);
|
||||
tplSend(connData, __DATE__, -1);
|
||||
}
|
||||
else if (streq(token, "time")) {
|
||||
httpdSend(connData, __TIME__, -1);
|
||||
tplSend(connData, __TIME__, -1);
|
||||
}
|
||||
else if (streq(token, "vers_httpd")) {
|
||||
httpdSend(connData, HTTPDVER, -1);
|
||||
tplSend(connData, httpdGetVersion(), -1);
|
||||
}
|
||||
else if (streq(token, "vers_sdk")) {
|
||||
httpdSend(connData, STR(ESP_SDK_VERSION), -1);
|
||||
tplSend(connData, STR(ESP_SDK_VERSION), -1);
|
||||
}
|
||||
else if (streq(token, "githubrepo")) {
|
||||
httpdSend(connData, TERMINAL_GITHUB_REPO, -1);
|
||||
tplSend(connData, TERMINAL_GITHUB_REPO, -1);
|
||||
}
|
||||
|
||||
return HTTPD_CGI_DONE;
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
|
||||
httpd_cgi_state tplScreen(HttpdConnData *connData, char *token, void **arg);
|
||||
httpd_cgi_state tplAbout(HttpdConnData *connData, char *token, void **arg);
|
||||
httpd_cgi_state cgiTermInitialImage(HttpdConnData *connData);
|
||||
|
||||
#endif // CGI_MAIN_H
|
||||
|
||||
+5
-4
@@ -3,6 +3,7 @@ configuring the network settings
|
||||
*/
|
||||
|
||||
#include <esp8266.h>
|
||||
#include <httpdespfs.h>
|
||||
#include "cgi_network.h"
|
||||
#include "wifimgr.h"
|
||||
#include "persist.h"
|
||||
@@ -27,9 +28,9 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiNetworkSetParams(HttpdConnData *connData)
|
||||
{
|
||||
static ETSTimer timer;
|
||||
|
||||
char buff[50];
|
||||
char buff[20];
|
||||
char redir_url_buf[100];
|
||||
|
||||
char redir_url_buf[300];
|
||||
char *redir_url = redir_url_buf;
|
||||
redir_url += sprintf(redir_url, SET_REDIR_ERR);
|
||||
// we'll test if anything was printed by looking for \0 in failed_keys_buf
|
||||
@@ -200,7 +201,7 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiNetworkSetParams(HttpdConnData *connData)
|
||||
//Template code for the WLAN page.
|
||||
httpd_cgi_state ICACHE_FLASH_ATTR tplNetwork(HttpdConnData *connData, char *token, void **arg)
|
||||
{
|
||||
char buff[100];
|
||||
char buff[20];
|
||||
u8 mac[6];
|
||||
|
||||
if (token == NULL) {
|
||||
@@ -246,6 +247,6 @@ httpd_cgi_state ICACHE_FLASH_ATTR tplNetwork(HttpdConnData *connData, char *toke
|
||||
sprintf(buff, MACSTR, MAC2STR(mac));
|
||||
}
|
||||
|
||||
httpdSend(connData, buff, -1);
|
||||
tplSend(connData, buff, -1);
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ verify_admin_pw(const char *pw)
|
||||
httpd_cgi_state ICACHE_FLASH_ATTR
|
||||
cgiPersistWriteDefaults(HttpdConnData *connData)
|
||||
{
|
||||
char buff[50];
|
||||
char buff[PASSWORD_LEN];
|
||||
|
||||
if (connData->conn == NULL) {
|
||||
//Connection aborted. Clean up.
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
#include <esp8266.h>
|
||||
#include <httpd.h>
|
||||
|
||||
#include "cgi_ping.h"
|
||||
|
||||
httpd_cgi_state ICACHE_FLASH_ATTR cgiPing(HttpdConnData *connData)
|
||||
{
|
||||
if (connData->conn==NULL) {
|
||||
//Connection aborted. Clean up.
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
httpdStartResponse(connData, 200);
|
||||
httpdHeader(connData, "Content-Type", "text/plain");
|
||||
httpdEndHeaders(connData);
|
||||
|
||||
httpdSend(connData, "pong\n", -1);
|
||||
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user