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