Compare commits
30
Commits
0.7.1
..
1.1.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0992702b9 | ||
|
|
a20ec8a49e | ||
|
|
831ebe3b54 | ||
|
|
ea3c815a39 | ||
|
|
85f707401d | ||
|
|
0dbfbf707f | ||
|
|
a18b1d726e | ||
|
|
f8e3454250 | ||
|
|
184ef1ec36 | ||
|
|
99b07399b4 | ||
|
|
e46abe175f | ||
|
|
48cd9c1a2a | ||
|
|
07d1a3d3b4 | ||
|
|
44ea1e5b49 | ||
|
|
6fdb23531b | ||
|
|
116201e232 | ||
|
|
a78830f805 | ||
|
|
3f198f8360 | ||
|
|
f693373a34 | ||
|
|
0af62f47e6 | ||
|
|
a673843a63 | ||
|
|
023a2db01a | ||
|
|
979f956e7e | ||
|
|
5f6566a6fd | ||
|
|
6d98d2c6da | ||
|
|
9fa9cb0ca2 | ||
|
|
ba9c757cdc | ||
|
|
d2b2b89593 | ||
|
|
6aa4747d3e | ||
|
|
5eec7d2bf6 |
@@ -1,4 +1,5 @@
|
||||
html/
|
||||
html_demo/
|
||||
build/
|
||||
firmware/
|
||||
espfs/mkespfsimage/*.o
|
||||
|
||||
+1
-1
@@ -141,7 +141,7 @@ set(SOURCE_FILES
|
||||
user/apars_osc.c
|
||||
user/apars_osc.h
|
||||
user/apars_dcs.c
|
||||
user/apars_dcs.h user/uart_buffer.c user/uart_buffer.h user/jstring.c user/jstring.h)
|
||||
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)
|
||||
|
||||
+373
@@ -0,0 +1,373 @@
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
@@ -1,92 +1,120 @@
|
||||
# ESPTerm
|
||||
|
||||
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).
|
||||
**ESPTerm is a VT100-like terminal emulator running on the ESP8266 WiFi chip.**
|
||||
|
||||
ESPTerm can add remote access via WiFi to any embeded project, all you need is a serial port.
|
||||
![Photo][photo-hw]<br>
|
||||
*Fig 1: Breadboard adapter developed for ESPTerm*
|
||||
|
||||
## Screenshots, photos
|
||||
Version 1.0.0 **passes most of VTTEST test cases** (from the main menu and some Xterm specific), making it
|
||||
functionally comparable to eg. gnome-terminal, terminator, konsole, GtkTerm or PuTTY.
|
||||
ESPTerm is **capable of running Midnight Commander** through agetty, **including full
|
||||
mouse support**, provided agetty is made to believe it's Xterm, which shows ESPTerm is sufficiently well
|
||||
implemented to work with ncurses.
|
||||
|
||||
- Look at the [GitHub releases page][releases], there are some pics.
|
||||
To see what escape sequences are supported, check out this [annotated Xterm manual page][xterm-compare]
|
||||
which was used for reference, or the built-in help page ([online demo][demo-help])
|
||||
|
||||
## ESPTerm features
|
||||
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 an ESP-12
|
||||
on a LoLin NodeMCU board from eBay for development).
|
||||
|
||||
- **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:**
|
||||
With ESPTerm, you can add remote access via WiFi to any embeded project, all you need is
|
||||
a serial port and some imagination!
|
||||
|
||||
## Try it online
|
||||
|
||||
You can try the web user interface here: [espterm.github.io][demo-term]
|
||||
|
||||
The demo is almost identical to the real thing, except, of course, it doesn't do much without the
|
||||
emulator backend that runs on the ESP8266. The web version will be updated to match this repository
|
||||
after each minor release.
|
||||
|
||||
## Main features
|
||||
|
||||
- **Almost complete VT102 emulation** with some extras from Xterm, eg.
|
||||
- Screen size up to 80x25
|
||||
- All standard text styles and 16 colors supported
|
||||
- Full UTF-8 support, alternate character sets
|
||||
- Standard mouse tracking modes
|
||||
- You can dynamically set screen title, button labels...
|
||||
- **Web Interface**
|
||||
- Real-time screen update via WebSocket
|
||||
- Button to open Android software keyboard
|
||||
- Button to open keyboard on Android
|
||||
- 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
|
||||
|
||||
\*) Linux console applications run via agetty at ttyUSB0 were used for testing, obviously you'll be better off using SSH for remote shell
|
||||
|
||||
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.
|
||||
- Text file upload tool with configurable delays and line endings
|
||||
- *Built-in help page* ([demo][demo-help]) with basic troubleshooting and command reference
|
||||
- **Robust WiFi configuration interface** (Demo: [WiFi][demo-wifi], [network][demo-network] config)
|
||||
- Static IP, DHCP, channel selection, power
|
||||
- SSID search utility for finding your existing network
|
||||
|
||||
## Running ESPTerm
|
||||
|
||||
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).
|
||||
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][esptool].
|
||||
|
||||
### Pins
|
||||
|
||||
- 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.
|
||||
|
||||
### 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
|
||||
- Pins Rx and Tx are used for the main communication UART, parameters of which can be set on the system config page.
|
||||
Connect your USB-serial dongle or application microcontroller here.
|
||||
|
||||
### 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.
|
||||
- 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 as ASCII hex.
|
||||
- Connect to the AP via a smartphone or laptop and configure WiFi as desired. If a captive portal page does not open,
|
||||
try 192.168.4.1 in your web browser.
|
||||
|
||||
### Rescue from messed up config
|
||||
|
||||
It can happen that some changes to the WiFi or network config make the module inaccessible.
|
||||
|
||||
- To re-enable the built-in AP, hold the BOOT (GPIO0 -> GND) button for about 1 s, until the blue LED starts flashing.
|
||||
Then quickly release the button.
|
||||
- To reset all settings to defaults, hold the button a couple seconds until the LED flashes rapidly, then release it.
|
||||
- You can cancel this wipe/reset operation (when triggered by accident) by pressing Reset or disconnecting the power supply.
|
||||
|
||||
### 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.
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
## Research resources
|
||||
|
||||
Developing ESPTerm wasn't an easy task, because the information is scattered across many places and the existing
|
||||
terminal emulators I originally used for reference (terminator, Konsole) are not implemented correctly in some details.
|
||||
|
||||
A great tool for checking my implementation has proven to be [VTTTEST][vttest] and Xterm as a reference
|
||||
implementation that is probably the most complete emulator available, although it's cumbersome to use and its age
|
||||
really shows in the looks.
|
||||
|
||||
I've comnpiled a list of those I found most helpful here: [VT100 emulation resources][resources]
|
||||
|
||||
## Development
|
||||
|
||||
ESPTerm's firmware is written in C and is based on SpriteTM's `libesphttpd` http server library forked to
|
||||
[MightyPork/libesphttpd][httpdlib]. This fork includes various improvements
|
||||
and changes required by the project.
|
||||
|
||||
### 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
|
||||
- Install [esp-open-sdk][opensdk] and build it with
|
||||
`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)
|
||||
- Install [esptool][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"
|
||||
@@ -123,3 +151,16 @@ build scripts manually, too.
|
||||
To flash, just run `make flash`.
|
||||
|
||||
[releases]: https://github.com/MightyPork/esp-vt100-firmware/releases
|
||||
[httpdlib]: https://github.com/MightyPork/libesphttpd
|
||||
[esptool]: https://github.com/espressif/esptool
|
||||
[opensdk]: https://github.com/pfalcon/esp-open-sdk/
|
||||
|
||||
[demo-help]: https://espterm.github.io/help.html
|
||||
[demo-wifi]: https://espterm.github.io/cfg_wifi.html
|
||||
[demo-network]: https://espterm.github.io/cfg_network.html
|
||||
[demo-term]: https://espterm.github.io/term.html
|
||||
|
||||
[xterm-compare]: https://espterm.github.io/docs/espterm-xterm.html
|
||||
[photo-hw]: https://espterm.github.io/docs/espterm-hw-small.jpg
|
||||
[resources]: https://espterm.github.io/docs/index.html
|
||||
[vttest]: http://invisible-island.net/vttest/
|
||||
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "-- Preparing WWW files --"
|
||||
|
||||
[[ -e html_demo ]] && rm -r html_demo
|
||||
mkdir -p html_demo/img
|
||||
mkdir -p html_demo/js
|
||||
mkdir -p html_demo/css
|
||||
|
||||
cd html_orig
|
||||
sh ./packjs.sh
|
||||
ESP_DEMO=1 php ./build_html.php
|
||||
cd ..
|
||||
|
||||
cp html_orig/js/app.js html_demo/js/
|
||||
|
||||
sass html_orig/sass/app.scss html_demo/css/app.css
|
||||
rm html_demo/css/app.css.map
|
||||
|
||||
cp html_orig/img/* html_demo/img/
|
||||
cp html_orig/favicon.ico html_demo/favicon.ico
|
||||
|
||||
# cleanup
|
||||
find html_demo/ -name "*.orig" -delete
|
||||
find html_demo/ -name "*.xcf" -delete
|
||||
find html_demo/ -name "*~" -delete
|
||||
@@ -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
|
||||
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
|
||||
./build_demo.sh
|
||||
|
||||
cp -r html_demo/* ../espterm.github.io/
|
||||
|
||||
cd ../espterm.github.io/
|
||||
|
||||
echo "Enter to deploy (^C to abort):"
|
||||
read
|
||||
|
||||
git add --all
|
||||
git commit -m "Deploy updates"
|
||||
git push
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,42 +5,56 @@
|
||||
* without esphttpd. This is needed mainly for places where the replacements
|
||||
* are given to JavaScript, to avoid syntax errors with
|
||||
*/
|
||||
|
||||
$vers = '???';
|
||||
$f = file_get_contents(__DIR__ . '/../user/version.h');
|
||||
preg_match_all('/#define FW_V_.*? (\d+)/', $f, $vm);
|
||||
#define FW_V_MAJOR 1
|
||||
#define FW_V_MINOR 0
|
||||
#define FW_V_PATCH 0
|
||||
|
||||
$vers = $vm[1][0].'.'.$vm[1][1].'.'.$vm[1][2];
|
||||
|
||||
return [
|
||||
'term_title' => 'ESPTerm local debug',
|
||||
'term_title' => ESP_DEMO ? 'ESPTerm Web UI Demo' : 'ESPTerm local debug',
|
||||
|
||||
'btn1' => '1',
|
||||
'btn2' => '2',
|
||||
'btn3' => '3',
|
||||
'btn1' => 'OK',
|
||||
'btn2' => 'Cancel',
|
||||
'btn3' => '',
|
||||
'btn4' => '',
|
||||
'btn5' => '5',
|
||||
'labels_seq' => 'TESPTerm local debug1235',
|
||||
|
||||
'screenData' => 'HELLOxNRE3',//'\u000b\u0001\u001b\u0001\u0001\u0001\u0001\u0001\f\u0005\u0001\u0010\u0003HELLOx\u0002\u000b\u0001\u0001N\u0001RE\u00023\u0001', //,
|
||||
'btn5' => 'Help',
|
||||
'bm1' => '01,'.ord('y'),
|
||||
'bm2' => '01,'.ord('n'),
|
||||
'bm3' => '',
|
||||
'bm4' => '',
|
||||
'bm5' => '05',
|
||||
'labels_seq' => ESP_DEMO ? 'TESPTerm Web UI DemoOKCancelHelp' : 'TESPTerm local debugOKCancelHelp',
|
||||
|
||||
'parser_tout_ms' => 10,
|
||||
'display_tout_ms' => 20,
|
||||
'display_tout_ms' => 15,
|
||||
'display_cooldown_ms' => 35,
|
||||
'fn_alt_mode' => '1',
|
||||
|
||||
'opmode' => '2',
|
||||
'sta_enable' => '0',
|
||||
'sta_enable' => '1',
|
||||
'ap_enable' => '1',
|
||||
|
||||
'tpw' => '60',
|
||||
'ap_channel' => '7',
|
||||
'ap_ssid' => 'ESP-123456',
|
||||
'ap_password' => 'Passw0rd!',
|
||||
'ap_ssid' => 'TERM-027451',
|
||||
'ap_password' => '',
|
||||
'ap_hidden' => '0',
|
||||
|
||||
'sta_ssid' => 'Chlivek',
|
||||
'sta_password' => 'windows XP is The Best',
|
||||
'sta_active_ip' => '1.2.3.4',
|
||||
'sta_active_ssid' => 'Chlivek',
|
||||
'sta_ssid' => 'Cisco',
|
||||
'sta_password' => 'Passw0rd!',
|
||||
'sta_active_ip' => ESP_DEMO ? '192.168.82.66' : '192.168.0.19',
|
||||
'sta_active_ssid' => 'Cisco',
|
||||
|
||||
'vers_fw' => '1.2.3',
|
||||
'vers_fw' => $vers,
|
||||
'date' => date('Y-m-d'),
|
||||
'time' => date('G:i'),
|
||||
'vers_httpd' => '4.5.6',
|
||||
'vers_sdk' => '1.52',
|
||||
'vers_httpd' => '0.4',
|
||||
'vers_sdk' => '010502',
|
||||
'githubrepo' => 'https://github.com/MightyPork/esp-vt100-firmware',
|
||||
|
||||
'ap_dhcp_time' => '120',
|
||||
@@ -54,17 +68,19 @@ return [
|
||||
'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',
|
||||
'sta_mac' => '5c:cf:7f:02:74:51',
|
||||
'ap_mac' => '5e:cf:7f:02:74:51',
|
||||
|
||||
'term_width' => '26',
|
||||
'term_height' => '10',
|
||||
'term_width' => '80',
|
||||
'term_height' => '25',
|
||||
'default_bg' => '0',
|
||||
'default_fg' => '7',
|
||||
'show_buttons' => '1',
|
||||
'show_config_links' => '1',
|
||||
|
||||
'uart_baud' => 115200,
|
||||
'uart_stopbits' => 1,
|
||||
'uart_parity' => 2,
|
||||
|
||||
'theme' => 5,
|
||||
'theme' => 0,
|
||||
];
|
||||
|
||||
@@ -41,6 +41,10 @@ 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');
|
||||
|
||||
pg('index', 'api', '', '/', '');
|
||||
|
||||
// ajax API
|
||||
|
||||
return $pages;
|
||||
|
||||
@@ -23,6 +23,30 @@ define('DEBUG', !$prod);
|
||||
$root = DEBUG ? json_encode(ESP_IP) : 'location.host';
|
||||
define('JS_WEB_ROOT', $root);
|
||||
|
||||
define('ESP_DEMO', (bool)getenv('ESP_DEMO'));
|
||||
if (ESP_DEMO) {
|
||||
define('DEMO_SCREEN', '"S\u0019\u0001Q\u0001\u0017\u0001K\u0001\u0015\u0004\u0003\b\u0001 \u0002P\u0001┌ESPTerm─Demo─\u0002\u0002\u0001\u0003\u0002\u000131\u0003\u0003\u000132\u0003\u0004\u00013\u0002\u0002\u0001\u0003\u0005\u000134\u0003\u0006\u000135\u0003\u0007\u000136\u0003\b\u000137\u0003\t\u000190\u0003\n\u000191\u0003\u000b\u000192\u0003\f\u000193\u0003\r\u000194\u0003\u000e\u000195\u0003\u000f\u000196\u0003\u0010\u000197\u0003\b\u0001─\u0002\r\u0001┐ \u0002\u0015\u0001│ \u00029\u0001│ \u0002\u0004\u0001│\u0002\t\u0001 \u0002\b\u0001│\u0004\u0002\u0001Bold \u0004\u0003\u0001F\u0004\u0003\u0001a\u0004\u0003\u0001i\u0004\u0003\u0001n\u0004\u0003\u0001t\u0004\u0003\u0001 \u0004\u0005\u0001I\u0004\u0005\u0001t\u0004\u0005\u0001a\u0004\u0005\u0001l\u0004\u0005\u0001i\u0004\u0005\u0001c\u0004\u0005\u0001 \u0004\t\u0001U\u0004\t\u0001n\u0004\t\u0001d\u0004\t\u0001e\u0004\t\u0001r\u0004\t\u0001l\u0004\t\u0001i\u0004\t\u0001n\u0004\t\u0001e\u0004\u0001\u0001 \u0004\u0011\u0001B\u0004\u0011\u0001l\u0004\u0011\u0001i\u0004\u0011\u0001n\u0004\u0011\u0001k\u0004\u0011\u0001 \u0001q\u0001\u0001Inverse\u0003\b\u0001 \u0004A\u0001S\u0004A\u0001t\u0004A\u0001r\u0004A\u0001i\u0004A\u0001k\u0004A\u0001e\u0004\u0001\u0001 \u0004!\u0001F\u0004!\u0001r\u0004!\u0001a\u0004!\u0001k\u0004!\u0001t\u0004!\u0001u\u0004!\u0001r\u0004\u0001\u0001 │ \u0002\u0002\u0001─\u0002\u0002\u0001\u0003\n\u0002 \u0002\t\u0001\u0003\b\u0001─\u0002\u0002\u0001 \u0002\u0006\u0001│ \u00029\u0001│ \u0002\u0002\u0001─\u0002\u0002\u0001\u0003\n\u0002 \u0003\u0002\u0002ESP826\u0002\u0002\u0001\u0003\n\u0002 \u0003\b\u0001─\u0002\u0002\u0001 \u0002\u0006\u0001└─\u00029\u0001┤ \u0002\u0002\u0001─\u0002\u0002\u0001\u0003\n\u0002 \u0002\t\u0001\u0003\b\u0001─\u0002\u0002\u0001 \u0002@\u0001│ \u0002\u0002\u0001─\u0002\u0002\u0001\u0003\n\u0002 \u0003\u0002\u0002(@)#\u0002\u0004\u0001\u0003\n\u0002 \u0003\b\u0001─\u0002\u0002\u0001 \u0002\u0007\u0001\u0003O\u0001 This is a static demo of the ESPTerm Web Interface \u0002\u0004\u0001\u0003\b\u0001 \u0002\u0002\u0001│ \u0002\u0002\u0001─\u0002\u0002\u0001\u0003\n\u0002 \u0002\t\u0001\u0003\b\u0001─\u0002\u0002\u0001 \u0002\u0007\u0001\u0003O\u0001 \u00027\u0001\u0003\b\u0001 \u0002\u0002\u0001│ \u0002\u0004\u0001│\u0002\t\u0001 \u0002\t\u0001\u0003O\u0001 Try the links beneath this scre\u0002\u0002\u0001n to browse the menu. \u0003\b\u0001 \u0002\u0002\u0001♦ \u0002\u0016\u0001\u0003O\u0001 \u00027\u0001\u0003\b\u0001 \u0002\u0019\u0001\u0003O\u0001 <°)\u0002\u0003\u0001>< ESPTerm ful\u0002\u0002\u0001y sup\u0002\u0002\u0001orts UTF-8 お は よ ー \u0002\u0002\u0001><(\u0002\u0003\u0001°> \u0003\b\u0001 \u0002\u0019\u0001\u0003O\u0001 \u00027\u0001\u0003\b\u0001 \u0002i\u0001\u0003\u000b\u0001Other interesting features:\u0003\b\u0001 \u0002\u0018\u0001↓ \u0002n\u0001\u0003\u0003\u0001- Almost ful\u0002\u0002\u0001 VT10\u0002\u0002\u0001 emulation \u0003\b\u0001 \u0003\u0006\u0001()\u0003\b\u0001 \u0003\u0006\u0001()\u0003\b\u0001 \u0002\b\u0001Funguje tu čeština! \u0002\u0011\u0001\u0003\u0005\u0001- Xterm-like mouse tracking\u0003\b\u0001 \u0002\u0003\u0001=\u0002\u0002\u0001\u0003\t\u0002°.°\u0003\b\u0001=\u0002\u0002\u0001 \u0003\u0006\u0001<-\u0002\u0003\u0001, \u0003\b\u0001 \u0002$\u0001\u0003\u0004\u0001- File upload utility\u0003\b\u0001 \u0002\n\u0001\'\u0002\u0002\u0001 \'\u0002\u0002\u0001 \u0002\u0002\u0001\u0003\u0006\u0001 \u0002\u0004\u0001mouse\u0003\b\u0001 \u0002!\u0001\u0003\u0002\u0001- User-friendly config interface\u0003\b\u0001 \u00020\u0001\u0003\u000e\u0001-\u0003\u0002\u0001 \u0003\u000e\u0001Advanced WiFi & network set\u0002\u0002\u0001ings\u0003\b\u0001 \u0002\u0011\u0001\u0003\f\u0001Try ESPTerm today!\u0003\b\u0001 \u0002\u000b\u0001- Built-in help page \u0002\u001a\u0001\u0003\u0007\u0001-\u0002\u0002\u0001>\u0003\b\u0001 \u0002\u0002\u0001\u0003\f\u0001Pre-built binaries\u0003\b\u0001 \u0003\f\u0001are\u0003\b\u0001 \u0002\"\u0001\u0003\u0007\u0001link on the About page \u0002\u0002\u0001\u0003\f\u0001available on GitHub! \u0003\b\u0001 \u0002U\u0001"');
|
||||
define('DEMO_APS', <<<APS
|
||||
{
|
||||
"result": {
|
||||
"inProgress": 0,
|
||||
"APs": [
|
||||
{"essid": "Cisco", "bssid": "88:f7:c7:52:b3:99", "rssi": 205, "rssi_perc": 100, "enc": 4, "channel": 7},
|
||||
{"essid": "UPC Wi-Free", "bssid": "8a:f7:c7:52:b3:9b", "rssi": 203, "rssi_perc": 100, "enc": 5, "channel": 1},
|
||||
{"essid": "UPC Wi-Free", "bssid": "0a:95:2a:0c:84:31", "rssi": 166, "rssi_perc": 32, "enc": 5, "channel": 1},
|
||||
{"essid": "MujO2Internet_2EEB96", "bssid": "d0:60:8c:2e:eb:96", "rssi": 174, "rssi_perc": 48, "enc": 4, "channel": 4},
|
||||
{"essid": "Internet", "bssid": "38:72:c0:32:bd:0d", "rssi": 164, "rssi_perc": 28, "enc": 2, "channel": 10},
|
||||
{"essid": "MyO2Internet_08C850", "bssid": "78:c1:a7:08:c8:50", "rssi": 186, "rssi_perc": 72, "enc": 4, "channel": 11},
|
||||
{"essid": "UPC Wi-Free", "bssid": "06:7c:34:9a:6f:7c", "rssi": 167, "rssi_perc": 34, "enc": 0, "channel": 11},
|
||||
{"essid": "Internet_B0", "bssid": "5c:f4:ab:11:3b:b3", "rssi": 175, "rssi_perc": 50, "enc": 3, "channel": 13},
|
||||
{"essid": "UPC5716805", "bssid": "08:95:2a:0c:84:3f", "rssi": 165, "rssi_perc": 30, "enc": 4, "channel": 1}
|
||||
]
|
||||
}
|
||||
}
|
||||
APS
|
||||
);
|
||||
}
|
||||
|
||||
define('LOCALE', isset($_GET['locale']) ? $_GET['locale'] : 'en');
|
||||
|
||||
$_messages = require(__DIR__ . '/lang/' . LOCALE . '.php');
|
||||
@@ -37,6 +61,7 @@ function url($name, $relative = false)
|
||||
if ($relative) return $_pages[$name]->path;
|
||||
|
||||
if (DEBUG) return "/index.php?page=$name";
|
||||
if (ESP_DEMO) return "$name.html";
|
||||
else return $_pages[$name]->path;
|
||||
}
|
||||
|
||||
@@ -52,6 +77,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 +100,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,21 @@ function process_html($s) {
|
||||
|
||||
$no_tpl_files = ['help', 'cfg_wifi_conn'];
|
||||
|
||||
$dest = ESP_DEMO ? __DIR__ . '/../html_demo/' : __DIR__ . '/../html/';
|
||||
|
||||
ob_start();
|
||||
foreach($_pages as $_k => $p) {
|
||||
if ($p->bodyclass == 'api') continue;
|
||||
if ($p->bodyclass == 'api') {
|
||||
if (ESP_DEMO) {
|
||||
$target = 'term.html';
|
||||
echo "Generating: ~$_k.html -> $target\n";
|
||||
$s = "<!DOCTYPE HTML><meta http-equiv=\"refresh\" content=\"0;url=$target\">";
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
echo "Generating: $_k ($p->title)\n";
|
||||
|
||||
$_GET['page'] = $_k;
|
||||
ob_flush(); // print the message
|
||||
ob_clean(); // clean up
|
||||
@@ -29,10 +40,10 @@ foreach($_pages as $_k => $p) {
|
||||
// remove newlines and comments
|
||||
// as tests have shown, it saves just a couple kilobytes,
|
||||
// making it not a very big improvement at the expense of ugly html.
|
||||
// $s = process_html($s);
|
||||
|
||||
ob_clean(); // clean up
|
||||
$of = __DIR__ . '/../html/' . $_k . (in_array($_k, $no_tpl_files) ? '.html' : '.tpl');
|
||||
// $s = process_html($s);
|
||||
ob_clean();
|
||||
} // clean up
|
||||
$of = $dest . $_k . ((in_array($_k, $no_tpl_files)||ESP_DEMO) ? '.html' : '.tpl');
|
||||
file_put_contents($of, $s); // write to a file
|
||||
}
|
||||
|
||||
|
||||
+170
-39
File diff suppressed because one or more lines are too long
@@ -8,6 +8,7 @@ $selected = [
|
||||
'wifi.connected_ip_is',
|
||||
'wifi.not_conn',
|
||||
'wifi.enter_passwd',
|
||||
'wifi.passwd_saved',
|
||||
];
|
||||
|
||||
$out = [];
|
||||
|
||||
Binary file not shown.
@@ -39,9 +39,12 @@ echo -e "@charset \"UTF-8\";\n\n/* Fontello data, processed by the unpack script
|
||||
#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.*?truetype'\);\n\}" "$OUTPUT_DIR/fontello-embedded.css" \
|
||||
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" \
|
||||
|
||||
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
+25
-28
@@ -7,47 +7,44 @@ if (!isset($_GET['page'])) $_GET['page'] = 'term';
|
||||
$_GET['PAGE_TITLE'] = $_pages[$_GET['page']]->title . ' :: ' . APP_NAME;
|
||||
$_GET['BODYCLASS'] = $_pages[$_GET['page']]->bodyclass;
|
||||
|
||||
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);
|
||||
}
|
||||
$_pf = __DIR__ . '/pages/'.$_GET['page'].'.php';
|
||||
|
||||
if (!file_exists($_pf)) {
|
||||
header("Location: /", true, 302);
|
||||
die();
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (DEBUG)
|
||||
// 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 || ESP_DEMO)
|
||||
$str = tplSubs($f, require(__DIR__ . '/_debug_replacements.php'));
|
||||
else $str = $f;
|
||||
|
||||
// special symbols
|
||||
$str = str_replace('\,', ' ', $str);
|
||||
$str = preg_replace('/(?<=\w)~(?=\w)/', ' ', $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";
|
||||
}
|
||||
|
||||
require __DIR__ . '/pages/_tail.php';
|
||||
|
||||
+664
-412
File diff suppressed because it is too large
Load Diff
@@ -25,7 +25,7 @@ $.ready(function () {
|
||||
$(h).on('click', hdl).on('keypress', cr(hdl));
|
||||
});
|
||||
|
||||
qsa('form').forEach(function(x) {
|
||||
$('form').forEach(function(x) {
|
||||
$(x).on('keypress', function(e) {
|
||||
if ((e.keyCode == 10 || e.keyCode == 13) && e.ctrlKey) {
|
||||
x.submit();
|
||||
@@ -96,7 +96,7 @@ $.ready(function () {
|
||||
|
||||
// remove tabindixes from h2 if wide
|
||||
if (window.innerWidth > 550) {
|
||||
qsa('.Box h2').forEach(function (x) {
|
||||
$('.Box h2').forEach(function (x) {
|
||||
x.removeAttribute('tabindex');
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
+2
-711
@@ -1,715 +1,6 @@
|
||||
/** Decode two-byte number */
|
||||
function parse2B(s, i) {
|
||||
return (s.charCodeAt(i++) - 1) + (s.charCodeAt(i) - 1) * 127;
|
||||
}
|
||||
|
||||
/** Decode three-byte number */
|
||||
function parse3B(s, i) {
|
||||
return (s.charCodeAt(i) - 1) + (s.charCodeAt(i+1) - 1) * 127 + (s.charCodeAt(i+2) - 1) * 127 * 127;
|
||||
}
|
||||
|
||||
function Chr(n) {
|
||||
return String.fromCharCode(n);
|
||||
}
|
||||
|
||||
function encode2B(n) {
|
||||
var lsb, msb;
|
||||
lsb = (n % 127);
|
||||
n = ((n - lsb) / 127);
|
||||
lsb += 1;
|
||||
msb = (n + 1);
|
||||
return Chr(lsb) + Chr(msb);
|
||||
}
|
||||
|
||||
function encode3B(n) {
|
||||
var lsb, msb, xsb;
|
||||
lsb = (n % 127);
|
||||
n = (n - lsb) / 127;
|
||||
lsb += 1;
|
||||
msb = (n % 127);
|
||||
n = (n - msb) / 127;
|
||||
msb += 1;
|
||||
xsb = (n + 1);
|
||||
return Chr(lsb) + Chr(msb) + Chr(xsb);
|
||||
}
|
||||
|
||||
var Screen = (function () {
|
||||
var W = 0, H = 0; // dimensions
|
||||
var inited = false;
|
||||
|
||||
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,
|
||||
hidden: false, // do not show
|
||||
hanging: false, // xenl
|
||||
};
|
||||
|
||||
var screen = [];
|
||||
var blinkIval;
|
||||
var cursorFlashStartIval;
|
||||
|
||||
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("Browser does not support AudioContext, can't beep.", 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());
|
||||
}
|
||||
|
||||
/** 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 elem = cell.e, fg, bg, cn, t;
|
||||
// Colors
|
||||
fg = inv ? cell.bg : cell.fg;
|
||||
bg = inv ? cell.fg : cell.bg;
|
||||
// Update
|
||||
elem.textContent = t = (cell.t + ' ')[0];
|
||||
|
||||
cn = 'fg' + fg + ' bg' + bg;
|
||||
if (cell.attrs & (1<<0)) cn += ' bold';
|
||||
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';
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
elem.textContent = t;
|
||||
}
|
||||
if (cell.attrs & (1<<6)) cn += ' strike';
|
||||
|
||||
if (cell.attrs & (1<<1)) {
|
||||
cn += ' faint';
|
||||
// faint requires special html - otherwise it would also dim the background.
|
||||
// we use opacity on the text...
|
||||
elem.innerHTML = '<span>' + e(elem.textContent) + '</span>';
|
||||
}
|
||||
|
||||
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 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('mouseenter', function (evt) {
|
||||
Input.onMouseMove(x, y);
|
||||
});
|
||||
e.addEventListener('mousedown', function (evt) {
|
||||
Input.onMouseDown(x, y, evt.button+1);
|
||||
});
|
||||
e.addEventListener('mouseup', function (evt) {
|
||||
Input.onMouseUp(x, y, evt.button+1);
|
||||
});
|
||||
e.addEventListener('contextmenu', function (evt) {
|
||||
evt.preventDefault();
|
||||
});
|
||||
e.addEventListener('mousewheel', function (evt) {
|
||||
Input.onMouseWheel(x, y, evt.deltaY>0?1:-1);
|
||||
return false;
|
||||
});
|
||||
})();
|
||||
|
||||
/* End of line */
|
||||
if ((i > 0) && (i % W == 0)) {
|
||||
scr.appendChild(mk('br'));
|
||||
}
|
||||
/* The cell */
|
||||
scr.appendChild(e);
|
||||
|
||||
cell = {
|
||||
t: ' ',
|
||||
fg: 7,
|
||||
bg: 0, // the colors will be replaced immediately as we receive data (user won't see this)
|
||||
attrs: 0,
|
||||
e: e,
|
||||
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;
|
||||
// TODO try to invent a new way to indicate "hanging" - this is copied from gtkterm
|
||||
if (cursor.hidden || cursor.hanging) {
|
||||
cursor.a = false;
|
||||
}
|
||||
|
||||
if (!cursor.suppress) {
|
||||
_draw(_curCell(), cursor.forceOn || cursor.a);
|
||||
}
|
||||
}, 500);
|
||||
|
||||
// blink attribute
|
||||
setInterval(function () {
|
||||
$('#screen').removeClass('blink-hide');
|
||||
setTimeout(function () {
|
||||
$('#screen').addClass('blink-hide');
|
||||
}, 800); // 200 ms ON
|
||||
}, 1000);
|
||||
|
||||
inited = true;
|
||||
}
|
||||
|
||||
var SEQ_SET_COLOR_ATTR = 1;
|
||||
var SEQ_REPEAT = 2;
|
||||
var SEQ_SET_COLOR = 3;
|
||||
var SEQ_SET_ATTR = 4;
|
||||
|
||||
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 bold hidden
|
||||
cursor.hidden = !(num & 0x0001);
|
||||
cursor.hanging = !!(num & 0x0002);
|
||||
// console.log("Attributes word ",num.toString(16)+'h');
|
||||
|
||||
Input.setAlts(
|
||||
!!(num & 0x0004), // cu
|
||||
!!(num & 0x0008), // np
|
||||
!!(num & 0x0010) // fn
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
function _load_labels(str) {
|
||||
var pieces = str.split('\x01');
|
||||
qs('h1').textContent = pieces[0];
|
||||
qsa('#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;
|
||||
});
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
load: load, // full load (string)
|
||||
};
|
||||
})();
|
||||
|
||||
/** Handle connections */
|
||||
var Conn = (function() {
|
||||
var ws;
|
||||
var heartbeatTout;
|
||||
var pingIv;
|
||||
|
||||
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
|
||||
if (evt.data != '.') {
|
||||
//console.log("RX: ", evt.data);
|
||||
// Assume all our messages are screen updates
|
||||
Screen.load(evt.data);
|
||||
}
|
||||
heartbeat();
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
function doSend(message) {
|
||||
console.log("TX: ", message);
|
||||
|
||||
if (!ws) return; // for dry testing
|
||||
if (ws.readyState != 1) {
|
||||
console.error("Socket not ready");
|
||||
return;
|
||||
}
|
||||
if (typeof message != "string") {
|
||||
message = JSON.stringify(message);
|
||||
}
|
||||
ws.send(message);
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
* 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,
|
||||
};
|
||||
|
||||
function sendStrMsg(str) {
|
||||
Conn.send("s"+str);
|
||||
}
|
||||
|
||||
function sendBtnMsg(n) {
|
||||
Conn.send("b"+Chr(n));
|
||||
}
|
||||
|
||||
function fa(alt, normal) {
|
||||
return opts.fn_alt ? alt : normal;
|
||||
}
|
||||
|
||||
function ca(alt, normal) {
|
||||
return opts.cu_alt ? alt : normal;
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
e.preventDefault();
|
||||
sendStrMsg(str)
|
||||
});
|
||||
}
|
||||
|
||||
function _initKeys() {
|
||||
// This takes care of text characters typed
|
||||
window.addEventListener('keypress', function(evt) {
|
||||
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();
|
||||
}
|
||||
|
||||
var mb1 = 0;
|
||||
var mb2 = 0;
|
||||
var mb3 = 0;
|
||||
|
||||
function init() {
|
||||
_initKeys();
|
||||
|
||||
// Button presses
|
||||
qsa('#buttons button').forEach(function(s) {
|
||||
s.addEventListener('click', function() {
|
||||
sendBtnMsg(+this.dataset['n']);
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener('mousedown', function(evt) {
|
||||
if (evt.button == 0) mb1 = 1;
|
||||
if (evt.button == 1) mb2 = 1;
|
||||
if (evt.button == 2) mb3 = 1;
|
||||
});
|
||||
|
||||
window.addEventListener('mouseup', function(evt) {
|
||||
if (evt.button == 0) mb1 = 0;
|
||||
if (evt.button == 1) mb2 = 0;
|
||||
if (evt.button == 2) mb3 = 0;
|
||||
});
|
||||
}
|
||||
|
||||
function packModifiersForMouse() {
|
||||
return (key.isModifier('ctrl')?1:0) |
|
||||
(key.isModifier('shift')?2:0) |
|
||||
(key.isModifier('alt')?4:0) |
|
||||
(key.isModifier('meta')?8:0);
|
||||
}
|
||||
|
||||
return {
|
||||
init: init,
|
||||
// onTap: sendPosMsg,
|
||||
sendString: sendStrMsg,
|
||||
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();
|
||||
}
|
||||
},
|
||||
onMouseMove: function (x, y) {
|
||||
var b = mb1 ? 1 : mb2 ? 2 : mb3 ? 3 : 0;
|
||||
var m = packModifiersForMouse();
|
||||
Conn.send("m" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m));
|
||||
},
|
||||
onMouseDown: function (x, y, b) {
|
||||
if (b > 3 || b < 1) return;
|
||||
var m = packModifiersForMouse();
|
||||
Conn.send("p" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m));
|
||||
console.log("B ",b," M ",m);
|
||||
},
|
||||
onMouseUp: function (x, y, b) {
|
||||
if (b > 3 || b < 1) return;
|
||||
var m = packModifiersForMouse();
|
||||
Conn.send("r" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m));
|
||||
console.log("B ",b," M ",m);
|
||||
},
|
||||
onMouseWheel: function (x, y, dir) {
|
||||
// -1 ... btn 4 (away from user)
|
||||
// +1 ... btn 5 (towards user)
|
||||
var m = packModifiersForMouse();
|
||||
var b = (dir < 0 ? 4 : 5);
|
||||
Conn.send("p" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m));
|
||||
console.log("B ",b," M ",m);
|
||||
},
|
||||
};
|
||||
})();
|
||||
|
||||
/** Init the terminal sub-module - called from HTML */
|
||||
window.termInit = function () {
|
||||
Conn.init();
|
||||
Input.init();
|
||||
TermUpl.init();
|
||||
};
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
/** 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) {
|
||||
if (_demo) {
|
||||
console.log("TX: ", message);
|
||||
return true; // Simulate success
|
||||
}
|
||||
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() {
|
||||
if (_demo) {
|
||||
console.log("Demo mode!");
|
||||
Screen.load(_demo_screen);
|
||||
showPage();
|
||||
return;
|
||||
}
|
||||
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
|
||||
$('#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,378 @@
|
||||
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];
|
||||
$('#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) {
|
||||
//console.log(JSON.stringify(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);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
$('#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'));
|
||||
@@ -121,7 +120,11 @@
|
||||
|
||||
/** Ask the CGI what APs are visible (async) */
|
||||
function scanAPs() {
|
||||
$.get('http://'+_root+'/cfg/wifi/scan', onScan);
|
||||
if (_demo) {
|
||||
onScan(_demo_aps, 200);
|
||||
} else {
|
||||
$.get('http://' + _root + '/cfg/wifi/scan', onScan);
|
||||
}
|
||||
}
|
||||
|
||||
function rescan(time) {
|
||||
|
||||
+19
-5
@@ -2,6 +2,7 @@
|
||||
|
||||
return [
|
||||
'appname' => 'ESPTerm',
|
||||
'appname_demo' => 'ESPTerm<sup> DEMO</sup>',
|
||||
|
||||
'menu.cfg_wifi' => 'WiFi Settings',
|
||||
'menu.cfg_network' => 'Network Settings',
|
||||
@@ -20,6 +21,10 @@ return [
|
||||
'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)',
|
||||
@@ -28,14 +33,19 @@ return [
|
||||
'net.details' => 'MAC addresses',
|
||||
|
||||
'term.defaults' => 'Initial Settings',
|
||||
'term.expert' => 'Expert Options',
|
||||
'term.explain_initials' => '
|
||||
Those are the initial settings used after ESPTerm powers on.
|
||||
The selected colors will also be used after receiving a
|
||||
screen- or attributes-reset command. Those settings will be
|
||||
applied immediately after saving to preview the changes live.',
|
||||
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.',
|
||||
|
||||
'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',
|
||||
@@ -45,6 +55,10 @@ return [
|
||||
'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',
|
||||
@@ -94,7 +108,7 @@ 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:',
|
||||
|
||||
@@ -10,4 +10,5 @@ cat jssrc/chibi.js \
|
||||
jssrc/appcommon.js \
|
||||
jssrc/lang.js \
|
||||
jssrc/wifi.js \
|
||||
jssrc/term_* \
|
||||
jssrc/term.js > js/app.js
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<nav id="menu">
|
||||
<div id="brand" tabindex=0><?= tr('appname') ?></div>
|
||||
<div id="brand" tabindex=0><?= tr('appname'.(ESP_DEMO?'_demo':'')) ?></div>
|
||||
<a href="<?= e(url('term')) ?>" class="icn-back"><?= tr('menu.term') ?></a>
|
||||
<?php
|
||||
// generate the menu
|
||||
|
||||
@@ -7,7 +7,12 @@
|
||||
<title><?= $_GET['PAGE_TITLE'] ?></title>
|
||||
<link href="/css/app.css" rel="stylesheet">
|
||||
<script src="/js/app.js"></script>
|
||||
<script>var _root = <?= JS_WEB_ROOT ?>;</script>
|
||||
<script>
|
||||
var _root = <?= JS_WEB_ROOT ?>;
|
||||
var _demo = <?= (int)ESP_DEMO ?>;
|
||||
<?php if($_GET['page']=='term'): ?>var _demo_screen = <?= ESP_DEMO ? DEMO_SCREEN : 0 ?>;<?php endif; ?>
|
||||
<?php if($_GET['page']=='cfg_wifi'): ?>var _demo_aps = <?= ESP_DEMO ? json_encode(DEMO_APS) : '' ?>;<?php endif; ?>
|
||||
</script>
|
||||
</head>
|
||||
<body class="<?= $_GET['BODYCLASS'] ?>">
|
||||
<div id="outer">
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
|
||||
<?php if ($_GET['BODYCLASS'] !== 'page-term'): ?>
|
||||
<div class="botpad"></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="NotifyMsg hidden" id="notif"></div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<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>
|
||||
|
||||
@@ -104,6 +108,18 @@
|
||||
<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>
|
||||
@@ -122,14 +138,41 @@
|
||||
<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-1').submit()"><?= tr('apply') ?></a>
|
||||
<a class="button icn-ok" href="#" onclick="qs('#form-2').submit()"><?= tr('apply') ?></a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<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>
|
||||
|
||||
+17
-597
@@ -1,600 +1,20 @@
|
||||
<div class="Box fold">
|
||||
<h2>Tips & Troubleshooting</h2>
|
||||
|
||||
<div class="Row v">
|
||||
<img src="/img/adapter.jpg" class="aside" alt="ESPTerm v2">
|
||||
<ul>
|
||||
<li><b>Communication UART (Rx, Tx)</b> can be configured in the <a href="<?= url('cfg_system') ?>">System Settings</a>.
|
||||
|
||||
<li><b>Boot log and debug messages</b> are available on pin <b>GPIO2</b> (P2) at 115200\,baud, 1 stop bit, no parity.
|
||||
Those messages may be disabled through compile flags.
|
||||
|
||||
<li><b>Loopback test</b>: 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><b>For best performance</b>, use the module in Client mode (connected to external network) and minimize the number
|
||||
of simultaneous connections. Enabling AP consumes extra RAM and strains the processor.
|
||||
|
||||
<li>In AP mode, <b>check that the WiFi channel used is clear</b>; 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 class="Box">
|
||||
<a href="#" onclick="hpfold(1);return false">Expand all</a> | <a href="#" onclick="hpfold(0);return false">Collapse all</a>
|
||||
</div>
|
||||
|
||||
<div class="Box fold">
|
||||
<h2>Escape Sequences Intro</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"; ?>
|
||||
|
||||
<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 <code>xterm</code>.
|
||||
All commonly used attributes and commands are supported.
|
||||
ESPTerm is capable of displaying ncurses applications such as <i>Midnight Commander</i> using <i>agetty</i>.</p>
|
||||
|
||||
<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).
|
||||
Escape sequences start with the control character ESC (27), followed by any number of ASCII characters
|
||||
forming the body of the command. On this page, we'll use <code>\e</code> to indicate ESC.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Escape sequences can be divided based on their first character and structure. Most common types are:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>CSI commands: <code>\e[</code> followed by 1 optional leading character, multiple numbers divided by
|
||||
semicolons, and one or two trailing characters. Those control the cursor, set attributes and manipulate screen content. E.g. <code>\e[?7;10h</code>, <code>\e[2J</code></li>
|
||||
<li>SGR commands: CSI terminated by <code>m</code>. Those set text attributes and colors. E.g. <code>\e[</code></li>
|
||||
<li>OSC commands: <code>\e]</code> followed by any number of UTF-8 characters (ESPTerm supports up to 64)
|
||||
terminated by <code>\e\\</code> (ESC and backslash) or Bell (7). Those are used to exchange text strings.</li>
|
||||
<li>There are a few other commands that don't follow this pattern, such as <code>\e7</code> or
|
||||
<code>\e#8</code>, mostly for historical reasons.</li>
|
||||
</ul>
|
||||
|
||||
<p>A list of the most important escape sequences is presented in the following sections.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="Box fold">
|
||||
<h2>Screen Behavior</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>
|
||||
|
||||
<div class="Box fold">
|
||||
<h2>Text Attributes</h2>
|
||||
|
||||
<div class="Row v">
|
||||
<p>
|
||||
All text attributes are set using SGR commands like <code>\e[10;20;30m</code>, with up to 10 numbers separated by semicolons.
|
||||
To restore all attributes to their default states, use SGR 0: <code>\e[0m</code> or <code>\e[m</code>.
|
||||
</p>
|
||||
|
||||
<p>Those are the supported text attributes SGR codes:</p>
|
||||
|
||||
<table>
|
||||
<thead><tr><th>Style</th><th>Enable</th><th>Disable</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><b>Bold</b></td><td>1</td><td>21, 22</td></tr>
|
||||
<tr><td style="opacity:.6">Faint</td><td>2</td><td>22</td></tr>
|
||||
<tr><td><i>Italic</i></td><td>3</td><td>23</td></tr>
|
||||
<tr><td><u>Underlined</u></td><td>4</td><td>24</td></tr>
|
||||
<tr><td>Blink</td><td>5</td><td>25</td></tr>
|
||||
<tr><td><span style="color:black;background:#ccc;">Inverse</span></td><td>7</td><td>27</td></tr>
|
||||
<tr><td><s>Striked</s></td><td>9</td><td>29</td></tr>
|
||||
<tr><td>𝔉𝔯𝔞𝔨𝔱𝔲𝔯</td><td>20</td><td>23</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="Box fold theme-0">
|
||||
<h2>Colors</h2>
|
||||
|
||||
<div class="Row v">
|
||||
<p>Colors are set using SGR commands (like <code>\e[10;20;30m</code>). The following tables list the SGR codes to use.
|
||||
Selected colors are used for any new text entered, as well as for empty space when using line and screen clearing commands.
|
||||
The configured default colors can be restored using SGR 39 for foreground and SGR 49 for background.</p>
|
||||
|
||||
<p>The actual color representation depends on a color theme which
|
||||
can be selected in <a href="<?= url('cfg_term') ?>">Terminal Settings</a>.</p>
|
||||
|
||||
<h3>Foreground colors</h3>
|
||||
|
||||
<div class="colorprev">
|
||||
<span class="bg7 fg0">30</span>
|
||||
<span class="bg0 fg1">31</span>
|
||||
<span class="bg0 fg2">32</span>
|
||||
<span class="bg0 fg3">33</span>
|
||||
<span class="bg0 fg4">34</span>
|
||||
<span class="bg0 fg5">35</span>
|
||||
<span class="bg0 fg6">36</span>
|
||||
<span class="bg0 fg7">37</span>
|
||||
</div>
|
||||
|
||||
<div class="colorprev">
|
||||
<span class="bg0 fg8">90</span>
|
||||
<span class="bg0 fg9">91</span>
|
||||
<span class="bg0 fg10">92</span>
|
||||
<span class="bg0 fg11">93</span>
|
||||
<span class="bg0 fg12">94</span>
|
||||
<span class="bg0 fg13">95</span>
|
||||
<span class="bg0 fg14">96</span>
|
||||
<span class="bg0 fg15">97</span>
|
||||
</div>
|
||||
|
||||
<h3>Background colors</h3>
|
||||
|
||||
<div class="colorprev">
|
||||
<span class="bg0 fg15">40</span>
|
||||
<span class="bg1 fg15">41</span>
|
||||
<span class="bg2 fg15">42</span>
|
||||
<span class="bg3 fg0">43</span>
|
||||
<span class="bg4 fg15">44</span>
|
||||
<span class="bg5 fg15">45</span>
|
||||
<span class="bg6 fg15">46</span>
|
||||
<span class="bg7 fg0">47</span>
|
||||
</div>
|
||||
|
||||
<div class="colorprev">
|
||||
<span class="bg8 fg15">100</span>
|
||||
<span class="bg9 fg0">101</span>
|
||||
<span class="bg10 fg0">102</span>
|
||||
<span class="bg11 fg0">103</span>
|
||||
<span class="bg12 fg0">104</span>
|
||||
<span class="bg13 fg0">105</span>
|
||||
<span class="bg14 fg0">106</span>
|
||||
<span class="bg15 fg0">107</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="Box fold">
|
||||
<h2>User Input</h2>
|
||||
|
||||
<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>🡑</td>
|
||||
<td><code>\e[A</code></td>
|
||||
<td>F1</td>
|
||||
<td><code>\eOP</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>🡓</td>
|
||||
<td><code>\e[B</code></td>
|
||||
<td>F2</td>
|
||||
<td><code>\eOQ</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>🡒</td>
|
||||
<td><code>\e[C</code></td>
|
||||
<td>F3</td>
|
||||
<td><code>\eOR</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>🡐</td>
|
||||
<td><code>\e[D</code></td>
|
||||
<td>F4</td>
|
||||
<td><code>\eOS</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Home</td>
|
||||
<td><code>\eOH</code></td>
|
||||
<td>F5</td>
|
||||
<td><code>\e[15~</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>End</td>
|
||||
<td><code>\eOF</code></td>
|
||||
<td>F6</td>
|
||||
<td><code>\e[17~</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Insert</td>
|
||||
<td><code>\e[2~</code></td>
|
||||
<td>F7</td>
|
||||
<td><code>\e[18~</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Delete</td>
|
||||
<td><code>\e[3~</code></td>
|
||||
<td>F8</td>
|
||||
<td><code>\e[19~</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Page Up</td>
|
||||
<td><code>\e[5~</code></td>
|
||||
<td>F9</td>
|
||||
<td><code>\e[20~</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Page Down</td>
|
||||
<td><code>\e[6~</code></td>
|
||||
<td>F10</td>
|
||||
<td><code>\e[21~</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Enter</td>
|
||||
<td><code>CR (13)</code></td>
|
||||
<td>F11</td>
|
||||
<td><code>\e[23~</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ctrl+Enter</td>
|
||||
<td><code>LF (10)</code></td>
|
||||
<td>F12</td>
|
||||
<td><code>\e[24~</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Tab</td>
|
||||
<td><code>TAB (9)</code></td>
|
||||
<td>ESC</td>
|
||||
<td><code>ESC (27)</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Backspace</td>
|
||||
<td><code>BS (8)</code></td>
|
||||
<td>Ctrl+A..Z</td>
|
||||
<td><code>ASCII 1-26</code></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>Action buttons</h3>
|
||||
|
||||
<p>
|
||||
The blue buttons under the screen send ASCII codes 1, 2, 3, 4, 5, which incidentally correspond to Ctrl+A,B,C,D,E.
|
||||
This choice was made to make button press parsing as simple as possible.
|
||||
</p>
|
||||
|
||||
<h3>Mouse</h3>
|
||||
|
||||
<p>
|
||||
ESPTerm in the current version does not implement standard mouse input. Instead, clicks/taps produce CSI sequences
|
||||
<code>\e[<i>r</i>;<i>c</i>M</code> (row, column). You can use this for virtual on-screen buttons.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="Box fold">
|
||||
<h2>Cursor Commands</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>
|
||||
<b>Legend:</b>
|
||||
Italic letters such as <i>n</i> are ASCII numbers that serve as arguments, separated with a semicolon.
|
||||
If an argument is left out, it's treated as 0 or 1, depending on what makes sense for the command.
|
||||
</p>
|
||||
|
||||
<h3>Movement</h3>
|
||||
|
||||
<table class="ansiref w100">
|
||||
<thead><tr><th>Code</th><th>Meaning</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
\e[<i>n</i>A<br>
|
||||
\e[<i>n</i>B<br>
|
||||
\e[<i>n</i>C<br>
|
||||
\e[<i>n</i>D
|
||||
</td>
|
||||
<td>Move cursor up (A), down (B), right (C), left (D)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
\e[<i>n</i>F<br>
|
||||
\e[<i>n</i>E
|
||||
</td>
|
||||
<td>Go <i>n</i> lines up (F) or down (E), start of line</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
\e[<i>r</i>d<br>
|
||||
\e[<i>c</i>G<br>
|
||||
\e[<i>r</i>;<i>c</i>H
|
||||
</td>
|
||||
<td>
|
||||
Go to absolute position - row (d), column (G), or both (H). Use <code>\e[H</code> to go to 1,1.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[6n</td>
|
||||
<td>Query cursor position. Sent back as <code>\e[<i>r</i>;<i>c</i>R</code>.</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>\e[s<br>\e[u</td>
|
||||
<td>Save (s) or restore (u) cursor position</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e7<br>\e8</td>
|
||||
<td>Save (7) or restore (8) cursor position and attributes</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<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 <i>a</i> through <i>b</i> and go to 1,1. By default, the
|
||||
scrolling region spans the entire screen height. The cursor can leave the region using
|
||||
absolute position commands, unless Origin Mode (see below) is active.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[?6h<br>\e[?6l</td>
|
||||
<td>Enable (h) or disable (l) Origin Mode and go to 1,1. In Origin Mode, all coordinates
|
||||
are relative to the Scrolling Region and the cursor can't leave the region.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
\e[<i>n</i>S<br>
|
||||
\e[<i>n</i>T
|
||||
</td>
|
||||
<td>Move contents of the Scrolling Region up (S) or down (T), pad with empty lines of the current background color.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<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>
|
||||
\e[<i>n</i>I<br>
|
||||
\e[<i>n</i>Z
|
||||
</td>
|
||||
<td>Advance (I) or go back (Z) <i>n</i> tab stops or end/start of line. ASCII TAB (9) is equivalent to <code>\e[1I</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
\e[0g<br>
|
||||
\e[3g<br>
|
||||
</td>
|
||||
<td>Clear tab stop at the current column (0), or all columns (3).</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Other options</h3>
|
||||
|
||||
<table class="ansiref w100">
|
||||
<thead><tr><th>Code</th><th>Meaning</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>\e[?7h<br>\e[?7l</td>
|
||||
<td>Enable (h) or disable (l) cursor auto-wrap and screen auto-scroll</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[?25h<br>\e[?25l</td>
|
||||
<td>Show (h) or hide (l) the cursor</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="Box fold">
|
||||
<h2>Screen Content Manipulation</h2>
|
||||
|
||||
<div class="Row v">
|
||||
<p>
|
||||
<b>Legend:</b>
|
||||
Italic letters such as <i>n</i> are ASCII numbers that serve as arguments, separated with a semicolon.
|
||||
If an argument is left out, it's treated as 0 or 1, depending on what makes sense for the command.
|
||||
</p>
|
||||
|
||||
<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. <i>m</i>: 0 - from cursor, 1 - to cursor, 2 - all</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[<i>m</i>K</td>
|
||||
<td>Erase part of line. <i>m</i>: 0 - from cursor, 1 - to cursor, 2 - all</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
\e[<i>n</i>X
|
||||
</td>
|
||||
<td>Erase <i>n</i> characters in line.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
\e[<i>n</i>L<br>
|
||||
\e[<i>n</i>M
|
||||
</td>
|
||||
<td>Insert (L) or delete (M) <i>n</i> lines. Following lines are pulled up or pushed down.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
\e[<i>n</i>@<br>
|
||||
\e[<i>nP
|
||||
</td>
|
||||
<td>Insert (@) or delete (P) <i>n</i> characters. The rest of the line is pulled left or pushed right.
|
||||
Characters going past the end of line are lost.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="Box fold">
|
||||
<h2>Alternate Character Sets</h2>
|
||||
|
||||
<div class="Row v">
|
||||
<p>
|
||||
ESPTerm implements Alternate Character Sets as a way to print box drawing characters
|
||||
and special symbols. A character set can change what each received ASCII character
|
||||
is printed as on the screen (eg. "{" is "π" in codepage 0). The implementation is based
|
||||
on the original VT devices.
|
||||
Since ESPTerm also fully supports UTF-8, you can probably ignore this feature and use
|
||||
Unicode directly. It's added for compatibility with some programs that use this.
|
||||
</p>
|
||||
|
||||
<p>The following codepages are implemented:</p>
|
||||
|
||||
<ul>
|
||||
<li>B - US ASCII (default)</li>
|
||||
<li>A - UK ASCII: # replaced with £</li>
|
||||
<li>0 - Symbols and basic line drawing (standard DEC alternate character set)</li>
|
||||
<li>1 - Symbols and advanced line drawing (based on DOS codepage 437)</li>
|
||||
</ul>
|
||||
|
||||
<p>To see what character maps to which symbol, look in the source code or try it. All codepages use codes 32-127, 32 being space.</p>
|
||||
|
||||
<p>
|
||||
There are two character set slots, G0 and G1.
|
||||
Those slots are selected as active using ASCII codes Shift In and Shift Out (those originally served for shifting
|
||||
a red-black typewriter tape). Each slot (G0 and G1) can have a different codepage assigned. G0 and G1 and the active slot number are
|
||||
saved and restored with the cursor and cleared with a screen reset (<code>\ec</code>).
|
||||
</p>
|
||||
|
||||
<p>The following commands are used:</p>
|
||||
|
||||
<table class="ansiref w100">
|
||||
<thead><tr><th>Code</th><th>Meaning</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>\e(<i>x</i></td>
|
||||
<td>Set G0 = codepage <i>x</i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e)<i>x</i></td>
|
||||
<td>Set G1 = codepage <i>x</i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SO (14)</td>
|
||||
<td>Activate G0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SI (15)</td>
|
||||
<td>Activate G1</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="Box fold">
|
||||
<h2>System Commands</h2>
|
||||
|
||||
<div class="Row v">
|
||||
<p>
|
||||
It's possible to dynamically change the screen title text and action button labels.
|
||||
Setting an empty label to a button makes it look disabled. The buttons send ASCII 1-5 when clicked.
|
||||
Those changes are not retained after restart.
|
||||
</p>
|
||||
|
||||
<table class="ansiref w100">
|
||||
<thead><tr><th>Code</th><th>Meaning</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>\ec</td>
|
||||
<td>
|
||||
Clear screen, reset attributes and cursor.
|
||||
The screen size, title and button labels remain unchanged.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[5n</td>
|
||||
<td>
|
||||
Query device status, ESPTerm replies with <code>\e[0n</code> "device is OK".
|
||||
Can be used to check if the terminal has booted up and is ready to receive commands.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CAN (24)</td>
|
||||
<td>
|
||||
This ASCII code is not a command, but is sent by ESPTerm when it becomes ready to receive commands.
|
||||
When this code is received on the UART, it means ESPTerm has restarted and is ready. Use this to detect
|
||||
spontaneous restarts which require a full screen repaint.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e]0;<i>title</i>\a</td>
|
||||
<td>Set screen title (this is a standard OSC command)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
\e]<i>81</i>;<i>btn1</i>\a<br>
|
||||
\e]<i>82</i>;<i>btn2</i>\a<br>
|
||||
\e]<i>83</i>;<i>btn3</i>\a<br>
|
||||
\e]<i>84</i>;<i>btn4</i>\a<br>
|
||||
\e]<i>85</i>;<i>btn5</i>\a<br>
|
||||
</td>
|
||||
<td>Set button 1-5 label - eg. <code>\e]81;Yes\a</code>
|
||||
sets the first button text to "Yes".</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[8;<i>r</i>;<i>c</i>t</td>
|
||||
<td>Set screen size (this is a command borrowed from xterm)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
+49
-18
@@ -5,31 +5,62 @@
|
||||
console.error("Page load failed, refreshing…");
|
||||
location.reload(true);
|
||||
}
|
||||
}, 2000);
|
||||
}, 3000);
|
||||
</script>
|
||||
|
||||
<h1></h1>
|
||||
<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>
|
||||
|
||||
<div id="termwrap">
|
||||
<h1><!-- Screen title gets loaded here by JS --></h1>
|
||||
|
||||
<div id="term-wrap">
|
||||
<div id="screen" class="theme-%theme%"></div>
|
||||
|
||||
<div id="buttons">
|
||||
<button data-n="1" class="btn-blue"></button><!--
|
||||
--><button data-n="2" class="btn-blue"></button><!--
|
||||
--><button data-n="3" class="btn-blue"></button><!--
|
||||
--><button data-n="4" class="btn-blue"></button><!--
|
||||
--><button data-n="5" class="btn-blue"></button>
|
||||
<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="botnav">
|
||||
<a href="#" onclick="toggleSoftKb(true); return false" class="icn-keyboard mq-tablet-max"></a><!--
|
||||
--><a href="<?= url('cfg_term') ?>"><?= tr('term_nav.config') ?></a><!--
|
||||
--><a href="<?= url('cfg_wifi') ?>"><?= tr('term_nav.wifi') ?></a><!--
|
||||
--><a href="<?= url('help') ?>"><?= tr('term_nav.help') ?></a><!--
|
||||
--><a href="<?= url('about') ?>"><?= tr('term_nav.about') ?></a>
|
||||
<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>
|
||||
@@ -48,13 +79,13 @@
|
||||
});
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
console.error("Fail, reloading in 1s…");
|
||||
console.error("Fail, reloading in 3s…");
|
||||
setTimeout(function() {
|
||||
location.reload(true);
|
||||
}, 1000);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function toggleSoftKb(yes) {
|
||||
function kbOpen(yes) {
|
||||
var i = qs('#softkb-input');
|
||||
if (yes) i.focus();
|
||||
else i.blur();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ form { @include naked(); }
|
||||
vertical-align: middle;
|
||||
margin: 12px auto;
|
||||
text-align: left;
|
||||
line-height: 1.35em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
.botpad {
|
||||
display:block;
|
||||
height: 5em;
|
||||
}
|
||||
|
||||
.Box {
|
||||
display: block;
|
||||
max-width: 900px;
|
||||
|
||||
@@ -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);
|
||||
@@ -48,15 +56,73 @@
|
||||
}
|
||||
|
||||
.ansiref {
|
||||
td:nth-child(1) {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
&.w100 {
|
||||
width: 100%;
|
||||
|
||||
td:nth-child(1) {
|
||||
width: 6em;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+123
-21
@@ -13,51 +13,68 @@ body.term {
|
||||
// longer duration to load everything in background nicely
|
||||
transition: opacity 0.25s ease-in;
|
||||
}
|
||||
}
|
||||
|
||||
#screen {
|
||||
@include noselect();
|
||||
font-family: monospace;
|
||||
font-size: 20px;//16pt; - 16pt causes vertical breaks in line drawing characters??
|
||||
#screen {
|
||||
white-space: nowrap;
|
||||
background: #111213;
|
||||
padding: 6px;
|
||||
display: inline-block;
|
||||
border: 2px solid #3983CD; //#1bc249;
|
||||
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;
|
||||
|
||||
display: inline-block;
|
||||
line-height: 1.15em; // this adjusts the spacing
|
||||
width: 0.6em; // horizontal spacing, estimated to be about right
|
||||
overflow: visible;
|
||||
&::before {
|
||||
content: " ";
|
||||
}
|
||||
|
||||
> span {
|
||||
position:absolute;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
#buttons {
|
||||
&.noselect {
|
||||
@include noselect();
|
||||
}
|
||||
}
|
||||
|
||||
#action-buttons {
|
||||
margin-top: 10px;
|
||||
white-space: nowrap;
|
||||
|
||||
button {
|
||||
margin: 0 3px;
|
||||
padding: 8px 5px;
|
||||
//width: 18%;
|
||||
min-width: 62px;
|
||||
//max-width: 65px;
|
||||
//min-width: initial;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
#botnav {
|
||||
&:focus {
|
||||
outline: 0 none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#term-nav {
|
||||
padding-top: 1.5em;
|
||||
text-align: center;
|
||||
|
||||
a {
|
||||
padding: 0 dist(-2);
|
||||
text-decoration: underline;
|
||||
text-decoration: none;
|
||||
padding: 5px 5px;
|
||||
border-radius: 2px;
|
||||
position: relative;
|
||||
|
||||
&, &:visited, &:link {
|
||||
color: #336085;
|
||||
@@ -65,26 +82,111 @@ body.term {
|
||||
|
||||
&:hover {
|
||||
color: #5abfff;
|
||||
background: #1b273c;
|
||||
}
|
||||
|
||||
&:active {
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
i::before {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
span {
|
||||
margin-left: .2em;
|
||||
}
|
||||
|
||||
@include media($phone) {
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
font-size: 130%;
|
||||
padding: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.icn-keyboard {
|
||||
text-decoration: none;
|
||||
font-size: 150%;
|
||||
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:
|
||||
|
||||
+106
-93
@@ -13,22 +13,22 @@ 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, 1, 13, 1, 14, 2,
|
||||
3, 6
|
||||
11, 1, 12, 1, 13, 1, 14, 1,
|
||||
15
|
||||
};
|
||||
|
||||
static const char _ansi_eof_actions[] = {
|
||||
0, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 0, 0, 0, 0, 0
|
||||
1, 1, 1, 0, 0, 0, 0
|
||||
};
|
||||
|
||||
static const int ansi_start = 1;
|
||||
static const int ansi_first_final = 10;
|
||||
static const int ansi_first_final = 11;
|
||||
static const int ansi_error = 0;
|
||||
|
||||
static const int ansi_en_CSI_body = 5;
|
||||
static const int ansi_en_STRCMD_body = 7;
|
||||
static const int ansi_en_charsetcmd_body = 9;
|
||||
static const int ansi_en_STRCMD_body = 8;
|
||||
static const int ansi_en_charsetcmd_body = 10;
|
||||
static const int ansi_en_main = 1;
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ ansi_parser_reset(void) {
|
||||
cs = ansi_start;
|
||||
inside_string = false;
|
||||
apars_reset_utf8buffer();
|
||||
ansi_warn("Parser timeout, state reset");
|
||||
ansi_warn("Parser state reset (timeout?)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +101,7 @@ ansi_parser(char newchar)
|
||||
{
|
||||
// The CSI code is built here
|
||||
static char leadchar;
|
||||
static char interchar; // intermediate CSI char
|
||||
static int arg_ni;
|
||||
static int arg_cnt;
|
||||
static int arg[CSI_N_MAX];
|
||||
@@ -113,12 +114,12 @@ ansi_parser(char newchar)
|
||||
// Init Ragel on the first run
|
||||
if (cs == -1) {
|
||||
|
||||
/* #line 117 "user/ansi_parser.c" */
|
||||
/* #line 118 "user/ansi_parser.c" */
|
||||
{
|
||||
cs = ansi_start;
|
||||
}
|
||||
|
||||
/* #line 91 "user/ansi_parser.rl" */
|
||||
/* #line 92 "user/ansi_parser.rl" */
|
||||
|
||||
#if DEBUG_ANSI
|
||||
memset(history, 0, sizeof(history));
|
||||
@@ -133,14 +134,12 @@ ansi_parser(char newchar)
|
||||
#endif
|
||||
|
||||
// Handle simple characters immediately (bypass parser)
|
||||
if (newchar < ' ') {
|
||||
if (newchar < ' ' && !inside_string) {
|
||||
switch (newchar) {
|
||||
case ESC:
|
||||
if (!inside_string) {
|
||||
// Reset state
|
||||
cs = ansi_start;
|
||||
// now the ESC will be processed by the parser
|
||||
}
|
||||
break; // proceed to parser
|
||||
|
||||
// Literally passed
|
||||
@@ -168,11 +167,8 @@ ansi_parser(char newchar)
|
||||
|
||||
case BEL:
|
||||
// bel is also used to terminate OSC
|
||||
if (!inside_string) {
|
||||
apars_handle_bel();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case ENQ:
|
||||
apars_handle_enq();
|
||||
@@ -185,14 +181,12 @@ ansi_parser(char newchar)
|
||||
return;
|
||||
|
||||
default:
|
||||
// Discard all others
|
||||
// Discard all other control codes
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// >= ' '
|
||||
|
||||
// bypass the parser for simple characters (speed-up)
|
||||
if (cs == ansi_start) {
|
||||
if (cs == ansi_start && newchar >= ' ') {
|
||||
apars_handle_plainchar(newchar);
|
||||
return;
|
||||
}
|
||||
@@ -205,7 +199,7 @@ ansi_parser(char newchar)
|
||||
|
||||
// The parser
|
||||
|
||||
/* #line 209 "user/ansi_parser.c" */
|
||||
/* #line 203 "user/ansi_parser.c" */
|
||||
{
|
||||
const char *_acts;
|
||||
unsigned int _nacts;
|
||||
@@ -262,7 +256,7 @@ case 3:
|
||||
} else if ( (*p) >= 70 )
|
||||
goto tr9;
|
||||
goto tr1;
|
||||
case 10:
|
||||
case 11:
|
||||
switch( (*p) ) {
|
||||
case 7: goto tr1;
|
||||
case 27: goto tr2;
|
||||
@@ -274,51 +268,63 @@ case 4:
|
||||
goto tr1;
|
||||
case 5:
|
||||
switch( (*p) ) {
|
||||
case 36: goto tr11;
|
||||
case 59: goto tr13;
|
||||
case 64: goto tr14;
|
||||
}
|
||||
if ( (*p) < 60 ) {
|
||||
if ( (*p) > 47 ) {
|
||||
if ( 48 <= (*p) && (*p) <= 57 )
|
||||
goto tr12;
|
||||
} else if ( (*p) >= 32 )
|
||||
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 if ( (*p) > 63 ) {
|
||||
if ( (*p) > 90 ) {
|
||||
if ( 96 <= (*p) && (*p) <= 122 )
|
||||
goto tr15;
|
||||
} else if ( (*p) >= 65 )
|
||||
goto tr15;
|
||||
} 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) == 59 )
|
||||
goto tr13;
|
||||
if ( (*p) < 64 ) {
|
||||
if ( 48 <= (*p) && (*p) <= 57 )
|
||||
goto tr12;
|
||||
} else if ( (*p) > 90 ) {
|
||||
if ( 96 <= (*p) && (*p) <= 122 )
|
||||
if ( (*p) > 90 ) {
|
||||
if ( 96 <= (*p) && (*p) <= 126 )
|
||||
goto tr15;
|
||||
} else
|
||||
} else if ( (*p) >= 64 )
|
||||
goto tr15;
|
||||
goto tr1;
|
||||
case 11:
|
||||
goto tr1;
|
||||
case 12:
|
||||
if ( (*p) == 59 )
|
||||
goto tr13;
|
||||
goto tr1;
|
||||
case 7:
|
||||
switch( (*p) ) {
|
||||
case 36: goto tr11;
|
||||
case 59: goto tr13;
|
||||
}
|
||||
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) <= 122 )
|
||||
if ( 96 <= (*p) && (*p) <= 126 )
|
||||
goto tr15;
|
||||
} else
|
||||
goto tr15;
|
||||
} else
|
||||
goto tr11;
|
||||
goto tr1;
|
||||
case 7:
|
||||
case 8:
|
||||
switch( (*p) ) {
|
||||
case 7: goto tr17;
|
||||
case 27: goto tr18;
|
||||
@@ -326,11 +332,11 @@ case 7:
|
||||
goto tr16;
|
||||
case 13:
|
||||
goto tr1;
|
||||
case 8:
|
||||
case 9:
|
||||
if ( (*p) == 92 )
|
||||
goto tr17;
|
||||
goto tr1;
|
||||
case 9:
|
||||
case 10:
|
||||
switch( (*p) ) {
|
||||
case 7: goto tr1;
|
||||
case 27: goto tr1;
|
||||
@@ -346,44 +352,44 @@ case 14:
|
||||
tr3: cs = 3; goto _again;
|
||||
tr4: cs = 4; goto _again;
|
||||
tr11: cs = 6; goto f8;
|
||||
tr12: cs = 6; goto f9;
|
||||
tr13: cs = 6; goto f10;
|
||||
tr16: cs = 7; goto f13;
|
||||
tr18: cs = 8; goto _again;
|
||||
tr5: cs = 10; goto f2;
|
||||
tr6: cs = 10; goto f3;
|
||||
tr7: cs = 10; goto f4;
|
||||
tr8: cs = 10; goto f5;
|
||||
tr9: cs = 10; goto f6;
|
||||
tr10: cs = 10; goto f7;
|
||||
tr15: cs = 11; goto f12;
|
||||
tr14: cs = 12; goto f11;
|
||||
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;
|
||||
|
||||
f0: _acts = _ansi_actions + 1; goto execFuncs;
|
||||
f1: _acts = _ansi_actions + 3; goto execFuncs;
|
||||
f5: _acts = _ansi_actions + 5; goto execFuncs;
|
||||
f8: _acts = _ansi_actions + 7; goto execFuncs;
|
||||
f11: _acts = _ansi_actions + 7; goto execFuncs;
|
||||
f9: _acts = _ansi_actions + 9; goto execFuncs;
|
||||
f10: _acts = _ansi_actions + 11; goto execFuncs;
|
||||
f12: _acts = _ansi_actions + 13; goto execFuncs;
|
||||
f4: _acts = _ansi_actions + 15; goto execFuncs;
|
||||
f13: _acts = _ansi_actions + 17; goto execFuncs;
|
||||
f14: _acts = _ansi_actions + 19; goto execFuncs;
|
||||
f7: _acts = _ansi_actions + 21; goto execFuncs;
|
||||
f3: _acts = _ansi_actions + 23; goto execFuncs;
|
||||
f6: _acts = _ansi_actions + 25; goto execFuncs;
|
||||
f2: _acts = _ansi_actions + 27; goto execFuncs;
|
||||
f15: _acts = _ansi_actions + 29; goto execFuncs;
|
||||
f11: _acts = _ansi_actions + 31; 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 185 "user/ansi_parser.rl" */
|
||||
/* #line 179 "user/ansi_parser.rl" */
|
||||
{
|
||||
ansi_warn("Parser error.");
|
||||
apars_show_context();
|
||||
@@ -392,7 +398,7 @@ execFuncs:
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
/* #line 194 "user/ansi_parser.rl" */
|
||||
/* #line 188 "user/ansi_parser.rl" */
|
||||
{
|
||||
if ((*p) != 0) {
|
||||
apars_handle_plainchar((*p));
|
||||
@@ -400,10 +406,11 @@ execFuncs:
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
/* #line 202 "user/ansi_parser.rl" */
|
||||
/* #line 196 "user/ansi_parser.rl" */
|
||||
{
|
||||
// Reset the CSI builder
|
||||
leadchar = NUL;
|
||||
interchar = NUL;
|
||||
arg_ni = 0;
|
||||
arg_cnt = 0;
|
||||
|
||||
@@ -416,13 +423,13 @@ execFuncs:
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
/* #line 216 "user/ansi_parser.rl" */
|
||||
/* #line 211 "user/ansi_parser.rl" */
|
||||
{
|
||||
leadchar = (*p);
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
/* #line 220 "user/ansi_parser.rl" */
|
||||
/* #line 215 "user/ansi_parser.rl" */
|
||||
{
|
||||
if (arg_cnt == 0) arg_cnt = 1;
|
||||
// x10 + digit
|
||||
@@ -432,7 +439,7 @@ execFuncs:
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
/* #line 228 "user/ansi_parser.rl" */
|
||||
/* #line 223 "user/ansi_parser.rl" */
|
||||
{
|
||||
if (arg_cnt == 0) arg_cnt = 1; // handle case when first arg is empty
|
||||
arg_cnt++;
|
||||
@@ -440,29 +447,35 @@ execFuncs:
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
/* #line 234 "user/ansi_parser.rl" */
|
||||
/* #line 229 "user/ansi_parser.rl" */
|
||||
{
|
||||
apars_handle_csi(leadchar, arg, arg_cnt, (*p));
|
||||
{cs = 1;goto _again;}
|
||||
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 = 7;goto _again;}
|
||||
{cs = 8;goto _again;}
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
case 9:
|
||||
/* #line 253 "user/ansi_parser.rl" */
|
||||
{
|
||||
string_buffer[str_ni++] = (*p);
|
||||
}
|
||||
break;
|
||||
case 9:
|
||||
case 10:
|
||||
/* #line 257 "user/ansi_parser.rl" */
|
||||
{
|
||||
inside_string = false;
|
||||
@@ -471,42 +484,42 @@ execFuncs:
|
||||
{cs = 1;goto _again;}
|
||||
}
|
||||
break;
|
||||
case 10:
|
||||
case 11:
|
||||
/* #line 270 "user/ansi_parser.rl" */
|
||||
{
|
||||
apars_handle_hash_cmd((*p));
|
||||
{cs = 1;goto _again;}
|
||||
}
|
||||
break;
|
||||
case 11:
|
||||
case 12:
|
||||
/* #line 275 "user/ansi_parser.rl" */
|
||||
{
|
||||
apars_handle_short_cmd((*p));
|
||||
{cs = 1;goto _again;}
|
||||
}
|
||||
break;
|
||||
case 12:
|
||||
case 13:
|
||||
/* #line 280 "user/ansi_parser.rl" */
|
||||
{
|
||||
apars_handle_space_cmd((*p));
|
||||
{cs = 1;goto _again;}
|
||||
}
|
||||
break;
|
||||
case 13:
|
||||
case 14:
|
||||
/* #line 287 "user/ansi_parser.rl" */
|
||||
{
|
||||
leadchar = (*p);
|
||||
{cs = 9;goto _again;}
|
||||
{cs = 10;goto _again;}
|
||||
}
|
||||
break;
|
||||
case 14:
|
||||
case 15:
|
||||
/* #line 292 "user/ansi_parser.rl" */
|
||||
{
|
||||
apars_handle_chs_designate(leadchar, (*p));
|
||||
{cs = 1;goto _again;}
|
||||
}
|
||||
break;
|
||||
/* #line 510 "user/ansi_parser.c" */
|
||||
/* #line 523 "user/ansi_parser.c" */
|
||||
}
|
||||
}
|
||||
goto _again;
|
||||
@@ -524,7 +537,7 @@ _again:
|
||||
while ( __nacts-- > 0 ) {
|
||||
switch ( *__acts++ ) {
|
||||
case 0:
|
||||
/* #line 185 "user/ansi_parser.rl" */
|
||||
/* #line 179 "user/ansi_parser.rl" */
|
||||
{
|
||||
ansi_warn("Parser error.");
|
||||
apars_show_context();
|
||||
@@ -534,7 +547,7 @@ _again:
|
||||
goto _again;}
|
||||
}
|
||||
break;
|
||||
/* #line 538 "user/ansi_parser.c" */
|
||||
/* #line 551 "user/ansi_parser.c" */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+14
-14
@@ -26,7 +26,7 @@ ansi_parser_reset(void) {
|
||||
cs = ansi_start;
|
||||
inside_string = false;
|
||||
apars_reset_utf8buffer();
|
||||
ansi_warn("Parser timeout, state reset");
|
||||
ansi_warn("Parser state reset (timeout?)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ ansi_parser(char newchar)
|
||||
{
|
||||
// The CSI code is built here
|
||||
static char leadchar;
|
||||
static char interchar; // intermediate CSI char
|
||||
static int arg_ni;
|
||||
static int arg_cnt;
|
||||
static int arg[CSI_N_MAX];
|
||||
@@ -102,14 +103,12 @@ ansi_parser(char newchar)
|
||||
#endif
|
||||
|
||||
// Handle simple characters immediately (bypass parser)
|
||||
if (newchar < ' ') {
|
||||
if (newchar < ' ' && !inside_string) {
|
||||
switch (newchar) {
|
||||
case ESC:
|
||||
if (!inside_string) {
|
||||
// Reset state
|
||||
cs = ansi_start;
|
||||
// now the ESC will be processed by the parser
|
||||
}
|
||||
break; // proceed to parser
|
||||
|
||||
// Literally passed
|
||||
@@ -137,11 +136,8 @@ ansi_parser(char newchar)
|
||||
|
||||
case BEL:
|
||||
// bel is also used to terminate OSC
|
||||
if (!inside_string) {
|
||||
apars_handle_bel();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case ENQ:
|
||||
apars_handle_enq();
|
||||
@@ -154,14 +150,12 @@ ansi_parser(char newchar)
|
||||
return;
|
||||
|
||||
default:
|
||||
// Discard all others
|
||||
// Discard all other control codes
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// >= ' '
|
||||
|
||||
// bypass the parser for simple characters (speed-up)
|
||||
if (cs == ansi_start) {
|
||||
if (cs == ansi_start && newchar >= ' ') {
|
||||
apars_handle_plainchar(newchar);
|
||||
return;
|
||||
}
|
||||
@@ -202,6 +196,7 @@ ansi_parser(char newchar)
|
||||
action CSI_start {
|
||||
// Reset the CSI builder
|
||||
leadchar = NUL;
|
||||
interchar = NUL;
|
||||
arg_ni = 0;
|
||||
arg_cnt = 0;
|
||||
|
||||
@@ -231,14 +226,19 @@ ansi_parser(char newchar)
|
||||
arg_ni++;
|
||||
}
|
||||
|
||||
action CSI_intermed {
|
||||
interchar = fc;
|
||||
}
|
||||
|
||||
action CSI_end {
|
||||
apars_handle_csi(leadchar, arg, arg_cnt, fc);
|
||||
apars_handle_csi(leadchar, arg, arg_cnt, interchar, fc);
|
||||
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 ---
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
#include <esp8266.h>
|
||||
#include <helpers.h>
|
||||
#include "ansi_parser_callbacks.h"
|
||||
#include "uart_driver.h"
|
||||
#include "cgi_sockets.h"
|
||||
#include "version.h"
|
||||
#include "uart_buffer.h"
|
||||
#include "screen.h"
|
||||
|
||||
/**
|
||||
* Send a response to UART0
|
||||
@@ -20,7 +20,6 @@ void ICACHE_FLASH_ATTR
|
||||
apars_respond(const char *str)
|
||||
{
|
||||
UART_SendAsync(str, -1);
|
||||
//UART_WriteString(UART0, str, UART_TIMEOUT_US);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+407
-258
@@ -3,19 +3,12 @@
|
||||
//
|
||||
// Handle CSI sequences
|
||||
// CSI <symbol?> Pm <symbol?> <char>
|
||||
// (CSI = ESC [)
|
||||
//
|
||||
// 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_
|
||||
//
|
||||
// Note:
|
||||
// not all sequences listed in the xterm manual are implemented, notably sequences with the trailing symbol,
|
||||
// graphic mode sequences, mouse reporting and complex multi-argument sequences that operate on regions.
|
||||
//
|
||||
// The screen size can be set using the xterm sequence: CSI Py ; Px t
|
||||
//
|
||||
|
||||
#include <esp8266.h>
|
||||
#include "apars_csi.h"
|
||||
@@ -29,86 +22,196 @@
|
||||
#include "version.h"
|
||||
#include "syscfg.h"
|
||||
|
||||
// TODO simplify file - split to subroutines
|
||||
/** Struct passed to subroutines */
|
||||
typedef struct {
|
||||
char lead;
|
||||
const int *n;
|
||||
int count;
|
||||
char inter;
|
||||
char key;
|
||||
} CSI_Data;
|
||||
|
||||
static void warn_bad_csi()
|
||||
// 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();
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
char lead;
|
||||
const int *n;
|
||||
int count;
|
||||
char aug; // augmenting
|
||||
char key;
|
||||
} CSI_Data;
|
||||
|
||||
static void do_csi_privattr(CSI_Data *opts);
|
||||
static void do_csi_sgr(CSI_Data *opts);
|
||||
static void do_csi_decreqtparm(CSI_Data *opts);
|
||||
|
||||
/**
|
||||
* Handle fully received CSI ANSI sequence
|
||||
* @param leadchar - private range leading character, 0 if none
|
||||
*
|
||||
* @param leadchar - leading character
|
||||
* @param params - array of CSI_N_MAX ints holding the numeric arguments
|
||||
* @param keychar - the char terminating the sequence
|
||||
* @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 keychar)
|
||||
apars_handle_csi(char leadchar, const int *params, int count, char interchar, char keychar)
|
||||
{
|
||||
CSI_Data opts = {leadchar, params, count, NUL, keychar};
|
||||
char buf[32];
|
||||
bool yn = false; // for ? l h
|
||||
CSI_Data opts = {leadchar, params, count, interchar, keychar};
|
||||
|
||||
int n1 = params[0];
|
||||
int n2 = params[1];
|
||||
int n3 = params[2];
|
||||
switch(leadchar) {
|
||||
case '?':
|
||||
switch_csi_LeadQuest(&opts);
|
||||
break;
|
||||
|
||||
// defaults - FIXME this may inadvertently affect some variants that should be left unchanged
|
||||
switch (keychar) {
|
||||
case 'A': // move
|
||||
case 'a':
|
||||
case 'e':
|
||||
case 'B':
|
||||
case 'C':
|
||||
case 'D':
|
||||
case 'b':
|
||||
case 'E':
|
||||
case 'F':
|
||||
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 '`':
|
||||
case 'S': // scrolling
|
||||
case 'T':
|
||||
case 'X': // clear in line
|
||||
case '`': // set X (alias)
|
||||
case 'd': // set Y
|
||||
case 'L':
|
||||
case 'M':
|
||||
case '@':
|
||||
case 'P':
|
||||
case 'I':
|
||||
case 'Z':
|
||||
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;
|
||||
|
||||
case 'H':
|
||||
// Two arguments, 1-based
|
||||
case 'H': // Absolute positioning
|
||||
case 'f':
|
||||
if (n1 == 0) n1 = 1;
|
||||
if (n2 == 0) n2 = 1;
|
||||
break;
|
||||
|
||||
case 'J':
|
||||
case 'K':
|
||||
// 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 (keychar) {
|
||||
switch (opts->key) {
|
||||
// CUU CUD CUF CUB
|
||||
case 'A': // Up
|
||||
screen_cursor_move(-n1, 0, false);
|
||||
@@ -161,63 +264,15 @@ apars_handle_csi(char leadchar, const int *params, int count, char keychar)
|
||||
|
||||
// SU, SD - scroll up/down
|
||||
case 'S':
|
||||
if (leadchar == NUL && count <= 1) {
|
||||
screen_scroll_up(n1);
|
||||
}
|
||||
else {
|
||||
// other:
|
||||
// CSI ? Pi; Pa; Pv S (sixel)
|
||||
warn_bad_csi();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'T':
|
||||
if (leadchar == NUL && count <= 1) {
|
||||
// CSI Ps T
|
||||
screen_scroll_down(n1);
|
||||
}
|
||||
else {
|
||||
// other:
|
||||
// CSI Ps ; Ps ; Ps ; Ps ; Ps T
|
||||
// CSI > Ps; Ps T
|
||||
warn_bad_csi();
|
||||
}
|
||||
break;
|
||||
|
||||
case 't': // xterm window commands
|
||||
if (leadchar == NUL && count <= 2) {
|
||||
// CSI Ps ; Ps ; Ps t
|
||||
switch (n1) {
|
||||
case 8: // set size
|
||||
screen_resize(n2, n3);
|
||||
break;
|
||||
|
||||
case 18: // report size
|
||||
printf(buf, "\033[8;%d;%dt", termconf_scratch.height, termconf_scratch.width);
|
||||
apars_respond(buf);
|
||||
break;
|
||||
|
||||
case 21: // Report title
|
||||
apars_respond("\033]L");
|
||||
apars_respond(termconf_scratch.title);
|
||||
apars_respond("\033\\");
|
||||
break;
|
||||
|
||||
case 24: // Set Height only
|
||||
screen_resize(n2, termconf_scratch.width);
|
||||
break;
|
||||
|
||||
default:
|
||||
ansi_noimpl("CSI %d t", n1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// other:
|
||||
// CSI > Ps; Ps t
|
||||
// CSI Ps SP t,
|
||||
warn_bad_csi();
|
||||
}
|
||||
do_csi_xterm_screen_cmd(opts);
|
||||
break;
|
||||
|
||||
// CUP,HVP - set position
|
||||
@@ -227,11 +282,6 @@ apars_handle_csi(char leadchar, const int *params, int count, char keychar)
|
||||
break; // 1-based
|
||||
|
||||
case 'J': // Erase screen
|
||||
if (leadchar == '?') {
|
||||
// TODO selective erase
|
||||
ansi_noimpl("Selective erase");
|
||||
}
|
||||
|
||||
if (n1 == 0) {
|
||||
screen_clear(CLEAR_FROM_CURSOR);
|
||||
} else if (n1 == 1) {
|
||||
@@ -243,11 +293,6 @@ apars_handle_csi(char leadchar, const int *params, int count, char keychar)
|
||||
break;
|
||||
|
||||
case 'K': // Erase lines
|
||||
if (leadchar == '?') {
|
||||
// TODO selective erase
|
||||
ansi_noimpl("Selective erase");
|
||||
}
|
||||
|
||||
if (n1 == 0) {
|
||||
screen_clear_line(CLEAR_FROM_CURSOR);
|
||||
} else if (n1 == 1) {
|
||||
@@ -259,53 +304,25 @@ apars_handle_csi(char leadchar, const int *params, int count, char keychar)
|
||||
|
||||
// SCP, RCP - save/restore position
|
||||
case 's':
|
||||
if (leadchar == NUL && count == 0) {
|
||||
screen_cursor_save(0);
|
||||
}
|
||||
else if (leadchar == '?') {
|
||||
// Save private attributes (CSI ? Pm h/l)
|
||||
ansi_noimpl("Save private attrs");
|
||||
}
|
||||
else {
|
||||
// other:
|
||||
// CSI ? Pm s
|
||||
// CSI Pl; Pr s
|
||||
warn_bad_csi();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
if (leadchar == NUL && (count == 2 || count == 0)) {
|
||||
screen_set_scrolling_region(n1-1, n2-1);
|
||||
}
|
||||
else if (leadchar == '?') {
|
||||
// Restore private attributes (CSI ? Pm h/l)
|
||||
ansi_noimpl("Restore private attrs");
|
||||
}
|
||||
else {
|
||||
// other:
|
||||
// CSI ? Pm r
|
||||
// CSI Pt; Pl; Pb; Pr; Ps$ r
|
||||
warn_bad_csi();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'u':
|
||||
if (leadchar == NUL && count == 0) {
|
||||
screen_cursor_restore(0);
|
||||
}
|
||||
else {
|
||||
warn_bad_csi();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'h': // DEC feature enable
|
||||
case 'l': // DEC feature disable
|
||||
do_csi_privattr(&opts);
|
||||
// --- DEC standard attributes ---
|
||||
do_csi_set_option(opts);
|
||||
break;
|
||||
|
||||
case 'm': // SGR - set graphics rendition
|
||||
do_csi_sgr(&opts);
|
||||
do_csi_sgr(opts);
|
||||
break;
|
||||
|
||||
case 'L': // Insert lines (shove down)
|
||||
@@ -341,31 +358,13 @@ apars_handle_csi(char leadchar, const int *params, int count, char keychar)
|
||||
screen_tab_forward(n1);
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
if (leadchar == '!') { // RIS
|
||||
/* On real VT there are differences between soft and hard reset, we treat both equally */
|
||||
screen_reset();
|
||||
}
|
||||
else {
|
||||
warn_bad_csi();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'n': // Queries
|
||||
if (leadchar == '>') {
|
||||
// some xterm garbage - discard
|
||||
// CSI > Ps n
|
||||
ansi_noimpl("CSI > %d n", n1);
|
||||
break;
|
||||
}
|
||||
|
||||
if (leadchar == NUL) {
|
||||
if (n1 == 6) {
|
||||
// Query cursor position
|
||||
int x, y;
|
||||
screen_cursor_get(&y, &x);
|
||||
sprintf(buf, "\033[%d;%dR", y + 1, x + 1);
|
||||
apars_respond(buf);
|
||||
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"
|
||||
@@ -374,28 +373,76 @@ apars_handle_csi(char leadchar, const int *params, int count, char keychar)
|
||||
else {
|
||||
warn_bad_csi();
|
||||
}
|
||||
}
|
||||
else {
|
||||
warn_bad_csi();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'c': // CSI-c - report capabilities
|
||||
if (leadchar == NUL) {
|
||||
apars_respond("\033[?64;9c"); // pretend we're vt400 with national character sets
|
||||
}
|
||||
else if (leadchar == '>') {
|
||||
// 41 - we're "VT400", 0 - ROM cartridge number
|
||||
sprintf(buf, "\033[>41;%d;0c", FIRMWARE_VERSION_NUM);
|
||||
apars_respond(buf);
|
||||
}
|
||||
else {
|
||||
warn_bad_csi();
|
||||
}
|
||||
// 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);
|
||||
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:
|
||||
@@ -404,14 +451,141 @@ apars_handle_csi(char leadchar, const int *params, int count, char keychar)
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI [?] Pm {h|l}
|
||||
* 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 void ICACHE_FLASH_ATTR do_csi_privattr(CSI_Data *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');
|
||||
|
||||
if (opts->lead == '?') {
|
||||
// --- DEC private attributes ---
|
||||
for (int i = 0; i < opts->count; i++) {
|
||||
int n = opts->n[i];
|
||||
@@ -460,7 +634,6 @@ static void ICACHE_FLASH_ATTR do_csi_privattr(CSI_Data *opts)
|
||||
// 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;
|
||||
@@ -469,6 +642,11 @@ static void ICACHE_FLASH_ATTR do_csi_privattr(CSI_Data *opts)
|
||||
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
|
||||
@@ -514,103 +692,73 @@ static void ICACHE_FLASH_ATTR do_csi_privattr(CSI_Data *opts)
|
||||
screen_cursor_restore(true);
|
||||
}
|
||||
}
|
||||
else if (n >= 1050 && n <= 1053) {
|
||||
// TODO Different kinds of function key emulation ?
|
||||
// (In practice this seems hardly ever used)
|
||||
|
||||
// Ps = 1 0 5 0 -> Set terminfo/termcap function-key mode.
|
||||
// Ps = 1 0 5 1 -> Set Sun function-key mode.
|
||||
// Ps = 1 0 5 2 -> Set HP function-key mode.
|
||||
// Ps = 1 0 5 3 -> Set SCO function-key mode.
|
||||
ansi_noimpl("FN key emul type");
|
||||
}
|
||||
else if (n == 2004) {
|
||||
// Bracketed paste mode
|
||||
// Discard, we don't implement this
|
||||
ansi_noimpl("Bracketed paste");
|
||||
}
|
||||
else if (n == 25) {
|
||||
screen_set_cursor_visible(yn);
|
||||
}
|
||||
else {
|
||||
ansi_noimpl("CSI ? %d %c", n, opts->key);
|
||||
}
|
||||
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 {
|
||||
// --- DEC standard attributes ---
|
||||
for (int i = 0; i < opts->count; i++) {
|
||||
int n = opts->n[i];
|
||||
|
||||
if (n == 4) {
|
||||
screen_set_insert_mode(yn);
|
||||
}
|
||||
else if (n == 20) {
|
||||
screen_set_newline_mode(yn);
|
||||
}
|
||||
else {
|
||||
ansi_noimpl("CSI %d %c", n, opts->key);
|
||||
}
|
||||
ansi_noimpl("?OPTION %d", n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* CSI [ Pm m
|
||||
* CSI Ps ; Ps ; Ps t
|
||||
* @param opts
|
||||
*/
|
||||
static void ICACHE_FLASH_ATTR do_csi_sgr(CSI_Data *opts)
|
||||
static inline void ICACHE_FLASH_ATTR
|
||||
do_csi_xterm_screen_cmd(CSI_Data *opts)
|
||||
{
|
||||
int count = opts->count;
|
||||
char resp_buf[20];
|
||||
switch (opts->n[0]) {
|
||||
case 8: // set size
|
||||
screen_resize(opts->n[1], opts->n[2]);
|
||||
break;
|
||||
|
||||
if (count == 0) {
|
||||
count = 1; // this makes it work as 0 (reset)
|
||||
}
|
||||
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;
|
||||
|
||||
if (opts->lead != NUL) {
|
||||
// some xterm garbage - discard
|
||||
// CSI > Ps; Ps m
|
||||
return;
|
||||
}
|
||||
case 20: // Report icon
|
||||
case 21: // Report title
|
||||
apars_respond("\033]l");
|
||||
apars_respond(termconf_scratch.title);
|
||||
apars_respond("\033\\");
|
||||
break;
|
||||
|
||||
// iterate arguments
|
||||
for (int i = 0; i < count; i++) {
|
||||
int n = opts->n[i];
|
||||
case 22:
|
||||
ansi_noimpl("Push title");
|
||||
break;
|
||||
case 23:
|
||||
ansi_noimpl("Pop title");
|
||||
break;
|
||||
|
||||
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);
|
||||
}
|
||||
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"},
|
||||
@@ -641,10 +789,11 @@ static const struct DECREPTPARM_baud DECREPTPARM_baud_arr[] = {
|
||||
};
|
||||
|
||||
/**
|
||||
* CSI [ Ps x
|
||||
* CSI Ps x
|
||||
* @param opts
|
||||
*/
|
||||
static void ICACHE_FLASH_ATTR do_csi_decreqtparm(CSI_Data *opts)
|
||||
static inline void ICACHE_FLASH_ATTR
|
||||
do_csi_decreqtparm(CSI_Data *opts)
|
||||
{
|
||||
const int n1 = opts->n[0];
|
||||
|
||||
|
||||
+1
-1
@@ -5,6 +5,6 @@
|
||||
#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 keychar);
|
||||
void apars_handle_csi(char leadchar, const int *params, int count, char interchar, char keychar);
|
||||
|
||||
#endif //ESP_VT100_FIRMWARE_APARS_CSI_H
|
||||
|
||||
+5
-3
@@ -22,7 +22,6 @@
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_handle_osc(const char *buffer)
|
||||
{
|
||||
const char *orig_buff = buffer;
|
||||
int n = 0;
|
||||
char c = 0;
|
||||
while ((c = *buffer++) != 0) {
|
||||
@@ -39,15 +38,18 @@ apars_handle_osc(const char *buffer)
|
||||
if (n == 0 || n == 2) {
|
||||
screen_set_title(buffer);
|
||||
}
|
||||
else if (n >= 81 && n <= 85) { // numbers chosen to not collide with any xterm supported codes
|
||||
else if (n >= 81 && n <= 85) { // 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", orig_buff);
|
||||
ansi_warn("BAD OSC: %s", buffer);
|
||||
apars_show_context();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,6 +121,10 @@ void ICACHE_FLASH_ATTR apars_handle_short_cmd(char c)
|
||||
screen_reset();
|
||||
break;
|
||||
|
||||
case '6': // back index
|
||||
screen_back_index(1);
|
||||
break;
|
||||
|
||||
case '7': // save cursor + attributes
|
||||
screen_cursor_save(true);
|
||||
break;
|
||||
|
||||
+57
-9
@@ -3,21 +3,24 @@
|
||||
#include <cgiwebsocket.h>
|
||||
|
||||
#include "cgi_sockets.h"
|
||||
#include "uart_driver.h"
|
||||
#include "screen.h"
|
||||
#include "uart_buffer.h"
|
||||
#include "ansi_parser.h"
|
||||
#include "jstring.h"
|
||||
#include "uart_driver.h"
|
||||
|
||||
#define LOOPBACK 0
|
||||
|
||||
// Heartbeat interval in ms
|
||||
#define HB_TIME 1000
|
||||
|
||||
// Buffer size (sent in one go)
|
||||
// Must be less than httpd sendbuf
|
||||
#define SOCK_BUF_LEN 2000
|
||||
|
||||
volatile bool notify_available = true;
|
||||
volatile bool notify_cooldown = false;
|
||||
|
||||
volatile bool browser_wants_xon = false;
|
||||
|
||||
static ETSTimer notifyContentTim;
|
||||
static ETSTimer notifyLabelsTim;
|
||||
static ETSTimer notifyCooldownTim;
|
||||
@@ -37,6 +40,10 @@ notifyCooldownTimCb(void *arg)
|
||||
notify_cooldown = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell browser we have new content
|
||||
* @param arg
|
||||
*/
|
||||
static void ICACHE_FLASH_ATTR
|
||||
notifyContentTimCb(void *arg)
|
||||
{
|
||||
@@ -71,6 +78,10 @@ notifyContentTimCb(void *arg)
|
||||
TIMER_START(¬ifyCooldownTim, notifyCooldownTimCb, termconf->display_cooldown_ms, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell browsers about the new text labels
|
||||
* @param arg
|
||||
*/
|
||||
static void ICACHE_FLASH_ATTR
|
||||
notifyLabelsTimCb(void *arg)
|
||||
{
|
||||
@@ -124,6 +135,14 @@ void ICACHE_FLASH_ATTR screen_notifyChange(ScreenNotifyChangeTopic topic)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mouse event rx
|
||||
* @param evt - event type: p, r, m
|
||||
* @param y - coord 0-based
|
||||
* @param x - coord 0-based
|
||||
* @param button - which button, 1-based. 0=none
|
||||
* @param mods - modifier keys bitmap: meta|alt|shift|ctrl
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR sendMouseAction(char evt, int y, int x, int button, u8 mods)
|
||||
{
|
||||
// one-based
|
||||
@@ -183,7 +202,7 @@ void ICACHE_FLASH_ATTR sendMouseAction(char evt, int y, int x, int button, u8 mo
|
||||
sprintf(buf, "\x1b[M%c%c%c", (u8)(32+eventcode), (u8)(32+x), (u8)(32+y));
|
||||
}
|
||||
else if (mte == MTE_SGR) {
|
||||
sprintf(buf, "\x1b[<%d;%d;%d%c", eventcode, x, y, evt == 'p' ? 'M' : 'm');
|
||||
sprintf(buf, "\x1b[<%d;%d;%d%c", eventcode, x, y, (evt == 'p'||(evt=='m'&&button>0)) ? 'M' : 'm');
|
||||
}
|
||||
else if (mte == MTE_URXVT) {
|
||||
sprintf(buf, "\x1b[%d;%d;%dM", (u8)(32+eventcode), (u8)(32+x), (u8)(32+y));
|
||||
@@ -207,21 +226,31 @@ void ICACHE_FLASH_ATTR updateSockRx(Websock *ws, char *data, int len, int flags)
|
||||
switch (c) {
|
||||
case 's':
|
||||
// pass string verbatim
|
||||
#if LOOPBACK
|
||||
for(int i=4;i<strlen(data); i++) {
|
||||
if (termconf_scratch.loopback) {
|
||||
for (int i = 1; i < len; i++) {
|
||||
ansi_parser(data[i]);
|
||||
}
|
||||
#else
|
||||
}
|
||||
UART_SendAsync(data+1, -1);
|
||||
#endif
|
||||
|
||||
// TODO base this on the actual buffer empty space, not rx chunk size
|
||||
if ((UART_AsyncTxGetEmptySpace() < 256) && !browser_wants_xon) {
|
||||
UART_WriteChar(UART1, '-', 100);
|
||||
cgiWebsockBroadcast(URL_WS_UPDATE, "-", 1, 0);
|
||||
browser_wants_xon = true;
|
||||
|
||||
system_soft_wdt_feed();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
// action button press
|
||||
btnNum = (u8) (data[1]);
|
||||
if (btnNum > 0 && btnNum < 10) {
|
||||
UART_SendAsync((const char *) &btnNum, 1); // TODO this is where we use user-configured codes
|
||||
UART_SendAsync(termconf_scratch.btn_msg[btnNum-1], -1);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'm':
|
||||
case 'p':
|
||||
case 'r':
|
||||
@@ -235,11 +264,13 @@ void ICACHE_FLASH_ATTR updateSockRx(Websock *ws, char *data, int len, int flags)
|
||||
|
||||
sendMouseAction(c,y,x,b,m);
|
||||
break;
|
||||
|
||||
default:
|
||||
ws_warn("Bad command.");
|
||||
}
|
||||
}
|
||||
|
||||
/** Send a heartbeat msg */
|
||||
void ICACHE_FLASH_ATTR heartbeatTimCb(void *unused)
|
||||
{
|
||||
if (notify_available) {
|
||||
@@ -257,3 +288,20 @@ void ICACHE_FLASH_ATTR updateSockConnect(Websock *ws)
|
||||
|
||||
TIMER_START(&heartbeatTim, heartbeatTimCb, HB_TIME, 1);
|
||||
}
|
||||
|
||||
ETSTimer xonTim;
|
||||
|
||||
void ICACHE_FLASH_ATTR notify_empty_txbuf_cb(void *unused)
|
||||
{
|
||||
UART_WriteChar(UART1, '+', 100);
|
||||
cgiWebsockBroadcast(URL_WS_UPDATE, "+", 1, 0);
|
||||
browser_wants_xon = false;
|
||||
}
|
||||
|
||||
|
||||
void notify_empty_txbuf(void)
|
||||
{
|
||||
if (browser_wants_xon) {
|
||||
TIMER_START(&xonTim, notify_empty_txbuf_cb, 1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -4,11 +4,11 @@
|
||||
#define URL_WS_UPDATE "/term/update.ws"
|
||||
|
||||
#include <cgiwebsocket.h>
|
||||
#include "screen.h"
|
||||
|
||||
/** Update websocket connect callback */
|
||||
void updateSockConnect(Websock *ws);
|
||||
void screen_notifyChange(ScreenNotifyChangeTopic topic);
|
||||
|
||||
void notify_empty_txbuf(void);
|
||||
|
||||
void send_beep(void);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "persist.h"
|
||||
#include "syscfg.h"
|
||||
#include "uart_driver.h"
|
||||
#include "ansi_parser.h"
|
||||
|
||||
#define SET_REDIR_SUC "/cfg/system"
|
||||
#define SET_REDIR_ERR SET_REDIR_SUC"?err="
|
||||
@@ -18,6 +19,22 @@ static void ICACHE_FLASH_ATTR tmrCb(void *arg)
|
||||
system_restart();
|
||||
}
|
||||
|
||||
httpd_cgi_state ICACHE_FLASH_ATTR cgiResetScreen(HttpdConnData *connData)
|
||||
{
|
||||
if (connData->conn==NULL) {
|
||||
//Connection aborted. Clean up.
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
info("--- User request to reset screen! ---");
|
||||
// this copies termconf to scratch and also resets the screen
|
||||
terminal_apply_settings();
|
||||
ansi_parser_reset();
|
||||
|
||||
httpdRedirect(connData, "/");
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
httpd_cgi_state ICACHE_FLASH_ATTR cgiResetDevice(HttpdConnData *connData)
|
||||
{
|
||||
if (connData->conn==NULL) {
|
||||
|
||||
@@ -8,5 +8,6 @@ httpd_cgi_state cgiPing(HttpdConnData *connData);
|
||||
httpd_cgi_state cgiResetDevice(HttpdConnData *connData);
|
||||
httpd_cgi_state cgiSystemCfgSetParams(HttpdConnData *connData);
|
||||
httpd_cgi_state tplSystemCfg(HttpdConnData *connData, char *token, void **arg);
|
||||
httpd_cgi_state cgiResetScreen(HttpdConnData *connData);
|
||||
|
||||
#endif // CGI_PING_H
|
||||
|
||||
+107
-7
@@ -120,6 +120,24 @@ cgiTermCfgSetParams(HttpdConnData *connData)
|
||||
termconf->fn_alt_mode = (bool)n;
|
||||
}
|
||||
|
||||
if (GET_ARG("show_buttons")) {
|
||||
dbg("Show buttons: %s", buff);
|
||||
n = atoi(buff);
|
||||
termconf->show_buttons = (bool)n;
|
||||
}
|
||||
|
||||
if (GET_ARG("show_config_links")) {
|
||||
dbg("Show config links: %s", buff);
|
||||
n = atoi(buff);
|
||||
termconf->show_config_links = (bool)n;
|
||||
}
|
||||
|
||||
if (GET_ARG("loopback")) {
|
||||
dbg("Loopback: %s", buff);
|
||||
n = atoi(buff);
|
||||
termconf->loopback = (bool)n;
|
||||
}
|
||||
|
||||
if (GET_ARG("default_fg")) {
|
||||
dbg("Screen default FG: %s", buff);
|
||||
n = atoi(buff);
|
||||
@@ -150,11 +168,67 @@ cgiTermCfgSetParams(HttpdConnData *connData)
|
||||
strncpy_safe(termconf->title, buff, 64); // ATTN those must match the values in
|
||||
}
|
||||
|
||||
for (int i = 1; i <= 5; i++) {
|
||||
sprintf(buff, "btn%d", i);
|
||||
for (int btn_i = 1; btn_i <= TERM_BTN_COUNT; btn_i++) {
|
||||
sprintf(buff, "btn%d", btn_i);
|
||||
if (GET_ARG(buff)) {
|
||||
dbg("Button%d default text: \"%s\"", i, buff);
|
||||
strncpy_safe(termconf->btn[i-1], buff, TERM_BTN_LEN);
|
||||
dbg("Button%d default text: \"%s\"", btn_i, buff);
|
||||
strncpy_safe(termconf->btn[btn_i-1], buff, TERM_BTN_LEN);
|
||||
}
|
||||
|
||||
sprintf(buff, "bm%d", btn_i);
|
||||
if (GET_ARG(buff)) {
|
||||
dbg("Button%d message (ASCII): \"%s\"", btn_i, buff);
|
||||
|
||||
// parse: comma,space or semicolon separated decimal values of ASCII codes
|
||||
char c;
|
||||
char *cp = buff;
|
||||
int char_i = 0;
|
||||
int acu = 0;
|
||||
bool lastsp = 1;
|
||||
char buff_bm[TERM_BTN_MSG_LEN];
|
||||
while ((c = *cp++) != 0) {
|
||||
if (c == ' ' || c == ',' || c == ';') {
|
||||
if(lastsp) continue;
|
||||
|
||||
if (acu==0 || acu>255) {
|
||||
warn("Bad value! %d", acu);
|
||||
redir_url += sprintf(redir_url, "bm%d,", btn_i);
|
||||
break;
|
||||
}
|
||||
|
||||
if (char_i >= TERM_BTN_MSG_LEN-1) {
|
||||
warn("Too long! %d", acu);
|
||||
redir_url += sprintf(redir_url, "bm%d,", btn_i);
|
||||
break;
|
||||
}
|
||||
|
||||
dbg("acu %d", acu);
|
||||
buff_bm[char_i++] = (char)acu;
|
||||
|
||||
// prepare for next char
|
||||
acu = 0;
|
||||
lastsp = 1;
|
||||
} else if (c>='0'&&c<='9') {
|
||||
lastsp = 0;
|
||||
acu *= 10;
|
||||
acu += c - '0';
|
||||
} else {
|
||||
warn("Bad syntax!");
|
||||
redir_url += sprintf(redir_url, "bm%d,", btn_i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (lastsp && char_i == 0) {
|
||||
warn("Required!");
|
||||
redir_url += sprintf(redir_url, "bm%d,", btn_i);
|
||||
}
|
||||
if (!lastsp) {
|
||||
buff_bm[char_i++] = (char)acu;
|
||||
}
|
||||
buff_bm[char_i] = 0;
|
||||
dbg("%s, chari = %d", buff_bm, char_i);
|
||||
|
||||
strncpy(termconf->btn_msg[btn_i-1], buff_bm, TERM_BTN_MSG_LEN);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,6 +286,15 @@ tplTermCfg(HttpdConnData *connData, char *token, void **arg)
|
||||
else if (streq(token, "fn_alt_mode")) {
|
||||
sprintf(buff, "%d", (int)termconf->fn_alt_mode);
|
||||
}
|
||||
else if (streq(token, "show_buttons")) {
|
||||
sprintf(buff, "%d", (int)termconf->show_buttons);
|
||||
}
|
||||
else if (streq(token, "show_config_links")) {
|
||||
sprintf(buff, "%d", (int)termconf->show_config_links);
|
||||
}
|
||||
else if (streq(token, "loopback")) {
|
||||
sprintf(buff, "%d", (int)termconf->loopback);
|
||||
}
|
||||
else if (streq(token, "theme")) {
|
||||
sprintf(buff, "%d", termconf->theme);
|
||||
}
|
||||
@@ -225,10 +308,27 @@ tplTermCfg(HttpdConnData *connData, char *token, void **arg)
|
||||
strncpy_safe(buff, termconf->title, BUFLEN);
|
||||
}
|
||||
else {
|
||||
for (int i = 1; i <= 5; i++) {
|
||||
sprintf(buff2, "btn%d", i);
|
||||
for (int btn_i = 1; btn_i <= TERM_BTN_COUNT; btn_i++) {
|
||||
sprintf(buff2, "btn%d", btn_i);
|
||||
if (streq(token, buff2)) {
|
||||
strncpy_safe(buff, termconf->btn[i-1], BUFLEN);
|
||||
strncpy_safe(buff, termconf->btn[btn_i-1], BUFLEN);
|
||||
break;
|
||||
}
|
||||
|
||||
sprintf(buff2, "bm%d", btn_i);
|
||||
if (streq(token, buff2)) {
|
||||
char c;
|
||||
char *bp = buff;
|
||||
char *cp = termconf->btn_msg[btn_i-1];
|
||||
int n = 0;
|
||||
while((c = *cp++) != 0) {
|
||||
if(n>0) {
|
||||
*bp = ',';
|
||||
bp++;
|
||||
}
|
||||
bp += sprintf(bp, "%d", (int)c);
|
||||
n++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/09/06.
|
||||
//
|
||||
|
||||
#ifndef ESPTERM_CHARACTER_SETS_H_H
|
||||
#define ESPTERM_CHARACTER_SETS_H_H
|
||||
|
||||
#include <c_types.h>
|
||||
|
||||
// Tables must be contiguous!
|
||||
|
||||
#define CODEPAGE_A_BEGIN 35
|
||||
#define CODEPAGE_A_END 35
|
||||
|
||||
static const u16 codepage_A[] =
|
||||
{// Unicode ASCII SYM
|
||||
// %%BEGIN:A%%
|
||||
0x20a4, // 35 # £
|
||||
// %%END:A%%
|
||||
};
|
||||
|
||||
#define CODEPAGE_0_BEGIN 96
|
||||
#define CODEPAGE_0_END 126
|
||||
|
||||
/**
|
||||
* translates VT100 ACS escape codes to Unicode values.
|
||||
* Based on rxvt-unicode screen.C table.
|
||||
*/
|
||||
static const u16 codepage_0[] =
|
||||
{// Unicode ASCII SYM
|
||||
// %%BEGIN:0%%
|
||||
0x2666, // 96 ` ♦
|
||||
0x2592, // 97 a ▒
|
||||
0x2409, // 98 b HT
|
||||
0x240c, // 99 c FF
|
||||
0x240d, // 100 d CR
|
||||
0x240a, // 101 e LF
|
||||
0x00b0, // 102 f °
|
||||
0x00b1, // 103 g ±
|
||||
0x2424, // 104 h NL
|
||||
0x240b, // 105 i VT
|
||||
0x2518, // 106 j ┘
|
||||
0x2510, // 107 k ┐
|
||||
0x250c, // 108 l ┌
|
||||
0x2514, // 109 m └
|
||||
0x253c, // 110 n ┼
|
||||
0x23ba, // 111 o ⎺
|
||||
0x23bb, // 112 p ⎻
|
||||
0x2500, // 113 q ─
|
||||
0x23bc, // 114 r ⎼
|
||||
0x23bd, // 115 s ⎽
|
||||
0x251c, // 116 t ├
|
||||
0x2524, // 117 u ┤
|
||||
0x2534, // 118 v ┴
|
||||
0x252c, // 119 w ┬
|
||||
0x2502, // 120 x │
|
||||
0x2264, // 121 y ≤
|
||||
0x2265, // 122 z ≥
|
||||
0x03c0, // 123 { π
|
||||
0x2260, // 124 | ≠
|
||||
0x20a4, // 125 } £
|
||||
0x00b7, // 126 ~ ·
|
||||
// %%END:0%%
|
||||
};
|
||||
|
||||
#define CODEPAGE_1_BEGIN 33
|
||||
#define CODEPAGE_1_END 126
|
||||
|
||||
static const u16 codepage_1[] =
|
||||
{// Unicode ASCII SYM DOS
|
||||
// %%BEGIN:1%%
|
||||
0x263A, // 33 ! ☺ (1) - low ASCII symbols from DOS, moved to +32
|
||||
0x263B, // 34 " ☻ (2)
|
||||
0x2665, // 35 # ♥ (3)
|
||||
0x2666, // 36 $ ♦ (4)
|
||||
0x2663, // 37 % ♣ (5)
|
||||
0x2660, // 38 & ♠ (6)
|
||||
0x2022, // 39 ' • (7) - inverse dot and circle left out, can be done with SGR
|
||||
0x231B, // 40 ( ⌛ - hourglass (timer icon)
|
||||
0x25CB, // 41 ) ○ (9)
|
||||
0x21AF, // 42 * ↯ - electricity (lightning monitor...)
|
||||
0x266A, // 43 + ♪ (13)
|
||||
0x266B, // 44 , ♫ (14)
|
||||
0x263C, // 45 - ☼ (15)
|
||||
0x2302, // 46 . ⌂ (127)
|
||||
0x2622, // 47 / ☢ - radioactivity (geiger counter...)
|
||||
0x2591, // 48 0 ░ (176) - this block is kept aligned and ordered from DOS, moved -128
|
||||
0x2592, // 49 1 ▒ (177)
|
||||
0x2593, // 50 2 ▓ (178)
|
||||
0x2502, // 51 3 │ (179)
|
||||
0x2524, // 52 4 ┤ (180)
|
||||
0x2561, // 53 5 ╡ (181)
|
||||
0x2562, // 54 6 ╢ (182)
|
||||
0x2556, // 55 7 ╖ (183)
|
||||
0x2555, // 56 8 ╕ (184)
|
||||
0x2563, // 57 9 ╣ (185)
|
||||
0x2551, // 58 : ║ (186)
|
||||
0x2557, // 59 ; ╗ (187)
|
||||
0x255D, // 60 < ╝ (188)
|
||||
0x255C, // 61 = ╜ (189)
|
||||
0x255B, // 62 > ╛ (190)
|
||||
0x2510, // 63 ? ┐ (191)
|
||||
0x2514, // 64 @ └ (192)
|
||||
0x2534, // 65 A ┴ (193)
|
||||
0x252C, // 66 B ┬ (194)
|
||||
0x251C, // 67 C ├ (195)
|
||||
0x2500, // 68 D ─ (196)
|
||||
0x253C, // 69 E ┼ (197)
|
||||
0x255E, // 70 F ╞ (198)
|
||||
0x255F, // 71 G ╟ (199)
|
||||
0x255A, // 72 H ╚ (200)
|
||||
0x2554, // 73 I ╔ (201)
|
||||
0x2569, // 74 J ╩ (202)
|
||||
0x2566, // 75 K ╦ (203)
|
||||
0x2560, // 76 L ╠ (204)
|
||||
0x2550, // 77 M ═ (205)
|
||||
0x256C, // 78 N ╬ (206)
|
||||
0x2567, // 79 O ╧ (207)
|
||||
0x2568, // 80 P ╨ (208)
|
||||
0x2564, // 81 Q ╤ (209)
|
||||
0x2565, // 82 R ╥ (210)
|
||||
0x2559, // 83 S ╙ (211)
|
||||
0x2558, // 84 T ╘ (212)
|
||||
0x2552, // 85 U ╒ (213)
|
||||
0x2553, // 86 V ╓ (214)
|
||||
0x256B, // 87 W ╫ (215)
|
||||
0x256A, // 88 X ╪ (216)
|
||||
0x2518, // 89 Y ┘ (217)
|
||||
0x250C, // 90 Z ┌ (218)
|
||||
0x2588, // 91 [ █ (219)
|
||||
0x2584, // 92 \ ▄ (220)
|
||||
0x258C, // 93 ] ▌ (221)
|
||||
0x2590, // 94 ^ ▐ (222)
|
||||
0x2580, // 95 _ ▀ (223)
|
||||
0x2195, // 96 ` ↕ (18) - moved from low DOS ASCII
|
||||
0x2191, // 97 a ↑ (24)
|
||||
0x2193, // 98 b ↓ (25)
|
||||
0x2192, // 99 c → (26)
|
||||
0x2190, // 100 d ← (27)
|
||||
0x2194, // 101 e ↔ (29)
|
||||
0x25B2, // 102 f ▲ (30)
|
||||
0x25BC, // 103 g ▼ (31)
|
||||
0x25BA, // 104 h ► (16)
|
||||
0x25C4, // 105 i ◄ (17)
|
||||
0x25E2, // 106 j ◢ - added for slanted corners
|
||||
0x25E3, // 107 k ◣
|
||||
0x25E4, // 108 l ◤
|
||||
0x25E5, // 109 m ◥
|
||||
0x256D, // 110 n ╭ - rounded corners
|
||||
0x256E, // 111 o ╮
|
||||
0x256F, // 112 p ╯
|
||||
0x2570, // 113 q ╰
|
||||
0x0, // 114 r - free positions for future expansion
|
||||
0x0, // 115 s
|
||||
0x0, // 116 t
|
||||
0x0, // 117 u
|
||||
0x0, // 118 v
|
||||
0x0, // 119 w
|
||||
0x0, // 120 x
|
||||
0x0, // 121 y
|
||||
0x0, // 122 z
|
||||
0x0, // 123 {
|
||||
0x0, // 124 |
|
||||
0x2714, // 125 } ✔ - checkboxes or checklist items
|
||||
0x2718, // 126 ~ ✘
|
||||
// %%END:1%%
|
||||
};
|
||||
|
||||
#endif //ESPTERM_CHARACTER_SETS_H_H
|
||||
@@ -38,6 +38,7 @@ HttpdBuiltInUrl routes[] = {
|
||||
// --- System control ---
|
||||
ROUTE_CGI("/system/reset/?", cgiResetDevice),
|
||||
ROUTE_CGI("/system/ping/?", cgiPing),
|
||||
ROUTE_CGI("/system/cls/?", cgiResetScreen),
|
||||
|
||||
// --- WiFi config --- (TODO make this conditional and configurable)
|
||||
#if WIFI_PROTECT
|
||||
|
||||
+109
-193
@@ -6,6 +6,7 @@
|
||||
#include "ascii.h"
|
||||
#include "apars_logging.h"
|
||||
#include "jstring.h"
|
||||
#include "character_sets.h"
|
||||
|
||||
TerminalConfigBundle * const termconf = &persist.current.termconf;
|
||||
TerminalConfigBundle termconf_scratch;
|
||||
@@ -49,11 +50,14 @@ static u32 tab_stops[TABSTOP_WORDS];
|
||||
* Screen state structure
|
||||
*/
|
||||
static struct {
|
||||
bool numpad_alt_mode; //!< Application Mode - affects how user input of control keys is sent
|
||||
bool cursors_alt_mode; //!< Application mode for cursor keys
|
||||
bool numpad_alt_mode; //!< DECNKM - Numpad Application Mode
|
||||
bool cursors_alt_mode; //!< DECCKM - Cursors Application mode
|
||||
|
||||
bool newline_mode; // LNM - CR automatically sends LF
|
||||
bool reverse;
|
||||
bool newline_mode; //!< LNM - CR automatically sends LF
|
||||
bool reverse; //!< DECSCNM - Reverse video
|
||||
|
||||
bool insert_mode; //!< IRM - Insert mode (move rest of the line to the right)
|
||||
bool cursor_visible; //!< DECTCEM - Cursor visible
|
||||
|
||||
// Vertical margin bounds (inclusive start/end of scrolling region)
|
||||
int vm0;
|
||||
@@ -63,29 +67,32 @@ static struct {
|
||||
#define R0 scr.vm0
|
||||
#define R1 scr.vm1
|
||||
#define RH (scr.vm1 - scr.vm0 + 1)
|
||||
// horizontal edges - will be useful if horizontal margin is implemented
|
||||
#define C0 0
|
||||
#define C1 (W-1)
|
||||
|
||||
typedef struct {
|
||||
/* Cursor position */
|
||||
int x; //!< X coordinate
|
||||
int y; //!< Y coordinate
|
||||
bool hanging; //!< xenl state - cursor half-wrapped
|
||||
|
||||
// SGR
|
||||
/* SGR */
|
||||
bool inverse; //!< not in attrs bc it's applied server-side (not sent to browser)
|
||||
u8 attrs;
|
||||
Color fg; //!< Foreground color for writing
|
||||
Color bg; //!< Background color for writing
|
||||
|
||||
// Other attribs
|
||||
|
||||
/* Character set */
|
||||
int charsetN;
|
||||
char charset0;
|
||||
char charset1;
|
||||
bool wraparound; //!< Wrapping when EOL
|
||||
bool origin_mode; // DECOM - absolute positioning is relative to vertical margins
|
||||
bool selective_erase; // TODO implement
|
||||
|
||||
// Not saved/restored FIXME those should not be saved, but are (a bug?)
|
||||
bool insert_mode; //!< Insert mode (move rest of the line to the right)
|
||||
bool visible; //!< Visible (not attribute, DEC special)
|
||||
/** Options saved with cursor */
|
||||
bool auto_wrap; //!< DECAWM - Wrapping when EOL
|
||||
bool origin_mode; //!< DECOM - absolute positioning is relative to vertical margins
|
||||
} CursorTypeDef;
|
||||
|
||||
/**
|
||||
@@ -129,17 +136,24 @@ static volatile int notifyLock = 0;
|
||||
void ICACHE_FLASH_ATTR
|
||||
terminal_restore_defaults(void)
|
||||
{
|
||||
termconf->default_bg = 0;
|
||||
termconf->default_fg = 7;
|
||||
termconf->width = SCR_DEF_WIDTH;
|
||||
termconf->height = SCR_DEF_HEIGHT;
|
||||
termconf->default_bg = 0;
|
||||
termconf->default_fg = 7;
|
||||
sprintf(termconf->title, SCR_DEF_TITLE);
|
||||
for(int i=1; i <= TERM_BTN_COUNT; i++) {
|
||||
sprintf(termconf->btn[i-1], "%d", i);
|
||||
sprintf(termconf->btn_msg[i-1], "%c", i);
|
||||
}
|
||||
termconf->theme = 0;
|
||||
termconf->parser_tout_ms = SCR_DEF_PARSER_TOUT_MS;
|
||||
termconf->display_tout_ms = SCR_DEF_DISPLAY_TOUT_MS;
|
||||
termconf->fn_alt_mode = SCR_DEF_FN_ALT_MODE;
|
||||
sprintf(termconf->title, SCR_DEF_TITLE);
|
||||
for(int i=1; i <= 5; i++) {
|
||||
sprintf(termconf->btn[i-1], "%d", i);
|
||||
}
|
||||
termconf->config_version = TERMCONF_VERSION;
|
||||
termconf->display_cooldown_ms = SCR_DEF_DISPLAY_COOLDOWN_MS;
|
||||
termconf->loopback = 0;
|
||||
termconf->show_buttons = 1;
|
||||
termconf->show_config_links = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,6 +179,24 @@ terminal_apply_settings_noclear(void)
|
||||
changed = 1;
|
||||
}
|
||||
|
||||
// Migrate to v2
|
||||
if (termconf->config_version < 2) {
|
||||
dbg("termconf: Updating to version 2");
|
||||
termconf->loopback = 0;
|
||||
termconf->show_config_links = 1;
|
||||
termconf->show_buttons = 1;
|
||||
changed = 1;
|
||||
}
|
||||
|
||||
// Migrate to v3
|
||||
if (termconf->config_version < 3) {
|
||||
dbg("termconf: Updating to version 3");
|
||||
for(int i=1; i <= TERM_BTN_COUNT; i++) {
|
||||
sprintf(termconf->btn_msg[i-1], "%c", i);
|
||||
}
|
||||
changed = 1;
|
||||
}
|
||||
|
||||
termconf->config_version = TERMCONF_VERSION;
|
||||
|
||||
// Validation...
|
||||
@@ -217,19 +249,26 @@ cursor_reset(void)
|
||||
cursor.x = 0;
|
||||
cursor.y = 0;
|
||||
cursor.hanging = false;
|
||||
cursor.visible = true;
|
||||
cursor.insert_mode = false;
|
||||
cursor.selective_erase = false;
|
||||
cursor.origin_mode = false;
|
||||
|
||||
cursor.charsetN = 0;
|
||||
cursor.charset0 = CS_USASCII;
|
||||
cursor.charset1 = CS_DEC_SUPPLEMENTAL;
|
||||
cursor.wraparound = true;
|
||||
cursor.charset0 = CS_B_USASCII;
|
||||
cursor.charset1 = CS_0_DEC_SUPPLEMENTAL;
|
||||
|
||||
screen_reset_sgr();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the cursor position & colors
|
||||
*/
|
||||
static void ICACHE_FLASH_ATTR
|
||||
decopt_reset(void)
|
||||
{
|
||||
scr.cursor_visible = true;
|
||||
scr.insert_mode = false;
|
||||
cursor.origin_mode = false;
|
||||
cursor.auto_wrap = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the cursor (\e[0m)
|
||||
*/
|
||||
@@ -251,6 +290,7 @@ screen_reset(void)
|
||||
NOTIFY_LOCK();
|
||||
|
||||
cursor_reset();
|
||||
decopt_reset();
|
||||
|
||||
scr.numpad_alt_mode = false;
|
||||
scr.cursors_alt_mode = false;
|
||||
@@ -746,6 +786,10 @@ screen_cursor_get(int *y, int *x)
|
||||
{
|
||||
*x = cursor.x;
|
||||
*y = cursor.y;
|
||||
|
||||
if (cursor.origin_mode) {
|
||||
*y -= R0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -875,6 +919,21 @@ screen_cursor_restore(bool withAttrs)
|
||||
|
||||
NOTIFY_DONE();
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
screen_back_index(int count)
|
||||
{
|
||||
NOTIFY_LOCK();
|
||||
int new_x = cursor.x - count;
|
||||
if (new_x >= 0) {
|
||||
cursor.x = new_x;
|
||||
} else {
|
||||
cursor.x = 0;
|
||||
screen_insert_characters(-new_x);
|
||||
}
|
||||
NOTIFY_DONE();
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
//region --- Attribute setting ---
|
||||
@@ -886,7 +945,7 @@ void ICACHE_FLASH_ATTR
|
||||
screen_set_cursor_visible(bool visible)
|
||||
{
|
||||
NOTIFY_LOCK();
|
||||
cursor.visible = visible;
|
||||
scr.cursor_visible = visible;
|
||||
NOTIFY_DONE();
|
||||
}
|
||||
|
||||
@@ -896,7 +955,7 @@ screen_set_cursor_visible(bool visible)
|
||||
void ICACHE_FLASH_ATTR
|
||||
screen_wrap_enable(bool enable)
|
||||
{
|
||||
cursor.wraparound = enable;
|
||||
cursor.auto_wrap = enable;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -936,19 +995,6 @@ screen_set_sgr_inverse(bool ena)
|
||||
cursor.inverse = ena;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if coords are in range - used for verifying mouse clicks
|
||||
*
|
||||
* @param y
|
||||
* @param x
|
||||
* @return OK
|
||||
*/
|
||||
bool ICACHE_FLASH_ATTR
|
||||
screen_isCoordValid(int y, int x)
|
||||
{
|
||||
return x >= 0 && y >= 0 && x < W && y < H;
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
screen_set_charset_n(int Gx)
|
||||
{
|
||||
@@ -966,7 +1012,7 @@ screen_set_charset(int Gx, char charset)
|
||||
void ICACHE_FLASH_ATTR
|
||||
screen_set_insert_mode(bool insert)
|
||||
{
|
||||
cursor.insert_mode = insert;
|
||||
scr.insert_mode = insert;
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
@@ -1071,7 +1117,7 @@ screen_putchar(const char *ch)
|
||||
if (cursor.hanging) {
|
||||
// perform the scheduled wrap if hanging
|
||||
// if auto-wrap = off, it overwrites the last char
|
||||
if (cursor.wraparound) {
|
||||
if (cursor.auto_wrap) {
|
||||
cursor.x = 0;
|
||||
cursor.y++;
|
||||
// Y wrap
|
||||
@@ -1088,7 +1134,7 @@ screen_putchar(const char *ch)
|
||||
Cell *c = &screen[cursor.x + cursor.y * W];
|
||||
|
||||
// move the rest of the line if we're in Insert Mode
|
||||
if (cursor.x < W-1 && cursor.insert_mode) screen_insert_characters(1);
|
||||
if (cursor.x < W-1 && scr.insert_mode) screen_insert_characters(1);
|
||||
|
||||
if (ch[1] == 0 && ch[0] <= 0x7f) {
|
||||
// we have len=1 and ASCII
|
||||
@@ -1119,143 +1165,6 @@ screen_putchar(const char *ch)
|
||||
NOTIFY_DONE();
|
||||
}
|
||||
|
||||
/**
|
||||
* translates VT100 ACS escape codes to Unicode values.
|
||||
* Based on rxvt-unicode screen.C table.
|
||||
*/
|
||||
static const u16 codepage_0[] =
|
||||
{// Unicode ASCII SYM
|
||||
0x2666, // 96 ` ♦
|
||||
0x2592, // 97 a ▒
|
||||
0x2409, // 98 b HT
|
||||
0x240c, // 99 c FF
|
||||
0x240d, // 100 d CR
|
||||
0x240a, // 101 e LF
|
||||
0x00b0, // 102 f °
|
||||
0x00b1, // 103 g ±
|
||||
0x2424, // 104 h NL
|
||||
0x240b, // 105 i VT
|
||||
0x2518, // 106 j ┘
|
||||
0x2510, // 107 k ┐
|
||||
0x250c, // 108 l ┌
|
||||
0x2514, // 109 m └
|
||||
0x253c, // 110 n ┼
|
||||
0x23ba, // 111 o ⎺
|
||||
0x23bb, // 112 p ⎻
|
||||
0x2500, // 113 q ─
|
||||
0x23bc, // 114 r ⎼
|
||||
0x23bd, // 115 s ⎽
|
||||
0x251c, // 116 t ├
|
||||
0x2524, // 117 u ┤
|
||||
0x2534, // 118 v ┴
|
||||
0x252c, // 119 w ┬
|
||||
0x2502, // 120 x │
|
||||
0x2264, // 121 y ≤
|
||||
0x2265, // 122 z ≥
|
||||
0x03c0, // 123 { π
|
||||
0x2260, // 124 | ≠
|
||||
0x20a4, // 125 } £
|
||||
0x00b7, // 126 ~ ·
|
||||
};
|
||||
|
||||
static const u16 codepage_1[] =
|
||||
{// Unicode ASCII SYM DOS
|
||||
0x263A, // 33 ! ☺ (1) - low ASCII symbols from DOS, moved to +32
|
||||
0x263B, // 34 " ☻ (2)
|
||||
0x2665, // 35 # ♥ (3)
|
||||
0x2666, // 36 $ ♦ (4)
|
||||
0x2663, // 37 % ♣ (5)
|
||||
0x2660, // 38 & ♠ (6)
|
||||
0x2022, // 39 ' • (7) - inverse dot and circle left out, can be done with SGR
|
||||
0x0,//0x231B, // 40 ( ⌛ - hourglass (timer icon)
|
||||
0x25CB, // 41 ) ○ (9)
|
||||
0x21AF, // 42 * ↯ - electricity (lightning monitor...)
|
||||
0x266A, // 43 + ♪ (13)
|
||||
0x266B, // 44 , ♫ (14)
|
||||
0x263C, // 45 - ☼ (15)
|
||||
0x2302, // 46 . ⌂ (127)
|
||||
0x0,//0x2622, // 47 / ☢ - radioactivity (geiger counter...)
|
||||
0x2591, // 48 0 ░ (176) - this block is kept aligned and ordered from DOS, moved -128
|
||||
0x2592, // 49 1 ▒ (177)
|
||||
0x2593, // 50 2 ▓ (178)
|
||||
0x2502, // 51 3 │ (179)
|
||||
0x2524, // 52 4 ┤ (180)
|
||||
0x2561, // 53 5 ╡ (181)
|
||||
0x2562, // 54 6 ╢ (182)
|
||||
0x2556, // 55 7 ╖ (183)
|
||||
0x2555, // 56 8 ╕ (184)
|
||||
0x2563, // 57 9 ╣ (185)
|
||||
0x2551, // 58 : ║ (186)
|
||||
0x2557, // 59 ; ╗ (187)
|
||||
0x255D, // 60 < ╝ (188)
|
||||
0x255C, // 61 = ╜ (189)
|
||||
0x255B, // 62 > ╛ (190)
|
||||
0x2510, // 63 ? ┐ (191)
|
||||
0x2514, // 64 @ └ (192)
|
||||
0x2534, // 65 A ┴ (193)
|
||||
0x252C, // 66 B ┬ (194)
|
||||
0x251C, // 67 C ├ (195)
|
||||
0x2500, // 68 D ─ (196)
|
||||
0x253C, // 69 E ┼ (197)
|
||||
0x255E, // 70 F ╞ (198)
|
||||
0x255F, // 71 G ╟ (199)
|
||||
0x255A, // 72 H ╚ (200)
|
||||
0x2554, // 73 I ╔ (201)
|
||||
0x2569, // 74 J ╩ (202)
|
||||
0x2566, // 75 K ╦ (203)
|
||||
0x2560, // 76 L ╠ (204)
|
||||
0x2550, // 77 M ═ (205)
|
||||
0x256C, // 78 N ╬ (206)
|
||||
0x2567, // 79 O ╧ (207)
|
||||
0x2568, // 80 P ╨ (208)
|
||||
0x2564, // 81 Q ╤ (209)
|
||||
0x2565, // 82 R ╥ (210)
|
||||
0x2559, // 83 S ╙ (211)
|
||||
0x2558, // 84 T ╘ (212)
|
||||
0x2552, // 85 U ╒ (213)
|
||||
0x2553, // 86 V ╓ (214)
|
||||
0x256B, // 87 W ╫ (215)
|
||||
0x256A, // 88 X ╪ (216)
|
||||
0x2518, // 89 Y ┘ (217)
|
||||
0x250C, // 90 Z ┌ (218)
|
||||
0x2588, // 91 [ █ (219)
|
||||
0x2584, // 92 \ ▄ (220)
|
||||
0x258C, // 93 ] ▌ (221)
|
||||
0x2590, // 94 ^ ▐ (222)
|
||||
0x2580, // 95 _ ▀ (223)
|
||||
0x2195, // 96 ` ↕ (18) - moved from low DOS ASCII
|
||||
0x2191, // 97 a ↑ (24)
|
||||
0x2193, // 98 b ↓ (25)
|
||||
0x2192, // 99 c → (26)
|
||||
0x2190, // 100 d ← (27)
|
||||
0x2194, // 101 e ↔ (29)
|
||||
0x25B2, // 102 f ▲ (30)
|
||||
0x25BC, // 103 g ▼ (31)
|
||||
0x25BA, // 104 h ► (16)
|
||||
0x25C4, // 105 i ◄ (17)
|
||||
0x0,//0x25E2, // 106 j ◢ - added for slanted corners
|
||||
0x0,//0x25E3, // 107 k ◣
|
||||
0x0,//0x25E4, // 108 l ◤
|
||||
0x0,//0x25E5, // 109 m ◥
|
||||
0x256D, // 110 n ╭ - rounded corners
|
||||
0x256E, // 111 o ╮
|
||||
0x256F, // 112 p ╯
|
||||
0x2570, // 113 q ╰
|
||||
0x0, // 114 r - free positions for future expansion
|
||||
0x0, // 115 s
|
||||
0x0, // 116 t
|
||||
0x0, // 117 u
|
||||
0x0, // 118 v
|
||||
0x0, // 119 w
|
||||
0x0, // 120 x
|
||||
0x0, // 121 y
|
||||
0x0, // 122 z
|
||||
0x0, // 123 {
|
||||
0x0, // 124 |
|
||||
0x0,//0x2714, // 125 } ✔ - checkboxes or checklist items
|
||||
0x0,//0x2718, // 126 ~ ✘
|
||||
};
|
||||
|
||||
/**
|
||||
* UTF remap
|
||||
* @param out - output char[4]
|
||||
@@ -1269,26 +1178,29 @@ utf8_remap(char *out, char g, char charset)
|
||||
u16 utf = (unsigned char)g;
|
||||
|
||||
switch (charset) {
|
||||
case CS_DEC_SUPPLEMENTAL: /* DEC Special Character & Line Drawing Set */
|
||||
if ((g >= 96) && (g < 0x7F)) {
|
||||
n = codepage_0[g - 96];
|
||||
case CS_0_DEC_SUPPLEMENTAL: /* DEC Special Character & Line Drawing Set */
|
||||
if ((g >= CODEPAGE_0_BEGIN) && (g <= CODEPAGE_0_END)) {
|
||||
n = codepage_0[g - CODEPAGE_0_BEGIN];
|
||||
if (n) utf = n;
|
||||
}
|
||||
break;
|
||||
|
||||
case CS_DOS_437: /* ESPTerm Character Rom 1 */
|
||||
if ((g >= 33) && (g < 0x7F)) {
|
||||
n = codepage_1[g - 33];
|
||||
case CS_1_DOS_437: /* ESPTerm Character Rom 1 */
|
||||
if ((g >= CODEPAGE_1_BEGIN) && (g <= CODEPAGE_1_END)) {
|
||||
n = codepage_1[g - CODEPAGE_1_BEGIN];
|
||||
if (n) utf = n;
|
||||
}
|
||||
break;
|
||||
|
||||
case CS_UKASCII: /* UK, replaces # with GBP */
|
||||
if (g == '#') utf = 0x20a4;
|
||||
case CS_A_UKASCII: /* UK, replaces # with GBP */
|
||||
if ((g >= CODEPAGE_A_BEGIN) && (g <= CODEPAGE_A_END)) {
|
||||
n = codepage_A[g - CODEPAGE_A_BEGIN];
|
||||
if (n) utf = n;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
case CS_USASCII:
|
||||
case CS_B_USASCII:
|
||||
// No change
|
||||
break;
|
||||
}
|
||||
@@ -1395,11 +1307,15 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, void **data)
|
||||
encode2B((u16) cursor.y, &w3);
|
||||
encode2B((u16) cursor.x, &w4);
|
||||
encode2B((u16) (
|
||||
(cursor.visible ? 0x01 : 0) |
|
||||
(cursor.hanging ? 0x02 : 0) |
|
||||
(scr.cursors_alt_mode ? 0x04 : 0) |
|
||||
(scr.numpad_alt_mode ? 0x08 : 0) |
|
||||
(termconf->fn_alt_mode ? 0x10 : 0)
|
||||
(scr.cursor_visible ? 1<<0 : 0) |
|
||||
(cursor.hanging ? 1<<1 : 0) |
|
||||
(scr.cursors_alt_mode ? 1<<2 : 0) |
|
||||
(scr.numpad_alt_mode ? 1<<3 : 0) |
|
||||
(termconf->fn_alt_mode ? 1<<4 : 0) |
|
||||
((mouse_tracking.mode>MTM_NONE) ? 1<<5 : 0) | // disables context menu
|
||||
((mouse_tracking.mode>=MTM_NORMAL) ? 1<<6 : 0) | // disables selecting
|
||||
(termconf_scratch.show_buttons ? 1<<7 : 0) |
|
||||
(termconf_scratch.show_config_links ? 1<<8 : 0)
|
||||
)
|
||||
, &w5);
|
||||
|
||||
|
||||
+17
-11
@@ -36,15 +36,17 @@
|
||||
|
||||
// Size designed for the terminal config structure
|
||||
// Must be constant to avoid corrupting user config after upgrade
|
||||
#define TERMCONF_SIZE 200
|
||||
#define TERMCONF_SIZE 300
|
||||
|
||||
#define TERM_BTN_LEN 10
|
||||
#define TERM_BTN_MSG_LEN 10
|
||||
#define TERM_TITLE_LEN 64
|
||||
#define TERM_BTN_COUNT 5
|
||||
|
||||
#define SCR_DEF_DISPLAY_TOUT_MS 10
|
||||
#define SCR_DEF_DISPLAY_COOLDOWN_MS 30
|
||||
#define SCR_DEF_PARSER_TOUT_MS 10
|
||||
#define SCR_DEF_FN_ALT_MODE false
|
||||
#define SCR_DEF_FN_ALT_MODE true // true - SS3 codes, easier to parse & for xterm compatibility
|
||||
#define SCR_DEF_WIDTH 26
|
||||
#define SCR_DEF_HEIGHT 10
|
||||
#define SCR_DEF_TITLE "ESPTerm"
|
||||
@@ -52,7 +54,7 @@
|
||||
/** Maximum screen size (determines size of the static data array) */
|
||||
#define MAX_SCREEN_SIZE (80*25)
|
||||
|
||||
#define TERMCONF_VERSION 1
|
||||
#define TERMCONF_VERSION 3
|
||||
|
||||
// --- Persistent Settings ---
|
||||
|
||||
@@ -62,13 +64,17 @@ typedef struct {
|
||||
u8 default_bg; // should be the Color typedef, but this way the size is more explicit
|
||||
u8 default_fg;
|
||||
char title[TERM_TITLE_LEN];
|
||||
char btn[5][TERM_BTN_LEN];
|
||||
char btn[TERM_BTN_COUNT][TERM_BTN_LEN];
|
||||
u8 theme;
|
||||
u32 parser_tout_ms;
|
||||
u32 display_tout_ms;
|
||||
bool fn_alt_mode; // xterm compatibility mode (alternate codes for some FN keys)
|
||||
u8 config_version;
|
||||
u32 display_cooldown_ms;
|
||||
bool loopback;
|
||||
bool show_buttons;
|
||||
bool show_config_links;
|
||||
char btn_msg[TERM_BTN_COUNT][TERM_BTN_MSG_LEN];
|
||||
} TerminalConfigBundle;
|
||||
|
||||
// Live config
|
||||
@@ -84,7 +90,7 @@ enum MTM {
|
||||
MTM_NONE = 0,
|
||||
MTM_X10 = 1,
|
||||
MTM_NORMAL = 2,
|
||||
MTM_BUTTON_MOTION = 3,
|
||||
MTM_BUTTON_MOTION = 3, // any higher mode tracks motion
|
||||
MTM_ANY_MOTION = 4,
|
||||
};
|
||||
|
||||
@@ -121,10 +127,10 @@ void screen_set_button_text(int num, const char *text);
|
||||
// --- Encoding ---
|
||||
|
||||
typedef enum {
|
||||
CS_USASCII = 'B',
|
||||
CS_UKASCII = 'A',
|
||||
CS_DEC_SUPPLEMENTAL = '0',
|
||||
CS_DOS_437 = '1',
|
||||
CS_B_USASCII = 'B',
|
||||
CS_A_UKASCII = 'A',
|
||||
CS_0_DEC_SUPPLEMENTAL = '0',
|
||||
CS_1_DOS_437 = '1',
|
||||
} CHARSET;
|
||||
|
||||
httpd_cgi_state screenSerializeToBuffer(char *buffer, size_t buf_len, void **data);
|
||||
@@ -248,6 +254,8 @@ void screen_clear_tab(void);
|
||||
void screen_tab_forward(int count);
|
||||
/** Move backward one tab */
|
||||
void screen_tab_reverse(int count);
|
||||
/** Move left, shift right if at the boundary */
|
||||
void screen_back_index(int count);
|
||||
|
||||
// --- Printing characters ---
|
||||
|
||||
@@ -265,8 +273,6 @@ void screen_fill_with_E(void);
|
||||
|
||||
// --- Queries ---
|
||||
|
||||
/** Check if coord is valid */
|
||||
bool screen_isCoordValid(int y, int x);
|
||||
/** Report current SGR as num;num;... for DAC query */
|
||||
void screen_report_sgr(char *buffer);
|
||||
|
||||
|
||||
+4
-4
@@ -40,10 +40,10 @@ static void buf_pop(void *unused)
|
||||
}
|
||||
}
|
||||
|
||||
LOCAL void my_putc(char c)
|
||||
{
|
||||
UART_WriteCharCRLF(UART1, (u8) c, 10);
|
||||
}
|
||||
//LOCAL void my_putc(char c)
|
||||
//{
|
||||
// UART_WriteCharCRLF(UART1, (u8) c, 10);
|
||||
//}
|
||||
|
||||
/**
|
||||
* Init the serial ports
|
||||
|
||||
@@ -1,830 +0,0 @@
|
||||
/*
|
||||
* File : uart.c
|
||||
* Copyright (C) 2013 - 2016, Espressif Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of version 3 of the GNU General Public License as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <esp8266.h>
|
||||
#include <uart_register.h>
|
||||
#include "uart_asyncdrv.h-"
|
||||
|
||||
// UartDev is defined and initialized in rom code.
|
||||
extern UartDevice UartDev;
|
||||
|
||||
struct UartBuffer {
|
||||
uint32 UartBuffSize;
|
||||
uint8 *pUartBuff;
|
||||
uint8 *pInPos;
|
||||
uint8 *pOutPos;
|
||||
STATUS BuffState;
|
||||
uint16 Space; //remanent space of the buffer
|
||||
uint8 TcpControl;
|
||||
struct UartBuffer *nextBuff;
|
||||
};
|
||||
|
||||
struct UartRxBuff {
|
||||
uint32 UartRxBuffSize;
|
||||
uint8 *pUartRxBuff;
|
||||
uint8 *pWritePos;
|
||||
uint8 *pReadPos;
|
||||
STATUS RxBuffState;
|
||||
uint32 Space; //remanent space of the buffer
|
||||
};
|
||||
|
||||
LOCAL struct UartBuffer* pTxBuffer = NULL;
|
||||
LOCAL struct UartBuffer* pRxBuffer = NULL;
|
||||
|
||||
// Forward declare
|
||||
#if UART_BUFF_EN
|
||||
LOCAL void Uart_Buf_Cpy(struct UartBuffer* pCur, char* pdata , uint16 data_len);
|
||||
void uart_buf_free(struct UartBuffer* pBuff);
|
||||
void tx_buff_enq(char* pdata, uint16 data_len );
|
||||
LOCAL void tx_fifo_insert(struct UartBuffer* pTxBuff, uint8 data_len, uint8 uart_no);
|
||||
void tx_start_uart_buffer(uint8 uart_no);
|
||||
uint16 rx_buff_deq(char* pdata, uint16 data_len );
|
||||
void Uart_rx_buff_enq();
|
||||
#endif
|
||||
|
||||
//void ICACHE_FLASH_ATTR uart_test_rx();
|
||||
STATUS uart_tx_one_char(uint8 uart, uint8 TxChar);
|
||||
STATUS uart_tx_one_char_no_wait(uint8 uart, uint8 TxChar);
|
||||
void uart1_sendStr_no_wait(const char *str);
|
||||
struct UartBuffer* UART_AsyncBufferInit(u32 size);
|
||||
|
||||
void uart_rx_intr_enable(uint8 uart_no);
|
||||
void uart_rx_intr_disable(uint8 uart_no);
|
||||
void uart0_tx_buffer(uint8 *buf, uint16 len);
|
||||
|
||||
void uart0_sendStr(const char *str);
|
||||
|
||||
|
||||
/*uart demo with a system task, to output what uart receives*/
|
||||
/*this is a example to process uart data from task,please change the priority to fit your application task if exists*/
|
||||
/*it might conflict with your task, if so,please arrange the priority of different task, or combine it to a different event in the same task. */
|
||||
#define uart_recvTaskPrio 0
|
||||
#define uart_recvTaskQueueLen 10
|
||||
os_event_t uart_recvTaskQueue[uart_recvTaskQueueLen];
|
||||
|
||||
#define DBG
|
||||
#define DBG1 uart1_sendStr_no_wait
|
||||
#define DBG2 os_printf
|
||||
|
||||
|
||||
LOCAL void uart0_rx_intr_handler(void *para);
|
||||
|
||||
/******************************************************************************
|
||||
* FunctionName : uart_config
|
||||
* Description : Internal used function
|
||||
* UART0 used for data TX/RX, RX buffer size is 0x100, interrupt enabled
|
||||
* UART1 just used for debug output
|
||||
* Parameters : uart_no, use UART0 or UART1 defined ahead
|
||||
* Returns : NONE
|
||||
*******************************************************************************/
|
||||
LOCAL void ICACHE_FLASH_ATTR
|
||||
uart_config(uint8 uart_no)
|
||||
{
|
||||
if (uart_no == UART1){
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_U1TXD_BK);
|
||||
}else{
|
||||
/* rcv_buff size if 0x100 */
|
||||
ETS_UART_INTR_ATTACH(uart0_rx_intr_handler, &(UartDev.rcv_buff));
|
||||
PIN_PULLUP_DIS(PERIPHS_IO_MUX_U0TXD_U);
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD);
|
||||
#if UART_HW_RTS
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_U, FUNC_U0RTS); //HW FLOW CONTROL RTS PIN
|
||||
#endif
|
||||
#if UART_HW_CTS
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, FUNC_U0CTS); //HW FLOW CONTROL CTS PIN
|
||||
#endif
|
||||
}
|
||||
uart_div_modify(uart_no, UART_CLK_FREQ / (UartDev.baut_rate));//SET BAUDRATE
|
||||
|
||||
WRITE_PERI_REG(UART_CONF0(uart_no), ((UartDev.exist_parity & UART_PARITY_EN_M) << UART_PARITY_EN_S) //SET BIT AND PARITY MODE
|
||||
| ((UartDev.parity & UART_PARITY_M) <<UART_PARITY_S )
|
||||
| ((UartDev.stop_bits & UART_STOP_BIT_NUM) << UART_STOP_BIT_NUM_S)
|
||||
| ((UartDev.data_bits & UART_BIT_NUM) << UART_BIT_NUM_S));
|
||||
|
||||
//clear rx and tx fifo,not ready
|
||||
SET_PERI_REG_MASK(UART_CONF0(uart_no), UART_RXFIFO_RST | UART_TXFIFO_RST); //RESET FIFO
|
||||
CLEAR_PERI_REG_MASK(UART_CONF0(uart_no), UART_RXFIFO_RST | UART_TXFIFO_RST);
|
||||
|
||||
if (uart_no == UART0){
|
||||
//set rx fifo trigger
|
||||
WRITE_PERI_REG(UART_CONF1(uart_no),
|
||||
((100 & UART_RXFIFO_FULL_THRHD) << UART_RXFIFO_FULL_THRHD_S) |
|
||||
#if UART_HW_RTS
|
||||
((110 & UART_RX_FLOW_THRHD) << UART_RX_FLOW_THRHD_S) |
|
||||
UART_RX_FLOW_EN | //enbale rx flow control
|
||||
#endif
|
||||
(0x02 & UART_RX_TOUT_THRHD) << UART_RX_TOUT_THRHD_S |
|
||||
UART_RX_TOUT_EN|
|
||||
((0x10 & UART_TXFIFO_EMPTY_THRHD)<<UART_TXFIFO_EMPTY_THRHD_S));//wjl
|
||||
#if UART_HW_CTS
|
||||
SET_PERI_REG_MASK( UART_CONF0(uart_no),UART_TX_FLOW_EN); //add this sentense to add a tx flow control via MTCK( CTS )
|
||||
#endif
|
||||
SET_PERI_REG_MASK(UART_INT_ENA(uart_no), UART_RXFIFO_TOUT_INT_ENA |UART_FRM_ERR_INT_ENA);
|
||||
}else{
|
||||
WRITE_PERI_REG(UART_CONF1(uart_no),((UartDev.rcv_buff.TrigLvl & UART_RXFIFO_FULL_THRHD) << UART_RXFIFO_FULL_THRHD_S));//TrigLvl default val == 1
|
||||
}
|
||||
//clear all interrupt
|
||||
WRITE_PERI_REG(UART_INT_CLR(uart_no), 0xffff);
|
||||
//enable rx_interrupt
|
||||
SET_PERI_REG_MASK(UART_INT_ENA(uart_no), UART_RXFIFO_FULL_INT_ENA|UART_RXFIFO_OVF_INT_ENA);
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* FunctionName : uart1_tx_one_char
|
||||
* Description : Internal used function
|
||||
* Use uart1 interface to transfer one char
|
||||
* Parameters : uint8 TxChar - character to tx
|
||||
* Returns : OK
|
||||
*******************************************************************************/
|
||||
STATUS uart_tx_one_char(uint8 uart, uint8 TxChar)
|
||||
{
|
||||
while (true){
|
||||
uint32 fifo_cnt = READ_PERI_REG(UART_STATUS(uart)) & (UART_TXFIFO_CNT<<UART_TXFIFO_CNT_S);
|
||||
if ((fifo_cnt >> UART_TXFIFO_CNT_S & UART_TXFIFO_CNT) < 126) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
WRITE_PERI_REG(UART_FIFO(uart) , TxChar);
|
||||
return OK;
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* FunctionName : uart1_write_char
|
||||
* Description : Internal used function
|
||||
* Do some special deal while tx char is '\r' or '\n'
|
||||
* Parameters : char c - character to tx
|
||||
* Returns : NONE
|
||||
*******************************************************************************/
|
||||
LOCAL void ICACHE_FLASH_ATTR
|
||||
uart1_write_char(char c)
|
||||
{
|
||||
if (c == '\n'){
|
||||
uart_tx_one_char(UART1, '\r');
|
||||
uart_tx_one_char(UART1, '\n');
|
||||
}else if (c == '\r'){
|
||||
|
||||
}else{
|
||||
uart_tx_one_char(UART1, c);
|
||||
}
|
||||
}
|
||||
|
||||
//os_printf output to fifo or to the tx buffer
|
||||
LOCAL void ICACHE_FLASH_ATTR
|
||||
uart0_write_char_no_wait(char c)
|
||||
{
|
||||
#if UART_BUFF_EN //send to uart0 fifo but do not wait
|
||||
uint8 chr;
|
||||
// if (c == '\n'){
|
||||
// chr = '\r';
|
||||
// tx_buff_enq(&chr, 1);
|
||||
// chr = '\n';
|
||||
// tx_buff_enq(&chr, 1);
|
||||
// }else if (c == '\r'){
|
||||
//
|
||||
// }else{
|
||||
tx_buff_enq(&c,1);
|
||||
// }
|
||||
#else //send to uart tx buffer
|
||||
if (c == '\n'){
|
||||
uart_tx_one_char_no_wait(UART0, '\r');
|
||||
uart_tx_one_char_no_wait(UART0, '\n');
|
||||
}else if (c == '\r'){
|
||||
|
||||
}
|
||||
else{
|
||||
uart_tx_one_char_no_wait(UART0, c);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* FunctionName : uart0_tx_buffer
|
||||
* Description : use uart0 to transfer buffer
|
||||
* Parameters : uint8 *buf - point to send buffer
|
||||
* uint16 len - buffer len
|
||||
* Returns :
|
||||
*******************************************************************************/
|
||||
void ICACHE_FLASH_ATTR
|
||||
uart0_tx_buffer(uint8 *buf, uint16 len)
|
||||
{
|
||||
uint16 i;
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
uart_tx_one_char(UART0, buf[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* FunctionName : uart0_sendStr
|
||||
* Description : use uart0 to transfer buffer
|
||||
* Parameters : uint8 *buf - point to send buffer
|
||||
* uint16 len - buffer len
|
||||
* Returns :
|
||||
*******************************************************************************/
|
||||
void ICACHE_FLASH_ATTR
|
||||
uart0_sendStr(const char *str)
|
||||
{
|
||||
while(*str){
|
||||
uart_tx_one_char(UART0, *str++);
|
||||
}
|
||||
}
|
||||
void at_port_print(const char *str) __attribute__((alias("uart0_sendStr")));
|
||||
/******************************************************************************
|
||||
* FunctionName : uart0_rx_intr_handler
|
||||
* Description : Internal used function
|
||||
* UART0 interrupt handler, add self handle code inside
|
||||
* Parameters : void *para - point to ETS_UART_INTR_ATTACH's arg
|
||||
* Returns : NONE
|
||||
*******************************************************************************/
|
||||
LOCAL void
|
||||
uart0_rx_intr_handler(void *para)
|
||||
{
|
||||
/* uart0 and uart1 intr combine togther, when interrupt occur, see reg 0x3ff20020, bit2, bit0 represents
|
||||
* uart1 and uart0 respectively
|
||||
*/
|
||||
uint8 RcvChar;
|
||||
uint8 uart_no = UART0;//UartDev.buff_uart_no;
|
||||
uint8 fifo_len = 0;
|
||||
uint8 buf_idx = 0;
|
||||
uint8 temp,cnt;
|
||||
//RcvMsgBuff *pRxBuff = (RcvMsgBuff *)para;
|
||||
|
||||
/*ATTENTION:*/
|
||||
/*IN NON-OS VERSION SDK, DO NOT USE "ICACHE_FLASH_ATTR" FUNCTIONS IN THE WHOLE HANDLER PROCESS*/
|
||||
/*ALL THE FUNCTIONS CALLED IN INTERRUPT HANDLER MUST BE DECLARED IN RAM */
|
||||
/*IF NOT , POST AN EVENT AND PROCESS IN SYSTEM TASK */
|
||||
if(UART_FRM_ERR_INT_ST == (READ_PERI_REG(UART_INT_ST(uart_no)) & UART_FRM_ERR_INT_ST)){
|
||||
DBG1("FRM_ERR\r\n");
|
||||
WRITE_PERI_REG(UART_INT_CLR(uart_no), UART_FRM_ERR_INT_CLR);
|
||||
}else if(UART_RXFIFO_FULL_INT_ST == (READ_PERI_REG(UART_INT_ST(uart_no)) & UART_RXFIFO_FULL_INT_ST)){
|
||||
// Full interrupt - FIFO threshold reached
|
||||
DBG("f");
|
||||
uart_rx_intr_disable(UART0);
|
||||
WRITE_PERI_REG(UART_INT_CLR(UART0), UART_RXFIFO_FULL_INT_CLR);
|
||||
system_os_post(uart_recvTaskPrio, 0, 0); // tell the handler
|
||||
}else if(UART_RXFIFO_TOUT_INT_ST == (READ_PERI_REG(UART_INT_ST(uart_no)) & UART_RXFIFO_TOUT_INT_ST)){
|
||||
// Timeout interrupt - Line idle for a while
|
||||
DBG("t");
|
||||
uart_rx_intr_disable(UART0);
|
||||
WRITE_PERI_REG(UART_INT_CLR(UART0), UART_RXFIFO_TOUT_INT_CLR);
|
||||
system_os_post(uart_recvTaskPrio, 0, 0); // tell the handler
|
||||
}else if(UART_TXFIFO_EMPTY_INT_ST == (READ_PERI_REG(UART_INT_ST(uart_no)) & UART_TXFIFO_EMPTY_INT_ST)){
|
||||
DBG("e");
|
||||
// TX fifo is empty, can send more.
|
||||
|
||||
/* to output uart data from uart buffer directly in empty interrupt handler*/
|
||||
/*instead of processing in system event, in order not to wait for current task/function to quit */
|
||||
/*ATTENTION:*/
|
||||
/*IN NON-OS VERSION SDK, DO NOT USE "ICACHE_FLASH_ATTR" FUNCTIONS IN THE WHOLE HANDLER PROCESS*/
|
||||
/*ALL THE FUNCTIONS CALLED IN INTERRUPT HANDLER MUST BE DECLARED IN RAM */
|
||||
CLEAR_PERI_REG_MASK(UART_INT_ENA(UART0), UART_TXFIFO_EMPTY_INT_ENA);
|
||||
#if UART_BUFF_EN
|
||||
tx_start_uart_buffer(UART0);
|
||||
#endif
|
||||
//system_os_post(uart_recvTaskPrio, 1, 0);
|
||||
WRITE_PERI_REG(UART_INT_CLR(uart_no), UART_TXFIFO_EMPTY_INT_CLR);
|
||||
|
||||
}else if(UART_RXFIFO_OVF_INT_ST == (READ_PERI_REG(UART_INT_ST(uart_no)) & UART_RXFIFO_OVF_INT_ST)){
|
||||
WRITE_PERI_REG(UART_INT_CLR(uart_no), UART_RXFIFO_OVF_INT_CLR);
|
||||
DBG1("RX OVF!!\r\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* FunctionName : uart_init
|
||||
* Description : user interface for init uart
|
||||
* Parameters : UartBautRate uart0_br - uart0 bautrate
|
||||
* UartBautRate uart1_br - uart1 bautrate
|
||||
* Returns : NONE
|
||||
*******************************************************************************/
|
||||
#if UART_SELFTEST&UART_BUFF_EN
|
||||
os_timer_t buff_timer_t;
|
||||
void ICACHE_FLASH_ATTR
|
||||
uart_test_rx()
|
||||
{
|
||||
uint8 uart_buf[128]={0};
|
||||
uint16 len = 0;
|
||||
len = rx_buff_deq(uart_buf, 128 );
|
||||
tx_buff_enq(uart_buf,len);
|
||||
}
|
||||
#endif
|
||||
|
||||
LOCAL void ICACHE_FLASH_ATTR ///////
|
||||
uart_recvTask(os_event_t *events)
|
||||
{
|
||||
if(events->sig == 0){
|
||||
#if UART_BUFF_EN
|
||||
Uart_rx_buff_enq();
|
||||
#else
|
||||
uint8 fifo_len = (READ_PERI_REG(UART_STATUS(UART0))>>UART_RXFIFO_CNT_S)&UART_RXFIFO_CNT;
|
||||
uint8 d_tmp = 0;
|
||||
uint8 idx=0;
|
||||
for(idx=0;idx<fifo_len;idx++) {
|
||||
d_tmp = READ_PERI_REG(UART_FIFO(UART0)) & 0xFF;
|
||||
uart_tx_one_char(UART0, d_tmp);
|
||||
}
|
||||
WRITE_PERI_REG(UART_INT_CLR(UART0), UART_RXFIFO_FULL_INT_CLR|UART_RXFIFO_TOUT_INT_CLR);
|
||||
uart_rx_intr_enable(UART0);
|
||||
#endif
|
||||
}else if(events->sig == 1){
|
||||
#if UART_BUFF_EN
|
||||
//already move uart buffer output to uart empty interrupt
|
||||
//tx_start_uart_buffer(UART0);
|
||||
#else
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
uart_init(UartBautRate uart0_br, UartBautRate uart1_br)
|
||||
{
|
||||
/*this is a example to process uart data from task,please change the priority to fit your application task if exists*/
|
||||
system_os_task(uart_recvTask, uart_recvTaskPrio, uart_recvTaskQueue, uart_recvTaskQueueLen); //demo with a task to process the uart data
|
||||
|
||||
UartDev.baut_rate = uart0_br;
|
||||
uart_config(UART0);
|
||||
UartDev.baut_rate = uart1_br;
|
||||
uart_config(UART1);
|
||||
ETS_UART_INTR_ENABLE();
|
||||
|
||||
#if UART_BUFF_EN
|
||||
pTxBuffer = UART_AsyncBufferInit(UART_TX_BUFFER_SIZE);
|
||||
pRxBuffer = UART_AsyncBufferInit(UART_RX_BUFFER_SIZE);
|
||||
#endif
|
||||
|
||||
|
||||
/*option 1: use default print, output from uart0 , will wait some time if fifo is full */
|
||||
//do nothing...
|
||||
|
||||
/*option 2: output from uart1,uart1 output will not wait , just for output debug info */
|
||||
/*os_printf output uart data via uart1(GPIO2)*/
|
||||
os_install_putc1((void *)uart1_write_char); //use this one to output debug information via uart1 //
|
||||
|
||||
/*option 3: output from uart0 will skip current byte if fifo is full now... */
|
||||
/*see uart0_write_char_no_wait:you can output via a buffer or output directly */
|
||||
/*os_printf output uart data via uart0 or uart buffer*/
|
||||
//os_install_putc1((void *)uart0_write_char_no_wait); //use this to print via uart0
|
||||
|
||||
#if UART_SELFTEST&UART_BUFF_EN
|
||||
os_timer_disarm(&buff_timer_t);
|
||||
os_timer_setfn(&buff_timer_t, uart_test_rx , NULL); //a demo to process the data in uart rx buffer
|
||||
os_timer_arm(&buff_timer_t,10,1);
|
||||
#endif
|
||||
}
|
||||
|
||||
//void ICACHE_FLASH_ATTR
|
||||
//uart_reattach()
|
||||
//{
|
||||
// uart_init(BIT_RATE_115200, BIT_RATE_115200);
|
||||
//}
|
||||
|
||||
/******************************************************************************
|
||||
* FunctionName : uart_tx_one_char_no_wait
|
||||
* Description : uart tx a single char without waiting for fifo
|
||||
* Parameters : uint8 uart - uart port
|
||||
* uint8 TxChar - char to tx
|
||||
* Returns : STATUS
|
||||
*******************************************************************************/
|
||||
STATUS uart_tx_one_char_no_wait(uint8 uart, uint8 TxChar)
|
||||
{
|
||||
uint8 fifo_cnt = (( READ_PERI_REG(UART_STATUS(uart))>>UART_TXFIFO_CNT_S)& UART_TXFIFO_CNT);
|
||||
if (fifo_cnt < 126) {
|
||||
WRITE_PERI_REG(UART_FIFO(uart) , TxChar);
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
//STATUS uart0_tx_one_char_no_wait(uint8 TxChar)
|
||||
//{
|
||||
// uint8 fifo_cnt = (( READ_PERI_REG(UART_STATUS(UART0))>>UART_TXFIFO_CNT_S)& UART_TXFIFO_CNT);
|
||||
// if (fifo_cnt < 126) {
|
||||
// WRITE_PERI_REG(UART_FIFO(UART0) , TxChar);
|
||||
// }
|
||||
// return OK;
|
||||
//}
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* FunctionName : uart1_sendStr_no_wait
|
||||
* Description : uart tx a string without waiting for every char, used for print debug info which can be lost
|
||||
* Parameters : const char *str - string to be sent
|
||||
* Returns : NONE
|
||||
*******************************************************************************/
|
||||
void uart1_sendStr_no_wait(const char *str)
|
||||
{
|
||||
while(*str){
|
||||
uart_tx_one_char_no_wait(UART1, *str++);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if UART_BUFF_EN
|
||||
/******************************************************************************
|
||||
* FunctionName : Uart_Buf_Init
|
||||
* Description : tx buffer enqueue: fill a first linked buffer
|
||||
* Parameters : char *pdata - data point to be enqueue
|
||||
* Returns : NONE
|
||||
*******************************************************************************/
|
||||
struct UartBuffer* ICACHE_FLASH_ATTR
|
||||
UART_AsyncBufferInit(uint32 buf_size)
|
||||
{
|
||||
uint32 heap_size = system_get_free_heap_size();
|
||||
if(heap_size <=buf_size){
|
||||
DBG1("no buf for uart\n\r");
|
||||
return NULL;
|
||||
}else{
|
||||
DBG("test heap size: %d\n\r",heap_size);
|
||||
struct UartBuffer* pBuff = (struct UartBuffer* )os_malloc(sizeof(struct UartBuffer));
|
||||
pBuff->UartBuffSize = buf_size;
|
||||
pBuff->pUartBuff = (uint8*)os_malloc(pBuff->UartBuffSize);
|
||||
pBuff->pInPos = pBuff->pUartBuff;
|
||||
pBuff->pOutPos = pBuff->pUartBuff;
|
||||
pBuff->Space = (uint16) pBuff->UartBuffSize;
|
||||
pBuff->BuffState = OK;
|
||||
pBuff->nextBuff = NULL;
|
||||
pBuff->TcpControl = RUN;
|
||||
return pBuff;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//copy uart buffer
|
||||
LOCAL void Uart_Buf_Cpy(struct UartBuffer* pCur, char* pdata , uint16 data_len)
|
||||
{
|
||||
if(data_len == 0) return;
|
||||
|
||||
uint16 tail_len = (uint16) (pCur->pUartBuff + pCur->UartBuffSize - pCur->pInPos);
|
||||
if(tail_len >= data_len){ //do not need to loop back the queue
|
||||
os_memcpy(pCur->pInPos , pdata , data_len );
|
||||
pCur->pInPos += ( data_len );
|
||||
pCur->pInPos = (pCur->pUartBuff + (pCur->pInPos - pCur->pUartBuff) % pCur->UartBuffSize );
|
||||
pCur->Space -=data_len;
|
||||
}else{
|
||||
os_memcpy(pCur->pInPos, pdata, tail_len);
|
||||
pCur->pInPos += ( tail_len );
|
||||
pCur->pInPos = (pCur->pUartBuff + (pCur->pInPos - pCur->pUartBuff) % pCur->UartBuffSize );
|
||||
pCur->Space -=tail_len;
|
||||
os_memcpy(pCur->pInPos, pdata+tail_len , data_len-tail_len);
|
||||
pCur->pInPos += ( data_len-tail_len );
|
||||
pCur->pInPos = (pCur->pUartBuff + (pCur->pInPos - pCur->pUartBuff) % pCur->UartBuffSize );
|
||||
pCur->Space -=( data_len-tail_len);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* FunctionName : uart_buf_free
|
||||
* Description : deinit of the tx buffer
|
||||
* Parameters : struct UartBuffer* pTxBuff - tx buffer struct pointer
|
||||
* Returns : NONE
|
||||
*******************************************************************************/
|
||||
void ICACHE_FLASH_ATTR
|
||||
uart_buf_free(struct UartBuffer* pBuff)
|
||||
{
|
||||
os_free(pBuff->pUartBuff);
|
||||
os_free(pBuff);
|
||||
}
|
||||
|
||||
|
||||
//rx buffer dequeue
|
||||
uint16 ICACHE_FLASH_ATTR
|
||||
rx_buff_deq(char* pdata, uint16 data_len)
|
||||
{
|
||||
uint16 buf_len = (pRxBuffer->UartBuffSize- pRxBuffer->Space);
|
||||
uint16 tail_len = pRxBuffer->pUartBuff + pRxBuffer->UartBuffSize - pRxBuffer->pOutPos ;
|
||||
uint16 len_tmp = 0;
|
||||
len_tmp = ((data_len > buf_len)?buf_len:data_len);
|
||||
if(pRxBuffer->pOutPos <= pRxBuffer->pInPos){
|
||||
os_memcpy(pdata, pRxBuffer->pOutPos,len_tmp);
|
||||
pRxBuffer->pOutPos+= len_tmp;
|
||||
pRxBuffer->Space += len_tmp;
|
||||
}else{
|
||||
if(len_tmp>tail_len){
|
||||
os_memcpy(pdata, pRxBuffer->pOutPos, tail_len);
|
||||
pRxBuffer->pOutPos += tail_len;
|
||||
pRxBuffer->pOutPos = (pRxBuffer->pUartBuff + (pRxBuffer->pOutPos- pRxBuffer->pUartBuff) % pRxBuffer->UartBuffSize );
|
||||
pRxBuffer->Space += tail_len;
|
||||
|
||||
os_memcpy(pdata+tail_len , pRxBuffer->pOutPos, len_tmp-tail_len);
|
||||
pRxBuffer->pOutPos+= ( len_tmp-tail_len );
|
||||
pRxBuffer->pOutPos= (pRxBuffer->pUartBuff + (pRxBuffer->pOutPos- pRxBuffer->pUartBuff) % pRxBuffer->UartBuffSize );
|
||||
pRxBuffer->Space +=( len_tmp-tail_len);
|
||||
}else{
|
||||
//os_printf("case 3 in rx deq\n\r");
|
||||
os_memcpy(pdata, pRxBuffer->pOutPos, len_tmp);
|
||||
pRxBuffer->pOutPos += len_tmp;
|
||||
pRxBuffer->pOutPos = (pRxBuffer->pUartBuff + (pRxBuffer->pOutPos- pRxBuffer->pUartBuff) % pRxBuffer->UartBuffSize );
|
||||
pRxBuffer->Space += len_tmp;
|
||||
}
|
||||
}
|
||||
if(pRxBuffer->Space >= UART_FIFO_LEN){
|
||||
uart_rx_intr_enable(UART0);
|
||||
}
|
||||
return len_tmp;
|
||||
}
|
||||
|
||||
|
||||
//move data from uart fifo to rx buffer
|
||||
void Uart_rx_buff_enq()
|
||||
{
|
||||
uint8 fifo_len,buf_idx;
|
||||
uint8 fifo_data;
|
||||
#if 1
|
||||
fifo_len = (READ_PERI_REG(UART_STATUS(UART0))>>UART_RXFIFO_CNT_S)&UART_RXFIFO_CNT;
|
||||
if(fifo_len >= pRxBuffer->Space){
|
||||
os_printf("buf full!!!\n\r");
|
||||
}else{
|
||||
buf_idx=0;
|
||||
while(buf_idx < fifo_len){
|
||||
buf_idx++;
|
||||
fifo_data = READ_PERI_REG(UART_FIFO(UART0)) & 0xFF;
|
||||
*(pRxBuffer->pInPos++) = fifo_data;
|
||||
if(pRxBuffer->pInPos == (pRxBuffer->pUartBuff + pRxBuffer->UartBuffSize)){
|
||||
pRxBuffer->pInPos = pRxBuffer->pUartBuff;
|
||||
}
|
||||
}
|
||||
pRxBuffer->Space -= fifo_len ;
|
||||
if(pRxBuffer->Space >= UART_FIFO_LEN){
|
||||
//os_printf("after rx enq buf enough\n\r");
|
||||
uart_rx_intr_enable(UART0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
//fill the uart tx buffer
|
||||
void ICACHE_FLASH_ATTR
|
||||
tx_buff_enq(char* pdata, uint16 data_len )
|
||||
{
|
||||
if(pTxBuffer == NULL){
|
||||
DBG1("\n\rnull, create buffer struct\n\r");
|
||||
pTxBuffer = UART_AsyncBufferInit(UART_TX_BUFFER_SIZE);
|
||||
if(pTxBuffer!= NULL){
|
||||
Uart_Buf_Cpy(pTxBuffer , pdata, data_len );
|
||||
}else{
|
||||
DBG1("uart tx MALLOC no buf \n\r");
|
||||
}
|
||||
}else{
|
||||
if(data_len <= pTxBuffer->Space){
|
||||
Uart_Buf_Cpy(pTxBuffer , pdata, data_len);
|
||||
}else{
|
||||
DBG1("UART TX BUF FULL!!!!\n\r");
|
||||
}
|
||||
}
|
||||
#if 0
|
||||
if(pTxBuffer->Space <= URAT_TX_LOWER_SIZE){
|
||||
set_tcp_block();
|
||||
}
|
||||
#endif
|
||||
SET_PERI_REG_MASK(UART_CONF1(UART0), (UART_TX_EMPTY_THRESH_VAL & UART_TXFIFO_EMPTY_THRHD)<<UART_TXFIFO_EMPTY_THRHD_S);
|
||||
SET_PERI_REG_MASK(UART_INT_ENA(UART0), UART_TXFIFO_EMPTY_INT_ENA);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//--------------------------------
|
||||
LOCAL void tx_fifo_insert(struct UartBuffer* pTxBuff, uint8 data_len, uint8 uart_no)
|
||||
{
|
||||
uint8 i;
|
||||
for(i = 0; i<data_len;i++){
|
||||
WRITE_PERI_REG(UART_FIFO(uart_no) , *(pTxBuff->pOutPos++));
|
||||
if(pTxBuff->pOutPos == (pTxBuff->pUartBuff + pTxBuff->UartBuffSize)){
|
||||
pTxBuff->pOutPos = pTxBuff->pUartBuff;
|
||||
}
|
||||
}
|
||||
pTxBuff->pOutPos = (pTxBuff->pUartBuff + (pTxBuff->pOutPos - pTxBuff->pUartBuff) % pTxBuff->UartBuffSize );
|
||||
pTxBuff->Space += data_len;
|
||||
}
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* FunctionName : tx_start_uart_buffer
|
||||
* Description : get data from the tx buffer and fill the uart tx fifo, co-work with the uart fifo empty interrupt
|
||||
* Parameters : uint8 uart_no - uart port num
|
||||
* Returns : NONE
|
||||
*******************************************************************************/
|
||||
void tx_start_uart_buffer(uint8 uart_no)
|
||||
{
|
||||
uint8 tx_fifo_len = (READ_PERI_REG(UART_STATUS(uart_no))>>UART_TXFIFO_CNT_S)&UART_TXFIFO_CNT;
|
||||
uint8 fifo_remain = UART_FIFO_LEN - tx_fifo_len ;
|
||||
uint8 len_tmp;
|
||||
uint16 tail_ptx_len,head_ptx_len,data_len;
|
||||
//struct UartBuffer* pTxBuff = *get_buff_prt();
|
||||
|
||||
if(pTxBuffer){
|
||||
data_len = (pTxBuffer->UartBuffSize - pTxBuffer->Space);
|
||||
if(data_len > fifo_remain){
|
||||
len_tmp = fifo_remain;
|
||||
tx_fifo_enq(pTxBuffer, len_tmp, uart_no);
|
||||
SET_PERI_REG_MASK(UART_INT_ENA(UART0), UART_TXFIFO_EMPTY_INT_ENA);
|
||||
}else{
|
||||
len_tmp = data_len;
|
||||
tx_fifo_enq(pTxBuffer, len_tmp, uart_no);
|
||||
}
|
||||
}else{
|
||||
DBG1("pTxBuff null \n\r");
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
void uart_rx_intr_disable(uint8 uart_no)
|
||||
{
|
||||
#if 1
|
||||
CLEAR_PERI_REG_MASK(UART_INT_ENA(uart_no), UART_RXFIFO_FULL_INT_ENA|UART_RXFIFO_TOUT_INT_ENA);
|
||||
#else
|
||||
ETS_UART_INTR_DISABLE();
|
||||
#endif
|
||||
}
|
||||
|
||||
void uart_rx_intr_enable(uint8 uart_no)
|
||||
{
|
||||
#if 1
|
||||
SET_PERI_REG_MASK(UART_INT_ENA(uart_no), UART_RXFIFO_FULL_INT_ENA|UART_RXFIFO_TOUT_INT_ENA);
|
||||
#else
|
||||
ETS_UART_INTR_ENABLE();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
//========================================================
|
||||
LOCAL void
|
||||
uart0_write_char(char c)
|
||||
{
|
||||
if (c == '\n') {
|
||||
uart_tx_one_char(UART0, '\r');
|
||||
uart_tx_one_char(UART0, '\n');
|
||||
} else if (c == '\r') {
|
||||
} else {
|
||||
uart_tx_one_char(UART0, c);
|
||||
}
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
UART_SetWordLength(uint8 uart_no, UartBitsNum4Char len)
|
||||
{
|
||||
SET_PERI_REG_BITS(UART_CONF0(uart_no),UART_BIT_NUM,len,UART_BIT_NUM_S);
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
UART_SetStopBits(uint8 uart_no, UartStopBitsNum bit_num)
|
||||
{
|
||||
SET_PERI_REG_BITS(UART_CONF0(uart_no),UART_STOP_BIT_NUM,bit_num,UART_STOP_BIT_NUM_S);
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
UART_SetLineInverse(uint8 uart_no, UART_LineLevelInverse inverse_mask)
|
||||
{
|
||||
CLEAR_PERI_REG_MASK(UART_CONF0(uart_no), UART_LINE_INV_MASK);
|
||||
SET_PERI_REG_MASK(UART_CONF0(uart_no), inverse_mask);
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
UART_SetParity(uint8 uart_no, UartParityMode Parity_mode)
|
||||
{
|
||||
CLEAR_PERI_REG_MASK(UART_CONF0(uart_no), UART_PARITY |UART_PARITY_EN);
|
||||
if(Parity_mode==NONE_BITS){
|
||||
}else{
|
||||
SET_PERI_REG_MASK(UART_CONF0(uart_no), Parity_mode|UART_PARITY_EN);
|
||||
}
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
UART_SetBaudrate(uint8 uart_no,uint32 baud_rate)
|
||||
{
|
||||
uart_div_modify(uart_no, UART_CLK_FREQ /baud_rate);
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
UART_SetFlowCtrl(uint8 uart_no,UART_HwFlowCtrl flow_ctrl,uint8 rx_thresh)
|
||||
{
|
||||
if(flow_ctrl&USART_HardwareFlowControl_RTS){
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_U, FUNC_U0RTS);
|
||||
SET_PERI_REG_BITS(UART_CONF1(uart_no),UART_RX_FLOW_THRHD,rx_thresh,UART_RX_FLOW_THRHD_S);
|
||||
SET_PERI_REG_MASK(UART_CONF1(uart_no), UART_RX_FLOW_EN);
|
||||
}else{
|
||||
CLEAR_PERI_REG_MASK(UART_CONF1(uart_no), UART_RX_FLOW_EN);
|
||||
}
|
||||
if(flow_ctrl&USART_HardwareFlowControl_CTS){
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, FUNC_UART0_CTS);
|
||||
SET_PERI_REG_MASK(UART_CONF0(uart_no), UART_TX_FLOW_EN);
|
||||
}else{
|
||||
CLEAR_PERI_REG_MASK(UART_CONF0(uart_no), UART_TX_FLOW_EN);
|
||||
}
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
UART_WaitTxFifoEmpty(uint8 uart_no , uint32 time_out_us) //do not use if tx flow control enabled
|
||||
{
|
||||
uint32 t_s = system_get_time();
|
||||
while (READ_PERI_REG(UART_STATUS(uart_no)) & (UART_TXFIFO_CNT << UART_TXFIFO_CNT_S)){
|
||||
|
||||
if(( system_get_time() - t_s )> time_out_us){
|
||||
break;
|
||||
}
|
||||
WRITE_PERI_REG(0X60000914, 0X73);//WTD
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool ICACHE_FLASH_ATTR
|
||||
UART_CheckOutputFinished(uint8 uart_no, uint32 time_out_us)
|
||||
{
|
||||
uint32 t_start = system_get_time();
|
||||
uint8 tx_fifo_len;
|
||||
uint32 tx_buff_len;
|
||||
while(1){
|
||||
tx_fifo_len =( (READ_PERI_REG(UART_STATUS(uart_no))>>UART_TXFIFO_CNT_S)&UART_TXFIFO_CNT);
|
||||
if(pTxBuffer){
|
||||
tx_buff_len = ((pTxBuffer->UartBuffSize)-(pTxBuffer->Space));
|
||||
}else{
|
||||
tx_buff_len = 0;
|
||||
}
|
||||
|
||||
if( tx_fifo_len==0 && tx_buff_len==0){
|
||||
return TRUE;
|
||||
}
|
||||
if( system_get_time() - t_start > time_out_us){
|
||||
return FALSE;
|
||||
}
|
||||
WRITE_PERI_REG(0X60000914, 0X73);//WTD
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
UART_ResetFifo(uint8 uart_no)
|
||||
{
|
||||
SET_PERI_REG_MASK(UART_CONF0(uart_no), UART_RXFIFO_RST | UART_TXFIFO_RST);
|
||||
CLEAR_PERI_REG_MASK(UART_CONF0(uart_no), UART_RXFIFO_RST | UART_TXFIFO_RST);
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
UART_ClearIntrStatus(uint8 uart_no,uint32 clr_mask)
|
||||
{
|
||||
WRITE_PERI_REG(UART_INT_CLR(uart_no), clr_mask);
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
UART_SetIntrEna(uint8 uart_no,uint32 ena_mask)
|
||||
{
|
||||
SET_PERI_REG_MASK(UART_INT_ENA(uart_no), ena_mask);
|
||||
}
|
||||
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
UART_SetPrintPort(uint8 uart_no)
|
||||
{
|
||||
if(uart_no==1){
|
||||
os_install_putc1(uart1_write_char);
|
||||
}else{
|
||||
/*option 1: do not wait if uart fifo is full,drop current character*/
|
||||
os_install_putc1(uart0_write_char_no_wait);
|
||||
/*option 2: wait for a while if uart fifo is full*/
|
||||
os_install_putc1(uart0_write_char);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//========================================================
|
||||
|
||||
//
|
||||
///*test code*/
|
||||
//void ICACHE_FLASH_ATTR
|
||||
//uart_init_2(UartBautRate uart0_br, UartBautRate uart1_br)
|
||||
//{
|
||||
// // rom use 74880 baut_rate, here reinitialize
|
||||
// UartDev.baut_rate = uart0_br;
|
||||
// UartDev.exist_parity = STICK_PARITY_EN;
|
||||
// UartDev.parity = EVEN_BITS;
|
||||
// UartDev.stop_bits = ONE_STOP_BIT;
|
||||
// UartDev.data_bits = EIGHT_BITS;
|
||||
//
|
||||
// uart_config(UART0);
|
||||
// UartDev.baut_rate = uart1_br;
|
||||
// uart_config(UART1);
|
||||
// ETS_UART_INTR_ENABLE();
|
||||
//
|
||||
// // install uart1 putc callback
|
||||
// os_install_putc1((void *)uart1_write_char);//print output at UART1
|
||||
//}
|
||||
//
|
||||
//
|
||||
@@ -1,178 +0,0 @@
|
||||
/*
|
||||
* File : uart.h
|
||||
* Copyright (C) 2013 - 2016, Espressif Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of version 3 of the GNU General Public License as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef UART_APP_H
|
||||
#define UART_APP_H
|
||||
|
||||
#include <esp8266.h>
|
||||
|
||||
#define UART_TX_BUFFER_SIZE 512 //Ring buffer length of tx buffer
|
||||
#define UART_RX_BUFFER_SIZE 512 //Ring buffer length of rx buffer
|
||||
|
||||
#define UART_BUFF_EN 1 //use uart buffer , FOR UART0
|
||||
#define UART_SELFTEST 0 //set 1:enable the loop test demo for uart buffer, FOR UART0
|
||||
|
||||
#define UART_HW_RTS 0 //set 1: enable uart hw flow control RTS, PIN MTDO, FOR UART0
|
||||
#define UART_HW_CTS 0 //set1: enable uart hw flow contrl CTS , PIN MTCK, FOR UART0
|
||||
|
||||
#define UART0 0
|
||||
#define UART1 1
|
||||
|
||||
typedef enum {
|
||||
FIVE_BITS = 0x0,
|
||||
SIX_BITS = 0x1,
|
||||
SEVEN_BITS = 0x2,
|
||||
EIGHT_BITS = 0x3
|
||||
} UartBitsNum4Char;
|
||||
|
||||
typedef enum {
|
||||
ONE_STOP_BIT = 0x1,
|
||||
ONE_HALF_STOP_BIT = 0x2,
|
||||
TWO_STOP_BIT = 0x3
|
||||
} UartStopBitsNum;
|
||||
|
||||
typedef enum {
|
||||
NONE_BITS = 0x2,
|
||||
ODD_BITS = 1,
|
||||
EVEN_BITS = 0
|
||||
} UartParityMode;
|
||||
|
||||
typedef enum {
|
||||
STICK_PARITY_DIS = 0,
|
||||
STICK_PARITY_EN = 1
|
||||
} UartExistParity;
|
||||
|
||||
typedef enum {
|
||||
UART_None_Inverse = 0x0,
|
||||
UART_Rxd_Inverse = UART_RXD_INV,
|
||||
UART_CTS_Inverse = UART_CTS_INV,
|
||||
UART_Txd_Inverse = UART_TXD_INV,
|
||||
UART_RTS_Inverse = UART_RTS_INV,
|
||||
} UART_LineLevelInverse;
|
||||
|
||||
|
||||
typedef enum {
|
||||
BIT_RATE_300 = 300,
|
||||
BIT_RATE_600 = 600,
|
||||
BIT_RATE_1200 = 1200,
|
||||
BIT_RATE_2400 = 2400,
|
||||
BIT_RATE_4800 = 4800,
|
||||
BIT_RATE_9600 = 9600,
|
||||
BIT_RATE_19200 = 19200,
|
||||
BIT_RATE_38400 = 38400,
|
||||
BIT_RATE_57600 = 57600,
|
||||
BIT_RATE_74880 = 74880,
|
||||
BIT_RATE_115200 = 115200,
|
||||
BIT_RATE_230400 = 230400,
|
||||
BIT_RATE_460800 = 460800,
|
||||
BIT_RATE_921600 = 921600,
|
||||
BIT_RATE_1843200 = 1843200,
|
||||
BIT_RATE_3686400 = 3686400,
|
||||
} UartBautRate;
|
||||
|
||||
typedef enum {
|
||||
NONE_CTRL,
|
||||
HARDWARE_CTRL,
|
||||
XON_XOFF_CTRL
|
||||
} UartFlowCtrl;
|
||||
|
||||
typedef enum {
|
||||
USART_HardwareFlowControl_None = 0x0,
|
||||
USART_HardwareFlowControl_RTS = 0x1,
|
||||
USART_HardwareFlowControl_CTS = 0x2,
|
||||
USART_HardwareFlowControl_CTS_RTS = 0x3
|
||||
} UART_HwFlowCtrl;
|
||||
|
||||
typedef enum {
|
||||
EMPTY,
|
||||
UNDER_WRITE,
|
||||
WRITE_OVER
|
||||
} RcvMsgBuffState;
|
||||
|
||||
typedef struct {
|
||||
uint32 RcvBuffSize;
|
||||
uint8 *pRcvMsgBuff;
|
||||
uint8 *pWritePos;
|
||||
uint8 *pReadPos;
|
||||
uint8 TrigLvl; //JLU: may need to pad
|
||||
RcvMsgBuffState BuffState;
|
||||
} RcvMsgBuff;
|
||||
|
||||
typedef struct {
|
||||
uint32 TrxBuffSize;
|
||||
uint8 *pTrxBuff;
|
||||
} TrxMsgBuff;
|
||||
|
||||
typedef enum {
|
||||
BAUD_RATE_DET,
|
||||
WAIT_SYNC_FRM,
|
||||
SRCH_MSG_HEAD,
|
||||
RCV_MSG_BODY,
|
||||
RCV_ESC_CHAR,
|
||||
} RcvMsgState;
|
||||
|
||||
typedef struct {
|
||||
UartBautRate baut_rate;
|
||||
UartBitsNum4Char data_bits;
|
||||
UartExistParity exist_parity;
|
||||
UartParityMode parity;
|
||||
UartStopBitsNum stop_bits;
|
||||
UartFlowCtrl flow_ctrl;
|
||||
RcvMsgBuff rcv_buff;
|
||||
TrxMsgBuff trx_buff;
|
||||
RcvMsgState rcv_state;
|
||||
int received;
|
||||
int buff_uart_no; //indicate which uart use tx/rx buffer
|
||||
} UartDevice;
|
||||
|
||||
void uart_init(UartBautRate uart0_br, UartBautRate uart1_br);
|
||||
|
||||
///////////////////////////////////////
|
||||
#define UART_FIFO_LEN 128 //define the tx fifo length
|
||||
#define UART_TX_EMPTY_THRESH_VAL 0x10
|
||||
|
||||
|
||||
typedef enum {
|
||||
RUN = 0,
|
||||
BLOCK = 1,
|
||||
} TCPState;
|
||||
|
||||
|
||||
//==============================================
|
||||
#define FUNC_UART0_CTS 4
|
||||
#define FUNC_U0CTS 4
|
||||
#define FUNC_U1TXD_BK 2
|
||||
#define UART_LINE_INV_MASK (0x3f<<19)
|
||||
void UART_SetWordLength(uint8 uart_no, UartBitsNum4Char len);
|
||||
void UART_SetStopBits(uint8 uart_no, UartStopBitsNum bit_num);
|
||||
void UART_SetLineInverse(uint8 uart_no, UART_LineLevelInverse inverse_mask);
|
||||
void UART_SetParity(uint8 uart_no, UartParityMode Parity_mode);
|
||||
void UART_SetBaudrate(uint8 uart_no, uint32 baud_rate);
|
||||
void UART_SetFlowCtrl(uint8 uart_no, UART_HwFlowCtrl flow_ctrl, uint8 rx_thresh);
|
||||
void UART_WaitTxFifoEmpty(uint8 uart_no, uint32 time_out_us); //do not use if tx flow control enabled
|
||||
void UART_ResetFifo(uint8 uart_no);
|
||||
void UART_ClearIntrStatus(uint8 uart_no, uint32 clr_mask);
|
||||
void UART_SetIntrEna(uint8 uart_no, uint32 ena_mask);
|
||||
void UART_SetPrintPort(uint8 uart_no);
|
||||
bool UART_CheckOutputFinished(uint8 uart_no, uint32 time_out_us);
|
||||
//==============================================
|
||||
|
||||
// Buffer functions
|
||||
uint16 rx_buff_deq(char* pdata, uint16 data_len);
|
||||
void tx_buff_enq(char* pdata, uint16 data_len);
|
||||
|
||||
#endif
|
||||
|
||||
+19
-2
@@ -8,7 +8,7 @@
|
||||
#include <esp8266.h>
|
||||
#include <uart_register.h>
|
||||
|
||||
#define UART_TX_BUFFER_SIZE 256 //Ring buffer length of tx buffer
|
||||
#define UART_TX_BUFFER_SIZE 512 //Ring buffer length of tx buffer
|
||||
#define UART_RX_BUFFER_SIZE 512 //Ring buffer length of rx buffer
|
||||
|
||||
struct UartBuffer {
|
||||
@@ -177,6 +177,11 @@ void UART_RxFifoCollect(void)
|
||||
}
|
||||
}
|
||||
|
||||
u16 ICACHE_FLASH_ATTR UART_AsyncTxGetEmptySpace(void)
|
||||
{
|
||||
return pTxBuffer->Space;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule data to be sent
|
||||
* @param pdata
|
||||
@@ -227,7 +232,7 @@ static void UART_TxFifoEnq(struct UartBuffer *pTxBuff, uint8 data_len, uint8 uar
|
||||
pTxBuff->Space += data_len;
|
||||
}
|
||||
|
||||
|
||||
volatile bool next_empty_it_only_for_notify = false;
|
||||
/******************************************************************************
|
||||
* FunctionName : TxFromBuffer
|
||||
* Description : get data from the tx buffer and fill the uart tx fifo, co-work with the uart fifo empty interrupt
|
||||
@@ -251,7 +256,19 @@ void UART_DispatchFromTxBuffer(uint8 uart_no)
|
||||
else {
|
||||
len_tmp = (uint8) data_len;
|
||||
UART_TxFifoEnq(pTxBuffer, len_tmp, uart_no);
|
||||
|
||||
// we get one more IT after fifo ends even if we have 0 more bytes
|
||||
// for notify
|
||||
if (next_empty_it_only_for_notify) {
|
||||
notify_empty_txbuf();
|
||||
next_empty_it_only_for_notify = 0;
|
||||
} else {
|
||||
// Done sending
|
||||
next_empty_it_only_for_notify = 1;
|
||||
SET_PERI_REG_MASK(UART_INT_ENA(UART0), UART_TXFIFO_EMPTY_INT_ENA);
|
||||
}
|
||||
}
|
||||
|
||||
// }
|
||||
// else {
|
||||
// error("pTxBuff null \n\r");
|
||||
|
||||
@@ -23,4 +23,8 @@ void UART_DispatchFromTxBuffer(uint8 uart_no);
|
||||
|
||||
u16 UART_AsyncRxCount(void);
|
||||
|
||||
u16 UART_AsyncTxGetEmptySpace(void);
|
||||
|
||||
extern void __attribute__((weak)) notify_empty_txbuf(void);
|
||||
|
||||
#endif //ESP_VT100_FIRMWARE_UART_BUFFER_H
|
||||
|
||||
+3
-5
@@ -94,13 +94,11 @@ void ICACHE_FLASH_ATTR user_init(void)
|
||||
wifi_set_opmode(NULL_MODE); // saves to flash if changed - this might avoid the current spike on startup?
|
||||
|
||||
printf("\r\n");
|
||||
banner("====== ESP8266 Remote Terminal ======");
|
||||
banner("====== ESPTerm ======");
|
||||
banner_info("Firmware (c) Ondrej Hruska, 2017");
|
||||
banner_info("github.com/MightyPork/esp-vt100-firmware");
|
||||
banner_info(TERMINAL_GITHUB_REPO);
|
||||
banner_info("");
|
||||
banner_info("Version " FIRMWARE_VERSION ", built " __DATE__ " at " __TIME__);
|
||||
banner_info("");
|
||||
banner_info("Department of Measurement, CTU Prague");
|
||||
banner_info("Version "FIRMWARE_VERSION", built " __DATE__ " at " __TIME__);
|
||||
printf("\r\n");
|
||||
|
||||
ioInit();
|
||||
|
||||
+3
-3
@@ -5,12 +5,12 @@
|
||||
#ifndef ESP_VT100_FIRMWARE_VERSION_H
|
||||
#define ESP_VT100_FIRMWARE_VERSION_H
|
||||
|
||||
#define FW_V_MAJOR 0
|
||||
#define FW_V_MINOR 7
|
||||
#define FW_V_MAJOR 1
|
||||
#define FW_V_MINOR 0
|
||||
#define FW_V_PATCH 1
|
||||
|
||||
#define FIRMWARE_VERSION STR(FW_V_MAJOR) "." STR(FW_V_MINOR) "." STR(FW_V_PATCH) "+" GIT_HASH
|
||||
#define FIRMWARE_VERSION_NUM (FW_V_MAJOR*10000 + FW_V_MINOR*100 + FW_V_PATCH) // this is used in ID queries
|
||||
#define FIRMWARE_VERSION_NUM (FW_V_MAJOR*1000 + FW_V_MINOR*10 + FW_V_PATCH) // this is used in ID queries
|
||||
#define TERMINAL_GITHUB_REPO "https://github.com/MightyPork/ESPTerm"
|
||||
|
||||
#endif //ESP_VT100_FIRMWARE_VERSION_H
|
||||
|
||||
Reference in New Issue
Block a user