CONDevice.java
     1: //========================================================================================
     2: //  CONDevice.java
     3: //    en:CON device control -- Paste a string from the platform clipboard or named pipe into the Human68k console.
     4: //    ja:CONデバイス制御 -- プラットフォームのクリップボードまたは名前付きパイプにある文字列をHuman68kのコンソールに貼り付けます。
     5: //  Copyright (C) 2003-2022 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.*;
    16: import java.awt.event.*;
    17: import java.io.*;
    18: import java.util.*;
    19: import java.util.concurrent.*;
    20: import javax.swing.*;
    21: 
    22: public class CONDevice {
    23: 
    24:   //貼り付けタスクの開始遅延(ms)
    25:   public static final long CON_PASTE_DELAY = 10L;
    26: 
    27:   //貼り付けタスクの動作間隔(ms)
    28:   //  長すぎると大量に貼り付けたとき時間がかかる
    29:   public static final long CON_PASTE_INTERVAL = 10L;
    30: 
    31:   //貼り付けパイプの名前
    32:   public static final String CON_PASTE_PIPE_NAME = "XEiJPaste";
    33:   //制御パイプの名前
    34:   public static final String CON_CONTROL_PIPE_NAME = "XEiJControl";
    35: 
    36:   //パイプインスタンスの数
    37:   //  Windowsのコマンドプロンプトで1個のバッチファイルから
    38:   //    echo echo ONE > \\.\pipe\XEiJPaste
    39:   //    echo echo TWO > \\.\pipe\XEiJPaste
    40:   //  のように2回続けて送信したとき、XEiJには2個のクライアントが間髪をいれずに送信してきたように見える
    41:   //  十分な数のパイプインスタンスを用意しておかないと送信が失敗する可能性がある
    42:   //!!! 少なくとも短いデータではインスタンス1個でも失敗しなくなったのでもっと減らせる
    43:   //  前半分を貼り付けパイプ、後半分を制御パイプとする
    44:   public static final int CON_PIPE_INSTANCES = 10;
    45: 
    46:   //キー
    47:   public static final String[] CON_KEY_BASE = (
    48:     "esc,1," +
    49:     "1,2," +
    50:     "2,3," +
    51:     "3,4," +
    52:     "4,5," +
    53:     "5,6," +
    54:     "6,7," +
    55:     "7,8," +
    56:     "8,9," +
    57:     "9,10," +
    58:     "0,11," +
    59:     "minus,12," +
    60:     "caret,13," +
    61:     "yen,14," +
    62:     "bs,15," +
    63:     "tab,16," +
    64:     "q,17," +
    65:     "w,18," +
    66:     "e,19," +
    67:     "r,20," +
    68:     "t,21," +
    69:     "y,22," +
    70:     "u,23," +
    71:     "i,24," +
    72:     "o,25," +
    73:     "p,26," +
    74:     "at,27," +
    75:     "leftbracket,28," +
    76:     "return,29," +
    77:     "a,30," +
    78:     "s,31," +
    79:     "d,32," +
    80:     "f,33," +
    81:     "g,34," +
    82:     "h,35," +
    83:     "j,36," +
    84:     "k,37," +
    85:     "l,38," +
    86:     "semicolon,39," +
    87:     "colon,40," +
    88:     "rightbracket,41," +
    89:     "z,42," +
    90:     "x,43," +
    91:     "c,44," +
    92:     "v,45," +
    93:     "b,46," +
    94:     "n,47," +
    95:     "m,48," +
    96:     "comma,49," +
    97:     "period,50," +
    98:     "slash,51," +
    99:     "underline,52," +
   100:     "space,53," +
   101:     "home,54," +
   102:     "del,55," +
   103:     "rollup,56," +
   104:     "rolldown,57," +
   105:     "undo,58," +
   106:     "left,59," +
   107:     "up,60," +
   108:     "right,61," +
   109:     "down,62," +
   110:     "clr,63," +
   111:     "tenkeyslash,64," +
   112:     "tenkeyasterisk,65," +
   113:     "tenkeyminus,66," +
   114:     "tenkey7,67," +
   115:     "tenkey8,68," +
   116:     "tenkey9,69," +
   117:     "tenkeyplus,70," +
   118:     "tenkey4,71," +
   119:     "tenkey5,72," +
   120:     "tenkey6,73," +
   121:     "tenkeyequal,74," +
   122:     "tenkey1,75," +
   123:     "tenkey2,76," +
   124:     "tenkey3,77," +
   125:     "enter,78," +
   126:     "tenkey0,79," +
   127:     "tenkeycomma,80," +
   128:     "tenkeyperiod,81," +
   129:     "kigou,82," +
   130:     "touroku,83," +
   131:     "help,84," +
   132:     "xf1,85," +
   133:     "xf2,86," +
   134:     "xf3,87," +
   135:     "xf4,88," +
   136:     "xf5,89," +
   137:     "kana,90," +
   138:     "roma,91," +
   139:     "code,92," +
   140:     "caps,93," +
   141:     "ins,94," +
   142:     "hiragana,95," +
   143:     "zenkaku,96," +
   144:     "break,97," +
   145:     "copy,98," +
   146:     "f1,99," +
   147:     "f2,100," +
   148:     "f3,101," +
   149:     "f4,102," +
   150:     "f5,103," +
   151:     "f6,104," +
   152:     "f7,105," +
   153:     "f8,106," +
   154:     "f9,107," +
   155:     "f10,108," +
   156:     //
   157:     "shift,112," +
   158:     "ctrl,113," +
   159:     "opt1,114," +
   160:     "opt2,115," +
   161:     "num,116," +
   162:     "").split (",");
   163: 
   164:   //貼り付けパイプを使うか
   165:   public static boolean conPipeOn;
   166:   //貼り付け設定メニュー
   167:   public static JMenu conSettingsMenu;
   168:   //貼り付けパイプチェックボックス
   169:   public static JCheckBoxMenuItem conPipeCheckBox;
   170: 
   171:   //後始末フラグ
   172:   public static boolean conCleanupFlag;
   173:   //CONデバイス
   174:   public static int conCON;
   175: 
   176:   //貼り付けタスク
   177:   public static CONPasteTask conPasteTask;
   178:   //貼り付けキュー
   179:   public static LinkedBlockingQueue<String> conPasteQueue;
   180:   //貼り付けキュースレッド
   181:   public static CONPasteQueueThread conPasteQueueThread;
   182: 
   183:   //キーマップ
   184:   public static HashMap<String,Integer> conKeyMap;
   185:   //制御キュー
   186:   public static LinkedBlockingQueue<String> conControlQueue;
   187:   //制御キュースレッド
   188:   public static CONControlQueueThread conControlQueueThread;
   189: 
   190:   //パイプスレッドの配列
   191:   public static CONPipeThread[] conPipeThreadArray;
   192: 
   193:   //conInit ()
   194:   //  初期化
   195:   //    パラメータを復元する
   196:   //    メニューを作る
   197:   //    後始末フラグをクリアする
   198:   //    CONデバイスは探していない
   199:   //    貼り付けタスクはない
   200:   //    貼り付けキューを作る
   201:   //    貼り付けキュースレッドを作る
   202:   //    貼り付けキュースレッドを開始する
   203:   //    制御コマンドマップを作る
   204:   //    制御キューを作る
   205:   //    制御キュースレッドを作る
   206:   //    パイプスレッドの配列を作る
   207:   //    すべてのパイプスレッドについて
   208:   //      パイプスレッドを作る
   209:   //      パイプストリームがあるとき
   210:   //        パイプスレッドを開始する
   211:   public static void conInit () {
   212:     //パラメータを復元する
   213:     conPipeOn = XEiJ.prgWindllLoaded && Settings.sgsGetOnOff ("pastepipe");
   214:     //メニューを作る
   215:     ActionListener listener = new ActionListener () {
   216:       @Override public void actionPerformed (ActionEvent ae) {
   217:         Object source = ae.getSource ();
   218:         String command = ae.getActionCommand ();
   219:         switch (command) {
   220:         case "Stop paste":  //貼り付け中止
   221:           conStopPaste ();
   222:           break;
   223:         case "Paste pipe":
   224:           conSetPipeOn (((JCheckBoxMenuItem) source).isSelected ());
   225:           break;
   226:         }
   227:       }
   228:     };
   229:     conSettingsMenu = Multilingual.mlnText (
   230:       ComponentFactory.createMenu (
   231:         "Paste settings",
   232:         //!!! 貼り付け中止がここにあると押しにくい。しかし貼り付けの近くに貼り付け中止があると貼り付け中止を押そうとして貼り付けを押してしまうおそれがある
   233:         Multilingual.mlnText (ComponentFactory.createMenuItem ("Stop paste", listener), "ja", "貼り付け中止"),
   234:         ComponentFactory.createHorizontalSeparator (),
   235:         conPipeCheckBox =
   236:         ComponentFactory.setEnabled (
   237:           Multilingual.mlnText (ComponentFactory.createCheckBoxMenuItem (conPipeOn, "Paste pipe", listener), "ja", "貼り付けパイプ"),
   238:           XEiJ.prgWindllLoaded)
   239:         ),
   240:       "ja", "貼り付け設定");
   241:     //
   242:     //後始末フラグをクリアする
   243:     conCleanupFlag = false;
   244:     //CONデバイスは探していない
   245:     conCON = 0;
   246:     //
   247:     //貼り付けタスクはない
   248:     conPasteTask = null;
   249:     //貼り付けキューを作る
   250:     conPasteQueue = new LinkedBlockingQueue<String> ();
   251:     //貼り付けキュースレッドを作る
   252:     conPasteQueueThread = new CONPasteQueueThread ();
   253:     //貼り付けキュースレッドを開始する
   254:     conPasteQueueThread.start ();
   255:     //
   256:     //キーマップを作る
   257:     conKeyMap = new HashMap<String,Integer> ();
   258:     for (int i = 0; i + 1 < CON_KEY_BASE.length; i += 2) {
   259:       conKeyMap.put (CON_KEY_BASE[i], Integer.parseInt (CON_KEY_BASE[i + 1], 10));
   260:     }
   261:     //制御キューを作る
   262:     conControlQueue = new LinkedBlockingQueue<String> ();
   263:     //制御キュースレッドを作る
   264:     conControlQueueThread = new CONControlQueueThread ();
   265:     //制御キュースレッドを開始する
   266:     conControlQueueThread.start ();
   267:     //
   268:     //パイプスレッドの配列を作る
   269:     conPipeThreadArray = new CONPipeThread[CON_PIPE_INSTANCES];
   270:     //すべてのパイプスレッドについて
   271:     for (int i = 0; i < CON_PIPE_INSTANCES; i++) {
   272:       if (conPipeOn) {
   273:         //パイプスレッドを作る
   274:         conPipeThreadArray[i] = new CONPipeThread (CON_PIPE_INSTANCES / 2 <= i);  //前半分を貼り付けパイプ、後半分を制御パイプとする
   275:         //パイプストリームがあるとき
   276:         if (conPipeThreadArray[i].getPipeStream () != null) {
   277:           //パイプスレッドを開始する
   278:           conPipeThreadArray[i].start ();
   279:         }
   280:       } else {
   281:         //パイプスレッドはない
   282:         conPipeThreadArray[i] = null;
   283:       }
   284:     }
   285:   }  //conInit
   286: 
   287:   //conTini ()
   288:   //  後始末
   289:   //    パラメータを保存する
   290:   //    後始末フラグをセットする
   291:   //    貼り付けキュースレッドがあるとき
   292:   //      貼り付けキュースレッドに割り込む
   293:   //      貼り付けキュースレッドが終了するまで待つ
   294:   //      貼り付けキュースレッドはない
   295:   //    貼り付けタスクがあるとき
   296:   //      貼り付けタスクをキャンセルする
   297:   //      貼り付けタスクはない
   298:   //    制御キュースレッドがあるとき
   299:   //      制御キュースレッドに割り込む
   300:   //      制御キュースレッドが終了するまで待つ
   301:   //      制御キュースレッドはない
   302:   //    すべてのパイプスレッドについて
   303:   //      パイプスレッドがあるとき
   304:   //        パイプスレッドを終了させる
   305:   //        パイプスレッドが終了するまで待つ
   306:   //        パイプスレッドはない
   307:   public static void conTini () {
   308:     //パラメータを保存する
   309:     Settings.sgsPutOnOff ("pastepipe", conPipeOn);
   310:     //後始末フラグをセットする
   311:     conCleanupFlag = true;
   312:     //貼り付けキュースレッドがあるとき
   313:     if (conPasteQueueThread != null) {
   314:       //貼り付けキュースレッドに割り込む
   315:       conPasteQueueThread.interrupt ();
   316:       //貼り付けキュースレッドが終了するまで待つ
   317:       try {
   318:         conPasteQueueThread.join (100);
   319:       } catch (InterruptedException ie) {
   320:       }
   321:       //貼り付けキュースレッドはない
   322:       conPasteQueueThread = null;
   323:     }
   324:     //貼り付けタスクがあるとき
   325:     CONPasteTask t = conPasteTask;
   326:     if (t != null) {
   327:       //貼り付けタスクをキャンセルする
   328:       t.cancel ();
   329:       //貼り付けタスクはない
   330:       conPasteTask = null;
   331:     }
   332:     //制御キュースレッドがあるとき
   333:     if (conControlQueueThread != null) {
   334:       //制御キュースレッドに割り込む
   335:       conControlQueueThread.interrupt ();
   336:       //制御キュースレッドが終了するまで待つ
   337:       try {
   338:         conControlQueueThread.join (100);
   339:       } catch (InterruptedException ie) {
   340:       }
   341:       //制御キュースレッドはない
   342:       conControlQueueThread = null;
   343:     }
   344:     //すべてのパイプスレッドについて
   345:     for (int i = 0; i < CON_PIPE_INSTANCES; i++) {
   346:       //パイプスレッドがあるとき
   347:       if (conPipeThreadArray[i] != null) {
   348:         //パイプを閉じる
   349:         conPipeThreadArray[i].closePipe ();
   350:         //パイプスレッドが終了するまで待つ
   351:         try {
   352:           conPipeThreadArray[i].join (100);
   353:         } catch (InterruptedException ie) {
   354:         }
   355:         //パイプスレッドはない
   356:         conPipeThreadArray[i] = null;
   357:       }
   358:     }
   359:   }  //conTini
   360: 
   361:   //conReset ()
   362:   //  リセット
   363:   //    CONデバイスは探していない
   364:   public static void conReset () {
   365:     //CONデバイスは探していない
   366:     conCON = 0;
   367:   }  //conReset
   368: 
   369:   //conDoPaste ()
   370:   //  貼り付け
   371:   //    クリップボードから文字列を取り出す
   372:   //    貼り付けキューに文字列を追加する
   373:   public static void conDoPaste () {
   374:     //クリップボードから文字列を取り出す
   375:     if (XEiJ.clpClipboard == null) {
   376:       return;
   377:     }
   378:     String string = null;
   379:     try {
   380:       string = (String) XEiJ.clpClipboard.getData (DataFlavor.stringFlavor);
   381:     } catch (Exception e) {
   382:       return;
   383:     }
   384:     if (string == null || string.equals ("")) {
   385:       return;
   386:     }
   387:     //貼り付けキューに文字列を追加する
   388:     conPasteQueue.add (string);
   389:   }  //conDoPaste
   390: 
   391:   //conStopPaste ()
   392:   //  貼り付け中止
   393:   //    貼り付けキューを空にする
   394:   //    貼り付けタスクがあるとき
   395:   //      貼り付けタスクに貼り付けを中止させる
   396:   public static void conStopPaste () {
   397:     //貼り付けキューを空にする
   398:     conPasteQueue.clear ();
   399:     //貼り付けタスクがあるとき
   400:     CONPasteTask t = conPasteTask;
   401:     if (t != null) {
   402:       //貼り付けタスクに貼り付けを中止させる
   403:       t.stopPaste ();
   404:     }
   405:   }  //conStopPaste
   406: 
   407:   //conSetPipeOn (on)
   408:   //  貼り付けパイプを使うか設定する
   409:   //    off→on
   410:   //      すべてのパイプスレッドについて
   411:   //        パイプスレッドがないとき
   412:   //          パイプスレッドを作る
   413:   //          パイプストリームがあるとき
   414:   //            パイプスレッドを開始する
   415:   //    on→off
   416:   //      すべてのパイプスレッドについて
   417:   //        パイプスレッドがあるとき
   418:   //          パイプを閉じる
   419:   //          パイプスレッドが終了するまで待つ
   420:   //          パイプスレッドはない
   421:   public static void conSetPipeOn (boolean on) {
   422:     on = XEiJ.prgWindllLoaded && on;
   423:     if (conPipeOn != on) {
   424:       conPipeOn = on;
   425:       conPipeCheckBox.setSelected (on);
   426:       if (on) {  //off→on
   427:         //すべてのパイプスレッドについて
   428:         for (int i = 0; i < CON_PIPE_INSTANCES; i++) {
   429:           //パイプスレッドがないとき
   430:           if (conPipeThreadArray[i] == null) {
   431:             //パイプスレッドを作る
   432:             conPipeThreadArray[i] = new CONPipeThread (CON_PIPE_INSTANCES / 2 <= i);  //前半分を貼り付けパイプ、後半分を制御パイプとする
   433:             //パイプストリームがあるとき
   434:             if (conPipeThreadArray[i].getPipeStream () != null) {
   435:               //パイプスレッドを開始する
   436:               conPipeThreadArray[i].start ();
   437:             }
   438:           }
   439:         }
   440:       } else {  //on→off
   441:         //すべてのパイプスレッドについて
   442:         for (int i = 0; i < CON_PIPE_INSTANCES; i++) {
   443:           //パイプスレッドがあるとき
   444:           if (conPipeThreadArray[i] != null) {
   445:             //パイプを閉じる
   446:             conPipeThreadArray[i].closePipe ();
   447:             //パイプスレッドが終了するまで待つ
   448:             try {
   449:               conPipeThreadArray[i].join (100);
   450:             } catch (InterruptedException ie) {
   451:             }
   452:             //パイプスレッドはない
   453:             conPipeThreadArray[i] = null;
   454:           }
   455:         }
   456:       }
   457:     }
   458:   }  //conSetPipeOn
   459: 
   460:   //class CONPasteQueueThread
   461:   //  貼り付けキュースレッド
   462:   //    以下を繰り返す
   463:   //      後始末フラグがセットされているとき
   464:   //        終了する
   465:   //      貼り付けタスクがないとき
   466:   //        貼り付けキューから文字列を取り出す
   467:   //        (貼り付けキューに文字列が追加されるか割り込まれるまでブロックする)
   468:   //        後始末フラグがセットされているとき
   469:   //          終了する
   470:   //        文字列が有効なとき
   471:   //          貼り付けタスクを作る
   472:   //          貼り付けタスクを開始する
   473:   //      200msスリープする
   474:   public static class CONPasteQueueThread extends Thread {
   475:     @Override public void run () {
   476:       //以下を繰り返す
   477:       for (;;) {
   478:         //後始末フラグがセットされているとき
   479:         if (conCleanupFlag) {
   480:           //終了する
   481:           return;
   482:         }
   483:         //貼り付けタスクがないとき
   484:         if (conPasteTask == null) {
   485:           //貼り付けキューから文字列を取り出す
   486:           //(貼り付けキューに文字列が追加されるか割り込まれるまでブロックする)
   487:           String string = null;
   488:           try {
   489:             string = conPasteQueue.take ();
   490:           } catch (InterruptedException ie) {
   491:           }
   492:           //後始末フラグがセットされているとき
   493:           if (conCleanupFlag) {
   494:             //終了する
   495:             return;
   496:           }
   497:           //文字列が有効なとき
   498:           if (string != null && !string.equals ("")) {
   499:             //貼り付けタスクを作る
   500:             conPasteTask = new CONPasteTask (string);
   501:             //貼り付けタスクを開始する
   502:             conPasteTask.start ();
   503:           }
   504:         }
   505:         //200msスリープする
   506:         try {
   507:           Thread.sleep (200);
   508:         } catch (InterruptedException ie) {
   509:         }
   510:       }  //for
   511:     }  //run
   512:   }  //CONPasteQueueThread
   513: 
   514:   //class CONControlQueueThread
   515:   //  制御キュースレッド
   516:   //    以下を繰り返す
   517:   //      後始末フラグがセットされているとき
   518:   //        終了する
   519:   //      制御キューから文字列を取り出す
   520:   //      (制御キューに文字列が追加されるか割り込まれるまでブロックする)
   521:   //      後始末フラグがセットされているとき
   522:   //        終了する
   523:   //      文字列が有効なとき
   524:   //        コマンドを切り取って実行する
   525:   //      200msスリープする
   526:   public static class CONControlQueueThread extends Thread {
   527:     @Override public void run () {
   528:       StringBuilder controlBuilder = new StringBuilder ();
   529:       //以下を繰り返す
   530:       for (;;) {
   531:         //後始末フラグがセットされているとき
   532:         if (conCleanupFlag) {
   533:           //終了する
   534:           return;
   535:         }
   536:         //制御キューから文字列を取り出す
   537:         //(制御キューに文字列が追加されるか割り込まれるまでブロックする)
   538:         String string = null;
   539:         try {
   540:           string = conControlQueue.take ();
   541:         } catch (InterruptedException ie) {
   542:         }
   543:         //後始末フラグがセットされているとき
   544:         if (conCleanupFlag) {
   545:           //終了する
   546:           return;
   547:         }
   548:         //文字列が有効なとき
   549:         if (string != null && !string.equals ("")) {
   550:           //コマンドを切り取って実行する
   551:           controlBuilder.append (string);
   552:           for (;;) {
   553:             int l = controlBuilder.length ();
   554:             //改行またはコロンを読み飛ばす
   555:             int i = 0;
   556:             for (; i < l; i++) {
   557:               int c = controlBuilder.charAt (i);
   558:               if (!(c == '\n' || c == '\r' || c == ':')) {
   559:                 break;
   560:               }
   561:             }
   562:             if (l <= i) {
   563:               break;
   564:             }
   565:             //改行またはコロンを探す
   566:             int j = i;
   567:             for (; j < l; j++) {
   568:               int c = controlBuilder.charAt (j);
   569:               if (c == '\n' || c == '\r' || c == ':') {
   570:                 break;
   571:               }
   572:             }
   573:             if (l <= j) {
   574:               break;
   575:             }
   576:             //コマンドを切り取る
   577:             String s = controlBuilder.substring (i, j);
   578:             controlBuilder.delete (0, j);
   579:             //コマンドを実行する
   580:             conCommand (s);
   581:           }
   582:         }
   583:         //200msスリープする
   584:         try {
   585:           Thread.sleep (200);
   586:         } catch (InterruptedException ie) {
   587:         }
   588:       }  //for
   589:     }  //run
   590:   }  //CONControlQueueThread
   591: 
   592:   //conCommand (command)
   593:   //  コマンドを実行する
   594:   //  interrupt
   595:   //  presskey opt1
   596:   //  releasekey opt1
   597:   //  reset
   598:   //  typekey ctrl c
   599:   public static void conCommand (String command) {
   600:     //trimして小文字にする
   601:     command = command.trim ().toLowerCase (Locale.ROOT);
   602:     //空のとき何もしない
   603:     if (command.length () == 0) {
   604:       return;
   605:     }
   606:     //空白の並びで分割する
   607:     String[] args = command.split ("[\\x00-\\x20]+");
   608:     //コマンドで分岐する
   609:     switch (args[0]) {
   610:     case "interrupt":  //インタラプト
   611:       XEiJ.sysInterrupt ();
   612:       return;
   613:     case "presskey":  //キーを押す
   614:       conPressKey (args);
   615:       return;
   616:     case "releasekey":  //キーを離す
   617:       conReleaseKey (args);
   618:       return;
   619:     case "typekey":  //キーを押して離す
   620:       conPressKey (args);
   621:       conReleaseKey (args);
   622:       return;
   623:     case "opt1reset":  //OPT.1キーを押しながらリセット
   624:       XEiJ.mpuReset (0, -1);
   625:       return;
   626:     case "reset":  //リセット
   627:       XEiJ.mpuReset (-1, -1);
   628:       return;
   629:     default:
   630:       System.out.println ("unknown command " + args[0]);
   631:       return;
   632:     }
   633:   }  //conCommand
   634: 
   635:   //conPressKey (args)
   636:   //  キーを押す
   637:   public static void conPressKey (String[] args) {
   638:     for (int k = 1; k < args.length; k++) {  //昇順に押す
   639:       String key = args[k];
   640:       if (conKeyMap.containsKey (key)) {
   641:         Keyboard.kbdCommandPress (conKeyMap.get (key).intValue ());
   642:       } else {
   643:         System.out.println ("unknown key " + key);
   644:         return;
   645:       }
   646:     }
   647:   }  //conPressKey
   648: 
   649:   //conReleaseKey (args)
   650:   //  キーを離す
   651:   public static void conReleaseKey (String[] args) {
   652:     for (int k = args.length - 1; 1 <= k; k--) {  //降順に離す
   653:       String key = args[k];
   654:       if (conKeyMap.containsKey (key)) {
   655:         Keyboard.kbdCommandRelease (conKeyMap.get (key).intValue ());
   656:       } else {
   657:         System.out.println ("unknown key " + key);
   658:         return;
   659:       }
   660:     }
   661:   }  //conReleaseKey
   662: 
   663:   //class CONPipeThread
   664:   //  パイプスレッド
   665:   public static class CONPipeThread extends Thread {
   666: 
   667:     //制御パイプか
   668:     public boolean isControlPipe;
   669:     //パイプストリーム
   670:     public NamedPipeInputStream pipeStream;
   671:     //閉じるフラグ
   672:     public boolean closeFlag;
   673: 
   674:     //new CONPipeThread
   675:     //  コンストラクタ
   676:     //    制御パイプか
   677:     //    パイプストリームはない
   678:     //    閉じるフラグをクリアする
   679:     //    パイプストリームを開く
   680:     public CONPipeThread (boolean isControlPipe) {
   681:       //制御パイプか
   682:       this.isControlPipe = isControlPipe;
   683:       //パイプストリームはない
   684:       pipeStream = null;
   685:       //閉じるフラグをクリアする
   686:       closeFlag = false;
   687:       //パイプストリームを開く
   688:       try {
   689:         pipeStream = NamedPipeInputStream.createInputStream (isControlPipe ? CON_CONTROL_PIPE_NAME : CON_PASTE_PIPE_NAME);
   690:       } catch (IOException ioe) {
   691:         ioe.printStackTrace ();
   692:       }
   693:     }  //CONPipeThread
   694: 
   695:     //pipeStream = getPipeStream ()
   696:     //  パイプストリームを返す
   697:     //    パイプストリームを返す
   698:     //    (コンストラクタの直後にnullを返したらパイプ使用不可)
   699:     public NamedPipeInputStream getPipeStream () {
   700:       //パイプストリームを返す
   701:       return pipeStream;
   702:     }  //getInputStream
   703: 
   704:     //closePipe ()
   705:     //  パイプを閉じる
   706:     //    閉じるフラグをセットする
   707:     //    パイプストリームをキャンセルする
   708:     public void closePipe () {
   709:       //閉じるフラグをセットする
   710:       closeFlag = true;
   711:       //パイプストリームをキャンセルする
   712:       if (pipeStream != null) {
   713:         try {
   714:           pipeStream.cancel ();
   715:         } catch (IOException ioe) {
   716:           ioe.printStackTrace ();
   717:         }
   718:       }
   719:     }  //closePipe
   720: 
   721:     //run ()
   722:     //  パイプタスク
   723:     //    (パイプストリームがnullでないことを確認してからstartすること)
   724:     //    以下を繰り返す
   725:     //      閉じるフラグがセットされているとき
   726:     //        終了する
   727:     //      パイプストリームがないとき
   728:     //        終了する
   729:     //      パイプストリームが接続するまで待つ
   730:     //      (パイプストリームが接続するかキャンセルされるまでブロックする)
   731:     //      パイプストリームから入力する
   732:     //      パイプストリームを閉じる
   733:     //      閉じるフラグがセットされているとき
   734:     //        終了する
   735:     //      パイプストリームを開く
   736:     //      SJISをデコードする
   737:     //      制御パイプのとき
   738:     //        制御キューに文字列を追加する
   739:     //      貼り付けパイプのとき
   740:     //        貼り付けキューに文字列を追加する
   741:     @Override public void run () {
   742:       byte[] buffer = new byte[1024];
   743:       int length = 0;
   744:       //以下を繰り返す
   745:       for (;;) {
   746:         //閉じるフラグがセットされているとき
   747:         if (closeFlag) {
   748:           //終了する
   749:           return;
   750:         }
   751:         //パイプストリームがないとき
   752:         if (pipeStream == null) {
   753:           //終了する
   754:           return;
   755:         }
   756:         //パイプストリームが接続するまで待つ
   757:         //(パイプストリームが接続するかキャンセルされるまでブロックする)
   758:         try {
   759:           pipeStream.connect ();
   760:         } catch (IOException ioe) {
   761:           ioe.printStackTrace ();
   762:           return;
   763:         }
   764:         //パイプストリームから入力する
   765:         while (!conCleanupFlag) {
   766:           if (length == buffer.length) {
   767:             byte[] newBuffer = new byte[length * 2];
   768:             System.arraycopy (buffer, 0, newBuffer, 0, length);
   769:             buffer = newBuffer;
   770:           }
   771:           try {
   772:             int t = pipeStream.read (buffer, length, buffer.length - length);
   773:             if (t == 0) {
   774:               break;
   775:             }
   776:             length += t;
   777:           } catch (IOException ioe) {
   778:             ioe.printStackTrace ();
   779:             break;
   780:           }
   781:         }
   782:         //パイプストリームを閉じる
   783:         try {
   784:           pipeStream.close ();
   785:         } catch (IOException ioe) {
   786:           ioe.printStackTrace ();
   787:           return;
   788:         }
   789:         //閉じるフラグがセットされているとき
   790:         if (closeFlag) {
   791:           //終了する
   792:           return;
   793:         }
   794:         //パイプストリームを開く
   795:         pipeStream = null;
   796:         try {
   797:           pipeStream = NamedPipeInputStream.createInputStream (isControlPipe ? CON_CONTROL_PIPE_NAME : CON_PASTE_PIPE_NAME);
   798:         } catch (IOException ioe) {
   799:           ioe.printStackTrace ();
   800:         }
   801:         if (length != 0) {
   802:           //SJISをデコードする
   803:           StringBuilder sb = new StringBuilder ();
   804:           for (int i = 0; i < length; i++) {
   805:             int s = buffer[i] & 0xff;
   806:             if ((0x80 <= s && s <= 0x9f) || 0xe0 <= s) {
   807:               i++;
   808:               s = s << 8 | (i < length ? buffer[i] & 0xff : 0x00);
   809:             }
   810:             int c = CharacterCode.chrSJISToChar[s];
   811:             sb.append ((char) (s != 0 && c == 0 ? '※' : c));
   812:           }
   813:           if (isControlPipe) {  //制御パイプのとき
   814:             //制御キューに文字列を追加する
   815:             conControlQueue.add (sb.toString ());
   816:           } else {  //貼り付けパイプのとき
   817:             //貼り付けキューに文字列を追加する
   818:             conPasteQueue.add (sb.toString ());
   819:           }
   820:           length = 0;
   821:         }
   822:       }  //for
   823:     }  //run
   824: 
   825:   }  //class PipeThread
   826: 
   827:   //class CONPasteTask
   828:   public static class CONPasteTask extends TimerTask {
   829: 
   830:     //貼り付ける文字列
   831:     public String string;
   832:     //貼り付ける文字列の長さ
   833:     public int length;
   834:     //貼り付ける文字列の次に貼り付ける文字の位置
   835:     public int index;
   836:     //貼り付け中止フラグ
   837:     public boolean stopFlag;
   838: 
   839:     //new CONPasteTask ()
   840:     //  コンストラクタ
   841:     //    貼り付け中止フラグをクリアする
   842:     public CONPasteTask (String string) {
   843:       this.string = string;
   844:       length = string.length ();
   845:       index = 0;
   846:       //貼り付け中止フラグをクリアする
   847:       stopFlag = false;
   848:     }  //PasteTask
   849: 
   850:     //start ()
   851:     //  開始
   852:     //    コアスレッドで貼り付けタスクを固定遅延実行で開始する
   853:     public void start () {
   854:       //コアスレッドで貼り付けタスクを固定遅延実行で開始する
   855:       XEiJ.tmrTimer.schedule (conPasteTask, CON_PASTE_DELAY, CON_PASTE_INTERVAL);
   856:     }  //start
   857: 
   858:     //stopPaste ()
   859:     //  貼り付け中止
   860:     //    貼り付け中止フラグをセットする
   861:     public void stopPaste () {
   862:       //貼り付け中止フラグをセットする
   863:       stopFlag = true;
   864:     }  //stopPaste
   865: 
   866:     //run ()
   867:     //  貼り付けタスク(固定遅延実行)
   868:     //    CONデバイスを探していないとき
   869:     //      CONデバイスを探す
   870:     //    CONデバイスが見つからないか貼り付け中止フラグがセットされているとき
   871:     //      貼り付ける文字列を空にする
   872:     //      貼り付けキューを空にする
   873:     //      貼り付けタスクをキャンセルする
   874:     //      貼り付けタスクはない
   875:     //      終了する
   876:     //    コンソール入力バッファが空でないとき
   877:     //      終了する
   878:     //    貼り付ける文字列の先頭を切り取ってコンソール入力バッファへ書き込む
   879:     //    貼り付ける文字列が空のとき
   880:     //      貼り付けタスクをキャンセルする
   881:     //      貼り付けタスクはない
   882:     //      終了する
   883:     //    (ASK68K 3.02のコンソール入力バッファは200バイトしかないので、貼り付ける文字列がなくなるまで繰り返し呼び出される)
   884:     //
   885:     //!!! 入力と貼り付けが競合するとデータが混ざったり欠落したりする可能性がある
   886:     //  コアスレッドを用いるので命令の実行中にコンソール入力バッファを書き換えることはないが、
   887:     //  割り込みルーチンでコンソール入力バッファを書き換えた場合と同様の壊れ方をする可能性はある
   888:     @Override public void run () {
   889:       //CONデバイスを探していないとき
   890:       int con = conCON;
   891:       if (con == 0) {
   892:         //CONデバイスを探す
   893:         con = MainMemory.mmrHumanDev ('C' << 24 | 'O' << 16 | 'N' << 8 | ' ',
   894:                                       ' ' << 24 | ' ' << 16 | ' ' << 8 | ' ');
   895:         if (0 <= con &&  //CONデバイスが見つかった
   896:             (//MC68060.mmuPeekLongData (con + 0x000168, 1) == 0x93fa967b &&  //"日本"
   897:              //MC68060.mmuPeekLongData (con + 0x00016c, 1) == 0x8cea8374 &&  //"語フ"
   898:              //MC68060.mmuPeekLongData (con + 0x000170, 1) == 0x838d8393 &&  //"ロン"
   899:              //MC68060.mmuPeekLongData (con + 0x000174, 1) == 0x83678376 &&  //"トプ"
   900:              //MC68060.mmuPeekLongData (con + 0x000178, 1) == 0x838d835a &&  //"ロセ"
   901:              //MC68060.mmuPeekLongData (con + 0x00017c, 1) == 0x83628354 &&  //"ッサ"
   902:              MC68060.mmuPeekLongData (con + 0x000180, 1) == 0x20826082 &&  //" AS"
   903:              MC68060.mmuPeekLongData (con + 0x000184, 1) == 0x72826a82 &&  //"SK6"
   904:              MC68060.mmuPeekLongData (con + 0x000188, 1) == 0x55825782 &&  //"68K"
   905:              MC68060.mmuPeekLongData (con + 0x00018c, 1) == 0x6a20666f &&  //"K fo"
   906:              MC68060.mmuPeekLongData (con + 0x000190, 1) == 0x72205836 &&  //"r X6"
   907:              MC68060.mmuPeekLongData (con + 0x000194, 1) == 0x38303030 &&  //"8000"
   908:              MC68060.mmuPeekLongData (con + 0x000198, 1) == 0x20766572 &&  //" ver"
   909:              MC68060.mmuPeekLongData (con + 0x00019c, 1) == 0x73696f6e &&  //"sion"
   910:              MC68060.mmuPeekLongData (con + 0x0001a0, 1) == 0x20332e30 &&  //" 3.0"
   911:              MC68060.mmuPeekLongData (con + 0x0001a4, 1) == 0x320d0a43// &&  //"2\r\nC"
   912:              //MC68060.mmuPeekLongData (con + 0x0001a8, 1) == 0x6f707972 &&  //"opyr"
   913:              //MC68060.mmuPeekLongData (con + 0x0001ac, 1) == 0x69676874 &&  //"ight"
   914:              //MC68060.mmuPeekLongData (con + 0x0001b0, 1) == 0x20313938 &&  //" 198"
   915:              //MC68060.mmuPeekLongData (con + 0x0001b4, 1) == 0x372d3934 &&  //"7-94"
   916:              //MC68060.mmuPeekLongData (con + 0x0001b8, 1) == 0x20534841 &&  //" SHA"
   917:              //MC68060.mmuPeekLongData (con + 0x0001bc, 1) == 0x52502043 &&  //"RP C"
   918:              //MC68060.mmuPeekLongData (con + 0x0001c0, 1) == 0x6f72702e &&  //"orp."
   919:              //MC68060.mmuPeekLongData (con + 0x0001c4, 1) == 0x2f414343 &&  //"/ACC"
   920:              //MC68060.mmuPeekLongData (con + 0x0001c8, 1) == 0x45535320 &&  //"ESS "
   921:              //MC68060.mmuPeekLongData (con + 0x0001cc, 1) == 0x434f2e2c &&  //"CO.,"
   922:              //MC68060.mmuPeekLongData (con + 0x0001d0, 1) == 0x4c54442e &&  //"LTD."
   923:              //MC68060.mmuPeekLongData (con + 0x0001d4, 1) == 0x0d0a0000  //"\r\n\0\0"
   924:              )) {  //ASK68K 3.02
   925:           conCON = con;
   926:         }
   927:       }
   928:       //CONデバイスが見つからないか貼り付け中止フラグがセットされているとき
   929:       if (con == 0 || stopFlag) {
   930:         //貼り付けキューを空にする
   931:         conPasteQueue.clear ();
   932:         //貼り付けタスクをキャンセルする
   933:         cancel ();
   934:         //貼り付けタスクはない
   935:         conPasteTask = null;
   936:         //終了する
   937:         return;
   938:       }
   939:       //コンソール入力バッファが空でないとき
   940:       int read = MC68060.mmuPeekLongData (con + 0x00e460, 1);  //コンソール入力バッファから最後に読み出した位置、または、これから読み出そうとしている位置
   941:       int write = MC68060.mmuPeekLongData (con + 0x00e464, 1);  //コンソール入力バッファへ最後に書き込んだ位置。入力中はこの後に書き込み始めている場合がある
   942:       if (write != read) {
   943:         //終了する
   944:         return;
   945:       }
   946:       //貼り付ける文字列の先頭を切り取ってコンソール入力バッファへ書き込む
   947:       int head = con + 0x010504;  //コンソール入力バッファの先頭
   948:       int tail = head + 200;  //コンソール入力バッファの末尾
   949:       for (; index < length; index++) {
   950:         int c = CharacterCode.chrCharToSJIS[string.charAt (index)];  //UTF16→SJIS変換
   951:         if (c == 0) {  //変換できない
   952:           continue;  //無視する
   953:         }
   954:         if (c == '\r' && index + 1 < length && string.charAt (index + 1) == '\n') {  //CRLF
   955:           index++;  //CRにする
   956:         } else if (c == '\n') {  //LF
   957:           c = '\r';  //CRにする
   958:         }
   959:         if (!(c >= ' ' || c == '\t' || c == '\r' || c == 0x1b)) {  //タブと改行とエスケープ以外の制御コード
   960:           continue;  //無視する
   961:         }
   962:         int write1 = write + 1 == tail ? head : write + 1;  //1バイト目を書き込む位置
   963:         int write2 = write1 + 1 == tail ? head : write1 + 1;  //2バイト目を書き込む位置
   964:         int write3 = write2 + 1 == tail ? head : write2 + 1;  //3バイト目を書き込む位置。予備
   965:         if (write1 == read || write2 == read || write3 == read || write3 == read) {  //コンソール入力バッファフル。readの位置はまだ読み出されていない場合がある
   966:           break;  //書き込みを延期する
   967:         }
   968:         if (c < 0x0100) {  //1バイトのとき
   969:           MC68060.mmuPokeByteData (write1, c, 1);
   970:           write = write1;
   971:         } else {  //2バイトのとき
   972:           MC68060.mmuPokeByteData (write1, c >> 8, 1);
   973:           MC68060.mmuPokeByteData (write2, c, 1);
   974:           write = write2;
   975:         }
   976:       }
   977:       MC68060.mmuPokeLongData (con + 0x00e464, write, 1);  //コンソール入力バッファへ最後に書き込んだ位置
   978:       //貼り付ける文字列が空のとき
   979:       if (index == length) {
   980:         //貼り付けタスクをキャンセルする
   981:         cancel ();
   982:         //貼り付けタスクはない
   983:         conPasteTask = null;
   984:         //終了する
   985:         return;
   986:       }
   987:       //(ASK68K 3.02のコンソール入力バッファは200バイトしかないので、貼り付ける文字列がなくなるまで繰り返し呼び出される)
   988:     }  //run
   989: 
   990:   }  //class PasteTask
   991: 
   992: }  //class CONDevice