RS232CTerminal.java
     1: //========================================================================================
     2: //  RS232CTerminal.java
     3: //    en:RS-232C settings and terminal
     4: //    ja:RS-232C設定とターミナル
     5: //  Copyright (C) 2003-2026 Makoto Kamada
     6: //
     7: //  This file is part of the XEiJ (X68000 Emulator in Java).
     8: //  You can use, modify and redistribute the XEiJ if the conditions are met.
     9: //  Read the XEiJ License for more details.
    10: //  https://stdkmd.net/xeij/
    11: //========================================================================================
    12: 
    13: package xeij;
    14: 
    15: import java.awt.datatransfer.*;  //DataFlavor
    16: import java.awt.event.*;  //ActionListener
    17: import java.io.*;  //UnsupportedEncodingException
    18: import java.net.*;  //URLEncoder
    19: import java.nio.*;  //ByteBuffer
    20: import java.nio.channels.*;  //AsynchronousServerSocketChannel
    21: import java.nio.charset.*;  //Charset
    22: import java.util.*;  //HashSet
    23: import java.util.concurrent.*;  //ScheduledExecutorService
    24: import java.util.zip.*;  //CRC32
    25: import javax.swing.*;
    26: import javax.swing.event.*;  //DocumentListener
    27: 
    28: import com.fazecast.jSerialComm.*;  //SerialPort
    29: 
    30: public class RS232CTerminal {
    31: 
    32:   public static final int TRM_MAX_OUTPUT_LENGTH = 1024 * 256;  //出力の上限を256KBとする
    33:   public static final int TRM_CUT_OUTPUT_LENGTH = TRM_MAX_OUTPUT_LENGTH + 1024 * 16;  //出力が上限よりも16KB以上長くなったら上限でカットする
    34: 
    35:   //コンポーネント
    36:   public static JFrame trmFrame;  //ウインドウ
    37:   public static ScrollTextArea trmBoard;  //テキストエリア
    38:   public static JPopupMenu trmPopupMenu;  //ポップアップメニュー
    39:   public static JMenuItem trmPopupCutMenuItem;  //切り取り
    40:   public static JMenuItem trmPopupCopyMenuItem;  //コピー
    41:   public static JMenuItem trmPopupPasteMenuItem;  //貼り付け
    42:   public static JMenuItem trmPopupSelectAllMenuItem;  //すべて選択
    43:   public static JMenuItem trmPopupSendCtrlCMenuItem;  //^C送信
    44:   public static StringBuilder trmOutputBuilder;  //ターミナルを最初に開くまでに出力された文字列を貯めておくバッファ
    45:   public static int trmOutputEnd;  //出力された文字列の末尾。リターンキーが押されたらこれ以降に書かれた文字列をまとめて入力する
    46:   public static int trmOutputSJIS1;  //出力するときに繰り越したSJISの1バイト目
    47: 
    48:   //SerialPort
    49:   public static SerialPort[] trmPortArray;  //[row-1]=SerialPort。SerialPortの配列
    50:   public static int trmNumberOfPorts;  //SerialPortの数。SerialPortの行の上限
    51:   //TCP/IP
    52:   public static int trmTcpipRow;  //TCP/IPの行。-1=なし
    53:   //PPPサーバー
    54:   public static int trmPppRow;  //PPPサーバーの行。-1=なし
    55:   //行
    56:   //  0  Terminal
    57:   //  1  SerialPort[0]
    58:   //  :
    59:   //  trmNumberOfPorts  SerialPort[trmNumberOfPorts-1]
    60:   //  trmTcpipRow  TCP/IP
    61:   //  trmPppRow  PPPサーバー
    62:   public static int trmRows;  //行数。1+trmNumberOfPorts+(0<=trmTcpipRow?1:0)+(0<=trmPppRow?1:0)
    63:   public static String[] trmRowName;  //行の名前
    64:   public static int[] trmRowToCol;  //行に接続している列。なければ-1
    65:   //AUX*
    66:   public static int trmNumberOfAUXs;  //AUX*の数
    67:   //列
    68:   //  Terminal,AUX,AUX2,…
    69:   public static int trmCols;  //列数。1+trmNumberOfAUXs
    70:   public static String[] trmColName;  //列の名前
    71:   public static int[] trmColToRow;  //列に接続している行。なければ-1
    72: 
    73: 
    74: 
    75:   //接続
    76:   //  Terminalから送信するとき。TerminalでENTERキーが押されてそれまでに入力/貼り付けされた文字列を送信するとき
    77:   //    KeyListenerでENTERキーが押されたとき
    78:   //      それまでに入力/貼り付けされた文字列を取り出す(trmEnter)
    79:   //        SJISに変換してキューに書き込む(trmSendString)
    80:   //  Terminalが受信するとき。入力/貼り付け以外でTerminalに文字列を表示するとき
    81:   //    スレッドでキューをポーリングする
    82:   //      取り出してSJIS逆変換してテキストエリアへ書き込む
    83:   //      テキストエリアが大きくなりすぎたときは先頭を削って短くする
    84:   //  AUXから送信するとき。SCCのデータレジスタへライトされたとき
    85:   //    キューに書き込むだけ
    86:   //    OUT232CはTx Buffer Emptyが1になるのを待ってからデータポートへライトするが、
    87:   //    Tx Buffer Emptyが1になるのを待たずにライトしても送信される
    88:   //    Tx Buffer Emptyの動作についてはtrmAUXSendTickerを参照
    89:   //  AUXが受信するとき。SCCのデータレジスタからリードされたとき
    90:   //    レジスタのデータを返すだけ
    91:   //    ポーリング間隔またはボーレートなどから決めた受信間隔でティッカーを繰り返す
    92:   //    (受信間隔は短くてもよいがデバッガの誤動作をどうにかしなければならない)
    93:   //      キューが空でなくReset Highest IUSコマンド(WR0=$38)によりIUS(Interrupt Under Service)がリセットされているとき
    94:   //        キューからデータを取り出してレジスタを更新して割り込みを要求する
    95:   //  SerialPortから送信するとき。jSerialCommが受信するとき
    96:   //    SerialPortDataListenerでSerialPortからreadBytesしてキューへ書き込む
    97:   //    キューが満杯になることは事実上ないのでreadBytesが長く滞ることはないはず
    98:   //  SerialPortが受信するとき。jSerialCommが送信するとき
    99:   //    スレッドでキューをポーリングする
   100:   //      データがあれば取り出してwriteBytesでjSerialCommに渡す
   101:   //      writeBytesがブロックするとポーリングが止まる
   102:   static class Connection implements SerialPortDataListener {
   103:     int row;  //行。Terminal,SerialPort,SerialPort,…
   104:     int col;  //列。Terminal,AUX,AUX2,…。row==0&&col==0は不可
   105:     int index;  //trmConnectionArrayとtrmConnectionBoxでのインデックス。trmCols*row+col-1
   106:     String text;  //checkBoxのtext兼actionCommand
   107:     boolean connected;  //接続している
   108:     JCheckBox checkBox;  //接続チェックボックス
   109:     Box box;  //checkBoxを入れる箱
   110: 
   111:     //キュー
   112:     ByteQueue row2colQueue;  //Terminal/SerialPort→Terminal/AUX
   113:     ByteQueue col2rowQueue;  //Terminal/AUX→Terminal/SerialPort
   114:     boolean row2colReset;  //true=row2colQueueをクリアする
   115:     boolean col2rowReset;  //true=col2rowQueueをクリアする
   116: 
   117:     //CTS/DCD/DSR/RI
   118:     boolean cts;
   119:     boolean dcd;
   120:     boolean dsr;
   121:     boolean ri;
   122: 
   123:     //シリアルポートデータリスナー。row2col。SerialPort→?
   124:     @Override public int getListeningEvents () {
   125:       return (SerialPort.LISTENING_EVENT_DATA_AVAILABLE |
   126:               //SerialPort.LISTENING_EVENT_CTS |
   127:               SerialPort.LISTENING_EVENT_CARRIER_DETECT |
   128:               SerialPort.LISTENING_EVENT_DSR |
   129:               SerialPort.LISTENING_EVENT_RING_INDICATOR);
   130:     }  //getListeningEvents
   131:     @Override public void serialEvent (SerialPortEvent spe) {
   132:       SerialPort port = spe.getSerialPort ();
   133:       int type = spe.getEventType ();
   134:       if ((type & SerialPort.LISTENING_EVENT_DATA_AVAILABLE) != 0) {
   135:         for (;;) {
   136:           int k = Math.min (port.bytesAvailable (),  //ポートから読み出せるバイト数と
   137:                             row2colQueue.unused ());  //キューへ書き込めるバイト数の小さい方
   138:           if (k == 0) {
   139:             break;
   140:           }
   141:           byte[] b = new byte[k];
   142:           port.readBytes (b, k);  //ポートから読み出す
   143:           row2colQueue.write (b, 0, k);  //キューへ書き込む
   144:         }
   145:       }
   146:       //キューが溢れることは想定しない
   147:       //if ((type & SerialPort.LISTENING_EVENT_CTS) != 0) {
   148:       //  cts = port.getCTS ();
   149:       //}
   150:       if ((type & SerialPort.LISTENING_EVENT_CARRIER_DETECT) != 0) {
   151:         dcd = port.getDCD ();
   152:       }
   153:       if ((type & SerialPort.LISTENING_EVENT_DSR) != 0) {
   154:         dsr = port.getDSR ();
   155:       }
   156:       if ((type & SerialPort.LISTENING_EVENT_RING_INDICATOR) != 0) {
   157:         ri = port.getRI ();
   158:       }
   159:     }  //serialEvent
   160: 
   161:     //ポーリングスレッド
   162:     boolean polling;  //false=ポーリング終了
   163:     Thread row2colThread;  //Terminal/SerialPort→Terminal/AUX。TerminalThread
   164:     Thread col2rowThread;  //Terminal/AUX→Terminal/SerialPort。TerminalThreadまたはSerialPortThread
   165: 
   166:     //  ?→Terminalポーリングスレッド
   167:     class TerminalThread extends Thread {
   168:       @Override public void run () {
   169:         ByteQueue queue = (col == 0 ? row2colQueue : col2rowQueue);
   170:         while (polling) {
   171:           if (col == 0 ? row2colReset : col2rowReset) {
   172:             if (col == 0) {
   173:               row2colReset = false;
   174:             } else {
   175:               col2rowReset = false;
   176:             }
   177:             queue.clear ();
   178:           } else {
   179:             for (int k = queue.used (); k != 0; k = queue.used ()) {  //キューが空になるまでスリープしない
   180:               byte[] b = new byte[k];
   181:               queue.read (b, 0, k);  //キューから読み出して
   182:               for (int i = 0; i < k; i++) {
   183:                 trmPrintSJIS (b[i] & 0xff);  //ターミナルへ書き込む
   184:               }
   185:             }
   186:           }
   187:           try {
   188:             Thread.sleep (100);
   189:           } catch (InterruptedException ie) {
   190:           }
   191:         }  //while polling
   192:       }  //run
   193:     }  //class TerminalThread
   194: 
   195:     //  ?→SerialPortポーリングスレッド
   196:     class SerialPortThread extends Thread {
   197:       @Override public void run () {
   198:         SerialPort port = trmPortArray[row - 1];
   199:         ByteQueue queue = col2rowQueue;
   200:         while (polling) {
   201:           if (col2rowReset) {
   202:             col2rowReset = false;
   203:             queue.clear ();
   204:             port.flushIOBuffers ();
   205:           } else {
   206:             for (int k = queue.used (); k != 0; k = queue.used ()) {  //キューが空になるまでスリープしない
   207:               byte[] b = new byte[k];
   208:               queue.read (b, 0, k);  //キューから読み出して
   209:               port.writeBytes (b, k);  //シリアルポートへ書き込む。ブロックすることがある
   210:             }
   211:           }
   212:           try {
   213:             Thread.sleep (10);
   214:           } catch (InterruptedException ie) {
   215:           }
   216:         }  //while polling
   217:       }  //run
   218:     }  //class SerialPortThread
   219: 
   220:     //  ?→TCP/IPポーリングスレッド
   221:     class TCPIPThread extends Thread {
   222:       @Override public void run () {
   223:         ByteQueue queue = col2rowQueue;
   224:         while (polling) {
   225:           if (col2rowReset) {
   226:             col2rowReset = false;
   227:             queue.clear ();
   228:           } else {
   229:             for (int k = queue.used (); k != 0; k = queue.used ()) {  //キューが空になるまでスリープしない
   230:               byte[] b = new byte[k];
   231:               queue.read (b, 0, k);  //キューから読み出して
   232:               trmTcpipWrite (b, 0, k);  //TCP/IPポートへ書き込む。ブロックすることがある
   233:             }
   234:           }
   235:           try {
   236:             Thread.sleep (10);
   237:           } catch (InterruptedException ie) {
   238:           }
   239:         }  //while polling
   240:       }  //run
   241:     }  //class TCPIPThread
   242: 
   243:     //  ?→PPPサーバーポーリングスレッド
   244:     class PPPThread extends Thread {
   245:       @Override public void run () {
   246:         ByteQueue queue = col2rowQueue;
   247:         try {
   248:           while (polling) {
   249:             if (col2rowReset) {
   250:               col2rowReset = false;
   251:               queue.clear ();
   252:             } else {
   253:               for (int k = queue.used (); k != 0; k = queue.used ()) {  //キューが空になるまでスリープしない
   254:                 byte[] b = new byte[k];
   255:                 queue.read (b, 0, k);  //キューから読み出す
   256:                 int i = 0;
   257:                 while (trmPppModemLayer && i < k) {  //モデム層
   258:                   int c = b[i++] & 0xff;
   259:                   if (c != '\r') {
   260:                     trmPppModemCommand.append ((char) c);
   261:                   } else {
   262:                     String s = trmPppModemCommand.toString ();
   263:                     trmPppModemCommand.delete (0, trmPppModemCommand.length ());
   264:                     byte[] bb;
   265:                     if (false) {
   266:                       bb = "NO CARRIER\r\n".getBytes ();
   267:                     } else if (s.startsWith ("ATDT")) {
   268:                       bb = "CONNECT\r\n".getBytes ();
   269:                       trmPppModemLayer = false;  //モデム層終了
   270:                       if (trmPppDebug) {
   271:                         System.out.println ("modem layer ended");
   272:                       }
   273:                     } else if (s.startsWith ("AT")) {
   274:                       bb = "OK\r\n".getBytes ();
   275:                     } else {
   276:                       bb = "ERROR\r\n".getBytes ();
   277:                     }
   278:                     row2colQueue.write (bb, 0, bb.length);
   279:                   }
   280:                 }  //while
   281:                 if (i < k) {  //残りを
   282:                   if (trmPppDebug) {
   283:                     System.out.print ("pppd stdin");
   284:                     for (int j = i; j < k; j++) {
   285:                       System.out.printf (" %02X", b[j] & 0xff);
   286:                     }
   287:                     System.out.println ();
   288:                   }
   289:                   trmPppStdinStream.write (b, i, k - i);  //pppdの標準入力に書き込む。ブロックする
   290:                   trmPppStdinStream.flush ();
   291:                 }
   292:               }  //for k
   293:             }
   294:             try {
   295:               Thread.sleep (10);
   296:             } catch (InterruptedException ie) {
   297:             }
   298:           }  //while polling
   299:         } catch (IOException ioe) {
   300:         }
   301:       }  //run
   302:     }  //class PPPThread
   303: 
   304:   }  //class Connection
   305: 
   306: 
   307: 
   308:   //TCP/IPポート
   309:   //  参考
   310:   //    https://x.com/yunkya2/status/1940779786017951890
   311:   //    https://github.com/yunkya2/xeij/commit/cba358a58197a05b25b64a4ceaa90d110a564aec
   312:   public static final String TRM_TCPIP_DEFAULT_HOST = "0.0.0.0";  //デフォルトのホスト
   313:   public static final int TRM_TCPIP_DEFAULT_PORT = 54321;  //デフォルトのポート番号
   314:   public static final boolean TRM_TCPIP_DEFAULT_UTF8 = false;  //デフォルトのSJIS⇔UTF-8変換
   315:   public static String trmTcpipHost;  //次に開くホスト
   316:   public static int trmTcpipPort;  //次に開くポート番号
   317:   public static boolean trmTcpipUTF8;  //SJIS⇔UTF-8変換
   318:   public static DecimalSpinner trmTcpipSpinner;  //ポート番号スピナー
   319:   public static JCheckBox trmTcpipCheckBox;  //UTF-8チェックボックス
   320:   public static ByteBuffer trmTcpipReadBuffer;  //受信バッファ
   321:   public static CompletionHandler<Integer, Void> trmTcpipReadHander;  //受信ハンドラ
   322:   public static CompletionHandler<AsynchronousSocketChannel, Void> trmTcpipAcceptHandler;  //接続ハンドラ
   323:   public static ByteQueue trmTcpipReadQueue;  //受信キュー
   324:   public static SocketAddress trmTcpipPortAddress;  //ポートアドレス
   325:   public static AsynchronousServerSocketChannel trmTcpipPortChannel;  //ポートチャネル
   326:   public static final int TRM_TCPIP_BUFFER_SIZE = 4096;  //受信バッファのサイズ
   327:   public static AsynchronousSocketChannel trmTcpipSessionChannel;  //セッションチャネル
   328:   public static SocketAddress trmTcpipClientAddress;  //クライアントアドレス
   329:   public static int trmTcpipToUTF8Pool;  //SJIS→UTF-8変換プール
   330:   public static int trmTcpipToSJISPool;  //UTF-8→SJIS変換プール
   331: 
   332:   //trmTcpipInit ()
   333:   //  初期化。ここでは開かない
   334:   public static void trmTcpipInit () {
   335:     //パラメータを復元する
   336:     trmTcpipHost = Settings.sgsGetString ("tcpiphost", TRM_TCPIP_DEFAULT_HOST);
   337:     trmTcpipPort = Settings.sgsGetInt ("tcpipport", TRM_TCPIP_DEFAULT_PORT, 0, 65535);
   338:     trmTcpipUTF8 = Settings.sgsGetOnOff ("tcpiputf8", TRM_TCPIP_DEFAULT_UTF8);
   339:     //ポート番号スピナーを作る
   340:     trmTcpipSpinner = ComponentFactory.createDecimalSpinner (
   341:       trmTcpipPort, 0, 65535, 1, 0,
   342:       new ChangeListener () {
   343:         @Override public void stateChanged (ChangeEvent ce) {
   344:           trmTcpipPort = trmTcpipSpinner.getIntValue ();
   345:         }
   346:       });
   347:     //UTF-8チェックボックスを作る
   348:     trmTcpipCheckBox = ComponentFactory.createCheckBox (
   349:       trmTcpipUTF8, "UTF-8",
   350:       new ActionListener () {
   351:         @Override public void actionPerformed (ActionEvent ae) {
   352:           trmTcpipUTF8 = trmTcpipCheckBox.isSelected ();
   353:         }
   354:       });
   355:     //受信バッファを作る
   356:     trmTcpipReadBuffer = ByteBuffer.allocate (TRM_TCPIP_BUFFER_SIZE);
   357:     //受信ハンドラを作る
   358:     trmTcpipReadHander = new CompletionHandler<Integer, Void> () {
   359:       //  受信成功
   360:       @Override public void completed (Integer bytesRead, Void a) {
   361:         int l = (int) bytesRead;  //受信したバイト数
   362:         if (l == -1) {  //切断されたとき
   363:           trmTcpipCloseSession (true);  //セッションを閉じて次の接続を待つ
   364:           return;
   365:         }
   366:         byte[] b = new byte[l];
   367:         int o = 0;
   368:         trmTcpipReadBuffer.flip ();
   369:         trmTcpipReadBuffer.get (b, o, l);  //ポートから読み出す
   370:         if (false) {
   371:           for (int i = 0; i < l; i++) {
   372:             System.out.printf ("%02x ", 0xff & b[o + i]);
   373:           }
   374:           System.out.println ();
   375:         }
   376:         byte[] bb = b;
   377:         int ll = l;
   378:         if (trmTcpipUTF8) {
   379:           //UTF-8→SJIS
   380:           int pool = trmTcpipToSJISPool;
   381:           bb = new byte[2 * l];
   382:           ll = 0;
   383:           for (int i = 0; i < l; i++) {
   384:             int u = (pool << 8) | (0xff & b[o + i]);
   385:             if ((u & 0xffffffe0) == 0x000000c0 ||  //2バイトコードの1バイト目
   386:                 (u & 0xfffffff0) == 0x000000e0 ||  //3バイトコードの1バイト目
   387:                 (u & 0xfffff0c0) == 0x0000e080 ||  //3バイトコードの2バイト目
   388:                 (u & 0xfffffff8) == 0x000000f0 ||  //4バイトコードの1バイト目
   389:                 (u & 0xfffff8c0) == 0x0000f080 ||  //4バイトコードの2バイト目
   390:                 (u & 0xfff8c0c0) == 0x00f08080) {  //4バイトコードの3バイト目
   391:               pool = u;
   392:               continue;
   393:             }
   394:             pool = 0;
   395:             if ((u & 0xffffff80) == 0x00000000) {  //1バイトコード 7bit
   396:             } else if ((u & 0xffffe0c0) == 0x0000c080) {  //2バイトコード
   397:               u = ((u & 0x00001f00) >> 2) | (u & 0x0000003f);  //5+6=11bit
   398:             } else if ((u & 0xfff0c0c0) == 0x00e08080) {  //3バイトコード
   399:               u = ((u & 0x000f0000) >> 4) | ((u & 0x00003f00) >> 2) | (u & 0x0000003f);  //4+6+6=16bit
   400:             } else if ((u & 0xf8c0c0c0) == 0xf0808080) {  //4バイトコード
   401:               u = ((u & 0x07000000) >> 6) | ((u & 0x003f0000) >> 4) | ((u & 0x00003f00) >> 2) | (u & 0x0000003f);  //3+6+6+6=21bit
   402:               if (0x10ffff < u) {  //不正なコード
   403:                 u = '※';
   404:               } else if (0x00ffff < u) {  //サロゲートペア
   405:                 //u -= 0x10000;
   406:                 //int v = u & 0x3ff;
   407:                 //u >>= 10;
   408:                 //u += 0xd800;
   409:                 //v += 0xdc00;
   410:                 u = '※';
   411:               }
   412:             } else {  //不正なコード
   413:               u = '※';
   414:             }
   415:             int s = CharacterCode.chrCharToSJIS[u];  //UTF-16(サロゲートペアなし)→SJIS変換
   416:             if (s == 0 && u != 0) {  //0でないコードが0になった
   417:               s = 0x81a6;  //対応なし'※'
   418:             }
   419:             if (0x00ff < s) {
   420:               bb[ll++] = (byte) (s >> 8);
   421:             }
   422:             bb[ll++] = (byte) s;
   423:           }  //for i
   424:           trmTcpipToSJISPool = pool;
   425:         }
   426:         trmTcpipReadQueue.write (bb, 0, ll);  //キューへ書き込む。キューへ書き込めるバイト数の確認は省略
   427:         //次の受信を開始する
   428:         if (trmTcpipSessionChannel != null) {
   429:           trmTcpipReadBuffer.clear ();
   430:           trmTcpipSessionChannel.read (trmTcpipReadBuffer, null, trmTcpipReadHander);
   431:         }
   432:       }
   433:       //  受信失敗
   434:       @Override public void failed (Throwable t, Void a) {
   435:         String message = t.getMessage ();
   436:         if (message != null) {
   437:           System.err.println (message);
   438:         }
   439:         trmTcpipCloseSession (true);  //セッションを閉じて次の接続を待つ
   440:       }
   441:     };
   442:     //接続ハンドラを作る
   443:     trmTcpipAcceptHandler = new CompletionHandler<AsynchronousSocketChannel, Void> () {
   444:       //  接続成功
   445:       @Override public void completed (AsynchronousSocketChannel sessionChannel, Void a) {
   446:         trmTcpipSessionChannel = sessionChannel;
   447:         //セッションを開く
   448:         trmTcpipToUTF8Pool = 0;
   449:         trmTcpipToSJISPool = 0;
   450:         try {
   451:           trmTcpipClientAddress = trmTcpipSessionChannel.getRemoteAddress ();
   452:         } catch (IOException ioe) {
   453:           ioe.printStackTrace ();
   454:         }
   455:         System.out.println (Multilingual.mlnJapanese ?
   456:                             trmTcpipClientAddress + " と接続しました" :
   457:                             trmTcpipClientAddress + " connected");
   458:         //受信を開始する
   459:         if (trmTcpipSessionChannel != null) {
   460:           trmTcpipReadBuffer.clear ();
   461:           trmTcpipSessionChannel.read (trmTcpipReadBuffer, null, trmTcpipReadHander);
   462:         }
   463:       }
   464:       //  接続失敗
   465:       @Override public void failed (Throwable t, Void a) {
   466:         String message = t.getMessage ();
   467:         if (message != null) {
   468:           System.err.println (message);
   469:         }
   470:         trmTcpipClosePort ();
   471:       }
   472:     };
   473:   }  //trmTcpipInit
   474: 
   475:   //trmTcpipTini ()
   476:   //  後始末。ここでは閉じない
   477:   public static void trmTcpipTini () {
   478:     //パラメータを保存する
   479:     Settings.sgsPutString ("tcpiphost", trmTcpipHost);
   480:     Settings.sgsPutInt ("tcpipport", trmTcpipPort);
   481:     Settings.sgsPutOnOff ("tcpiputf8", trmTcpipUTF8);
   482:   }  //trmTcpipTini
   483: 
   484:   //success = trmTcpipOpenPort (readQueue)
   485:   //  ポートを開く
   486:   //  readQueue  受信キュー
   487:   public static boolean trmTcpipOpenPort (ByteQueue readQueue) {
   488:     trmTcpipReadQueue = readQueue;
   489:     trmTcpipPortAddress = new InetSocketAddress (trmTcpipHost, trmTcpipPort);
   490:     //ポートを開く
   491:     try {
   492:       trmTcpipPortChannel = AsynchronousServerSocketChannel.open ().bind (trmTcpipPortAddress);
   493:     } catch (IOException ioe) {
   494:       ioe.printStackTrace ();
   495:       System.out.println (Multilingual.mlnJapanese ?
   496:                           trmTcpipPortAddress + " を開けません" :
   497:                           trmTcpipPortAddress + " not opened");
   498:       return false;
   499:     }
   500:     System.out.println (Multilingual.mlnJapanese ?
   501:                         trmTcpipPortAddress + " を開きました" :
   502:                         trmTcpipPortAddress + " opened");
   503:     //接続を待つ
   504:     if (trmTcpipPortChannel != null) {
   505:       trmTcpipPortChannel.accept (null, trmTcpipAcceptHandler);
   506:     }
   507:     return true;
   508:   }  //trmTcpipOpenPort
   509: 
   510:   //trmTcpipClosePort ()
   511:   //  ポートを閉じる
   512:   public static void trmTcpipClosePort () {
   513:     if (trmTcpipPortAddress != null) {
   514:       System.out.println (Multilingual.mlnJapanese ?
   515:                           trmTcpipPortAddress + " を閉じました" :
   516:                           trmTcpipPortAddress + " closed");
   517:       trmTcpipPortAddress = null;
   518:     }
   519:     //ポートを閉じる
   520:     if (trmTcpipPortChannel != null &&
   521:         trmTcpipPortChannel.isOpen ()) {
   522:       try {
   523:         trmTcpipPortChannel.close ();
   524:       } catch (IOException ioe) {
   525:         ioe.printStackTrace ();
   526:       }
   527:     }
   528:     trmTcpipPortChannel = null;
   529:     //セッションを閉じる
   530:     trmTcpipCloseSession (false);  //セッションを閉じる。次の接続を待たない
   531:   }  //trmTcpipClosePort
   532: 
   533:   //trmTcpipWrite (b, o, l)
   534:   //  送信する
   535:   //  b  配列
   536:   //  o  オフセット
   537:   //  l  長さ
   538:   public static void trmTcpipWrite (byte[] b, int o, int l) {
   539:     if (trmTcpipSessionChannel != null) {
   540:       byte[] bb = b;
   541:       int ll = l;
   542:       if (trmTcpipUTF8) {
   543:         //SJIS→UTF-8
   544:         int pool = trmTcpipToUTF8Pool;
   545:         bb = new byte[3 * l];
   546:         ll = 0;
   547:         for (int i = 0; i < l; i++) {
   548:           int s = (pool << 8) | (0xff & b[o + i]);
   549:           if ((0x0080 <= s && s <= 0x009f) || (0x00e0 <= s && s <= 0x00ff)) {  //2バイトコードの1バイト目
   550:             pool = s;
   551:             continue;
   552:           }
   553:           pool = 0;
   554:           int u = CharacterCode.chrSJISToChar[s];  //SJIS→UTF-16(サロゲートペアなし)変換
   555:           if (u == 0 && s != 0) {  //0でないコードが0になった
   556:             u = '\ufffd';  //対応なし
   557:           }
   558:           if (u <= 0x007f) {
   559:             bb[ll++] = (byte) u;
   560:           } else if (u <= 0x07ff) {
   561:             bb[ll++] = (byte) (0xc0 | (u >> 6));
   562:             bb[ll++] = (byte) (0x80 | (u & 0x3f));
   563:           } else {
   564:             bb[ll++] = (byte) (0xe0 | (u >> 12));
   565:             bb[ll++] = (byte) (0x80 | ((u >> 6) & 0x3f));
   566:             bb[ll++] = (byte) (0x80 | (u & 0x3f));
   567:           }
   568:         }  //for i
   569:         trmTcpipToUTF8Pool = pool;
   570:       }
   571:       ByteBuffer buffer = ByteBuffer.allocate (ll);
   572:       buffer.put (bb, 0, ll);
   573:       buffer.flip ();
   574:       try {
   575:         trmTcpipSessionChannel.write (buffer).get ();
   576:       } catch (Throwable t) {
   577:         String message = t.getMessage ();
   578:         if (message != null) {
   579:           System.err.println (message);
   580:         }
   581:         trmTcpipCloseSession (true);  //セッションを閉じて次の接続を待つ
   582:       }
   583:     }
   584:   }  //trmTcpipWrite
   585: 
   586:   //trmTcpipCloseSession (reconnect)
   587:   //  セッションを閉じる
   588:   //  reconnect  次の接続を待つか
   589:   public static void trmTcpipCloseSession (boolean reconnect) {
   590:     if (trmTcpipSessionChannel == null) {
   591:       return;
   592:     }
   593:     if (trmTcpipClientAddress != null) {
   594:       System.out.println (Multilingual.mlnJapanese ?
   595:                           trmTcpipClientAddress + " との接続を閉じました" :
   596:                           trmTcpipClientAddress + " disconnected");
   597:       trmTcpipClientAddress = null;
   598:     }
   599:     //セッションを閉じる
   600:     try {
   601:       trmTcpipSessionChannel.close ();
   602:     } catch (IOException ioe) {
   603:       ioe.printStackTrace ();
   604:     }
   605:     trmTcpipSessionChannel = null;
   606:     if (reconnect) {
   607:       //次の接続を待つ
   608:       if (trmTcpipPortChannel != null) {
   609:         trmTcpipPortChannel.accept (null, trmTcpipAcceptHandler);
   610:       }
   611:     }
   612:   }  //trmTcpipCloseSession
   613: 
   614: 
   615: 
   616:   //PPPサーバーになりたかった何か
   617:   //  WSLにpppdをインストールしても使えないことを知らずに作りかけたコード
   618:   //  想定された手順
   619:   //  (1)sudo visudoでsudoersに<username> ALL=NOPASSWD: /usr/sbin/pppdを追加する
   620:   //  (2)XEiJにパラメータ-pppserver=onを指定する
   621:   //  (3)RS-232CとターミナルでPPP serverとAUXを接続する
   622:   //  (4)CONFIG.SYSにprocess=32 10 100を書く
   623:   //  (5)ニフティなどにPPP接続していたときと同じ設定でtmsio.x、xip.x、ppp.xを実行する
   624:   public static String trmPppAddress;  //ローカルIPアドレス:リモートIPアドレス
   625:   public static boolean trmPppDebug;  //true=デバッグ表示あり
   626:   public static boolean trmPppDirect;  //true=モデム層を省略する
   627:   public static boolean trmPppLog;  //true=pppdの標準エラー出力をターミナルに出力する
   628:   public static boolean trmPppServer;  //true=PPPサーバー有効
   629:   //
   630:   public static boolean trmPppAvailable;  //true=pppdが使える
   631:   public static ByteQueue trmPppReadQueue;  //pppdの受信キュー
   632:   public static Process trmPppProcess;  //pppdのプロセス
   633:   public static OutputStream trmPppStdinStream;  //pppdの標準入力ストリーム
   634:   public static Thread trmPppStdoutGobbler;  //pppdの標準出力ゴブラー
   635:   public static Thread trmPppStderrGobbler;  //pppdの標準エラー出力ゴブラー
   636:   public static boolean trmPppModemLayer;  //true=モデム層
   637:   public static StringBuilder trmPppModemCommand;  //モデムコマンド
   638: 
   639:   //trmPppInit ()
   640:   //  初期化。ここでは開かない
   641:   public static void trmPppInit () {
   642:     //パラメータを復元する
   643:     trmPppAddress = Settings.sgsGetString ("pppaddress", "192.168.10.1:192.168.10.2");
   644:     trmPppDebug = Settings.sgsGetOnOff ("pppdebug", false);
   645:     trmPppDirect = Settings.sgsGetOnOff ("pppdirect", false);
   646:     trmPppLog = Settings.sgsGetOnOff ("ppplog", false);
   647:     trmPppServer = Settings.sgsGetOnOff ("pppserver", false);
   648:     //pppdが使えるか
   649:     trmPppAvailable = trmPppServer && new File ("/usr/sbin/pppd").exists ();
   650:   }  //trmPppInit
   651: 
   652:   //trmPppTini ()
   653:   //  後始末。ここでは閉じない
   654:   public static void trmPppTini () {
   655:     //パラメータを保存する
   656:     Settings.sgsPutString ("pppaddress", trmPppAddress);
   657:     Settings.sgsPutOnOff ("pppdebug", trmPppDebug);
   658:     Settings.sgsPutOnOff ("pppdirect", trmPppDirect);
   659:     Settings.sgsPutOnOff ("ppplog", trmPppLog);
   660:     Settings.sgsPutOnOff ("pppserver", trmPppServer);
   661:   }  //trmPppTini
   662: 
   663:   //success = trmPppOpenPort (readQueue)
   664:   //  ポートを開く
   665:   //  readQueue  受信キュー
   666:   public static boolean trmPppOpenPort (ByteQueue readQueue) {
   667:     //pppdを開始する
   668:     trmPppReadQueue = readQueue;
   669:     trmPppProcess = null;
   670:     trmPppStdinStream = null;
   671:     trmPppModemLayer = false;
   672:     if (!trmPppDirect) {
   673:       trmPppModemLayer = true;  //モデム層開始
   674:       if (trmPppDebug) {
   675:         System.out.println ("modem layer started");
   676:       }
   677:     }
   678:     trmPppModemCommand = new StringBuilder ();
   679:     try {
   680:       trmPppProcess = new ProcessBuilder (
   681:         "sudo", "pppd",
   682:         trmPppAddress,  //ローカルIPアドレス:リモートIPアドレス
   683:         "asyncmap", "0",
   684:         "default-asyncmap",
   685:         "local",  //モデム制御線を使わない
   686:         "mru", "1500",
   687:         "mtu", "1500",
   688:         "noaccomp",
   689:         "noauth",  //認証を要求しない
   690:         "nobsdcomp",
   691:         "noccp",  //圧縮しない
   692:         "nodeflate",
   693:         "nodeflatedraft",
   694:         "nodetach",  //フォアグラウンドで動かす
   695:         "noipv6",  //IPv6を使わない。指定しないとppp.xがUnknown protocol 0x8057を出す
   696:         "nopcomp",
   697:         "nopredictor1",
   698:         "notty",  //標準入出力を使う
   699:         "novj",
   700:         "novjccomp",
   701:         "passive",  //受信を待つ
   702:         "silent"  //受信するまで送信しない
   703:         ).start ();
   704:       System.out.println ("pppd started");
   705:       trmPppStdinStream = trmPppProcess.getOutputStream ();
   706:     } catch (IOException ioe) {
   707:       ioe.printStackTrace ();
   708:       return false;
   709:     }
   710:     //pppdの標準出力ゴブラーを開始する
   711:     trmPppStdoutGobbler = new Thread (() -> {
   712:       if (trmPppDebug) {
   713:         System.out.println ("pppd stdout gobbler started");
   714:       }
   715:       try (var is = trmPppProcess.getInputStream ()) {
   716:         byte[] b = new byte[1024];
   717:         while (trmPppProcess != null) {
   718:           int k = is.read (b);  //pppdの標準出力から読み出す。ブロックする
   719:           if (0 < k) {
   720:             if (trmPppDebug) {
   721:               System.out.print ("pppd stdout");
   722:               for (int i = 0; i < k; i++) {
   723:                 System.out.printf (" %02X", b[i] & 0xff);
   724:               }
   725:               System.out.println ();
   726:             }
   727:             trmPppReadQueue.write (b, 0, k);  //キューに書き込む
   728:           }
   729:         }
   730:       } catch (IOException ioe) {
   731:       }
   732:       if (trmPppDebug) {
   733:         System.out.println ("pppd stdout gobbler ended");
   734:       }
   735:     });
   736:     trmPppStdoutGobbler.start ();
   737:     //pppdの標準エラー出力ゴブラーを開始する
   738:     trmPppStderrGobbler = new Thread (() -> {
   739:       if (trmPppDebug) {
   740:         System.out.println ("pppd stderr gobbler started");
   741:       }
   742:       try (var is = trmPppProcess.getErrorStream ()) {
   743:         byte[] b = new byte[1024];
   744:         while (trmPppProcess != null) {
   745:           int k = is.read (b);  //pppdの標準エラー出力から読み出す。ブロックする
   746:           if (0 < k && trmPppLog) {
   747:             System.out.print ("pppd stderr ");
   748:             for (int i = 0; i < k; i++) {
   749:               System.out.printf ("%c", b[i] & 0xff);
   750:             }
   751:           }
   752:         }
   753:       } catch (IOException ioe) {
   754:       }
   755:       if (trmPppDebug) {
   756:         System.out.println ("pppd stderr gobbler ended");
   757:       }
   758:     });
   759:     trmPppStderrGobbler.start ();
   760:     return true;
   761:   }  //trmPppOpenPort
   762: 
   763:   //trmPppClosePort ()
   764:   //  ポートを閉じる
   765:   public static void trmPppClosePort () {
   766:     //pppdを終了する
   767:     trmPppProcess.destroy ();
   768:     trmPppProcess = null;
   769:     System.out.println ("pppd destroyed");
   770:     //pppdの標準出力ゴブラーが終了するまで待つ
   771:     try {
   772:       trmPppStdoutGobbler.join (100L);
   773:     } catch (InterruptedException ie) {
   774:     }
   775:     //pppdの標準エラー出力ゴブラーが終了するまで待つ
   776:     try {
   777:       trmPppStderrGobbler.join (100L);
   778:     } catch (InterruptedException ie) {
   779:     }
   780:   }  //trmPppClosePort
   781: 
   782: 
   783: 
   784:   //AUXフロー制御
   785:   //  X68000のRS-232Cのフロー制御は通信ドライバが処理している
   786:   //  プログラムが連続的に動作する実機と違い、間欠的に動作するエミュレータではフロー制御が適切なタイミングで行われない
   787:   //  SerialPort側のフロー制御をjSerialCommに任せ、通信ドライバ側のフロー制御をSCCで完結させることで、取りこぼしを防ぐ
   788:   //  フロー制御がXONでバッファの3/4が埋まって通信ドライバがXOFF$13を送信したとき
   789:   //  フロー制御がRTSでバッファの3/4が埋まって通信ドライバがWR5 bit1 0=/RTS is Highにしたとき
   790:   //    直ちにSCCの受信を停止する。瞬時に止まるのでどんなに速く通信していても通信ドライバの受信バッファは溢れない
   791:   //  フロー制御がXONでバッファの3/4が空いて通信ドライバがXON$11を送信したとき
   792:   //  フロー制御がRTSでバッファの3/4が空いて通信ドライバがWR5 bit1 1=/RTS is Lowにしたとき
   793:   //    直ちにSCCの受信を再開する。
   794:   //!!! 反応が速すぎると誤動作するドライバがあるかも?
   795:   //  フロー制御の設定はどこにも接続されていないときも有効でなければならない
   796:   public static boolean trmAUXFlowControlRTS;  //true=AUXのフロー制御はRTS。接続するときに使うので接続していなくても有効
   797:   public static boolean trmAUXFlowControlXON;  //true=AUXのフロー制御はXON。接続するときに使うので接続していなくても有効
   798:   public static boolean trmAUXNotReceiving;  //false=受信可($11=XONまたは1=/RTS is Low),true=受信不可($13=XOFFまたは0=/RTS is High)
   799: 
   800:   //AUX受信データバッファ
   801:   public static int trmAUXDataBuffer;  //0xffでマスクしておくこと
   802:   public static boolean trmAUXDataAvailable;  //ReadCommandのbit0:Rx Character Available。DataBufferの更新でセット、ReadDataでクリア
   803: 
   804:   //data = trmAUXReadData ()
   805:   //  AUXリードデータ
   806:   public static int trmAUXReadData () {
   807:     trmAUXDataAvailable = false;
   808:     return trmAUXDataBuffer;
   809:   }  //trmAUXReadData
   810: 
   811:   //trmAUXWriteData (data)
   812:   //  AUXライトデータ
   813:   public static void trmAUXWriteData (int data) {
   814:     int col = 1;
   815:     int row = trmColToRow[col];
   816:     if (row < 0) {
   817:       return;
   818:     }
   819:     data &= 0xff;  //!!! ビット長
   820:     if (trmAUXFlowControlXON) {
   821:       if (data == 0x11) {  //$11=XON
   822:         trmAUXNotReceiving = false;  //受信可
   823:         return;
   824:       } else if (data == 0x13) {  //$13=XOFF
   825:         trmAUXNotReceiving = true;  //受信不可
   826:         return;
   827:       }
   828:     }
   829:     trmConnectionArray[trmCols * row + col - 1].col2rowQueue.write (data);  //AUX→Terminal/SerialPort
   830:     //AUX送信割り込み
   831:     trmAUXSendEmpty = false;  //送信バッファ空フラグをクリア
   832:     TickerQueue.tkqAdd (trmAUXSendTicker, XEiJ.mpuClockTime + Z8530.scc1aInterval);  //現在時刻+間隔の時刻に送信ティッカーを設定
   833:   }  //trmAUXWriteData
   834: 
   835:   //trmAUXSetNotReceiving (notReceiving)
   836:   //  notReceiving  false  WR5 bit1 1=/RTS is Low   受信可
   837:   //                true   WR5 bit1 0=/RTS is High  受信不可
   838:   public static void trmAUXSetNotReceiving (boolean notReceiving) {
   839:     trmAUXNotReceiving = notReceiving;
   840:   }  //trmAUXSetNotReceiving
   841: 
   842: 
   843:   //DTR
   844:   public static boolean trmAUXDTR;
   845:   public static void trmAUXSetDTR (boolean dtr) {
   846:     if (trmAUXDTR != dtr) {
   847:       trmAUXDTR = dtr;
   848:       if (trmAUXConnection != null) {
   849:         int row = trmAUXConnection.row;
   850:         SerialPort port = row < 1 || trmNumberOfPorts < row ? null : trmPortArray[row - 1];
   851:         if (port != null) {
   852:           if (dtr) {
   853:             port.setDTR ();
   854:           } else {
   855:             port.clearDTR ();
   856:           }
   857:         }
   858:       }
   859:     }
   860:   }  //trmAUXSetDTR
   861: 
   862: 
   863:   //data = trmAUXReadRR0 ()
   864:   //  Z85C30UM 5-21
   865:   //  RR0
   866:   //    bit7  Break/Abort
   867:   //    bit6  Tx Underrun/EOM
   868:   //    bit5  CTS
   869:   //      SCC 21 CTSA ← RS 5 CTS
   870:   //      SCC 25 CTSB ← RS 22 CI Call Indicator (RI Ring Indicator)
   871:   //          相手のRTS
   872:   //          0=/CTS is High  相手が受信バッファに余裕がないから送るのやめてと言っている
   873:   //          1=/CTS is Low   相手が受信バッファに余裕があるから送っていいよと言っている
   874:   //    bit4  Sync/Hunt
   875:   //      SCC 12 SYNCA ← VCC
   876:   //      SCC 25 CTSB ← RS 22 CI Call Indicator (RI Ring Indicator)
   877:   //    bit3  DCD
   878:   //      SCC 22 DCDA ← RS 6 DSR Data Set Ready
   879:   //      SCC 24 DCDB ← RS 8 CD Carrier Detect
   880:   //    bit2  Tx Buffer Empty
   881:   //    bit1  Zero Count
   882:   //    bit0  Rx Character Available
   883:   public static int trmAUXReadRR0 () {
   884:     return (trmAUXConnection == null ? 0 :
   885:             (trmAUXConnection.cts ? 1 << 5 : 0 << 5) |  //CTSA。CTS(相手のRTS)が接続されている
   886:             (trmAUXConnection.dsr ? 1 << 3 : 0 << 3) |  //DCDA。DSR(相手のDTR)が接続されている
   887:             (trmAUXSendEmpty ? 1 << 2 : 0 << 2) |  //Tx Buffer Empty
   888:             (trmAUXDataAvailable ? 1 << 0 : 0 << 0));  //Rx Character Available
   889:   }  //trmAUXReadRR0
   890: 
   891:   //data = trmMUSReadRR0 ()
   892:   //  RS-232CのCIとCDはCTSBとDCDBに接続されている
   893:   public static int trmMUSReadRR0 () {
   894:     return (trmAUXConnection == null ? 0 :
   895:             (trmAUXConnection.ri ? 1 << 5 : 0 << 5) |  //CTSB。RS-232CのCIが接続されている
   896:             (trmAUXConnection.dcd ? 1 << 3 : 0 << 3));  //DCDB。RS-232CのCDが接続されている
   897:   }  //trmMUSReadRR0
   898: 
   899:   //AUX送信ティッカー
   900:   //  送信
   901:   //    データをキューに追加
   902:   //    送信ティッカーを消去
   903:   //    送信バッファ空フラグをクリア
   904:   //    現在時刻+間隔の時刻に送信ティッカーを設定
   905:   //  送信ティッカー
   906:   //    送信バッファ空フラグをセット
   907:   //    送信割り込みが許可されているとき
   908:   //      送信割り込み発生
   909:   public static boolean trmAUXSendEmpty;  //true=送信バッファ空
   910:   public static final TickerQueue.Ticker trmAUXSendTicker = new TickerQueue.Ticker () {
   911:     @Override protected void tick () {
   912:       trmAUXSendEmpty = true;  //送信バッファ空フラグをセット
   913:       if ((Z8530.sccMIE & Z8530.scc1aSendMask) != 0) {  //送信割り込みが許可されているとき
   914:         Z8530.scc1aSendRR3 = Z8530.SCC_1A_SEND_MASK;  //送信割り込み発生
   915:         Z8530.scc1aSendRequest = Z8530.SCC_1A_SEND_MASK;
   916:         XEiJ.mpuIRR |= XEiJ.MPU_SCC_INTERRUPT_MASK;
   917:       }
   918:     }  //tick
   919:   };  //trmAUXSendTicker
   920: 
   921:   //AUXポーリングティッカー
   922:   public static Connection trmAUXConnection;  //?→AUXの接続。null=なし
   923:   public static final TickerQueue.Ticker trmAUXTicker = new TickerQueue.Ticker () {
   924:     @Override protected void tick () {
   925:       long interval = XEiJ.TMR_FREQ / 1000 * 1;  //1ms
   926:       ByteQueue queue = trmAUXConnection.row2colQueue;  //?→AUX キュー
   927:       if (trmAUXConnection.row2colReset) {  //?→AUX リセット
   928:         trmAUXConnection.row2colReset = false;
   929:         queue.clear ();
   930:       } else if (!trmAUXNotReceiving &&  //受信可で
   931:                  queue.used () != 0) {  //キューが空でないとき
   932:         trmAUXDataBuffer = queue.read ();  //キューから読み出してDataBufferへ書き込む
   933:         trmAUXDataAvailable = true;  //DataBufferは有効
   934:         if ((Z8530.sccMIE & Z8530.scc1aReceiveMask) != 0 &&  //割り込みが許可されていて
   935:             Z8530.scc1aReceiveRR3 == 0) {  //割り込み発生からIUSリセットまでではないとき
   936:           Z8530.scc1aReceiveRR3 = Z8530.SCC_1A_RECEIVE_MASK;  //割り込み発生
   937:           Z8530.scc1aReceiveRequest = Z8530.SCC_1A_RECEIVE_MASK;
   938:           XEiJ.mpuIRR |= XEiJ.MPU_SCC_INTERRUPT_MASK;
   939:         }
   940:         interval = Z8530.scc1aInterval;
   941:       }
   942:       TickerQueue.tkqAdd (trmAUXTicker, XEiJ.mpuClockTime + interval);
   943:     }  //tick
   944:   };  //trmAUXTicker
   945: 
   946:   public static int trmRSDRV202Head;  //RSDRV.SYS 2.02の先頭アドレス
   947:   public static int trmTMSIO031Head;  //tmsio.x 0.31の先頭アドレス
   948:   public static int trmBSIO021Head;  //bsio.x 0.21の先頭アドレス
   949: 
   950:   //trmAUXFlowControlTicker
   951:   //  フロー制御をSerialPortに反映させる
   952:   //  ボーレートジェネレータが動き始めるとき通信設定をSerialPortに反映させるが、
   953:   //  その時点でワークエリアが更新されていないためフロー制御を反映させることができない
   954:   //  フロー制御だけ少し遅れて反映させることにする
   955:   //  遅らせすぎると最初のデータに間に合わない可能性がある
   956:   //  バッファが一杯になるまではフロー制御は行われないと仮定すれば間に合わなくても問題ないかも知れない
   957:   public static final TickerQueue.Ticker trmAUXFlowControlTicker = new TickerQueue.Ticker () {
   958:     @Override protected void tick () {
   959:       int set232c = MC68060.mmuPeekLongData (0x04c0, 1);  //IOCS _SET232Cベクタ
   960:       int modeAddress = (0x00fc0000 <= set232c && set232c < 0x01000000 ? 0x0926 :  //IPLROM
   961:                          set232c == trmRSDRV202Head + 0x03ba ? trmRSDRV202Head + 0x0ab2 :  //RSDRV.SYS 2.02
   962:                          set232c == trmTMSIO031Head + 0x0210 ? trmTMSIO031Head + 0x0a42 :  //tmsio.x 0.31
   963:                          set232c == trmBSIO021Head + 0x013A ? trmBSIO021Head + 0x074a :  //bsio.x 0.21
   964:                          0);  //不明
   965:       if (modeAddress == 0) {
   966:         return;
   967:       }
   968:       int mode = MC68060.mmuPeekWordZeroData (modeAddress, 1);  //通信設定
   969:       if (mode == 0x0000 || mode == 0xffff) {
   970:         return;
   971:       }
   972:       boolean rts = (mode & 0x0080) != 0;
   973:       boolean xon = !rts && (mode & 0x0200) != 0;
   974:       if (trmAUXFlowControlRTS == rts &&
   975:           trmAUXFlowControlXON == xon) {
   976:         return;
   977:       }
   978:       trmAUXFlowControlRTS = rts;
   979:       trmAUXFlowControlXON = xon;
   980:       if (false) {
   981:         System.out.printf ("flowcontrol=%s\n", rts ? "rts" : xon ? "xon" : "none");
   982:       }
   983:       int row = trmColToRow[1];
   984:       SerialPort port = row < 1 || trmNumberOfPorts < row ? null : trmPortArray[row - 1];
   985:       if (port != null) {
   986:         port.setFlowControl (rts ? (SerialPort.FLOW_CONTROL_RTS_ENABLED |
   987:                                     SerialPort.FLOW_CONTROL_CTS_ENABLED) :
   988:                              xon ? (SerialPort.FLOW_CONTROL_XONXOFF_IN_ENABLED |
   989:                                     SerialPort.FLOW_CONTROL_XONXOFF_OUT_ENABLED) :
   990:                              SerialPort.FLOW_CONTROL_DISABLED);
   991:       }
   992:     }  //tick
   993:   };  //trmAUXFlowControlTicker
   994: 
   995:   //trmAUXReset ()
   996:   //  AUXリセット
   997:   public static void trmAUXReset () {
   998:     if (trmAUXConnection != null) {  //接続しているとき
   999:       TickerQueue.tkqRemove (trmAUXFlowControlTicker);
  1000:       //キューをクリアする
  1001:       trmAUXConnection.row2colReset = true;
  1002:       trmAUXConnection.col2rowReset = true;
  1003:     }
  1004:   } //trmAUXReset
  1005: 
  1006: 
  1007:   public static Connection[] trmConnectionArray;  //Connectionの配列
  1008:   public static Box trmConnectionBox;  //Connectionのboxを入れる箱
  1009:   public static ActionListener trmConnectionListener;  //ConnectionのcheckBoxのactionListener
  1010:   public static boolean trmRefreshEnabled;  //更新ボタンは有効か
  1011:   public static JButton trmRefreshButton;  //更新ボタン
  1012:   //通信設定
  1013:   public static boolean trmSettingsEnabled;  //通信設定は有効か
  1014:   public static String[] trmBaudRateArray;  //ボーレートの選択肢
  1015:   public static int trmBaudRateIndex;  //ボーレートのインデックス
  1016:   public static String[] trmDataBitsArray;  //データビットの選択肢
  1017:   public static int trmDataBitsIndex;  //データビットのインデックス
  1018:   public static String[] trmParityArray;  //パリティの選択肢
  1019:   public static int trmParityIndex;  //パリティのインデックス
  1020:   public static String[] trmStopBitsArray;  //ストップビットの選択肢
  1021:   public static int trmStopBitsIndex;  //ストップビットのインデックス
  1022:   public static String[] trmFlowControlArray;  //フロー制御の選択肢
  1023:   public static int trmFlowControlIndex;  //フロー制御のインデックス
  1024:   public static JComboBox<String> trmBaudRateComboBox;  //ボーレート
  1025:   public static JComboBox<String> trmDataBitsComboBox;  //データビット
  1026:   public static JComboBox<String> trmParityComboBox;  //パリティ
  1027:   public static JComboBox<String> trmStopBitsComboBox;  //ストップビット
  1028:   public static JComboBox<String> trmFlowControlComboBox;  //フロー制御
  1029:   //ファイル転送
  1030:   public static boolean trmSendEnabled;  //送信ボタンは有効か
  1031:   public static JButton trmSendButton;  //送信ボタン
  1032:   public static JFileChooser trmSendFileChooser;  //送信ダイアログのファイルチューザー
  1033:   public static JDialog trmSendDialog;  //送信ダイアログ
  1034:   public static SendThread trmSendThread;  //送信スレッド
  1035:   //時計合わせ
  1036:   public static JButton trmSetClockButton;  //時計合わせボタン
  1037:   public static ScheduledExecutorService trmScheduler;
  1038:   //追加ポート
  1039:   public static JTextField trmAdditionalTextField;
  1040: 
  1041:   //trmInitConnection ()
  1042:   //  接続を初期化する
  1043:   public static void trmInitConnection () {
  1044:     //通信設定
  1045:     trmBaudRateArray = new String[] { "75", "150", "300", "600", "1200", "2400", "4800", "9600", "19200", "31250", "38400", "50000", "57600", "76800", "115200", "230400" };
  1046:     trmBaudRateIndex = 10;
  1047:     trmDataBitsArray = new String[] { "B5", "B6", "B7", "B8" };
  1048:     trmDataBitsIndex = 3;
  1049:     trmParityArray = new String[] { "PN", "PO", "PE" };
  1050:     trmParityIndex = 0;
  1051:     trmStopBitsArray = new String[] { "S1", "S1.5", "S2" };
  1052:     trmStopBitsIndex = 0;
  1053:     trmFlowControlArray = new String[] { "NONE", "XON", "RTS" };
  1054:     trmFlowControlIndex = 2;
  1055:     trmBaudRateComboBox = null;
  1056:     trmDataBitsComboBox = null;
  1057:     trmParityComboBox = null;
  1058:     trmStopBitsComboBox = null;
  1059:     trmFlowControlComboBox = null;
  1060:     //通信設定を復元する
  1061:     for (String keyword : Settings.sgsGetString ("terminalsettings").split ("/")) {
  1062:       for (int i = 0; i < trmBaudRateArray.length; i++) {
  1063:         if (trmBaudRateArray[i].equals (keyword)) {
  1064:           trmBaudRateIndex = i;
  1065:           break;
  1066:         }
  1067:       }
  1068:       for (int i = 0; i < trmDataBitsArray.length; i++) {
  1069:         if (trmDataBitsArray[i].equals (keyword)) {
  1070:           trmDataBitsIndex = i;
  1071:           break;
  1072:         }
  1073:       }
  1074:       for (int i = 0; i < trmParityArray.length; i++) {
  1075:         if (trmParityArray[i].equals (keyword)) {
  1076:           trmParityIndex = i;
  1077:           break;
  1078:         }
  1079:       }
  1080:       for (int i = 0; i < trmStopBitsArray.length; i++) {
  1081:         if (trmStopBitsArray[i].equals (keyword)) {
  1082:           trmStopBitsIndex = i;
  1083:           break;
  1084:         }
  1085:       }
  1086:       for (int i = 0; i < trmFlowControlArray.length; i++) {
  1087:         if (trmFlowControlArray[i].equals (keyword)) {
  1088:           trmFlowControlIndex = i;
  1089:           break;
  1090:         }
  1091:       }
  1092:     }  //for keyword
  1093:     //ファイル転送
  1094:     trmSendButton = null;
  1095:     trmSendFileChooser = null;
  1096:     trmSendDialog = null;
  1097:     trmSendThread = null;
  1098:     //時計合わせ
  1099:     trmSetClockButton = null;
  1100:     trmScheduler = Executors.newSingleThreadScheduledExecutor ();
  1101:     //SerialPort
  1102:     trmPortArray = new SerialPort[0];
  1103:     trmNumberOfPorts = 0;
  1104:     //TCP/IP
  1105:     trmTcpipRow = -1;
  1106:     //PPPサーバー
  1107:     trmPppRow = -1;
  1108:     //行
  1109:     trmRows = 1;
  1110:     trmRowName = new String[1];
  1111:     trmRowName[0] = "Terminal";
  1112:     trmRowToCol = new int[1];
  1113:     Arrays.fill (trmRowToCol, -1);
  1114:     //AUX*
  1115:     trmNumberOfAUXs = 0;
  1116:     //列
  1117:     trmCols = 1;
  1118:     trmColName = new String[1];
  1119:     trmColName[0] = "Terminal";
  1120:     trmColToRow = new int[1];
  1121:     Arrays.fill (trmColToRow, -1);
  1122:     //追加ポート
  1123:     {
  1124:       String text = Settings.sgsGetString ("additionalport");
  1125:       try {
  1126:         text = URLDecoder.decode (text, "UTF-8");
  1127:       } catch (UnsupportedEncodingException uee) {
  1128:         text = "";
  1129:       }
  1130:       trmAdditionalTextField = ComponentFactory.createTextField (text, 15);
  1131:     }
  1132:     //TCP/IPポート
  1133:     trmTcpipInit ();
  1134:     //PPPサーバー
  1135:     trmPppInit ();
  1136:     //接続
  1137:     trmConnectionArray = new Connection[0];
  1138:     trmConnectionBox = ComponentFactory.createVerticalBox (
  1139:       Box.createVerticalGlue (),
  1140:       ComponentFactory.createHorizontalBox (
  1141:         Multilingual.mlnText (
  1142:           ComponentFactory.createLabel ("Additional port "),
  1143:           "ja", "追加ポート "
  1144:           ),
  1145:         trmAdditionalTextField,
  1146:         Multilingual.mlnText (
  1147:           ComponentFactory.createLabel (" TCP/IP port "),
  1148:           "ja", " TCP/IP ポート "
  1149:           ),
  1150:         trmTcpipSpinner,
  1151:         Box.createHorizontalStrut (5),
  1152:         trmTcpipCheckBox,
  1153:         Box.createHorizontalGlue ()
  1154:         )
  1155:       );
  1156:     trmConnectionListener = new ActionListener () {
  1157:       @Override public void actionPerformed (ActionEvent ae) {
  1158:         String command = ae.getActionCommand ();
  1159:         for (Connection connection : trmConnectionArray) {
  1160:           if (connection.text.equals (command)) {
  1161:             if (connection.connected) {
  1162:               trmDisconnect (connection);
  1163:             } else {
  1164:               trmConnect (connection);
  1165:             }
  1166:             break;
  1167:           }
  1168:         }
  1169:       }
  1170:     };
  1171:     trmRefreshEnabled = false;
  1172:     trmRefreshButton = null;
  1173:     trmSettingsEnabled = false;
  1174:     trmSendEnabled = false;
  1175:     //接続を更新する
  1176:     trmUpdateConnection ();
  1177:     //接続を復元する
  1178:     HashSet<String> map = new HashSet<String> ();
  1179:     for (String encodedText : Settings.sgsGetString ("rs232cconnection").split ("/")) {
  1180:       try {
  1181:         map.add (URLDecoder.decode (encodedText, "UTF-8"));
  1182:       } catch (UnsupportedEncodingException uee) {
  1183:       }
  1184:     }
  1185:     for (Connection connection : trmConnectionArray) {
  1186:       if (map.contains (connection.text)) {
  1187:         trmConnect (connection);
  1188:       }
  1189:     }
  1190:   }  //trmInitConnection
  1191: 
  1192:   //trmTiniConnection ()
  1193:   //  接続を後始末する
  1194:   public static void trmTiniConnection () {
  1195:     //時計合わせ
  1196:     trmScheduler.shutdownNow ();
  1197:     //接続を保存する
  1198:     {
  1199:       StringBuilder sb = new StringBuilder ();
  1200:       for (Connection connection : trmConnectionArray) {
  1201:         if (connection.connected) {  //接続している
  1202:           if (sb.length () != 0) {
  1203:             sb.append ('/');
  1204:           }
  1205:           try {
  1206:             sb.append (URLEncoder.encode (connection.text, "UTF-8"));
  1207:           } catch (UnsupportedEncodingException uee) {
  1208:           }
  1209:         }
  1210:       }
  1211:       Settings.sgsPutString ("rs232cconnection", sb.toString ());
  1212:     }
  1213:     //追加ポート
  1214:     {
  1215:       String text = trmAdditionalTextField.getText ();
  1216:       try {
  1217:         text = URLEncoder.encode (text, "UTF-8");
  1218:       } catch (UnsupportedEncodingException uee) {
  1219:         text = "";
  1220:       }
  1221:       Settings.sgsPutString ("additionalport", text);
  1222:     }
  1223:     //TCP/IPポート
  1224:     trmTcpipTini ();
  1225:     //PPPサーバー
  1226:     trmPppTini ();
  1227:     //通信設定を保存する
  1228:     {
  1229:       StringBuilder sb = new StringBuilder ();
  1230:       sb.append (trmBaudRateArray[trmBaudRateIndex]);
  1231:       sb.append ('/');
  1232:       sb.append (trmDataBitsArray[trmDataBitsIndex]);
  1233:       sb.append ('/');
  1234:       sb.append (trmParityArray[trmParityIndex]);
  1235:       sb.append ('/');
  1236:       sb.append (trmStopBitsArray[trmStopBitsIndex]);
  1237:       sb.append ('/');
  1238:       sb.append (trmFlowControlArray[trmFlowControlIndex]);
  1239:       Settings.sgsPutString ("terminalsettings", sb.toString ());
  1240:     }
  1241:     //すべて切断する
  1242:     for (Connection connection : trmConnectionArray) {
  1243:       trmDisconnect (connection);
  1244:     }
  1245:   }  //trmTiniConnection
  1246: 
  1247:   //trmIsConnectionUpdatable ()
  1248:   //  接続を更新できるか
  1249:   public static boolean trmIsConnectionUpdatable () {
  1250:     for (Connection connection : trmConnectionArray) {
  1251:       if (connection.row != 0 &&  //SerialPortに接続している
  1252:           connection.connected) {  //接続している
  1253:         return false;  //更新できない
  1254:       }
  1255:     }
  1256:     return true;  //更新できる
  1257:   }  //trmIsConnectionUpdatable
  1258: 
  1259:   //trmUpdateConnection ()
  1260:   //  接続を更新する
  1261:   public static void trmUpdateConnection () {
  1262:     //更新できないときは何もしない
  1263:     if (!trmIsConnectionUpdatable ()) {
  1264:       return;
  1265:     }
  1266:     //SerialPort
  1267:     ArrayList<SerialPort> portList = new ArrayList<SerialPort> ();
  1268:     try {
  1269:       for (SerialPort port : SerialPort.getCommPorts ()) {
  1270:         portList.add (port);
  1271:       }
  1272:     } catch (Throwable t) {
  1273:       //  https://github.com/Fazecast/jSerialComm/issues/608
  1274:     }
  1275:     for (String descriptor : trmAdditionalTextField.getText ().split (",")) {  //追加ポート
  1276:       descriptor = descriptor.trim ();
  1277:       if (!descriptor.equals ("")) {
  1278:         try {
  1279:           SerialPort port = SerialPort.getCommPort (descriptor);
  1280:           if (port != null) {
  1281:             if (false) {  //既にリストにあるポートを追加できないようにする。getDescriptivePortName()はおそらく適切でない
  1282:               for (SerialPort anotherPort : portList) {
  1283:                 if (port.getDescriptivePortName ().equals (anotherPort.getDescriptivePortName ())) {  //port.equals(anotherPort)は不可
  1284:                   port = null;
  1285:                   break;
  1286:                 }
  1287:               }
  1288:             }
  1289:             if (port != null) {
  1290:               portList.add (port);
  1291:             }
  1292:           } else {
  1293:             System.out.println (descriptor + " not found");
  1294:           }
  1295:         } catch (SerialPortInvalidPortException spipe) {
  1296:           System.out.println (spipe.toString ());
  1297:         }
  1298:       }
  1299:     }
  1300:     trmNumberOfPorts = portList.size ();
  1301:     trmPortArray = portList.toArray (new SerialPort[trmNumberOfPorts]);
  1302:     //TCP/IP
  1303:     trmTcpipRow = (1 +  //Terminal
  1304:                    trmNumberOfPorts);  //SerialPort
  1305:     //PPPサーバー
  1306:     if (trmPppAvailable) {
  1307:       trmPppRow = (1 +  //Terminal
  1308:                    trmNumberOfPorts +  //SerialPort
  1309:                    (0 <= trmTcpipRow ? 1 : 0));  //TCP/IP
  1310:     } else {
  1311:       trmPppRow = -1;
  1312:     }
  1313:     //行
  1314:     trmRows = (1 +  //Terminal
  1315:                trmNumberOfPorts +  //SerialPort
  1316:                (0 <= trmTcpipRow ? 1 : 0) +  //TCP/IP
  1317:                (0 <= trmPppRow ? 1 : 0));  //PPPサーバー
  1318:     trmRowName = new String[trmRows];
  1319:     //Terminal
  1320:     trmRowName[0] = "Terminal";
  1321:     //SerialPort
  1322:     for (int row = 1; row <= trmNumberOfPorts; row++) {
  1323:       SerialPort port = trmPortArray[row - 1];
  1324:       trmRowName[row] = port.getSystemPortName () + "(" + port.getPortDescription () + ")";
  1325:     }
  1326:     //TCP/IP
  1327:     if (0 <= trmTcpipRow) {
  1328:       trmRowName[trmTcpipRow] = "TCP/IP";
  1329:     }
  1330:     //PPPサーバー
  1331:     if (0 <= trmPppRow) {
  1332:       trmRowName[trmPppRow] = "PPP server";
  1333:     }
  1334:     trmRowToCol = new int[trmRows];
  1335:     Arrays.fill (trmRowToCol, -1);
  1336:     //AUX*
  1337:     trmNumberOfAUXs = 1;
  1338:     //列
  1339:     trmCols = (1 +  //Terminal
  1340:                trmNumberOfAUXs);  //AUX
  1341:     trmColName = new String[trmCols];
  1342:     trmColName[0] = "Terminal";
  1343:     for (int col = 1; col < trmCols; col++) {
  1344:       trmColName[col] = col == 1 ? "AUX" : "AUX" + col;
  1345:     }
  1346:     trmColToRow = new int[trmCols];
  1347:     Arrays.fill (trmColToRow, -1);
  1348:     //接続
  1349:     for (int index = trmConnectionArray.length - 1; 0 <= index; index--) {
  1350:       trmConnectionBox.remove (index);
  1351:     }
  1352:     trmConnectionArray = new Connection[trmCols * trmRows - 1];
  1353:     for (int row = 0; row < trmRows; row++) {
  1354:       for (int col = 0; col < trmCols; col++) {
  1355:         if (col == 0 && row == 0) {
  1356:           continue;
  1357:         }
  1358:         Connection connection = new Connection ();
  1359:         connection.row = row;
  1360:         connection.col = col;
  1361:         connection.index = trmCols * row + col - 1;
  1362:         connection.text = trmRowName[row] + " ⇔ " + trmColName[col];
  1363:         connection.connected = false;
  1364:         connection.checkBox =
  1365:           ComponentFactory.createCheckBox (connection.connected, connection.text, trmConnectionListener);
  1366:         connection.box =
  1367:           ComponentFactory.createHorizontalBox (
  1368:             connection.checkBox,
  1369:             Box.createHorizontalGlue ());
  1370:         trmConnectionArray[connection.index] = connection;
  1371:         trmConnectionBox.add (connection.box, connection.index);
  1372:       }  //for col
  1373:     }  //for row
  1374:     trmUpdateComponents ();
  1375:     trmConnectionBox.validate ();
  1376:   }  //trmUpdateConnection
  1377: 
  1378:   //trmUpdateComponents ()
  1379:   //  接続できないConnectionのcheckBoxを無効にする
  1380:   //  SerialPortに接続しているConnectionがあるとき更新ボタンを無効にする
  1381:   //  Terminal-SerialPortがないとき通信設定を表示しない
  1382:   public static void trmUpdateComponents () {
  1383:     boolean updatable = true;  //更新できる
  1384:     boolean configurable = false;  //設定できない
  1385:     boolean transferable = false;  //転送できない
  1386:     for (Connection connection : trmConnectionArray) {
  1387:       if (connection.connected) {  //接続している
  1388:         connection.checkBox.setEnabled (true);  //切断できる
  1389:         if (1 <= connection.row && connection.row <= trmNumberOfPorts) {  //SerialPortを接続している
  1390:           updatable = false;  //更新できない
  1391:           if (connection.col == 0) {  //TerminalとSerialPortを接続している
  1392:             configurable = true;  //設定できる
  1393:           }
  1394:         }
  1395:         if (connection.row == 0 ||
  1396:             connection.col == 0) {  //Terminalを接続している
  1397:           transferable = true;  //転送できる
  1398:         }
  1399:       } else {  //接続していない
  1400:         connection.checkBox.setEnabled (trmIsConnectable (connection));  //接続できるときだけ有効
  1401:       }
  1402:     }
  1403:     trmRefreshEnabled = updatable;
  1404:     if (trmRefreshButton != null) {
  1405:       trmRefreshButton.setEnabled (updatable);
  1406:     }
  1407:     trmSettingsEnabled = configurable;
  1408:     trmSendEnabled = transferable;
  1409:     if (trmBaudRateComboBox != null) {
  1410:       trmBaudRateComboBox.setEnabled (configurable);
  1411:       trmDataBitsComboBox.setEnabled (configurable);
  1412:       trmParityComboBox.setEnabled (configurable);
  1413:       trmStopBitsComboBox.setEnabled (configurable);
  1414:       trmFlowControlComboBox.setEnabled (configurable);
  1415:       trmSendButton.setEnabled (transferable);
  1416:       trmSetClockButton.setEnabled (transferable);
  1417:     }
  1418:   }  //trmUpdateComponents
  1419: 
  1420:   //connectable = trmIsConnectable (connection)
  1421:   //  接続できるか
  1422:   public static boolean trmIsConnectable (Connection connection) {
  1423:     if (!connection.connected) {  //自分が接続していないとき
  1424:       for (Connection connection2 : trmConnectionArray) {
  1425:         if (connection != connection2 &&  //自分以外で
  1426:             connection2.connected &&  //接続していて
  1427:             (((connection.row == 0 || connection.col == 0) &&
  1428:               (connection2.row == 0 || connection2.col == 0)) ||  //Terminalが衝突しているまたは
  1429:              connection.row == connection2.row ||  //SerialPortが衝突しているまたは
  1430:              connection.col == connection2.col)) {  //AUX*が衝突しているとき
  1431:           return false;  //接続できない
  1432:         }
  1433:       }
  1434:     }
  1435:     return true;  //接続できる
  1436:   }  //trmIsConnectable
  1437: 
  1438:   //trmConnect (connection)
  1439:   //  接続する
  1440:   public static void trmConnect (Connection connection) {
  1441:     //接続しているか接続できないときは何もしない
  1442:     if (connection.connected ||  //接続している
  1443:         !trmIsConnectable (connection)) {  //接続できない
  1444:       return;
  1445:     }
  1446:     //接続する
  1447:     connection.connected = true;
  1448:     connection.checkBox.setSelected (true);
  1449:     trmRowToCol[connection.row] = connection.col;
  1450:     trmColToRow[connection.col] = connection.row;
  1451:     trmUpdateComponents ();
  1452:     //キューを作る
  1453:     connection.row2colQueue = new ByteQueue ();
  1454:     connection.col2rowQueue = new ByteQueue ();
  1455:     //CTS/DCD/DSR/RI
  1456:     connection.cts = true;  //キューが溢れることは想定しない
  1457:     connection.dcd = true;
  1458:     connection.dsr = false;
  1459:     connection.ri = false;
  1460:     if (1 <= connection.row && connection.row <= trmNumberOfPorts) {  //SerialPort→?
  1461:       //シリアルポートを開く
  1462:       SerialPort port = trmPortArray[connection.row - 1];
  1463:       port.openPort ();
  1464:       System.out.println (Multilingual.mlnJapanese ?
  1465:                           connection.text + " を開きました" :
  1466:                           connection.text + " opened");
  1467:       port.setComPortTimeouts (SerialPort.TIMEOUT_WRITE_BLOCKING | SerialPort.TIMEOUT_READ_BLOCKING, 0, 0);
  1468:       port.setFlowControl (SerialPort.FLOW_CONTROL_DISABLED);
  1469:       //通信設定をSerialPortに反映させる
  1470:       trmReflectSettings (connection.col);
  1471:       //DTR
  1472:       if (trmAUXDTR) {
  1473:         port.setDTR ();  //openPortがセットしているはずだが念の為
  1474:       } else {
  1475:         port.clearDTR ();
  1476:       }
  1477:       //CTS/DCD/DSR/RI
  1478:       //connection.cts = true;
  1479:       connection.dcd = port.getDCD ();
  1480:       connection.dsr = port.getDSR ();
  1481:       connection.ri = port.getRI ();
  1482:       //シリアルポートデータリスナーを設定する
  1483:       port.addDataListener (connection);
  1484:     } else if (connection.row == trmTcpipRow) {  //TCP/IP→?
  1485:       //TCP/IPポートを開く
  1486:       if (!trmTcpipOpenPort (connection.row2colQueue)) {
  1487:         //開けなかった
  1488:         //!!!
  1489:       }
  1490:       //CTS/DCD/DSR/RI
  1491:       //connection.cts = true;
  1492:       //connection.dcd = true;
  1493:       //connection.dsr = false;
  1494:       //connection.ri = false;
  1495:     } else if (connection.row == trmPppRow) {  //PPPサーバー→?
  1496:       //PPPサーバーを開く
  1497:       if (!trmPppOpenPort (connection.row2colQueue)) {
  1498:         //開けなかった
  1499:         //!!!
  1500:       }
  1501:       //CTS/DCD/DSR/RI
  1502:       //connection.cts = true;
  1503:       //connection.dcd = true;
  1504:       //connection.dsr = false;
  1505:       //connection.ri = false;
  1506:     }
  1507:     //ポーリングスレッドを開始する
  1508:     connection.polling = true;
  1509:     connection.row2colThread = (connection.col == 0 ? connection.new TerminalThread () :  //?→Terminal
  1510:                                 null);  //?→AUX。AUXはスレッドではなくティッカーを使う
  1511:     connection.col2rowThread = (connection.row == 0 ? connection.new TerminalThread () :  //?→Terminal
  1512:                                 connection.row == trmTcpipRow ? connection.new TCPIPThread () :  //?→TCP/IP
  1513:                                 connection.row == trmPppRow ? connection.new PPPThread () :  //?→PPPサーバー
  1514:                                 connection.new SerialPortThread ());  //?→SerialPort
  1515:     for (int i = 0; i < 2; i++) {
  1516:       Thread thread = (i == 0 ? connection.row2colThread : connection.col2rowThread);
  1517:       if (thread != null) {
  1518:         thread.start ();
  1519:       }
  1520:     }
  1521:     //ポーリングティッカーを開始する
  1522:     if (connection.col == 1) {  //?→AUX
  1523:       trmAUXNotReceiving = false;  //受信可
  1524:       trmAUXDataBuffer = 0;
  1525:       trmAUXDataAvailable = false;
  1526:       trmAUXConnection = connection;
  1527:       TickerQueue.tkqAdd (trmAUXTicker, XEiJ.mpuClockTime + XEiJ.TMR_FREQ / 1000 * 1);  //1ms
  1528:       //AUX送信割り込み
  1529:       trmAUXSendEmpty = true;  //送信バッファ空フラグをセット
  1530:     }
  1531:   }  //trmConnect
  1532: 
  1533:   //trmDisconnect (connection)
  1534:   //  切断する
  1535:   public static void trmDisconnect (Connection connection) {
  1536:     //接続していないときは何もしない
  1537:     if (!connection.connected) {
  1538:       return;
  1539:     }
  1540:     //切断する
  1541:     connection.connected = false;
  1542:     connection.checkBox.setSelected (connection.connected);
  1543:     trmRowToCol[connection.row] = -1;
  1544:     trmColToRow[connection.col] = -1;
  1545:     trmUpdateComponents ();
  1546:     //ポーリングティッカーを終了する
  1547:     if (trmAUXConnection != null) {
  1548:       TickerQueue.tkqRemove (trmAUXTicker);
  1549:       trmAUXConnection = null;
  1550:     }
  1551:     //ポーリングスレッドを停止する
  1552:     connection.polling = false;
  1553:     for (int i = 0; i < 2; i++) {
  1554:       Thread thread = (i == 0 ? connection.row2colThread : connection.col2rowThread);
  1555:       if (thread != null) {
  1556:         connection.row2colThread = null;
  1557:         if (thread.isAlive ()) {  //スレッドがある
  1558:           thread.interrupt ();  //割り込む
  1559:           try {
  1560:             thread.join (100L);  //止まるまで待つ
  1561:           } catch (InterruptedException ie) {
  1562:           }
  1563:         }
  1564:       }
  1565:     }
  1566:     //AUX送信割り込み
  1567:     if (connection.col == 1) {  //?→AUX
  1568:       TickerQueue.tkqRemove (trmAUXSendTicker);  //送信ティッカーを消去
  1569:     }
  1570:     //CTS/DCD/DSR/RI
  1571:     connection.cts = false;
  1572:     connection.dcd = false;
  1573:     connection.dsr = false;
  1574:     connection.ri = false;
  1575:     if (1 <= connection.row && connection.row <= trmNumberOfPorts) {  //SerialPort→?
  1576:       SerialPort port = trmPortArray[connection.row - 1];
  1577:       //シリアルポートデータリスナーを削除する
  1578:       port.removeDataListener ();
  1579:       //シリアルポートを閉じる
  1580:       port.closePort ();
  1581:       System.out.println (Multilingual.mlnJapanese ?
  1582:                           connection.text + " を閉じました" :
  1583:                           connection.text + " closed");
  1584:     } else if (connection.row == trmTcpipRow) {
  1585:       //TCP/IPポートを閉じる
  1586:       trmTcpipClosePort ();
  1587:     } else if (connection.row == trmPppRow) {
  1588:       //PPPサーバーを閉じる
  1589:       trmPppClosePort ();
  1590:     }
  1591:     //キューを消す
  1592:     connection.row2colQueue.clear ();
  1593:     connection.col2rowQueue.clear ();
  1594:     connection.row2colQueue = null;
  1595:     connection.col2rowQueue = null;
  1596:   }  //trmDisconnect
  1597: 
  1598:   //trmSetBaudRate (index)
  1599:   //  ボーレートを設定する
  1600:   public static void trmSetBaudRate (int index) {
  1601:     if (0 <= index && index < trmBaudRateArray.length) {
  1602:       trmBaudRateIndex = index;
  1603:       trmReflectSettings (0);
  1604:     }
  1605:   }  //trmSetBaudRate
  1606: 
  1607:   //trmSetDataBits (index)
  1608:   //  データビットを設定する
  1609:   public static void trmSetDataBits (int index) {
  1610:     if (0 <= index && index < trmDataBitsArray.length) {
  1611:       trmDataBitsIndex = index;
  1612:       trmReflectSettings (0);
  1613:     }
  1614:   }  //trmSetDataBits
  1615: 
  1616:   //trmSetParity (index)
  1617:   //  パリティを設定する
  1618:   public static void trmSetParity (int index) {
  1619:     if (0 <= index && index < trmParityArray.length) {
  1620:       trmParityIndex = index;
  1621:       trmReflectSettings (0);
  1622:     }
  1623:   }  //trmSetParity
  1624: 
  1625:   //trmSetStopBits (index)
  1626:   //  ストップビットを設定する
  1627:   public static void trmSetStopBits (int index) {
  1628:     if (0 <= index && index < trmStopBitsArray.length) {
  1629:       trmStopBitsIndex = index;
  1630:       trmReflectSettings (0);
  1631:     }
  1632:   }  //trmSetStopBits
  1633: 
  1634:   //trmSetFlowControl (index)
  1635:   //  フロー制御を設定する
  1636:   public static void trmSetFlowControl (int index) {
  1637:     if (0 <= index && index < trmFlowControlArray.length) {
  1638:       trmFlowControlIndex = index;
  1639:       trmReflectSettings (0);
  1640:     }
  1641:   }  //trmSetFlowControl
  1642: 
  1643:   //trmReflectSettings (col)
  1644:   //  通信設定をSerialPortに反映させる
  1645:   public static void trmReflectSettings (int col) {
  1646:     int row = trmColToRow[col];
  1647:     SerialPort port = row < 1 || trmNumberOfPorts < row ? null : trmPortArray[row - 1];
  1648:     if (col == 0) {  //Terminal
  1649:       String baudRate = trmBaudRateArray[trmBaudRateIndex];
  1650:       String dataBits = trmDataBitsArray[trmDataBitsIndex];
  1651:       String stopBits = trmStopBitsArray[trmStopBitsIndex];
  1652:       String parity = trmParityArray[trmParityIndex];
  1653:       String flowControl = trmFlowControlArray[trmFlowControlIndex];
  1654:       if (port != null) {
  1655:         port.setComPortParameters (Integer.parseInt (baudRate, 10),
  1656:                                    Integer.parseInt (dataBits.substring (1), 10),
  1657:                                    stopBits.equals ("S1.5") ? SerialPort.ONE_POINT_FIVE_STOP_BITS :
  1658:                                    stopBits.equals ("S2") ? SerialPort.TWO_STOP_BITS : SerialPort.ONE_STOP_BIT,
  1659:                                    parity.equals ("PO") ? SerialPort.ODD_PARITY :
  1660:                                    parity.equals ("PE") ? SerialPort.EVEN_PARITY : SerialPort.NO_PARITY);
  1661:         port.setFlowControl (flowControl.equals ("RTS") ? (SerialPort.FLOW_CONTROL_RTS_ENABLED |
  1662:                                                            SerialPort.FLOW_CONTROL_CTS_ENABLED) :
  1663:                              flowControl.equals ("XON") ? (SerialPort.FLOW_CONTROL_XONXOFF_IN_ENABLED |
  1664:                                                            SerialPort.FLOW_CONTROL_XONXOFF_OUT_ENABLED) :
  1665:                              SerialPort.FLOW_CONTROL_DISABLED);
  1666:       }
  1667:     } else if (col == 1) {  //AUX
  1668:       double rate = Z8530.sccFreq / (double) ((Z8530.scc1aBaudRateGen + 2) << (Z8530.scc1aClockModeShift + 1));
  1669:       double bits = (1.0 +  //start
  1670:                      (Z8530.scc1aRxBits == 0 ? 5.0 :
  1671:                       Z8530.scc1aRxBits == 1 ? 7.0 :
  1672:                       Z8530.scc1aRxBits == 2 ? 6.0 : 8.0) +  //data
  1673:                      ((Z8530.scc1aParity & 1) == 0 ? 0.0 : 1.0) +  //parity
  1674:                      (Z8530.scc1aStop == 0 ? 0.0 :
  1675:                       Z8530.scc1aStop == 1 ? 1.0 :
  1676:                       Z8530.scc1aStop == 2 ? 1.5 : 2.0));  //stop
  1677:       double interval = bits / rate;
  1678:       if (false) {
  1679:         System.out.printf ("%08x baudrate=%.3fbps interval=%.3fus\n", XEiJ.regPC0, rate, interval * 1e+6);
  1680:       }
  1681:       Z8530.scc1aInterval = Math.round (interval * (double) XEiJ.TMR_FREQ);
  1682:       //
  1683:       if (port != null) {
  1684:         port.setComPortParameters ((int) Math.round (rate),
  1685:                                    Z8530.scc1aRxBits == 0b00 ? 5 :
  1686:                                    Z8530.scc1aRxBits == 0b01 ? 7 :
  1687:                                    Z8530.scc1aRxBits == 0b10 ? 6 : 8,
  1688:                                    Z8530.scc1aStop == 0b10 ? SerialPort.ONE_POINT_FIVE_STOP_BITS :
  1689:                                    Z8530.scc1aStop == 0b11 ? SerialPort.TWO_STOP_BITS : SerialPort.ONE_STOP_BIT,
  1690:                                    Z8530.scc1aParity == 0b01 ? SerialPort.ODD_PARITY :
  1691:                                    Z8530.scc1aParity == 0b11 ? SerialPort.EVEN_PARITY : SerialPort.NO_PARITY);
  1692:       }
  1693:       //
  1694:       //フロー制御をSerialPortに反映させる
  1695:       TickerQueue.tkqAdd (trmAUXFlowControlTicker, XEiJ.mpuClockTime + XEiJ.TMR_FREQ * 500 / 1000000);  //500us後
  1696:     } else {  //AUX2~
  1697:       //!!! 未対応
  1698:     }
  1699:   }  //trmReflectSettings
  1700: 
  1701: 
  1702:   //trmInit ()
  1703:   //  ターミナルウインドウを初期化する
  1704:   public static void trmInit () {
  1705:     trmFrame = null;
  1706:     trmBoard = null;
  1707:     trmPopupMenu = null;
  1708:     trmPopupCutMenuItem = null;
  1709:     trmPopupCopyMenuItem = null;
  1710:     trmPopupPasteMenuItem = null;
  1711:     trmPopupSelectAllMenuItem = null;
  1712:     trmPopupSendCtrlCMenuItem = null;
  1713:     trmOutputBuilder = new StringBuilder ();
  1714:     trmOutputEnd = 0;
  1715:     trmOutputSJIS1 = 0;
  1716:     //
  1717:     trmInitConnection ();
  1718:     trmReset ();
  1719:   }
  1720: 
  1721:   public static void trmReset () {
  1722:     trmRSDRV202Head = 0;
  1723:     trmTMSIO031Head = 0;
  1724:     trmBSIO021Head = 0;
  1725:     trmAUXFlowControlXON = false;
  1726:     trmAUXFlowControlRTS = false;
  1727:   }
  1728: 
  1729:   //trmTini ()
  1730:   //  後始末
  1731:   public static void trmTini () {
  1732:     trmTiniConnection ();
  1733:   }  //trmTini
  1734: 
  1735:   //trmMake ()
  1736:   //  ターミナルウインドウを作る
  1737:   //  ここでは開かない
  1738:   public static void trmMake () {
  1739: 
  1740:     //テキストエリア
  1741:     trmBoard = ComponentFactory.createScrollTextArea (
  1742:       trmOutputBuilder.toString (),  //作る前に出力されていた文字列を設定する
  1743:       650, 350,
  1744:       true);
  1745:     trmOutputBuilder = null;  //これはもういらない
  1746:     trmBoard.setUnderlineCursorOn (true);
  1747:     trmBoard.setLineWrap (true);  //行を折り返す
  1748:     trmBoard.addDocumentListener (new DocumentListener () {
  1749:       @Override public void changedUpdate (DocumentEvent de) {
  1750:       }
  1751:       @Override public void insertUpdate (DocumentEvent de) {
  1752:         if (de.getOffset () < trmOutputEnd) {
  1753:           trmOutputEnd += de.getLength ();  //出力された文字列の末尾を調整する
  1754:         }
  1755:       }
  1756:       @Override public void removeUpdate (DocumentEvent de) {
  1757:         if (de.getOffset () < trmOutputEnd) {
  1758:           trmOutputEnd -= Math.min (de.getLength (), trmOutputEnd - de.getOffset ());  //出力された文字列の末尾を調整する
  1759:         }
  1760:       }
  1761:     });
  1762:     trmBoard.addKeyListener (new KeyAdapter () {
  1763:       @Override public void keyPressed (KeyEvent ke) {
  1764:         int keyCode = ke.getKeyCode ();
  1765:         if (keyCode == KeyEvent.VK_ENTER) {  //ENTERキーが押された
  1766:           ke.consume ();  //ENTERキーをキャンセルする
  1767:           trmEnter ();  //ENTERキーを処理する
  1768:         } else if (keyCode == KeyEvent.VK_PAUSE) {  //Pauseキーが押された
  1769:           ke.consume ();  //Pauseキーをキャンセルする
  1770:           trmSendString ("\u0003");  //^Cを送信する
  1771:         }
  1772:       }
  1773:     });
  1774: 
  1775:     //ポップアップメニュー
  1776:     ActionListener popupActionListener = new ActionListener () {
  1777:       @Override public void actionPerformed (ActionEvent ae) {
  1778:         switch (ae.getActionCommand ()) {
  1779:         case "Cut":  //切り取り
  1780:           trmCut ();
  1781:           break;
  1782:         case "Copy":  //コピー
  1783:           trmCopy ();
  1784:           break;
  1785:         case "Paste":  //貼り付け
  1786:           trmPaste ();
  1787:           break;
  1788:         case "Select All":  //すべて選択
  1789:           trmSelectAll ();
  1790:           break;
  1791:         case "Send ^C":  //^C 送信
  1792:           trmSendString ("\u0003");  //^Cを送信する
  1793:           break;
  1794:         }
  1795:       }
  1796:     };
  1797:     trmPopupMenu = ComponentFactory.createPopupMenu (
  1798:       trmPopupCutMenuItem = Multilingual.mlnText (
  1799:         ComponentFactory.createMenuItem ("Cut", 'T', popupActionListener),
  1800:         "ja", "切り取り"),
  1801:       trmPopupCopyMenuItem = Multilingual.mlnText (
  1802:         ComponentFactory.createMenuItem ("Copy", 'C', popupActionListener),
  1803:         "ja", "コピー"),
  1804:       trmPopupPasteMenuItem = Multilingual.mlnText (
  1805:         ComponentFactory.createMenuItem ("Paste", 'P', popupActionListener),
  1806:         "ja", "貼り付け"),
  1807:       ComponentFactory.createHorizontalSeparator (),
  1808:       trmPopupSelectAllMenuItem = Multilingual.mlnText (
  1809:         ComponentFactory.createMenuItem ("Select All", 'A', popupActionListener),
  1810:         "ja", "すべて選択"),
  1811:       ComponentFactory.createHorizontalSeparator (),
  1812:       trmPopupSendCtrlCMenuItem = Multilingual.mlnText (
  1813:         ComponentFactory.createMenuItem ("Send ^C", popupActionListener),
  1814:         "ja", "^C 送信")
  1815:       );
  1816:     trmBoard.addMouseListener (new MouseAdapter () {
  1817:       @Override public void mousePressed (MouseEvent me) {
  1818:         trmShowPopup (me);
  1819:       }
  1820:       @Override public void mouseReleased (MouseEvent me) {
  1821:         trmShowPopup (me);
  1822:       }
  1823:     });
  1824: 
  1825:     //アクションリスナー
  1826:     ActionListener listener = new ActionListener () {
  1827:       @Override public void actionPerformed (ActionEvent ae) {
  1828:         Object source = ae.getSource ();
  1829:         String command = ae.getActionCommand ();
  1830:         switch (command) {
  1831:         case "Refresh":
  1832:           trmUpdateConnection ();
  1833:           break;
  1834:         case "Baud rate":
  1835:           trmSetBaudRate (((JComboBox) source).getSelectedIndex ());
  1836:           break;
  1837:         case "Data bits":
  1838:           trmSetDataBits (((JComboBox) source).getSelectedIndex ());
  1839:           break;
  1840:         case "Parity":
  1841:           trmSetParity (((JComboBox) source).getSelectedIndex ());
  1842:           break;
  1843:         case "Stop bits":
  1844:           trmSetStopBits (((JComboBox) source).getSelectedIndex ());
  1845:           break;
  1846:         case "Flow control":
  1847:           trmSetFlowControl (((JComboBox) source).getSelectedIndex ());
  1848:           break;
  1849:         case "Send file":
  1850:           trmSendFile ();
  1851:           break;
  1852:         case "Set clock":
  1853:           trmSetClock ();
  1854:           break;
  1855:         default:
  1856:           System.out.println ("unknown action command " + command);
  1857:         }
  1858:       }
  1859:     };
  1860: 
  1861:     //ボタンとコンボボックス
  1862:     trmRefreshButton =
  1863:       ComponentFactory.setEnabled (
  1864:         Multilingual.mlnText (
  1865:           ComponentFactory.createButton ("Refresh", listener),
  1866:           "ja", "更新"),
  1867:         trmRefreshEnabled);
  1868:     trmBaudRateComboBox =
  1869:       ComponentFactory.setEnabled (
  1870:         Multilingual.mlnToolTipText (
  1871:           ComponentFactory.createComboBox (
  1872:             trmBaudRateIndex, "Baud rate", listener, trmBaudRateArray),
  1873:           "ja", "ボーレート"),
  1874:         trmSettingsEnabled);
  1875:     trmDataBitsComboBox =
  1876:       ComponentFactory.setEnabled (
  1877:         Multilingual.mlnToolTipText (
  1878:           ComponentFactory.createComboBox (
  1879:             trmDataBitsIndex, "Data bits", listener, trmDataBitsArray),
  1880:           "ja", "データビット"),
  1881:         trmSettingsEnabled);
  1882:     trmParityComboBox =
  1883:       ComponentFactory.setEnabled (
  1884:         Multilingual.mlnToolTipText (
  1885:           ComponentFactory.createComboBox (
  1886:             trmParityIndex, "Parity", listener, trmParityArray),
  1887:           "ja", "パリティ"),
  1888:         trmSettingsEnabled);
  1889:     trmStopBitsComboBox =
  1890:       ComponentFactory.setEnabled (
  1891:         Multilingual.mlnToolTipText (
  1892:           ComponentFactory.createComboBox (
  1893:             trmStopBitsIndex, "Stop bits", listener, trmStopBitsArray),
  1894:           "ja", "ストップビット"),
  1895:         trmSettingsEnabled);
  1896:     trmFlowControlComboBox =
  1897:       ComponentFactory.setEnabled (
  1898:         Multilingual.mlnToolTipText (
  1899:           ComponentFactory.createComboBox (
  1900:             trmFlowControlIndex, "Flow control", listener, trmFlowControlArray),
  1901:           "ja", "フロー制御"),
  1902:         trmSettingsEnabled);
  1903:     trmSendButton =
  1904:       ComponentFactory.setEnabled (
  1905:         Multilingual.mlnText (
  1906:           ComponentFactory.createButton ("Send file", listener),
  1907:           "ja", "ファイル送信"),
  1908:         trmSendEnabled);
  1909:     trmSetClockButton =
  1910:       ComponentFactory.setEnabled (
  1911:         Multilingual.mlnText (
  1912:           ComponentFactory.createButton ("Set clock", listener),
  1913:           "ja", "時計合わせ"),
  1914:         trmSendEnabled);
  1915: 
  1916:     //ウインドウ
  1917:     //  ┏━━━━━━━━━━━━━━━━┓
  1918:     //  ┃┌接続────────────┐┃
  1919:     //  ┃│                            │┃
  1920:     //  ┃│                            │┃NORTH
  1921:     //  ┃│                            │┃
  1922:     //  ┃└──────────────┘┃
  1923:     //  ┃                                ┃CENTER
  1924:     //  ┣━━━━━━━━━━━━━━━━┫
  1925:     //  ┃┌通信設定┐┌コマンド┐        ┃
  1926:     //  ┃│        ││        │        ┃NORTH
  1927:     //  ┃└────┘└────┘        ┃
  1928:     //  ┃┌ターミナル─────────┐┃
  1929:     //  ┃│                            │┃
  1930:     //  ┃│                            │┃CENTER
  1931:     //  ┃│                            │┃
  1932:     //  ┃└──────────────┘┃
  1933:     //  ┗━━━━━━━━━━━━━━━━┛
  1934:     //接続
  1935:     Box connectionBox = Multilingual.mlnTitledBorder (
  1936:       ComponentFactory.setTitledLineBorder (
  1937:         ComponentFactory.createHorizontalBox (
  1938:           Box.createHorizontalStrut (5),
  1939:           ComponentFactory.createVerticalBox (
  1940:             Box.createVerticalGlue (),
  1941:             trmRefreshButton,
  1942:             Box.createVerticalGlue ()
  1943:             ),
  1944:           Box.createHorizontalStrut (10),
  1945:           trmConnectionBox,
  1946:           Box.createHorizontalGlue ()
  1947:           ),
  1948:         "Connection"),
  1949:       "ja", "接続");
  1950:     //通信設定
  1951:     Box settingsBox = Multilingual.mlnTitledBorder (
  1952:       ComponentFactory.setTitledLineBorder (
  1953:         ComponentFactory.createHorizontalBox (
  1954:           Box.createHorizontalStrut (5),
  1955:           trmBaudRateComboBox,
  1956:           trmDataBitsComboBox,
  1957:           trmParityComboBox,
  1958:           trmStopBitsComboBox,
  1959:           trmFlowControlComboBox,
  1960:           Box.createHorizontalStrut (5)
  1961:           ),  //createHorizontalBox
  1962:         "Communication Settings"),  //setTitledLineBorder
  1963:       "ja", "通信設定");  //mlnTitledBorder
  1964:     //コマンド
  1965:     Box commandxBox = Multilingual.mlnTitledBorder (
  1966:       ComponentFactory.setTitledLineBorder (
  1967:         ComponentFactory.createHorizontalBox (
  1968:           Box.createHorizontalStrut (5),
  1969:           trmSendButton,
  1970:           trmSetClockButton,
  1971:           Box.createHorizontalStrut (5)
  1972:           ),  //createHorizontalBox
  1973:         "Command"),  //setTitledLineBorder
  1974:       "ja", "コマンド");  //mlnTitledBorder
  1975:     //ウインドウ
  1976:     trmFrame = Multilingual.mlnTitle (
  1977:       ComponentFactory.createRestorableSubFrame (
  1978:         Settings.SGS_TRM_FRAME_KEY,
  1979:         "RS-232C and terminal",
  1980:         null,
  1981:         ComponentFactory.createVerticalSplitPane (
  1982:           ComponentFactory.createScrollPane (
  1983:             ComponentFactory.createBorderPanel (
  1984:               ComponentFactory.createHorizontalBox (
  1985:                 Box.createHorizontalGlue ()
  1986:                 ),  //CENTER
  1987:               connectionBox  //NORTH
  1988:               )  //createBorderPanel
  1989:             ),  //createScrollPane
  1990:           ComponentFactory.createBorderPanel (
  1991:             Multilingual.mlnTitledBorder (
  1992:               ComponentFactory.setTitledLineBorder (
  1993:                 trmBoard,
  1994:                 "Terminal"),
  1995:               "ja", "ターミナル"),  //CENTER
  1996:             ComponentFactory.createHorizontalBox (
  1997:               settingsBox,
  1998:               commandxBox,
  1999:               Box.createHorizontalGlue ()
  2000:               )  //createHorizontalBox  NORTH
  2001:             )  //createBorderPanel
  2002:           )  //createVerticalSplitPane
  2003:         ),  //createRestorableSubFrame
  2004:       "ja", "RS-232C とターミナル");
  2005: 
  2006:   }  //trmMake()
  2007: 
  2008:   //trmShowPopup (me)
  2009:   //  ポップアップメニューを表示する
  2010:   //  テキストエリアのマウスリスナーが呼び出す
  2011:   public static void trmShowPopup (MouseEvent me) {
  2012:     if (me.isPopupTrigger ()) {
  2013:       //選択範囲があれば切り取りとコピーが有効
  2014:       boolean enableCutAndCopy = XEiJ.clpClipboard != null && trmBoard.getSelectionStart () != trmBoard.getSelectionEnd ();
  2015:       ComponentFactory.setEnabled (trmPopupCutMenuItem, enableCutAndCopy);
  2016:       ComponentFactory.setEnabled (trmPopupCopyMenuItem, enableCutAndCopy);
  2017:       //クリップボードに文字列があれば貼り付けが有効
  2018:       ComponentFactory.setEnabled (trmPopupPasteMenuItem, XEiJ.clpClipboard != null && XEiJ.clpClipboard.isDataFlavorAvailable (DataFlavor.stringFlavor));
  2019:       //クリップボードがあればすべて選択が有効
  2020:       ComponentFactory.setEnabled (trmPopupSelectAllMenuItem, XEiJ.clpClipboard != null);
  2021:       //Terminalが接続していれば^C送信が有効
  2022:       ComponentFactory.setEnabled (trmPopupSendCtrlCMenuItem,
  2023:                                    trmRowToCol[0] == 1 ||  //Terminal→AUX
  2024:                                    0 < trmColToRow[0]);  //Terminal→SerialPort
  2025:       //ポップアップメニューを表示する
  2026:       trmPopupMenu.show (me.getComponent (), me.getX (), me.getY ());
  2027:     }
  2028:   }  //trmShowPopup(MouseEvent)
  2029: 
  2030:   //trmCut ()
  2031:   //  切り取り
  2032:   public static void trmCut () {
  2033:     if (XEiJ.clpClipboard != null) {
  2034:       //選択範囲の文字列をコピーする
  2035:       XEiJ.clpClipboardString = trmBoard.getSelectedText ();
  2036:       try {
  2037:         XEiJ.clpClipboard.setContents (XEiJ.clpStringContents, XEiJ.clpClipboardOwner);
  2038:         XEiJ.clpIsClipboardOwner = true;  //自分がコピーした
  2039:       } catch (Exception e) {
  2040:         return;
  2041:       }
  2042:       //選択範囲の文字列を削除する
  2043:       trmBoard.replaceRange ("", trmBoard.getSelectionStart (), trmBoard.getSelectionEnd ());
  2044:     }
  2045:   }  //trmCut()
  2046: 
  2047:   //trmCopy ()
  2048:   //  コピー
  2049:   public static void trmCopy () {
  2050:     if (XEiJ.clpClipboard != null) {
  2051:       //選択範囲の文字列をコピーする
  2052:       String selectedText = trmBoard.getSelectedText ();
  2053:       if (selectedText != null) {
  2054:         XEiJ.clpClipboardString = selectedText;
  2055:         try {
  2056:           XEiJ.clpClipboard.setContents (XEiJ.clpStringContents, XEiJ.clpClipboardOwner);
  2057:           XEiJ.clpIsClipboardOwner = true;  //自分がコピーした
  2058:         } catch (Exception e) {
  2059:           return;
  2060:         }
  2061:       }
  2062:     }
  2063:   }  //trmCopy()
  2064: 
  2065:   //trmPaste ()
  2066:   //  貼り付け
  2067:   public static void trmPaste () {
  2068:     if (XEiJ.clpClipboard != null) {
  2069:       //クリップボードから文字列を取り出す
  2070:       String string = null;
  2071:       try {
  2072:         string = (String) XEiJ.clpClipboard.getData (DataFlavor.stringFlavor);
  2073:       } catch (Exception e) {
  2074:         return;
  2075:       }
  2076:       //選択範囲の文字列を置換する
  2077:       trmBoard.replaceRange (string, trmBoard.getSelectionStart (), trmBoard.getSelectionEnd ());
  2078:     }
  2079:   }  //trmPaste()
  2080: 
  2081:   //trmSelectAll ()
  2082:   //  すべて選択
  2083:   public static void trmSelectAll () {
  2084:     if (XEiJ.clpClipboard != null) {
  2085:       //すべて選択する
  2086:       trmBoard.selectAll ();
  2087:     }
  2088:   }  //trmSelectAll()
  2089: 
  2090:   //trmStart ()
  2091:   public static void trmStart () {
  2092:     if (RestorableFrame.rfmGetOpened (Settings.SGS_TRM_FRAME_KEY)) {
  2093:       trmOpen ();
  2094:     }
  2095:   }  //trmStart()
  2096: 
  2097:   //trmOpen ()
  2098:   //  ターミナルウインドウを開く
  2099:   public static void trmOpen () {
  2100:     if (trmFrame == null) {
  2101:       trmMake ();
  2102:     }
  2103:     XEiJ.pnlExitFullScreen (false);
  2104:     trmFrame.setVisible (true);
  2105:   }  //trmOpen()
  2106: 
  2107:   //trmPrintSJIS (d)
  2108:   //  SJISで1バイト追加する
  2109:   //  SJISの1バイト目は繰り越して2バイト目が来たときに表示する
  2110:   public static void trmPrintSJIS (int d) {
  2111:     d &= 0xff;
  2112:     if (trmOutputSJIS1 != 0) {  //前回SJISの1バイト目を繰り越した
  2113:       if (0x40 <= d && d != 0x7f && d <= 0xfc) {  //SJISの2バイト目が来た
  2114:         int c = CharacterCode.chrSJISToChar[trmOutputSJIS1 << 8 | d];  //2バイトで変換する
  2115:         if (c != 0) {  //2バイトで変換できた
  2116:           trmPrintChar (c);  //1文字表示する
  2117:         } else {  //2バイトで変換できなかった
  2118:           //2バイトで変換できなかったがSJISの1バイト目と2バイト目であることはわかっているので2バイト分のコードを表示する
  2119:           trmPrintChar ('[');
  2120:           trmPrintChar (XEiJ.fmtHexc (trmOutputSJIS1 >> 4));
  2121:           trmPrintChar (XEiJ.fmtHexc (trmOutputSJIS1 & 15));
  2122:           trmPrintChar (XEiJ.fmtHexc (d >> 4));
  2123:           trmPrintChar (XEiJ.fmtHexc (d & 15));
  2124:           trmPrintChar (']');
  2125:         }
  2126:         trmOutputSJIS1 = 0;
  2127:         return;
  2128:       }
  2129:       //SJISの2バイト目が来なかった
  2130:       //前回繰り越したSJISの1バイト目を吐き出す
  2131:       trmPrintChar ('[');
  2132:       trmPrintChar (XEiJ.fmtHexc (trmOutputSJIS1 >> 4));
  2133:       trmPrintChar (XEiJ.fmtHexc (trmOutputSJIS1 & 15));
  2134:       trmPrintChar (']');
  2135:       trmOutputSJIS1 = 0;
  2136:     }
  2137:     if (0x81 <= d && d <= 0x9f || 0xe0 <= d && d <= 0xef) {  //SJISの1バイト目が来た
  2138:       trmOutputSJIS1 = d;  //次回に繰り越す
  2139:     } else {  //SJISの1バイト目が来なかった
  2140:       int c = CharacterCode.chrSJISToChar[d];  //1バイトで変換する
  2141:       if (c != 0) {  //1バイトで変換できた
  2142:         trmPrintChar (c);  //1文字表示する
  2143:       } else {  //1バイトで変換できなかった
  2144:         //1バイトで変換できなかったがSJISの1バイト目でないことはわかっているので1バイト分のコードを表示する
  2145:         trmPrintChar ('[');
  2146:         trmPrintChar (XEiJ.fmtHexc (d >> 4));
  2147:         trmPrintChar (XEiJ.fmtHexc (d & 15));
  2148:         trmPrintChar (']');
  2149:       }
  2150:     }
  2151:   }  //trmPrintSJIS(int)
  2152: 
  2153:   //trmPrintChar (c)
  2154:   //  末尾に1文字追加する
  2155:   public static void trmPrintChar (int c) {
  2156:     if (c == 0x08) {  //バックスペース
  2157:       if (trmOutputEnd > 0) {
  2158:         if (trmBoard != null) {
  2159:           trmBoard.replaceRange ("", trmOutputEnd - 1, trmOutputEnd);  //1文字削除
  2160:           trmOutputEnd--;
  2161:           trmBoard.setCaretPosition (trmOutputEnd);
  2162:         } else {
  2163:           trmOutputBuilder.delete (trmOutputEnd - 1, trmOutputEnd);  //1文字削除
  2164:           trmOutputEnd--;
  2165:         }
  2166:       }
  2167:     } else if (c >= 0x20 && c != 0x7f || c == 0x09 || c == 0x0a) {  //タブと改行以外の制御コードを除く
  2168:       if (trmBoard != null) {
  2169:         trmBoard.insert (String.valueOf ((char) c), trmOutputEnd);  //1文字追加
  2170:         trmOutputEnd++;
  2171:         if (trmOutputEnd >= TRM_CUT_OUTPUT_LENGTH) {
  2172:           trmBoard.replaceRange ("", 0, trmOutputEnd - TRM_MAX_OUTPUT_LENGTH);  //先頭を削って短くする
  2173:           trmOutputEnd = TRM_MAX_OUTPUT_LENGTH;
  2174:         }
  2175:         trmBoard.setCaretPosition (trmOutputEnd);
  2176:       } else {
  2177:         trmOutputBuilder.append ((char) c);  //1文字追加
  2178:         trmOutputEnd++;
  2179:         if (trmOutputEnd >= TRM_CUT_OUTPUT_LENGTH) {
  2180:           trmOutputBuilder.delete (0, trmOutputEnd - TRM_MAX_OUTPUT_LENGTH);  //先頭を削って短くする
  2181:           trmOutputEnd = TRM_MAX_OUTPUT_LENGTH;
  2182:         }
  2183:       }
  2184:     }
  2185:   }  //trmPrintChar(int)
  2186: 
  2187:   //trmPrint (s)
  2188:   //  末尾に文字列を追加する
  2189:   //  情報表示用
  2190:   //  制御コードを処理しないのでタブと改行以外の制御コードを含めないこと
  2191:   public static void trmPrint (String s) {
  2192:     if (s == null) {
  2193:       return;
  2194:     }
  2195:     if (trmFrame != null) {
  2196:       trmBoard.insert (s, trmOutputEnd);  //文字列追加
  2197:       trmOutputEnd += s.length ();
  2198:       if (trmOutputEnd >= TRM_CUT_OUTPUT_LENGTH) {
  2199:         trmBoard.replaceRange ("", 0, trmOutputEnd - TRM_MAX_OUTPUT_LENGTH);  //先頭を削って短くする
  2200:         trmOutputEnd = TRM_MAX_OUTPUT_LENGTH;
  2201:       }
  2202:       trmBoard.setCaretPosition (trmOutputEnd);
  2203:     } else {
  2204:       trmOutputBuilder.append (s);  //文字列追加
  2205:       trmOutputEnd += s.length ();
  2206:       if (trmOutputEnd >= TRM_CUT_OUTPUT_LENGTH) {
  2207:         trmOutputBuilder.delete (0, trmOutputEnd - TRM_MAX_OUTPUT_LENGTH);  //先頭を削って短くする
  2208:         trmOutputEnd = TRM_MAX_OUTPUT_LENGTH;
  2209:       }
  2210:     }
  2211:   }  //trmPrint(String)
  2212: 
  2213:   //trmPrintln (s)
  2214:   //  末尾に文字列と改行を追加する
  2215:   //  情報表示用
  2216:   //  制御コードを処理しないのでタブと改行以外の制御コードを含めないこと
  2217:   public static void trmPrintln (String s) {
  2218:     trmPrint (s);
  2219:     trmPrintChar ('\n');
  2220:   }  //trmPrintln(String)
  2221: 
  2222:   //trmEnter ()
  2223:   //  ENTERキーを処理する
  2224:   public static void trmEnter () {
  2225:     String text = trmBoard.getText ();  //テキスト全体
  2226:     int length = text.length ();  //テキスト全体の長さ
  2227:     int outputLineStart = text.lastIndexOf ('\n', trmOutputEnd - 1) + 1;  //出力の末尾の行の先頭。プロンプトの先頭
  2228:     int caretLineStart = text.lastIndexOf ('\n', trmBoard.getCaretPosition () - 1) + 1;  //キャレットがある行の先頭
  2229:     if (outputLineStart <= caretLineStart) {  //出力の末尾の行の先頭以降でENTERキーが押された
  2230:       trmBoard.replaceRange ("", trmOutputEnd, length);  //入力された文字列を一旦削除する
  2231:       trmSendString (text.substring (trmOutputEnd, length) + "\r");  //入力された文字列を送信する
  2232:     } else if (outputLineStart < trmOutputEnd) {  //出力の末尾の行の先頭よりも手前でENTERキーが押されて、出力の末尾の行にプロンプトがあるとき
  2233:       String prompt = text.substring (outputLineStart, trmOutputEnd);  //出力の末尾の行のプロンプト
  2234:       int caretLineEnd = text.indexOf ('\n', caretLineStart);  //キャレットがある行の末尾
  2235:       if (caretLineEnd == -1) {
  2236:         caretLineEnd = length;
  2237:       }
  2238:       String line = text.substring (caretLineStart, caretLineEnd);  //キャレットがある行
  2239:       int start = line.indexOf (prompt);  //キャレットがある行のプロンプトの先頭
  2240:       if (start >= 0) {  //キャレットがある行にプロンプトがあるとき
  2241:         trmOutputEnd = length;  //入力された文字列を無効化する
  2242:         if (text.charAt (trmOutputEnd - 1) != '\n') {  //改行で終わっていないとき
  2243:           trmBoard.insert ("\n", trmOutputEnd);  //末尾にENTERを追加する
  2244:           trmOutputEnd++;
  2245:           if (trmOutputEnd >= TRM_CUT_OUTPUT_LENGTH) {
  2246:             trmBoard.replaceRange ("", 0, trmOutputEnd - TRM_MAX_OUTPUT_LENGTH);  //先頭を削って短くする
  2247:             trmOutputEnd = TRM_MAX_OUTPUT_LENGTH;
  2248:           }
  2249:         }
  2250:         trmBoard.setCaretPosition (trmOutputEnd);
  2251:         trmSendString (line.substring (start + prompt.length ()) + "\r");  //プロンプトの後ろから行の末尾までを送信する
  2252:       }
  2253:     }
  2254:   }  //trmEnter()
  2255: 
  2256:   //trmSendString (s)
  2257:   //  文字列をSJISに変換してAUXまたはSerialPortへ送信する
  2258:   public static void trmSendString (String s) {
  2259:     int l = s.length ();
  2260:     if (l == 0) {
  2261:       return;
  2262:     }
  2263:     byte[] b = new byte[l * 2];
  2264:     int k = 0;
  2265:     for (int i = 0; i < l; i++) {
  2266:       int c = CharacterCode.chrCharToSJIS[s.charAt (i)];
  2267:       if (0x00ff < c) {
  2268:         b[k++] = (byte) (c >> 8);
  2269:       }
  2270:       b[k++] = (byte) c;
  2271:     }
  2272:     if (trmRowToCol[0] == 1) {  //Terminal→AUX。row2col
  2273:       int row = 0;
  2274:       int col = 1;
  2275:       trmConnectionArray[trmCols * row + col - 1].row2colQueue.write (b, 0, k);
  2276:     } else if (0 < trmColToRow[0]) {  //Terminal→SerialPort。col2row
  2277:       int row = trmColToRow[0];
  2278:       int col = 0;
  2279:       trmConnectionArray[trmCols * row + col - 1].col2rowQueue.write (b, 0, k);
  2280:     }
  2281:   }  //trmSendString
  2282: 
  2283: 
  2284: 
  2285:   //trmSetClock ()
  2286:   //  時計合わせボタンが押された
  2287:   public static void trmSetClock () {
  2288:     String[] options = new String[] {
  2289:       Multilingual.mlnJapanese ? "送信" : "Send",
  2290:       Multilingual.mlnJapanese ? "キャンセル" : "Cancel",
  2291:     };
  2292:     if (JOptionPane.showOptionDialog (
  2293:       trmFrame,
  2294:       Multilingual.mlnJapanese ?
  2295:       "DATE と TIME を送信して時計を大まかに合わせます。\n" +
  2296:       "COMMAND.X を CTTY AUX で準備してください。":
  2297:       "Sending DATE and TIME to set clock roughly.\n" +
  2298:       "Prepare COMMAND.X with CTTY AUX.",
  2299:       Multilingual.mlnJapanese ? "時計合わせ" : "Set clock",
  2300:       JOptionPane.OK_CANCEL_OPTION,
  2301:       JOptionPane.PLAIN_MESSAGE,
  2302:       null,
  2303:       options,
  2304:       options[0]) == 0) {
  2305:       trmScheduler.schedule (
  2306:         () -> {
  2307:           long dttm = DnT.dntDttmCmil (System.currentTimeMillis () + RP5C15.rtcCmilGap);
  2308:           trmSendString (
  2309:             String.format ("date %04d-%02d-%02d\rtime %02d:%02d:%02d\r",
  2310:                            DnT.dntYearDttm (dttm),
  2311:                            DnT.dntMontDttm (dttm),
  2312:                            DnT.dntMdayDttm (dttm),
  2313:                            DnT.dntHourDttm (dttm),
  2314:                            DnT.dntMinuDttm (dttm),
  2315:                            DnT.dntSecoDttm (dttm))
  2316:             );
  2317:         },
  2318:         0L, TimeUnit.MILLISECONDS);  //schedule
  2319:     }  //if
  2320:   }  //trmSetClock
  2321: 
  2322: 
  2323: 
  2324:   //trmSendFile ()
  2325:   //  ファイル送信ボタンが押された
  2326:   public static void trmSendFile () {
  2327:     if (trmSendDialog == null) {
  2328:       ActionListener listener = new ActionListener () {
  2329:         @Override public void actionPerformed (ActionEvent ae) {
  2330:           switch (ae.getActionCommand ()) {
  2331:           case JFileChooser.APPROVE_SELECTION:
  2332:           case "Send":  //送信
  2333:             trmSendDialog.setVisible (false);
  2334:             File file = trmSendFileChooser.getSelectedFile ();
  2335:             if (file != null) {
  2336:               trmSendThread = new SendThread (file);
  2337:               trmSendThread.start ();
  2338:             }
  2339:             break;
  2340:           case JFileChooser.CANCEL_SELECTION:
  2341:           case "Cancel":  //キャンセル
  2342:             trmSendDialog.setVisible (false);
  2343:             break;
  2344:           }  //switch
  2345:         }  //actionPerformed
  2346:       };  //ActionListener
  2347:       trmSendFileChooser = new JFileChooser (new File ("a.txt"));
  2348:       trmSendFileChooser.setMultiSelectionEnabled (false);  //複数選択不可
  2349:       trmSendFileChooser.setControlButtonsAreShown (false);  //デフォルトのボタンを消す
  2350:       trmSendFileChooser.addActionListener (listener);
  2351:       trmSendDialog =
  2352:         Multilingual.mlnTitle (
  2353:           ComponentFactory.createModalDialog (
  2354:             trmFrame,
  2355:             "Send file",
  2356:             ComponentFactory.createBorderPanel (
  2357:               0, 0,
  2358:               ComponentFactory.createVerticalBox (
  2359:                 trmSendFileChooser,
  2360:                 ComponentFactory.createHorizontalBox (
  2361:                   Box.createHorizontalStrut (12),
  2362:                   Box.createHorizontalGlue (),
  2363:                   Multilingual.mlnText (
  2364:                     ComponentFactory.createLabel (
  2365:                       "Prepare COMMAND.X with CTTY AUX"),
  2366:                     "ja", "COMMAND.X を CTTY AUX で準備して"),
  2367:                   Box.createHorizontalStrut (12),
  2368:                   Multilingual.mlnText (
  2369:                     ComponentFactory.createButton ("Send", KeyEvent.VK_S, listener),
  2370:                     "ja", "送信"),
  2371:                   Box.createHorizontalStrut (12),
  2372:                   Multilingual.mlnText (
  2373:                     ComponentFactory.createButton ("Cancel", KeyEvent.VK_C, listener),
  2374:                     "ja", "キャンセル"),
  2375:                   Box.createHorizontalStrut (12)
  2376:                   ),  //createHorizontalBox
  2377:                 Box.createVerticalStrut (12)
  2378:                 )  //createVerticalBox
  2379:               )  //createBorderPanel
  2380:             ),  //createModalDialog
  2381:           "ja", "ファイル送信");  //mlnTitle
  2382:     }  //if
  2383:     XEiJ.pnlExitFullScreen (true);
  2384:     trmSendDialog.setVisible (true);
  2385:   }  //trmSendFile
  2386: 
  2387:   //class SendThread
  2388:   //  送信スレッド
  2389:   //  ファイルをa.rに変換する
  2390:   //    a.rは実行ファイルだが終端の$1A以外に$00~$1Fを含まない
  2391:   //  a.rをcopy aux a.rとa.rで挟んで送信する
  2392:   //  X68000でa.rが作られて実行されてファイルが復元される
  2393:   static class SendThread extends Thread {
  2394:     File file;
  2395:     SendThread (File file) {
  2396:       this.file = file;
  2397:     }
  2398:     @Override public void run () {
  2399:       trmSendProcess (file);
  2400:     }
  2401:   }  //class SendThread
  2402: 
  2403:   //buf = load (file)
  2404:   //  ファイルを読み込む
  2405:   static byte[] load (File file) {
  2406:     if (!file.isFile ()) {
  2407:       return null;
  2408:     }
  2409:     int len = (int) file.length ();
  2410:     if (len == 0) {
  2411:       return null;
  2412:     }
  2413:     byte[] buf = new byte[len];
  2414:     try (BufferedInputStream bis = new BufferedInputStream (new FileInputStream (file))) {
  2415:       if (bis.read (buf) != len) {
  2416:         return null;
  2417:       }
  2418:     } catch (IOException ioe) {
  2419:       return null;
  2420:     }
  2421:     return buf;
  2422:   }  //load
  2423: 
  2424:   //class BitWriter
  2425:   //  ビットライタ
  2426:   static class BitWriter {
  2427: 
  2428:     byte[] buffer;  //バッファ
  2429:     int byteIndex;  //バイト書き込み位置=現在の長さ
  2430:     int bitIndex;  //ビット書き込み位置
  2431:     int bitCount;  //[bitIndex]の残りビット数。上位から書く。下位bitCountビットが空いている
  2432: 
  2433:     //new BitWriter ()
  2434:     //  コンストラクタ
  2435:     BitWriter () {
  2436:       buffer = new byte[16];
  2437:       byteIndex = 0;
  2438:       bitIndex = -1;
  2439:       bitCount = 0;
  2440:     }  //BitWriter
  2441: 
  2442:     //writeByte (data)
  2443:     //  バイト書き込み
  2444:     //  data  データ。下位8ビットだけ使う
  2445:     void writeByte (int data) {
  2446:       if (byteIndex == buffer.length) {  //bufferが満杯なので長さを2倍にする
  2447:         byte[] temporary = new byte[byteIndex * 2];
  2448:         System.arraycopy (buffer, 0,  //from
  2449:                           temporary, 0,  //to
  2450:                           byteIndex);  //length
  2451:         buffer = temporary;
  2452:       }
  2453:       buffer[byteIndex++] = (byte) data;
  2454:     }  //writeByte
  2455: 
  2456:     //writeBits (width, data)
  2457:     //  ビット書き込み
  2458:     //  width  ビット数。0~32
  2459:     //  data   データ。下位widthビットだけ使う
  2460:     void writeBits (int width, int data) {
  2461:       while (width != 0) {  //書き込む残りビット数
  2462:         if (bitCount == 0) {  //[bitIndex]が満杯なので新しい[bitIndex]を用意する
  2463:           if (byteIndex == buffer.length) {  //bufferが満杯なので長さを2倍にする
  2464:             byte[] temporary = new byte[byteIndex * 2];
  2465:             System.arraycopy (buffer, 0,  //from
  2466:                               temporary, 0,  //to
  2467:                               byteIndex);  //length
  2468:             buffer = temporary;
  2469:           }
  2470:           bitIndex = byteIndex;
  2471:           bitCount = 8;
  2472:           buffer[byteIndex++] = 0;
  2473:         }
  2474:         data &= (1 << width) - 1;  //dataのゴミを消す
  2475:         int n = Math.min (bitCount, width);  //今回書き込むビット数
  2476:         bitCount -= n;  //今回書き込んだ後の[bitIndex]の残りビット数
  2477:         width -= n;  //今回書き込んだ後の書き込む残りビット数
  2478:         buffer[bitIndex] |= (byte) ((data >>> width) << bitCount);  //符号なし右シフト
  2479:       }  //while
  2480:     }  //writeBits
  2481: 
  2482:     //getBuffer ()
  2483:     //  バッファを返す
  2484:     byte[] getBuffer () {
  2485:       return buffer;
  2486:     }  //getBuffer
  2487: 
  2488:     //getLength ()
  2489:     //  現在の長さを返す
  2490:     int getLength () {
  2491:       return byteIndex;
  2492:     }  //getLength
  2493: 
  2494:   }  //class BitWriter
  2495: 
  2496:   static class DIC {
  2497:     int ptr;  //開始位置
  2498:     int len;  //長さ
  2499:   }
  2500: 
  2501:   //outbuf = compress (inpbuf, dicbit)
  2502:   //  圧縮
  2503:   //  inpbuf  入力バッファ
  2504:   //  dicbit  辞書のページ数のビット数。1~15
  2505:   static byte[] compress (byte[] inpbuf, int dicbit) {
  2506:     int dicsiz = 1 << dicbit;  //辞書のページ数
  2507:     //辞書を初期化する
  2508:     DIC[] dicbuf = new DIC[dicsiz];  //辞書
  2509:     for (int pag = 0; pag < dicsiz; pag++) {
  2510:       dicbuf[pag] = new DIC ();
  2511:       dicbuf[pag].ptr = 0;
  2512:       dicbuf[pag].len = 0;
  2513:     }
  2514:     int dicent = 0;  //辞書エントリ
  2515:     //入力長さ
  2516:     int inplen = inpbuf.length;
  2517:     BitWriter bw = new BitWriter ();
  2518:     bw.writeBits (24, inplen);
  2519:     //辞書のページ数のビット数
  2520:     bw.writeBits (4, dicbit);
  2521:     //圧縮ループ
  2522:     int inpptr = 0;  //入力位置
  2523:     while (inpptr < inplen) {
  2524:       //辞書から探す
  2525:       int dicpag = -1;  //辞書にある単語のページ番号
  2526:       int dicptr = -1;  //辞書にある単語の開始位置
  2527:       int diclen = 0;  //辞書にある単語の長さ
  2528:       int fstchr = inpbuf[inpptr] & 255;  //1文字目
  2529:       for (int pag = 0; pag < dicsiz; pag++) {
  2530:         int len = dicbuf[pag].len;  //辞書にある単語の長さ
  2531:         if (diclen < len &&  //これまでより長い
  2532:             inpptr + len + 1 <= inplen) {  //1文字伸ばしてもはみ出さない
  2533:           int ptr = dicbuf[pag].ptr;  //辞書にある単語の開始位置
  2534:         cmp:
  2535:           if (fstchr == (inpbuf[ptr] & 255)) {
  2536:             for (int i = 1; i < len; i++) {
  2537:               if (inpbuf[inpptr + i] != inpbuf[ptr + i]) {
  2538:                 break cmp;
  2539:               }
  2540:             }
  2541:             dicpag = pag;
  2542:             dicptr = ptr;
  2543:             diclen = len;
  2544:           }
  2545:         }
  2546:       }  //for pag
  2547:       if (diclen == 0) {  //辞書にない
  2548:         bw.writeBits (1, 0);
  2549:       } else {  //辞書にある
  2550:         bw.writeBits (1, 1);
  2551:         bw.writeBits (dicbit, dicpag);
  2552:       }
  2553:       int chr = inpbuf[inpptr + diclen] & 255;  //今回の文字
  2554:       //文字を出力する
  2555:       bw.writeByte (chr);
  2556:       //1文字伸ばす
  2557:       diclen++;
  2558:       //新しい単語を辞書に登録する
  2559:       dicbuf[dicent].ptr = inpptr;
  2560:       dicbuf[dicent].len = diclen;
  2561:       dicent++;
  2562:       if (dicent == dicsiz) {
  2563:         dicent = 0;
  2564:       }
  2565:       //次の文字へ
  2566:       inpptr += diclen;
  2567:     }  //while
  2568:     //出力バッファを返す
  2569:     byte[] outbuf = bw.getBuffer ();
  2570:     int outlen = bw.getLength ();
  2571:     if (outbuf.length != outlen) {
  2572:       byte[] tmpbuf = new byte[outlen];
  2573:       System.arraycopy (outbuf, 0, tmpbuf, 0, outlen);
  2574:       outbuf = tmpbuf;
  2575:     }
  2576:     return outbuf;
  2577:   }  //compress
  2578: 
  2579:   //dst = encode (src)
  2580:   //  エンコード
  2581:   //    Pxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  2582:   //    Qxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  2583:   //    Rxxxxxxxxxxxxxxx
  2584:   //      ↓
  2585:   //    0xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  2586:   //    0xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  2587:   //    0xxxxxxxxxxxxxxxPQR0000000000000
  2588:   public static byte[] encode (byte[] src) {
  2589:     int r = src.length;
  2590:     int q = r / 62;
  2591:     r -= 62 * q;
  2592:     if (r != 0) {  //62で割り切れないとき
  2593:       r = ((r + 2 + 3) & -4) - 2;  //余りを4*n+2に切り上げる
  2594:       if (src.length < 62 * q + r) {
  2595:         byte[] tmp = new byte[62 * q + r];
  2596:         System.arraycopy (src, 0, tmp, 0, src.length);
  2597:         src = tmp;
  2598:       }
  2599:     }
  2600:     byte[] dst = new byte[64 * q + (r == 0 ? 0 : r + 2)];
  2601:     int[] w = new int[16];
  2602:     for (int i = 0; 64 * i < dst.length; i++) {  //ブロックの番号
  2603:       int n = Math.min (64, dst.length - 64 * i) / 4;  //ブロックに含まれるintの数
  2604:       //intを集める
  2605:       for (int k = 0; k < n - 1; k++) {
  2606:         w[k] = ((src[62 * i + 4 * k + 0] & 255) << 24 |
  2607:                 (src[62 * i + 4 * k + 1] & 255) << 16 |
  2608:                 (src[62 * i + 4 * k + 2] & 255) << 8 |
  2609:                 (src[62 * i + 4 * k + 3] & 255));
  2610:       }
  2611:       w[n - 1] = ((src[62 * i + 4 * (n - 1) + 0] & 255) << 24 |
  2612:                   (src[62 * i + 4 * (n - 1) + 1] & 255) << 16);
  2613:       //intの最上位ビットを最後のintの下位2バイトに移して31ビット整数にする
  2614:       for (int k = 0; k < n; k++) {
  2615:         if ((w[k] & 0x80000000) != 0) {
  2616:           w[k] &= 0x7fffffff;
  2617:           w[n - 1] |= 1 << (15 - k);
  2618:         }
  2619:       }
  2620:       //31ビット整数を4桁の224進数に変換して各桁に32を加えて出力する
  2621:       for (int k = 0; k < n; k++) {
  2622:         int t3 = w[k];
  2623:         int t2 = t3 / 224;
  2624:         t3 -= 224 * t2;
  2625:         int t1 = t2 / 224;
  2626:         t2 -= 224 * t1;
  2627:         int t0 = t1 / 224;
  2628:         t1 -= 224 * t0;
  2629:         dst[64 * i + 4 * k + 0] = (byte) (32 + t0);
  2630:         dst[64 * i + 4 * k + 1] = (byte) (32 + t1);
  2631:         dst[64 * i + 4 * k + 2] = (byte) (32 + t2);
  2632:         dst[64 * i + 4 * k + 3] = (byte) (32 + t3);
  2633:       }
  2634:     }
  2635:     return dst;
  2636:   }  //encode
  2637: 
  2638:   //trmSendProcess (file)
  2639:   //  送信処理
  2640:   public static void trmSendProcess (File file) {
  2641:     //ファイルを読み込む
  2642:     byte[] fileData = load (file);  //本体
  2643:     if (fileData == null) {
  2644:       return;
  2645:     }
  2646:     if (0x00ffff00 < fileData.length) {
  2647:       return;
  2648:     }
  2649:     //------------------------------------------------
  2650:     //step3_data
  2651:     //byte[] adotr3 = load ("adotr3.r");
  2652:     byte[] step3Data = new byte[adotr3.length +  //adotr3
  2653:                                 24 +  //ファイル名
  2654:                                 4 +  //日時
  2655:                                 4 +  //本体のCRC32
  2656:                                 4 +  //本体の長さ
  2657:                                 fileData.length];  //本体
  2658:     int index3 = 0;
  2659:     //adotr3
  2660:     System.arraycopy (adotr3, 0,
  2661:                       step3Data, index3,
  2662:                       adotr3.length);
  2663:     index3 += adotr3.length;
  2664:     //ファイル名
  2665:     //  ・SJISに変換できない
  2666:     //  ・SJISに変換できたが、Human68kの標準のファイル名に使えない文字がある
  2667:     //  ・SJISに変換できたが、/^[^\.]{1,18}(?:\.[^\.]{1,3})?$/にマッチしない
  2668:     //  はすべてエラー
  2669:     String name = file.getName ();
  2670:     {
  2671:       byte[] b = new byte[2 * name.length ()];
  2672:       int k = 0;
  2673:       int p = -1;
  2674:       for (int i = 0; i < name.length (); i++) {
  2675:         int c = CharacterCode.chrCharToSJIS[name.charAt (i)];
  2676:         if (c == 0) {  //変換できない文字がある
  2677:           return;
  2678:         }
  2679:         if (c <= ' ' || c == ':' || c == '*' || c == '?' ||
  2680:             c == '\\' || c == '/' || (c == '-' && k == 0) ||
  2681:             c == '"' || c == '\'' || c == '+' || c == ';' ||
  2682:             c == '<' || c == '=' || c == '>' ||
  2683:             c == '[' || c == ']' || c == '|') {  //Human68kの標準のファイル名に使えない文字がある
  2684:           return;
  2685:         }
  2686:         if (c == '.') {  //'.'
  2687:           if (p < 0) {  //初回
  2688:             p = k;
  2689:           } else {  //'.'が2つある
  2690:             return;
  2691:           }
  2692:         }
  2693:         if (0x00ff < c) {  //2バイト
  2694:           b[k++] = (byte) (c >> 8);
  2695:         }
  2696:         b[k++] = (byte) c;
  2697:         if (p < 0 ? 18 < k : p + 1 + 3 < k) {  //18+3文字に収まっていない
  2698:           return;
  2699:         }
  2700:       }
  2701:       for (int i = 0; i < k; i++) {
  2702:         step3Data[index3++] = b[i];
  2703:       }
  2704:       for (int i = k; i < 24; i++) {
  2705:         step3Data[index3++] = 0;
  2706:       }
  2707:     }
  2708:     //日時
  2709:     {
  2710:       long dttm = DnT.dntDttmCmil (file.lastModified () + RP5C15.rtcCmilGap);  //西暦年<<42|月<<38|月通日<<32|時<<22|分<<16|秒<<10|ミリ秒。FCBの日時はRTCの日時なのでオフセットを加える
  2711:       int date = DnT.dntYearDttm (dttm) - 1980 << 9 | DnT.dntMontDttm (dttm) << 5 | DnT.dntMdayDttm (dttm);  //(西暦年-1980)<<9|月<<5|月通日
  2712:       int time = DnT.dntHourDttm (dttm) << 11 | DnT.dntMinuDttm (dttm) << 5 | DnT.dntSecoDttm (dttm) >> 1;  //時<<11|分<<5|秒/2
  2713:       step3Data[index3++] = (byte) (date >> 8);
  2714:       step3Data[index3++] = (byte) date;
  2715:       step3Data[index3++] = (byte) (time >> 8);
  2716:       step3Data[index3++] = (byte) time;
  2717:     }
  2718:     //本体のCRC32
  2719:     {
  2720:       CRC32 crc32 = new CRC32 ();
  2721:       crc32.update (fileData, 0, fileData.length);
  2722:       int t = (int) crc32.getValue ();
  2723:       step3Data[index3++] = (byte) (t >> 24);
  2724:       step3Data[index3++] = (byte) (t >> 16);
  2725:       step3Data[index3++] = (byte) (t >> 8);
  2726:       step3Data[index3++] = (byte) (t);
  2727:     }
  2728:     //本体の長さ
  2729:     {
  2730:       int t = fileData.length;
  2731:       step3Data[index3++] = (byte) (t >> 24);
  2732:       step3Data[index3++] = (byte) (t >> 16);
  2733:       step3Data[index3++] = (byte) (t >> 8);
  2734:       step3Data[index3++] = (byte) (t);
  2735:     }
  2736:     //本体
  2737:     System.arraycopy (fileData, 0,
  2738:                       step3Data, index3,
  2739:                       fileData.length);
  2740:     index3 += fileData.length;
  2741:     //------------------------------------------------
  2742:     //step2_data
  2743:     //byte[] adotr2 = load ("adotr2.r");
  2744:     byte[] step3DataCompressed = null;
  2745:     int dicbit = 0;
  2746:     //辞書のサイズのビット数
  2747:     //  圧縮しにくいときは小さい方がよいが縮まらず捨てられるビット数を試しても意味がない
  2748:     //  圧縮しやすいときは大きい方がよいが圧縮に時間がかかる
  2749:     //  9に固定してもよい
  2750:     for (int i = 8; i <= 10; i++) {
  2751:       byte[] t = compress (step3Data, i);
  2752:       if (step3DataCompressed == null ||
  2753:           t.length < step3DataCompressed.length) {
  2754:         step3DataCompressed = t;
  2755:         dicbit = i;
  2756:       }
  2757:     }
  2758:     byte[] step2Data = new byte[adotr2.length + step3DataCompressed.length];
  2759:     int index2 = 0;
  2760:     //adotr2
  2761:     System.arraycopy (adotr2, 0,
  2762:                       step2Data, index2,
  2763:                       adotr2.length);
  2764:     index2 += adotr2.length;
  2765:     //本体
  2766:     System.arraycopy (step3DataCompressed, 0,
  2767:                       step2Data, index2,
  2768:                       step3DataCompressed.length);
  2769:     index2 += step3DataCompressed.length;
  2770:     //------------------------------------------------
  2771:     //step1_data
  2772:     //byte[] adotr1 = load ("adotr1.r");
  2773:     byte[] step3DataEncoded = encode (step3Data);  //圧縮なし
  2774:     byte[] step2DataEncoded = encode (step2Data);  //圧縮あり
  2775:     byte[] step1Data;
  2776:     if (step3DataEncoded.length <= step2DataEncoded.length) {  //圧縮効果なし
  2777:       step1Data = new byte[adotr1.length + step3DataEncoded.length + 1];
  2778:       int index1 = 0;
  2779:       System.arraycopy (adotr1, 0,
  2780:                         step1Data, index1,
  2781:                         adotr1.length);
  2782:       index1 += adotr1.length;
  2783:       System.arraycopy (step3DataEncoded, 0,
  2784:                         step1Data, index1,
  2785:                         step3DataEncoded.length);
  2786:       index1 += step3DataEncoded.length;
  2787:       step1Data[index1++] = 0x1a;
  2788:       if (false) {
  2789:         System.out.printf ("                 original: %d bytes (%.1f%%)\n",
  2790:                            fileData.length,
  2791:                            100.0);
  2792:         System.out.printf ("   generator concatenated: %d bytes (%.1f%%)\n",
  2793:                            step3Data.length,
  2794:                            100.0 * (double) step3Data.length / (double) fileData.length);
  2795:         System.out.printf ("                  encoded: %d bytes (%.1f%%)\n",
  2796:                            step3DataEncoded.length,
  2797:                            100.0 * (double) step3DataEncoded.length / (double) fileData.length);
  2798:         System.out.printf ("     decoder concatenated: %d bytes (%.1f%%)\n",
  2799:                            step1Data.length,
  2800:                            100.0 * (double) step1Data.length / (double) fileData.length);
  2801:       }
  2802:     } else {  //圧縮効果あり
  2803:       step1Data = new byte[adotr1.length + step2DataEncoded.length + 1];
  2804:       int index1 = 0;
  2805:       System.arraycopy (adotr1, 0,
  2806:                         step1Data, index1,
  2807:                         adotr1.length);
  2808:       index1 += adotr1.length;
  2809:       System.arraycopy (step2DataEncoded, 0,
  2810:                         step1Data, index1,
  2811:                         step2DataEncoded.length);
  2812:       index1 += step2DataEncoded.length;
  2813:       step1Data[index1++] = 0x1a;
  2814:       if (false) {
  2815:         System.out.printf ("                 original: %d bytes (%.1f%%)\n",
  2816:                            fileData.length,
  2817:                            100.0);
  2818:         System.out.printf ("   generator concatenated: %d bytes (%.1f%%)\n",
  2819:                            step3Data.length,
  2820:                            100.0 * (double) step3Data.length / (double) fileData.length);
  2821:         System.out.printf ("               compressed: %d bytes (%.1f%%) (%d bits dictionary)\n",
  2822:                            step3DataCompressed.length,
  2823:                            100.0 * (double) step3DataCompressed.length / (double) fileData.length,
  2824:                            dicbit);
  2825:         System.out.printf ("decompressor concatenated: %d bytes (%.1f%%)\n",
  2826:                            step2Data.length,
  2827:                            100.0 * (double) step2Data.length / (double) fileData.length);
  2828:         System.out.printf ("                  encoded: %d bytes (%.1f%%)\n",
  2829:                            step2DataEncoded.length,
  2830:                            100.0 * (double) step2DataEncoded.length / (double) fileData.length);
  2831:         System.out.printf ("     decoder concatenated: %d bytes (%.1f%%)\n",
  2832:                            step1Data.length,
  2833:                            100.0 * (double) step1Data.length / (double) fileData.length);
  2834:       }
  2835:     }
  2836:     if (false) {
  2837:       int l = step1Data.length;
  2838:       System.out.printf ("step1Data %d bytes\n", l);
  2839:       for (int i = 0; i < l; i++) {
  2840:         if ((i & 15) == 0) {
  2841:           System.out.printf ("%08x", i);
  2842:         }
  2843:         System.out.printf (" %02x", step1Data[i] & 255);
  2844:         if ((i & 15) == 15) {
  2845:           System.out.println ();
  2846:         }
  2847:       }
  2848:       if ((l & 15) != 0) {
  2849:         System.out.println ();
  2850:       }
  2851:     }
  2852:     //copy aux a.rとa.rで挟んで送信する
  2853:     trmSendString (String.format (
  2854:       "rem a.r/%s %d/%d %.1f%%\rcopy aux a.r\r",
  2855:       name, step1Data.length, fileData.length,
  2856:       100.0 * (double) step1Data.length / (double) fileData.length));
  2857:     try {
  2858:       Thread.sleep (1000);
  2859:     } catch (InterruptedException ie) {
  2860:     }
  2861:     if (trmRowToCol[0] == 1) {  //Terminal→AUX。row2col
  2862:       int row = 0;
  2863:       int col = 1;
  2864:       trmConnectionArray[trmCols * row + col - 1].row2colQueue.write (step1Data, 0, step1Data.length);
  2865:     } else if (0 < trmColToRow[0]) {  //Terminal→SerialPort。col2row
  2866:       int row = trmColToRow[0];
  2867:       int col = 0;
  2868:       trmConnectionArray[trmCols * row + col - 1].col2rowQueue.write (step1Data, 0, step1Data.length);
  2869:     }
  2870:     try {
  2871:       Thread.sleep (1000);
  2872:     } catch (InterruptedException ie) {
  2873:     }
  2874:     trmSendString ("a.r\r");
  2875:   }  //trmSendProcess
  2876: 
  2877:   //perl misc/ftob.pl adotr1 misc/adotr1.r
  2878:   public static final byte[] adotr1 = "B\202G\373 h\324\211\304|\377\374(B\"Kx@\330\213B\200\320\214\220\204\223\201\300\201\330\200$I\"<\337\337\337\340\322\233B\200v( @\347\210\220\210\353\210\341\231\320\201Q\301\220\201\342Kd\354\"\300\267\304e\3322!B\200\322A\342\220\321\232\265\311e\364\267\314e\266A\372\377\370t\370N\360\" BAVAp\254NO".getBytes (XEiJ.ISO_8859_1);
  2879:   //perl misc/ftob.pl adotr2 misc/adotr2.r
  2880:   public static final byte[] adotr2 = "p\1\320\211\300|\377\376\"@|\0~\0E\372\0\372r\30a\0\0\322&\0\326\211p\1\320\203\300|\377\376*@r\4a\0\0\276g\0\0\246\30\0z\b\351\255\332\215\377\201\"\5\222\200/\1/\0\377JP\217J\200ktp\0 M \300 \300\261\305e\370(I,M\265\311dvr\1a\0\0\206f\b,\314p\1,\300`0\22\4av\347\210 u\b\0 5\b\4gVR\200\"\0\322\214\262\203bL,\314,\300U\200H@H@\30\330Q\310\377\374H@Q\310\377\364\30\332\275\305f\2,M\271\303e\260p\372N\373\2\16r\3p\254NO IN\320\377\t\377\0Hz\0\4`\366out of memory\r\n\0Hz\0\4`\340data error\r\n\0\0p\0J\7f\4\34\32P\7\24\1\264\7c\2\24\7\20\6\345\250\345.\236\2\222\2f\344\340\210Nu".getBytes (XEiJ.ISO_8859_1);
  2881:   //perl misc/ftob.pl adotr3 misc/adotr3.r
  2882:   public static final byte[] adotr3 = "E\372\1\2&*\0 G\352\0$ \13\320\203R\200\300|\377\376(@K\354\4\0\377\201\"\r\222\200/\1/\0\377JP\217J\200k\0\0\230 Lr\0 \1t\7\342\210d\6\n\200\355\270\203 Q\312\377\364 \300R\1d\350\"Lp\0\"\3 KF\200`\22HAt\0\24\30\261\2\340\210\345J$1(\0\265\200Q\311\377\356HAQ\311\377\346F\200\260\252\0\34f`?<\0 /\n\377<\\\217J\200kd/\3/\13?\0\377@\"\0/j\0\30\0\2\377\207\377>O\357\0\n\262\203g\b/\n\377AX\217`@/\n\377\tHz\0\6\377\t\377\0 created\r\n\0\0Hz\0\4`\352out of memory\r\n\0Hz\0\4`\324crc error\r\n\0Hz\0\4`\302cannot write\r\n\0\0".getBytes (XEiJ.ISO_8859_1);
  2883: 
  2884: }  //class RS232CTerminal