|
|
nono 1.7.0
//
// nono
// Copyright (C) 2024 nono project
// Licensed under nono-license.txt
//
//
// メイン画面を vt220 互換コンソールにする
//
// https://vt100.net/docs/vt220-rm/contents.html
// vttest ver2.7 (20210210) result
//
// OK 1. (1) 枠表示、DECALN、80桁
// OK 1. (2) 枠表示、DECALN、132桁
// OK 1. (3) Test of autowrap、80桁
// OK 1. (4) Test of autowrap、132桁
// OK 1. (5) Test of cursor-control
// OK 1. (6) Test of leading zeros in ESC
// FAIL 2. (1) Test of WRAP AROUND
// FAIL 2. (2) Test of TAB setting/resetting
// OK* 2. (3) 132 column, light background (*背景色の区別なし)
// OK* 2. (4) 80 column, light background (*背景色の区別なし)
// OK* 2. (5) 132 column, dark background (*背景色の区別なし)
// OK* 2. (6) 80 column, dark background (*背景色の区別なし)
// OK* 2. (7) Soft scroll down size 2 (*Soft/Jump の区別なし)
// OK* 2. (8) Soft scroll down size 24 (*Soft/Jump の区別なし)
// OK* 2. (9) Jump scroll down size 2 (*Soft/Jump の区別なし)
// OK* 2. (10) Jump scroll down size 24 (*Soft/Jump の区別なし)
// OK 2. (11) Origin mode test. Bottom
// OK 2. (12) Origin mode test. Top
// OK* 2. (13) Graphic rendition test. Dark (*背景色の区別なし)
// OK* 2. (14) Graphic rendition test. Light (*背景色の区別なし)
// OK 2. (15) Test of SAVE/RESTORE
// OK* 3. character sets (DEC 罫線以外対応予定なし)
// FAIL 4. double-sized characters は対応予定なし
// - 5.1 LED Lights
// FAIL 5.2 Auto Repeat
// - 5.3 KeyBoard Layout
// OK 5.4 Cursor Keys (VT52 モードのテストは動かない)
// - 5.5 Numeric Keypad
// - 5.6 Editing Keypad (?)
// - 5.7 Function Keys (?)
// - 5.8 AnswerBack (?)
// FAIL 5.9 Control Keys (CTRL-@ が通らない)
// - 6.1 <ENQ> (?)
// FAIL 6.2 Set/Reset Mode
// OK 6.3 Device Status Report (DSR)
// OK 6.4 Primary Device Attributes (DA)
// OK 6.5 Secondary Device Attributes (DA)
// - 6.6 Tertiary Device Attributes (DA) (*VT420以上)
// - 6.7 Request Terminal Parameters (DECREQTPARM) (*VT100)
// - 7. VT52 mode
// OK 8. (1) Screen accordion test
// OK 8. (2) Top A, Bottom X
// OK 8. (3) Test of Insert mode, A**B
// OK 8. (4) Test of Delete Char, AB
// OK 8. (5) The right column should be staggered
// FAIL 8. (6) 〃 (double-sized)
// OK 8. (7) ANSI Insert Char
// OK 8. (8) Screen Accordion test (以下132桁)
// OK 8. (9) Top A, Bottom X
// OK 8. (10) Test of Insert mode, A**B
// OK 8. (11) Test of Delete char, AB
// OK 8. (12) The right column should be staggered
// FAIL 8. (13) 〃 (double-sized)
// OK 8. (14) ANSI Insert Char
// - 9. Test of known bugs
// OK? 10.1 RIS
// FAIL 10.2 DECTST
// OK? 10.3 DECSTR
// OK 11.1.1.1.1 Keyboard Status
// OK 11.1.1.1.2 Operating Status
// OK 11.1.1.1.3 Printer Status
// OK 11.1.1.1.4 UDK Status
// ? 11.1.2.1 SRM
// OK 11.1.2.2 DECTCEM
// OK 11.1.2.3 ECH
// FAIL 11.1.2.4 (1) DECSCA, DECSED, DECSEL
// OK 11.1.2.4 (2) DECSCA, ignoring EL/ED
// OK 11.1.2.4 (3) DECSCA, ignoring ECH/ICH/DCH
// FAIL 11.1.3 S7C1T/S8C1T
// FAIL 11.1.4 Printer(MC)
// FAIL 11.1.5 DECDLD
// OK 11.1.6 DECSTR
// FAIL 11.1.7 DECUDK
// - 11.2 VT320 features
// - 11.3 VT420 features
// - 11.4 VT520 features
// - 11.5 ISO-6429 cursor-movement
// - 11.6 ISO-6429 colors
// - 11.7 Other ISO-6429 features
// - 11.8 XTERM special features
#include "console.h"
#include "bitmap.h"
#include "builtinrom.h"
#include "event.h"
#include "comdriver_cons.h"
#include "mainapp.h"
#include "scheduler.h"
#include "syncer.h"
#include <algorithm>
// エスケープシーケンス等。
#define ESC '\x1b'
#define PRINTLOG(fmt...) do { \
if (log) { \
fprintf(log, fmt); \
fflush(log); \
} \
} while (0)
// コンストラクタ
ConsoleDevice::ConsoleDevice()
: inherited(OBJ_CONSOLE)
{
screen.resize(fullwidth * height);
SetPalette(false);
}
// デストラクタ
ConsoleDevice::~ConsoleDevice()
{
if (log) {
fclose(log);
log = NULL;
}
if (comdriver) {
comdriver->DetachConsole();
comdriver = NULL;
}
}
// 初期化
bool
ConsoleDevice::Init()
{
renderer = GetVideoRenderer();
keyboard = gMainApp.GetObject<ConsoleKeyboard>(OBJ_KEYBOARD);
syncer = GetSyncer();
// ログファイル。
if (gMainApp.console_logfile) {
log = fopen(gMainApp.console_logfile, "w");
if (log == NULL) {
warn("console logfile \"%s\"", gMainApp.console_logfile);
return false;
}
}
auto evman = GetEventManager();
vsync_event = evman->Regist(this,
ToEventCallback(&ConsoleDevice::VSyncCallback),
"Console V-Sync");
vsync_event->time = 16666.667_usec;
return true;
}
// COM ドライバを接続。
// (HostCOM の cons ドライバが呼ぶ)
void
ConsoleDevice::Attach(COMDriverConsole *comdriver_)
{
comdriver = comdriver_;
SetPalette((comdriver != NULL));
}
void
ConsoleDevice::ResetHard(bool poweron)
{
if (poweron) {
// 最初に一回フチを含めて描画する。
init_screen = true;
// シリアルコンソールなのでリセットには反応しない。
Clear();
Locate(0, 0);
}
scheduler->RestartEvent(vsync_event);
ResetTerminalHard();
}
void
ConsoleDevice::VSyncCallback(Event *ev)
{
bool update_needed;
// 更新のあった行については、点滅があるか全て調べ直す。
for (int y = 0; y < dirty.size(); y++) {
if (__predict_false(dirty[y])) {
has_blink.set(y, std::any_of(
screen.begin() + y * fullwidth,
screen.begin() + y * fullwidth + fullwidth,
[](int v) { return (v & ATTR_BLINK); }));
}
}
// 点滅を持つ行は周期ごとに dirty に追加。
if (__predict_false(has_blink.any())) {
auto rtime = syncer->GetRealTime();
// nsec を >>29 で 536_msec くらいになる
auto new_blinking = (bool)(((rtime/*_nsec*/) >> 29) & 1);
if (blinking != new_blinking) {
blinking = new_blinking;
dirty |= has_blink;
}
} else {
// 点滅が一つもなくなったら点滅状態を解除。
blinking = false;
}
// 更新 (dirty[]) があれば pending[] に重ねる。
{
std::lock_guard<std::mutex> lock(mtx);
pending |= dirty;
update_needed = pending.any();
}
dirty.reset();
// 更新の必要があれば Render に作画指示。
if (update_needed) {
renderer->NotifyRender();
}
scheduler->StartEvent(ev);
}
// パレットの状態を変更する。
void
ConsoleDevice::SetPalette(bool console_is_active)
{
if (console_is_active) {
palette[0] = UD_LIGHT_GREY; // BG
palette[1] = UD_BLACK; // FG
} else {
palette[0] = UD_LIGHT_GREY; // BG
palette[1] = UD_GREY; // FG
}
// 反転色。
palette[2] = palette[0];
// 画面に即反映させる。
{
std::lock_guard<std::mutex> lock(mtx);
pending.set();
}
}
// ソフトリセット (DECSTR 用)。
void
ConsoleDevice::ResetTerminalSoft()
{
cursor_visible = true;
insert_mode = false;
decom = false;
keyboard->SetCKM(false);
ResetScroll();
save_x = 0;
save_y = 0;
save_attr = 0;
save_wrap = false;
save_decom = false;
}
// 端末の状態をハードリセットする。
// (画面のクリアはここではしない?)
void
ConsoleDevice::ResetTerminalHard()
{
ResetTerminalSoft();
ResetState();
SetAttr(0);
wrap_pending = false;
width = 80;
ResetMargin();
cur_x = 0;
cur_y = 0;
cr_on_lf = false;
}
// 左右マージンをリセットする。
void
ConsoleDevice::ResetMargin()
{
// margin は両閉区間で表す。
margin_left = 0;
margin_right = width - 1;
}
// スクロール領域をリセットする。
void
ConsoleDevice::ResetScroll()
{
// scroll は両閉区間で表す。
scroll_top = 0;
scroll_btm = height - 1;
}
// エスケープシーケンスの状態を初期化する。
void
ConsoleDevice::ResetState()
{
state = 0;
seq.clear();
arg.clear();
argv.clear();
argv_idx = -1;
}
// ch をログ出力する。
void
ConsoleDevice::PutcharDebug(uint32 ch)
{
if (ch == '\n') {
// 改行が来たらログも改行する。
fputs("\\n\n", log);
log_newline = true;
} else {
if (ch == ESC) {
// 読みやすさのため ESC の前で改行したい。
if (log_newline == false) {
fputc('\n', log);
}
fputs("ESC", log);
} else if (ch == '\r') {
fputs("\\r", log);
} else if (ch == '\t') {
fputs("\\t", log);
} else if (ch < 0x20 || ch >= 0x7f) {
fprintf(log, "\\x%02x", ch);
} else {
fputc(ch, log);
}
log_newline = false;
}
fflush(log);
}
// シリアルコンソールから来る出力。
// ホストドライバスレッドから呼ばれる。
void
ConsoleDevice::Putchar(uint32 ch)
{
if (__predict_false(log)) {
PutcharDebug(ch);
}
// 処理したら return。処理できなかったら switch を抜ける。
switch (state) {
case ESC:
if (PutcharESC(ch)) {
return;
}
break;
case '[': // CSI
if (PutcharCSI(ch)) {
return;
}
break;
case '(': // G0
if (PutcharG0(ch)) {
return;
}
break;
case '?': // CSI '?'
if (PutcharCSIQ(ch)) {
return;
}
break;
case '#':
if (PutcharSharp(ch)) {
return;
}
break;
case ')': // G1
case '*': // G2
case '+': // G3
// とりあえず何もしない。
ResetState();
return;
default:
PutcharPlain(ch);
return;
}
// シーケンスを破棄して平文に戻る。
putlog(0, "Unknown Sequence:%s", Dump(ch).c_str());
ResetState();
}
// 何のシーケンスでもない1文字を表示する。
void
ConsoleDevice::PutcharPlain(uint32 ch)
{
switch (ch) {
case '\x00': // NUL
// 何もしない。パディングというか NOP 代わりに大量に送られてくる。
return;
case '\x01': // SOH
case '\x02': // STX
case '\x03': // ETX
case '\x04': // EOT
case '\x05': // ENQ
case '\x06': // ACK
// 何もしない。
return;
case '\x07': // BEL
// とりあえず無視。
putlog(2, "BEL (Ignored)");
return;
case '\x08': // BS
// カーソルを 1 文字左に移動。文字は消さない。
// DECLRMM(左右マージン)の影響を受ける。
LocateXinMargin(cur_x - 1);
return;
case '\t': // HT
HT();
return;
case '\n': // LF
case '\x0b': // VT
case '\x0c': // FF
LF();
return;
case '\r': // CR
CR();
return;
case '\x0e': // SO
case '\x0f': // SI
case '\x10': // DLE
case '\x11': // DC1/XON
case '\x12': // DC2
case '\x13': // DC3/XOFF
case '\x14': // DC4
case '\x15': // NAK
case '\x16': // SYN
case '\x17': // ETB
// 何もしない。
return;
case '\x18': // CAN
// Cancel
ResetState();
return;
case '\x19': // EM
// 何もしない。
return;
case '\x1a': // SUB
ResetState();
return;
case ESC: // ESC
seq.push_back(ch);
state = ch;
return;
case '\x1c': // FS (File Separator)
case '\x1d': // GS (Group Separator)
case '\x1e': // RS (Record Separator)
case '\x1f': // US (Unit Separator)
// 何もしない。
return;
case '\x7f': // DEL
// 何もしない。
return;
default:
// ここが通常の1文字出力。
// 8ビット文字はここでは素通しするが、レンダラで豆腐を出力する。
// NetBSD の sysinst が言語選択画面で1文字送ってくる。
// 8ビットのエスケープシーケンス (\x80〜\x9f) のことは無視する。
PutcharNormal(ch);
return;
}
}
// ESC の次の文字を処理する。
// 処理すれば true、知らないシーケンスなので中止する場合は false を返す。
bool
ConsoleDevice::PutcharESC(uint32 ch)
{
switch (ch) {
case '[': // CSI
case '(': // G0
case ')': // G1
case '*': // G2
case '+': // G3
case '#': // Font width/height control
seq.push_back(ch);
state = ch;
return true;
case '\\': // ST
putlog(2, "ST");
break;
case ' ': // Select C1 Controls
// ESC ' ' F (S7C1T)
// ESC ' ' G (S8C1T)
return Unimpl(ch, "S*C1T");
// DECKPAM/DECKPNM はテンキー入力をそのまま送る(Keypad Numeric Mode)か、
// Keypad Application Mode で送るかの切り替えだが、今の実装では
// 文字コードしかここに来ていないためテンキーかどうかはもう区別できない。
// ので、テンキーが存在していないか、あってもあえて押していないのと等価。
// vi(1) の開始/終了時に送られてくる。
case '=': // DECKPAM
putlog(3, "DECKPAM (Keypad App. Mode)");
break;
case '>': // DECKPNM
putlog(3, "DECKPNM (Keypad Num. Mode)");
break;
case '7': // DECSC (カーソル位置保存)
putlog(2, "DECSC (Save)");
save_x = cur_x;
save_y = cur_y;
save_attr = cur_attr;
save_wrap = wrap_pending;
save_decom = decom;
break;
case '8': // DECRC (カーソル位置復帰)
// 変数の復帰順序注意。
putlog(2, "DECRC (Restore)");
wrap_pending = save_wrap;
decom = save_decom;
cur_attr = save_attr;
Locate(save_x, save_y);
break;
case 'c': // RIS (Hard terminal reset)
putlog(2, "RIS (Hard terminal reset)");
ResetTerminalHard();
Clear();
break;
case 'D': // IND
{
// カーソルを1行下に移動する。
// ただしカーソルがスクロール下端行にいる場合はスクロール領域全体を
// 1行上に移して、カーソル行を空行にする。
// DECLRMM(左右マージン)の影響は受けない。
//
// スクロール領域外でも1行下に移動するだけなので、スクロール上端か
// どうかだけ。
wrap_pending = false;
if (cur_y != scroll_btm) {
putlog(3, "IND (Index) ");
LocateY(cur_y + 1);
} else {
// 下端から一つ下に移動しようとした。
putlog(3, "IND (Index) scrolled");
for (int y = scroll_top + 1; y <= scroll_btm; y++) {
CopyLineWhole(y - 1, y);
}
EraseLineWhole(scroll_btm);
}
break;
}
case 'E': // NEL (Next Line)
// カーソルを次行の先頭に。スクロール領域下端に来たらスクロールアップ。
// なので CR+LF と等価。
putlog(3, "NEL (Next Line)");
CR();
LF();
break;
case 'H': // HTS (Horizontal tab set)
return Unimpl(ch, "HTS");
case 'M': // RI (Reverse Index)
{
// カーソルを1行上に移動する。
// ただしカーソルがスクロール上端行にいる場合はスクロール領域全体を
// 1行下に移して、カーソル行を空行にする。
// DECLRMM(左右マージン)の影響は受けない。
//
// スクロール領域外でも1行上に移動するだけなので、スクロール上端か
// どうかだけ。
wrap_pending = false;
if (cur_y != scroll_top) {
putlog(3, "RI (Rev. Index)");
LocateY(cur_y - 1);
} else {
// 上端から一つ上に移動しようとした。
putlog(3, "RI (Rev. Index) scrolled");
for (int y = scroll_btm - 1; y >= scroll_top; y--) {
CopyLineWhole(y + 1, y);
}
EraseLineWhole(scroll_top);
}
break;
}
case 'N': // SS2
return Unimpl(ch, "SS2");
case 'O': // SS3
return Unimpl(ch, "SS3");
case 'Z': // DECID (Identify Terminal)
// VT100 か問い合わせなので、常に VT100 相当を返答。
putlog(3, "DECID (Identify Terminal)");
SendString("\x1b/Z");
break;
case '$': // 94 multibyte (VT300 シリーズ以降?)
default:
return false;
}
ResetState();
return true;
}
// CSI の次の文字を処理する。
// 処理すれば true、知らないシーケンスなので中止する場合は false を返す。
bool
ConsoleDevice::PutcharCSI(uint32 ch)
{
switch (ch) {
case '\x00'
... '\x17':
case '\x19': // EM
case '\x1c': // FS (File Separator)
case '\x1d': // GS (Group Separator)
case '\x1e': // RS (Record Separator)
case '\x1f': // US (Unit Separator)
case '\x7f': // DEL
// 制御文字はその場で実行される。
PutcharPlain(ch);
// ステート完了ではないので break ではなく return。
return true;
case '\x18': // CAN
// Cancel
putlog(3, "CAN");
break;
case '\x1a': // SUB
putlog(3, "SUB");
break;
case ESC: // ESC
// ここまでのを捨てて新しく始める?らしい。
ResetState();
PutcharPlain(ch);
return true;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case ';':
case '\"':
case '$':
case '>':
case '!':
seq.push_back(ch);
arg.push_back(ch);
return true;
case '@': // ICH (Insert Character)
{
// カーソル位置に n 個空白を挿入。
// カーソル位置とそれ以降の文字は右にずれる。
// カーソル位置は変わらない。
// DECLRMM(左右マージン)の影響を受ける。
int n = GetArgDefault(1);
putlog(3, "ICH (Insert Char) %u", n);
int maxlen = margin_right - cur_x + 1;
n = std::min(n, maxlen);
memmove(&screen[cur_y * fullwidth + (cur_x + n)],
&screen[cur_y * fullwidth + cur_x],
(maxlen - n) * sizeof(screen[0]));
for (int i = 0; i < n; i++) {
Setchar(cur_x + i, cur_y, ' ' | cur_attr);
}
dirty.set(cur_y);
break;
}
case 'A': // CUU
{
// カーソルを n 行上に移動。
// DECSTBM(スクロール領域)の影響を受ける。
int n = GetArgDefault(1);
putlog(3, "CUU (Cursor Up) %u", n);
LocateYinScroll(cur_y - n);
break;
}
case 'B': // CUD
{
// カーソルを下に移動。
// DECSTBM(スクロール領域)の影響を受ける。
int n = GetArgDefault(1);
putlog(3, "CUD (Cursor Down) %u", n);
LocateYinScroll(cur_y + n);
break;
}
case 'C': // CUF
{
// カーソルを右に移動。
// DECLRMM(左右マージン)の影響を受ける。
int n = GetArgDefault(1);
putlog(3, "CUF (Cursor Forward) %u", n);
LocateXinMargin(cur_x + n);
break;
}
case 'D': // CUB
{
// カーソルを左に移動。
// DECLRMM(左右マージン)の影響を受ける。
int n = GetArgDefault(1);
putlog(3, "CUF (Cursor Backward) %u", n);
LocateXinMargin(cur_x - n);
break;
}
case 'c': // DA
{
// デバイス属性を応答する。
// CSI [0] c なら Primary DA (DA1)。
// CSI > c なら Secondary DA (DA2)。
int argc = ParseArg();
if (argc > 0 && argv[0][0] == '>') {
// DA2はファームウェアバージョンとハードウェアオプション。
// どちらも関係ないので VT220('1'), ver 1.0 ('10'),
// no options ('0') とだけ返す。
putlog(3, "DA2 (Device Attributes)");
SendString("\x1b[>1;10;0c");
} else {
// とりあえず他は全部 DA1 ということにしておく。
// DA1 に対する応答は今のところ 62 (VT220) のみ。
putlog(3, "DA (Device Attribute)");
SendString("\x1b[?62c");
}
break;
}
case 'f': // HVP
// HVP は VT52 互換性のため存在しており CUP と動作は同じらしい。
case 'H': // CUP
{
// CSI <y1> ; <x1> H … (x1, y1) にカーソルを移動。
// DECOM=0 なら画面左上が原点。
// DECOM=1 なら DECSTBM/DECLRMM の左上を原点とする。
int y1 = GetArgDefault(1);
int x1 = GetArgDefault(1);
putlog(3, "CUP (Cursor Position) row=%u col=%u", y1, x1);
int x = x1 - 1;
int y = y1 - 1;
if (decom) {
Locate(margin_left + x, scroll_top + y);
} else {
Locate(x, y);
}
break;
}
case 'J': // ED (Erase Display)
{
// CSI [<n>] J
// <n> が 0 か省略なら、カーソル位置から画面右下までを消去。
// <n> が 1 なら、カーソル位置から左上までを消去。
// <n> が 2 なら、全画面消去。
// DECLRMM(左右マージン)/DECSTBM(スクロール領域)の影響は受けない。
// wrap は解除される。文字の属性も消去される。
// XXX SPA と ERM は未対応。
wrap_pending = false;
int pos = cur_y * fullwidth + cur_x;
int n = GetArg();
switch (n) {
default: // 未定義値は 0 とする実装が多いらしい。知らんけど
case 0:
putlog(3, "ED (Erase Display) Forward");
std::fill(screen.begin() + pos, screen.end(), ' ');
for (int y = cur_y; y < height; y++) {
dirty.set(y);
}
break;
case 1:
putlog(3, "ED (Erase Display) Backward");
std::fill(screen.begin(), screen.begin() + pos + 1, ' ');
for (int y = 0; y <= cur_y; y++) {
dirty.set(y);
}
break;
case 2:
putlog(3, "ED (Erase Display) All");
std::fill(screen.begin(), screen.end(), ' ');
dirty.set();
break;
}
break;
}
case 'K': // EL (Erase Line)
{
// CSI [<n>] K
// <n> が 0 か省略なら、カーソル位置の右側を消去。
// <n> が 1 なら、カーソル位置の左側を消去。
// <n> が 2 なら、カーソル行を消去。
// DECLRMM (左右マージン) の影響は受けない。
int n = GetArg();
int x0;
int x1;
switch (n) {
default: // 未定義値は 0 とする実装が多いらしい。知らんけど
case 0:
putlog(3, "EL (Erase Line) Forward");
x0 = cur_x;
x1 = width - 1;
break;
case 1:
putlog(3, "EL (Erase Line) Backward");
x0 = 0;
x1 = cur_x;
break;
case 2:
putlog(3, "EL (Erase Line) All");
x0 = 0;
x1 = width - 1;
break;
}
for (int x = x0; x <= x1; x++) {
Setchar(x, cur_y, ' ' | cur_attr);
}
dirty.set(cur_y);
break;
}
case 'L': // IL (Insert Line)
{
// CSI [<n>] L
// カーソル行とそれより下を1行ずつ下に移動し、
// カーソル行は空行にする。を n 回繰り返したもの。
// DECLRMM (左右マージン) の影響は受けない。
// DECSTBM (スクロール領域) の影響を受ける。
int n = GetArgDefault(1);
putlog(3, "IL (Insert Line) %u", n);
// たぶん企図した時点で解除される?
wrap_pending = false;
// カーソルがスクロール領域外にいたら何もしない。
if (cur_y < scroll_top || cur_y > scroll_btm) {
break;
}
n = std::min(n, scroll_btm - cur_y + 1);
int y;
for (y = scroll_btm; y >= (cur_y + n); y--) {
CopyLineWhole(y, y - n);
}
for (; y >= cur_y; y--) {
EraseLineWhole(y);
}
// カーソルは先頭へ移動。
LocateX(0);
break;
}
case 'M': // DL (Delete Line)
{
// CSI [<n>] M
// カーソル行を削除し、次行以降を1行ずつ上に移動して、
// 最下行を空行にする。を n 回繰り返したもの。
//
// DECLRMM (左右マージン) の影響は受けない。
// DECSTBM (スクロール領域) の影響を受ける。
int n = GetArgDefault(1);
putlog(3, "DL (Delete Line) %u", n);
// たぶん企図した時点で解除される?
wrap_pending = false;
// カーソルがスクロール領域外にいたら何もしない。
if (cur_y < scroll_top || cur_y > scroll_btm) {
break;
}
n = std::min(n, scroll_btm + cur_y + 1);
int y;
for (y = cur_y; y < scroll_btm - n; y++) {
CopyLineWhole(y, y + n);
}
for (; y < scroll_btm; y++) {
EraseLineWhole(y);
}
// カーソルは先頭へ移動。
LocateX(0);
break;
}
case 'g': // TBC (タブ設定をクリア)
// CSI [<n>] g
// <n> が 0 (か省略)なら、カーソル位置のタブをクリア。
// <n> が 3 ならすべてのタブをクリア。
return Unimpl(ch, "TBC");
case 'i': // Printing
return Unimpl(ch, "Printing");
case 'h': // SM (オプションを設定)
case 'l': // RM (オプションを解除)
{
// CSI <n> h
// CSI <n> l
bool val = (ch == 'h');
int argc = ParseArg();
for (int i = 0; i < argc; i++) {
int n = GetArg();
switch (n) {
case 2: // KAM (オンでキー入力を無視)
return Unimpl(ch, "KAM");
case 4: // IRM (Insert/Replace)
if (val) {
putlog(3, "Set IRM (to Insert)");
} else {
putlog(3, "Reset IRM (to Replace)");
}
insert_mode = val;
break;
case 12: // SRM (オンでローカルエコーを無効)
return Unimpl(ch, "SRM");
case 20: // LNM (オンで LF/VT/FF が行頭に移動)
putlog(3, "%s LNM(New Line Mode)", val ? "Set" : "Reset");
cr_on_lf = val;
break;
default:
putlog(2, "Ignore option %u %c", n, ch);
break;
}
}
(void)val;
break;
}
case 'n': // DSR (Device Status Report)
{
int n = GetArg();
switch (n) {
case 5: // Request for terminal status
// 障害が起きているかどうかの問い合わせのようだ。応答は
// ESC [ 0 n … 障害なし
// ESC [ 3 n … 障害あり
putlog(3, "DSR 5(Request Terminal Status)");
SendString("\x1b[0n");
break;
case 6: // Request for cursor position
{
// カーソル位置の問い合わせ。(ANSI 仕様?)
// DECOM (マージン原点) の影響を受ける。(vttest 6-3)
putlog(3, "DSR 6(Request Cursor Position)");
int x = cur_x;
int y = cur_y;
if (decom) {
x -= margin_left;
y -= scroll_top;
}
SendString(string_format("\x1b[%u;%uR", y + 1, x + 1));
break;
}
default:
return false;
}
break;
}
case 'm': // SGR (Select Graphic Rendition)
{
// CSI 7 ; 37 m みたいにセミコロンで区切ってパラメータを複数書ける。
// パラメータには 38:2:rr:gg:bb みたいにコロンで区切ったひとかたまり
// もある。
int argc = ParseArg();
if (argc == 0) {
argc++;
}
if (loglevel >= 3) {
std::string msg;
for (int j = 0; j < argc; j++) {
int n = GetArg();
msg += string_format("; %u", n);
}
putlogn("SGR %s", msg.c_str() + 2);
// XXX Peek がないがインデックスを戻せばまた使える。
argv_idx = 0;
}
for (int i = 0; i < argc; i++) {
int n = GetArg();
switch (n) {
// VT220 で定義されているもの。
case 0: // すべての属性を解除
SetAttr(0);
break;
case 1: // ボールドをセット
SetAttr(cur_attr | ATTR_BOLD);
break;
case 4: // 下線をセット
SetAttr(cur_attr | ATTR_UNDERLINE);
break;
case 5: // 低速点滅をセット (対応予定なし)
SetAttr(cur_attr | ATTR_BLINK);
break;
case 7: // 反転をセット
SetAttr(cur_attr | ATTR_REVERSE);
break;
case 22: // ボールド(と低輝度)を解除
SetAttr(cur_attr & ~ATTR_BOLD);
break;
case 24: // 下線を解除
SetAttr(cur_attr & ~ATTR_UNDERLINE);
break;
case 25: // 点滅を解除
SetAttr(cur_attr & ~ATTR_BLINK);
break;
case 27: // 反転を解除
SetAttr(cur_attr & ~ATTR_REVERSE);
break;
// それ以降で拡張されたものに勝手に対応してみる。
case 3: // イタリックをセット
SetAttr(cur_attr | ATTR_ITALIC);
break;
case 9: // 打ち消し線をセット
SetAttr(cur_attr | ATTR_STRIKE);
break;
case 23: // イタリックを解除
SetAttr(cur_attr & ~ATTR_ITALIC);
break;
case 29: // 打ち消し線を解除
SetAttr(cur_attr & ~ATTR_STRIKE);
break;
case 53: // 上線をセット
SetAttr(cur_attr | ATTR_OVERLINE);
break;
case 55: // 上線を取り消し
SetAttr(cur_attr & ~ATTR_OVERLINE);
break;
// 色も現状対応予定はないがメモも兼ねて並べておく。
case 30 ... 37: // 文字色の設定
case 38: // 文字色(拡張)を設定
case 39: // 文字色をデフォルトに戻す
case 40 ... 47: // 背景色を設定
case 48: // 背景色(拡張)を設定
case 49: // 背景色をデフォルトに戻す
case 90 ... 97: // 文字色(高輝度)を設定
case 100 ... 107: // 背景色(高輝度)を設定
default:
// 無視
break;
}
}
break;
}
case 'p': // DECSCL / DECSTR
{
int argc = ParseArg();
if (argc == 1 && argv[0] == "!") {
// DECSTR
putlog(2, "DECSTR (Soft Terminal Reset)");
ResetTerminalSoft();
break;
} else {
// DECSCL
return Unimpl(ch, "DECSCL");
}
}
case 'P': // DCH (Delete Character)
{
// カーソル位置から n 文字削除。
// 左へずらして、右に空白を挿入。
// カーソル位置は変わらない。
int n = GetArgDefault(1);
putlog(3, "DCH (Delete Char) %u", n);
int maxlen = margin_right - cur_x + 1;
n = std::min(n, maxlen);
memmove(&screen[cur_y * fullwidth + cur_x],
&screen[cur_y * fullwidth + (cur_x + n)],
(maxlen - n) * sizeof(screen[0]));
for (int i = 0; i < n; i++) {
Setchar(margin_right - i, cur_y, ' ' | cur_attr);
}
dirty.set(cur_y);
break;
}
case 'r': // DECSTBM
{
// CSI <t1> ; <b1> r。
// スクロール範囲を [t1, b1] に設定。
int t1 = GetArg();
int b1 = GetArg();
if (t1 == 0 && b1 == 0) {
// 省略 (0;0) なら解除。
putlog(2, "DECSTBM (Set Top&Bottom Margin) Clear");
ResetScroll();
} else {
if (t1 == 0) {
t1 = 1;
}
if (b1 == 0) {
b1 = 1;
}
putlog(2, "DECSTBM (Set Top&Bottom Margin) %u; %u", t1, b1);
int top = t1 - 1;
int btm = b1 - 1;
// 無効値なら変更しない。
if (top < 0 || top >= height) {
break;
}
if (btm < 0 || btm >= height) {
break;
}
if (top >= btm) {
break;
}
scroll_top = top;
scroll_btm = btm;
}
// そのホームポジションに移動。
LocateXinMargin(0);
LocateYinScroll(0);
break;
}
case 'X': // ECH (Erase Character)
{
int n = GetArgDefault(1);
putlog(3, "ECH (Erase Char) %u", n);
int maxlen = margin_right - cur_x + 1;
n = std::min(n, maxlen);
for (int i = 0; i < n; i++) {
Setchar(cur_x + i, cur_y, ' ' | cur_attr);
}
dirty.set(cur_y);
break;
}
case 'y': // DECTST (Tests)
return Unimpl(ch, "DECTST");
case '?':
seq.push_back(ch);
state = ch;
return true;
default:
return false;
}
ResetState();
return true;
}
// CSI '?' の次の文字を処理する。
// 処理すれば true、知らないシーケンスなので中止する場合は false を返す。
bool
ConsoleDevice::PutcharCSIQ(uint32 ch)
{
switch (ch) {
case '\x00'
... '\x17':
case '\x19': // EM
case '\x1c': // FS (File Separator)
case '\x1d': // GS (Group Separator)
case '\x1e': // RS (Record Separator)
case '\x1f': // US (Unit Separator)
case '\x7f': // DEL
// 制御文字はその場で実行される。
PutcharPlain(ch);
// ステート完了ではないので break ではなく return。
return true;
case '\x18': // CAN
case '\x1a': // SUB
// Cancel
break;
case ESC: // ESC
// ここまでのを捨てて新しく始める?らしい。
ResetState();
PutcharPlain(ch);
return true;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case ';':
case '\"':
case '$':
case '>':
seq.push_back(ch);
arg.push_back(ch);
return true;
case 'h': // DECSET (拡張オプションを設定)
case 'l': // DECRST (拡張オプションを解除)
{
// CSI '?' <n> h
// CSI '?' <n> l
bool val = (ch == 'h');
int argc = ParseArg();
for (int i = 0; i < argc; i++) {
int n = GetArg();
switch (n) {
case 1: // DECCKM
// 上下左右キーの送信コード設定。
if (val) {
putlog(3, "Set DECCKM (Cursor App. Keycode)");
} else {
putlog(3, "Reset DECCKM (Cursor Keycode)");
}
keyboard->SetCKM(val);
break;
case 2: // DECANM (解除のみ)
return Unimpl(ch, "DECANM");
case 3: // DECCOLM (オンで 132 桁/オフで80桁)
// 内部的に桁数を切り替える。(表示は80桁固定)
if (val) {
putlog(2, "Set DECCOLM (to 132 columns)");
width = 132;
} else {
putlog(2, "Reset DECCOLM (to 80 columns)");
width = 80;
}
ResetMargin();
ResetScroll();
Clear();
Locate(0, 0);
break;
case 4: // DECSCLM (オンでスムーズスクロール)
putlog(3, "%s DECSCLM (Smooth/Jump Scroll)",
val ? "Set" : "Reset");
// 対応不要?
break;
case 5: // DECSCNM (オンで明るい背景…)
return Unimpl(ch, "DECSCNM");
case 6: // DECOM
putlog(3, "%s DECOM(Origin Mode)", val ? "Set" : "Reset");
decom = val;
break;
case 7: // DECAWM (Auto Wrap)
return Unimpl(ch, "DECAWM");
case 8: // DECARM (Auto Repeat)
return Unimpl(ch, "DECARM");
case 18: // DECPFF (Print Form Feed)
return Unimpl(ch, "DECPFF");
case 19: // DECPEX (オンで MC/DECMC が DECSTBM の影響を受けない)
return Unimpl(ch, "DECPEX");
case 25: // DECTCEM
putlog(3, "%s DECTCEM(Cursor Visible)", val ? "Set" : "Reset");
cursor_visible = val;
break;
case 42: // DECNRCM (Character set National/Multinational)
return Unimpl(ch, "DECRCM");
case 68008:
// パフォーマンス測定用のダミー。黙って無視する。
break;
default:
// 知らないのは無視。
putlog(2, "Ignore private option %u %c", n, ch);
break;
}
}
break;
}
case 'i': // Printing
return Unimpl(ch, "Printing");
case 'J': // DECSED (Selective Erase In Display)
return Unimpl(ch, "DECSED");
case 'K': // DECSEL (Selective Erase In Line)
return Unimpl(ch, "DECSEL");
case 'n': // DECDSR
{
int n = GetArg();
switch (n) {
case 15: // Request for printer status
// プリンタの状態問い合わせ。
// ここではプリンタなしを応答しておく。
putlog(3, "DECDSR 15(Request Printer Status)");
SendString("\x1b[?13n");
break;
case 25: // Request for UDK(User-Defined Key) status
// UDK は実装していないので、常時初期値(Unlocked = 20) を返す。
putlog(3, "DECDSR 25 (Request UDK Status)");
SendString("\x1b[?20n");
break;
case 26: // Request for keyboard language
// どの国(言語)のキーボードかを返す。
// とりあえず North America。
putlog(3, "DECDSR 26(Request Keyboard Language)");
SendString("\x1b[?27;1n");
break;
default:
return false;
}
break;
}
default:
return false;
}
ResetState();
return true;
}
// ESC '#' の次の文字を処理する。
bool
ConsoleDevice::PutcharSharp(uint32 ch)
{
switch (ch) {
case '3': // DECDHL 上
return Unimpl(ch, "DECDHL(hi)");
case '4': // DECDHL 下
return Unimpl(ch, "DECDHL(lo)");
case '5': // DECSWL
return Unimpl(ch, "DECSWL");
case '6': // DECDWL
return Unimpl(ch, "DECDWL");
case '8': // DECALN (Screen Alignment Test)
// 画面を全部 'E' で埋める。
// メンテ用なのでたぶん属性無視だろうか。
putlog(3, "DECALN (Screen Alignment Tests)");
std::fill(screen.begin(), screen.end(), 'E');
dirty.set();
break;
default:
return false;
}
ResetState();
return true;
}
// ESC '(' (G0) の次の文字を処理する。
// 処理すれば true、知らないシーケンスなので中止する場合は false を返す。
bool
ConsoleDevice::PutcharG0(uint32 ch)
{
switch (ch) {
case 'B': // ASCII
putlog(3, "G0 ASCII");
SetAttr(cur_attr & ~ATTR_DECSG);
break;
case '0': // DEC special graphics
putlog(3, "G0 DEC Special Graphics)");
SetAttr(cur_attr | ATTR_DECSG);
break;
// 各国語モード。同時にいずれか一つが有効らしい。いずれも対応しない。
case 'A': // British
case '4': // Dutch
case 'C': // Finnish
case '5': // Finnish
case 'R': // French
case 'Q': // French canadian
case 'K': // German
case 'Y': // Italian
case 'E': // Norwegian/Danish
case '6': // Norwegian/Danish
case 'Z': // Spanish
case 'H': // Swedish
case '7': // Swedish
case '=': // Swiss
putlog(2, "Ignore national mode:%s", Dump(ch).c_str());
break;
case '<': // DEC Supplemental
return Unimpl(ch, "DEC Supplemental");
default:
// VT220 にはない。
return false;
}
ResetState();
return true;
}
// 未実装シーケンスのログ表示をする。
// 処理済みを表す true を返す。
bool
ConsoleDevice::Unimpl(uint32 ch, const char *name)
{
std::string msg = Dump(ch);
if (name) {
msg += " (";
msg += name;
msg += ')';
}
putlog(1, "%s (NOT IMPLEMENTED)", msg.c_str() + 1);
ResetState();
return true;
}
// argv を一つ取り出して整数にして返す。
// 要素数が足りない場合、パラメータが空の場合、パラメータが数値に変換できない
// 場合はいずれも defval を返す。
// それをどう解釈するかは各シーケンスによる。
int
ConsoleDevice::GetArgDefault(int defval)
{
if (argv_idx < 0) {
ParseArg();
}
// パラメータがない。
if (argv_idx >= argv.size()) {
return defval;
}
const std::string& a = argv[argv_idx++];
// 空文字列。
if (a.empty()) {
return defval;
}
// '-' は arg に追加されないため負数にはならない。
char *end;
errno = 0;
long lval = strtol(a.c_str(), &end, 10);
if (errno == ERANGE || *end != '\0') {
return defval;
}
if (lval == 0) {
return defval;
}
return (int)lval;
}
// arg をセミコロンで分解して argv に代入(追加)する。
// argv の要素数を返す。
int
ConsoleDevice::ParseArg()
{
const char *s = arg.c_str();
while (*s) {
auto pos = s - arg.c_str();
const char *sep = strchr(s, ';');
if (sep) {
auto len = sep - s;
argv.emplace_back(arg, pos, len);
s = sep + 1;
} else {
argv.emplace_back(arg, pos);
break;
}
}
argv_idx = 0;
return argv.size();
}
// 現在の seq を表示用文字列にして返す。
std::string
ConsoleDevice::Dump() const
{
return Dump(seq);
}
// 現在の seq と ch を表示用文字列にして返す。
// 大抵の場合最後の1文字はまだ seq に入っていないのでこの形式があると便利。
std::string
ConsoleDevice::Dump(uint32 ch) const
{
std::vector<uint8> tmp = seq;
tmp.push_back(ch);
return Dump(tmp);
}
// エスケープシーケンスを表示用文字列にして返す。
// 戻り文字列には先頭に空白が入っている。
/*static*/ std::string
ConsoleDevice::Dump(const std::vector<uint8>& src)
{
std::string dst;
for (auto ch : src) {
if (ch == ESC) {
dst += " ESC";
} else if (ch < ' ' || ch >= 0x7f) {
dst += string_format(" \\x%02x", ch);
} else if (ch == ' ') {
dst += " ' '";
} else {
dst += ' ';
dst += (char)ch;
}
}
return dst;
}
// 画面を消去する。
// カーソル位置は変わらない。属性もクリアされない。
void
ConsoleDevice::Clear()
{
std::fill(screen.begin(), screen.end(), ' ' | cur_attr);
dirty.set();
}
// カーソルの X 座標を x に移動する。
// x が物理画面外なら画面の左端か右端にクリップされる。
// 左右マージンの影響は受けない。
void
ConsoleDevice::LocateX(int x)
{
wrap_pending = false;
// 物理画面内 (左右マージンではなく) にクリップ。
if (__predict_false(x < 0)) {
x = 0;
}
if (__predict_false(x > width - 1)) {
x = width - 1;
}
cur_x = x;
dirty.set(cur_y);
}
// カーソルの Y 座標を y に移動する。
// y が物理画面外なら画面の上端か下端にクリップされる。
// スクロール領域の影響は受けない。
void
ConsoleDevice::LocateY(int y)
{
wrap_pending = false;
// 物理画面内 (スクロール領域ではなく) にクリップ。
if (__predict_false(y < 0)) {
y = 0;
}
if (__predict_false(y > height - 1)) {
y = height - 1;
}
dirty.set(cur_y);
cur_y = y;
dirty.set(cur_y);
}
// カーソルの X 座標を x に移動する。
// x は常に画面左から数えるが、
// 左右マージンを超える場合はマージンの左端か右端にクリップされる。
void
ConsoleDevice::LocateXinMargin(int x)
{
if (__predict_false(x < margin_left)) {
x = margin_left;
}
if (__predict_false(x > margin_right)) {
x = margin_right;
}
LocateX(x);
}
// カーソルの Y 座標を y に移動する。
// y は常に画面全体で数えるが、
// スクロール領域を超える場合はスクロール領域の上端か下端にクリップされる。
void
ConsoleDevice::LocateYinScroll(int y)
{
if (__predict_false(y < scroll_top)) {
y = scroll_top;
}
if (__predict_false(y > scroll_btm)) {
y = scroll_btm;
}
LocateY(y);
}
// src 行目を dst 行にコピーする。スクロールの下請け。
// DECLRMM による左右マージンの影響は受けない。
// DECSTBM によるスクロール領域は呼び出し側で管理すること
// (基本どのスクロールも影響を受けるはず)。
void
ConsoleDevice::CopyLineWhole(int dst, int src)
{
assertmsg(0 <= src && src < height, "src=%d", src);
assertmsg(0 <= dst && dst < height, "dst=%d", dst);
memcpy(&screen[dst * fullwidth], &screen[src * fullwidth],
fullwidth * sizeof(screen[0]));
dirty.set(dst);
}
// src 行目を dst 行にコピーする。スクロールの下請け。
// DECLRMM による左右マージン内のみコピーする。
// DECSTBM によるスクロール領域は呼び出し側で管理すること
// (基本どのスクロールも影響を受けるはず)。
void
ConsoleDevice::CopyLineMargin(int dst, int src)
{
assertmsg(0 <= src && src < height, "src=%d", src);
assertmsg(0 <= dst && dst < height, "dst=%d", dst);
int cols = margin_right - margin_left + 1;
memcpy(&screen[dst * fullwidth + margin_left],
&screen[src * fullwidth + margin_left],
cols * sizeof(screen[0]));
dirty.set(dst);
}
// y 行目の内容を消去する。
// DECLRMM による左右マージンの影響は受けない。
// DECSTBM は無関係。
void
ConsoleDevice::EraseLineWhole(int y)
{
assertmsg(0 <= y && y < height, "y=%d", y);
for (int x = 0; x < width; x++) {
Setchar(x, y, ' ' | cur_attr);
}
dirty.set(y);
}
// y 行目の内容を消去する。
// DECLRMM による左右マージンの影響を受ける。
// DECSTBM は無関係。
void
ConsoleDevice::EraseLineMargin(int y)
{
assertmsg(0 <= y && y < height, "y=%d", y);
for (int x = margin_left; x <= margin_right; x++) {
Setchar(x, y, ' ' | cur_attr);
}
dirty.set(y);
}
// 現在位置に、現在の属性で文字 ch を出力する。
// 出力後カーソルは移動する。
void
ConsoleDevice::PutcharNormal(uint32 ch)
{
if (insert_mode) {
// 折り返し待ちは解除される?
wrap_pending = false;
// カーソル以降を1つ右へ移動して、ここに1文字出力。
memmove(&screen[cur_y * fullwidth + (cur_x + 1)],
&screen[cur_y * fullwidth + cur_x],
(margin_right - cur_x) * sizeof(screen[0]));
Setchar(cur_x, cur_y, (ch | cur_attr));
} else {
// 折り返し待ちの時はこの文字を表示する前に改行。
if (wrap_pending) {
// wrap_pending は CR() で解除される。
CR();
LF();
}
// 1文字出力して、1つ右へ移動する。
Setchar(cur_x, cur_y, (ch | cur_attr));
cur_x++;
// マージンの最終桁にいた場合は wrap pending を立てて、留まる。
if (cur_x > margin_right) {
wrap_pending = true;
cur_x--;
}
}
dirty.set(cur_y);
}
// (x, y) の位置に chattr を出力する。
// 出力後カーソルは移動しない。
// 現在の attr は参照しない (呼び出す側が指定すること)。
void
ConsoleDevice::Setchar(int x, int y, uint32 chattr)
{
int pos = y * fullwidth + x;
assert(pos >= 0);
assert(pos < screen.size());
screen[pos] = chattr;
}
// TAB。
// カーソルを次のタブ位置に移動。
void
ConsoleDevice::HT()
{
int x = cur_x;
int tabstop = ((x / 8) + 1) * 8;
if (tabstop > margin_right) {
tabstop = margin_right;
}
for (; x < tabstop; x++) {
PutcharNormal(' ');
}
}
// 復帰。
// DECLRMM による左右マージンの影響を受ける。
void
ConsoleDevice::CR()
{
LocateXinMargin(margin_left);
}
// 改行。
// DECLRMM による左右マージンの影響を受ける。
// DECSTBM によるスクロール領域の影響を受ける。
void
ConsoleDevice::LF()
{
wrap_pending = false;
dirty.set(cur_y);
cur_y++;
// スクロール領域下端を出るなら画面をスクロールアップ。
if (cur_y > scroll_btm) {
for (int y = scroll_top + 1; y <= scroll_btm; y++) {
CopyLineMargin(y - 1, y);
}
EraseLineMargin(scroll_btm);
cur_y = scroll_btm;
}
// New Line Mode なら LF/VT/FF は行頭へ戻る。
if (cr_on_lf) {
CR();
}
dirty.set(cur_y);
}
// 現在の属性を設定する。
// ほぼログ出力のため。
void
ConsoleDevice::SetAttr(uint32 new_attr)
{
if (cur_attr == new_attr) {
return;
}
cur_attr = new_attr;
if (log) {
std::string buf;
if ((cur_attr & ATTR_BOLD)) {
buf += ",ATTR_BOLD";
}
if ((cur_attr & ATTR_ITALIC)) {
buf += ",ATTR_ITALIC";
}
if ((cur_attr & ATTR_UNDERLINE)) {
buf += ",ATTR_UNDERLINE";
}
if ((cur_attr & ATTR_STRIKE)) {
buf += ",ATTR_STRIKE";
}
if ((cur_attr & ATTR_OVERLINE)) {
buf += ",ATTR_OVERLINE";
}
if ((cur_attr & ATTR_DECSG)) {
buf += ",ATTR_DECSG";
}
if ((cur_attr & ATTR_REVERSE)) {
buf += ",ATTR_REVERSE";
}
if (buf.empty()) {
buf = "<ATTR_NORMAL>";
} else {
buf[0] = '<';
buf += '>';
}
fputs(buf.c_str(), log);
fflush(log);
}
}
// 文字列を送信。終端の '\0' は送らない。
void
ConsoleDevice::SendString(const std::string& str)
{
if (__predict_false(loglevel >= 2)) {
// Dump() は vector を受け取るので…。
std::vector<uint8> vec(str.length());
for (int i = 0, end = str.length(); i < end; i++) {
vec[i] = str[i];
}
putlogn("SendString%s", Dump(vec).c_str());
}
if (comdriver) {
for (auto ch : str) {
comdriver->EnqueueChar(ch);
}
}
}
// 現在の画面を文字列にして返す。
std::string
ConsoleDevice::ToString() const
{
std::string dump;
for (int y = 0; y < height; y++) {
const uint32 *s = &screen[y * fullwidth];
const uint32 *e = s + width - 1;
// 行末方向から空白でない文字を探す。
for (; e >= s; e--) {
uint8 ch = *e & 0xff;
if (ch != ' ') {
break;
}
}
// 行頭からそこまでをコピー。
for (; s <= e; s++) {
uint8 ch = *s & 0xff;
dump += (char)ch;
}
// 論理的に折り返してるかどうかの区別はつかない。
dump += '\n';
}
return dump;
}
// 画面合成。
// レンダリングスレッドから呼ばれる。
bool
ConsoleDevice::Render(BitmapRGBX& dst)
{
BitmapRGBX row(width * font_width, font_height);
bool updated = false;
// フチも含めて再描画。
if (__predict_false(init_screen)) {
init_screen = false;
dst.Fill(palette[0]);
updated = true;
}
// 行ごとに更新があるか調べる。
bitsetH modified;
{
std::lock_guard<std::mutex> lock(mtx);
modified = pending;
pending.reset();
}
updated |= modified.any();
const uint8 *cgrom8x16 = Builtin::CGROM8x16(0);
for (uint y = 0; y < height; y++) {
if (__predict_false(modified.test(y))) {
row.Fill(palette[0]);
const uint32 *s = &screen[y * fullwidth];
for (uint x = 0; x < width; x++) {
uint32 attr = *s++;
uint32 ch = attr & 0xff;
// 属性による反転。
int paloff = 0;
if ((attr & ATTR_REVERSE) != 0) {
paloff ^= 1;
}
// カーソル位置は更に反転。
if (cursor_visible && x == cur_x && y == cur_y) {
paloff ^= 1;
}
// 点滅の滅の時はグリフを空白に差し替えて描画。
if (__predict_false(blinking) && (attr & ATTR_BLINK)) {
ch = ' ';
attr &= ~ATTR_LINEMASK;
}
const uint8 *font;
if (__predict_false(ch >= 0x80)) {
font = &tofu8x16[0];
} else if ((attr & ATTR_DECSG) && ('j' <= ch && ch <= 'x')) {
font = &decsg8x16[(ch - 'j') * font_height];
} else {
// 0x5c は CGROM では円記号なのでバックスラッシュ(0x80)
// にする。同様に 0x7e の ' ̄'(オーバーライン)をチルダ
// (0x81) にする。
if (ch == '\\') {
ch = 0x80;
} else if (ch == '~') {
ch = 0x81;
}
font = &cgrom8x16[ch * font_height];
}
BitmapI1 bc1(font, font_width, font_height);
if (__predict_false((attr & ATTR_ITALIC))) {
bc1 = bc1.ConvertToItalic();
}
if (__predict_false((attr & ATTR_BOLD))) {
bc1 = bc1.ConvertToBold();
}
uint px = x * font_width;
row.DrawBitmapI1(px, 0, bc1, &palette[paloff]);
// 下線、上線、取り消し線
if (__predict_false((attr & ATTR_LINEMASK))) {
Color fg = palette[paloff ^ 1];
if ((attr & ATTR_OVERLINE)) {
row.DrawLineH(fg, px, 0, px + font_width);
}
if ((attr & ATTR_STRIKE)) {
row.DrawLineH(fg, px, 9, px + font_width);
}
if ((attr & ATTR_UNDERLINE)) {
row.DrawLineH(fg, px, 14, px + font_width);
}
}
}
Rect rect(Padding, Padding + y * font_height,
dst.GetWidth() - Padding * 2, font_height);
dst.DrawBitmapStretch(rect, row);
}
}
return updated;
}
#define B(x) (0b##x)
#define B2(x) (B(x) >> 8), (B(x) & 0xff)
// DEC Special Graphics Character Set フォント。(罫線のみ)
/*static*/ const uint8 ConsoleDevice::decsg8x16[15 * font_height] = {
B(00010000), // [0] j:┘
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(11110000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000), // [1] k:┐
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(11110000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00000000), // [2] l:┌
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00011111),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000), // [3] m:└
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00011111),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00010000), // [4] n:┼
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(11111111),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00000000), // [5] o:─(Scan1)
B(11111111),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000), // [6] p:─(Scan3)
B(00000000),
B(00000000),
B(00000000),
B(11111111),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000), // [7] q:─(Scan5)
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(11111111),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000), // [8] r:─(Scan7)
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(11111111),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000), // [9] s:─(Scan9)
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(11111111),
B(00000000),
B(00000000),
B(00010000), // [10] t:├
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00011111),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000), // [11] u:┤
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(11110000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000), // [12] v:┴
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(11111111),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000), // [13] w:┬
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(00000000),
B(11111111),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000), // [14] x:│
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
B(00010000),
};
// 豆腐
/*static*/ const uint8 ConsoleDevice::tofu8x16[font_height] = {
B(00000000),
B(11111110),
B(10000010),
B(10000110),
B(10000110),
B(10001010),
B(10001010),
B(10010010),
B(10010010),
B(10100010),
B(10100010),
B(11000010),
B(11000010),
B(10000010),
B(11111110),
B(00000000),
};
//
// 入力担当デバイス
//
// コンストラクタ
ConsoleKeyboard::ConsoleKeyboard()
: inherited()
{
// Console 側から初期化されるはずだが、念のため。
SetCKM(false);
}
// デストラクタ
ConsoleKeyboard::~ConsoleKeyboard()
{
}
// COM ドライバを接続。
// (HostCOM の cons ドライバが呼ぶ)
void
ConsoleKeyboard::Attach(COMDriverConsole *comdriver_)
{
comdriver = comdriver_;
}
// 文字入力。
// WXMainView から呼ばれる。
void
ConsoleKeyboard::CharInput(uint charcode)
{
if (comdriver == NULL) {
return;
}
// charcode は 0x80 以上を独自に使っているので (keyboard.h 参照)、
// それらをシリアルコンソールのエスケープに置き換えて送り込む。
switch (charcode) {
case CC_NUL:
comdriver->EnqueueChar(0);
break;
case CC_F1 ... CC_F10:
break;
// 上下左右は Normal Cursor Mode なら ESC [ A/B/C/D、
// Application Cursor Mode なら ESC O A/B/C/D を送る。
// 拡張オプション 1 (DECCKM) で切り替える。
case CC_up:
comdriver->EnqueueChar(ESC);
comdriver->EnqueueChar(ckm_char);
comdriver->EnqueueChar('A');
break;
case CC_down:
comdriver->EnqueueChar(ESC);
comdriver->EnqueueChar(ckm_char);
comdriver->EnqueueChar('B');
break;
case CC_right:
comdriver->EnqueueChar(ESC);
comdriver->EnqueueChar(ckm_char);
comdriver->EnqueueChar('C');
break;
case CC_left:
comdriver->EnqueueChar(ESC);
comdriver->EnqueueChar(ckm_char);
comdriver->EnqueueChar('D');
break;
default:
comdriver->EnqueueChar(charcode);
break;
}
}
// DECCKM を設定/解除する。
void
ConsoleKeyboard::SetCKM(bool val)
{
if (val) {
ckm_char = 'O';
} else {
ckm_char = '[';
}
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.