TStatusBar
[fanfix.git] / src / jexer / TTerminalWindow.java
index 8730dfe7a8f98eadcc4df7af8ed9a76f89d7b17c..50c87d6c6d4db7c1329dca206e1e2e97891565a5 100644 (file)
@@ -32,6 +32,7 @@ import java.io.InputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Field;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
@@ -65,6 +66,76 @@ public class TTerminalWindow extends TWindow {
      */
     private TVScroller vScroller;
 
+    /**
+     * Claim the keystrokes the emulator will need.
+     */
+    private void addShortcutKeys() {
+        addShortcutKeypress(kbCtrlA);
+        addShortcutKeypress(kbCtrlB);
+        addShortcutKeypress(kbCtrlC);
+        addShortcutKeypress(kbCtrlD);
+        addShortcutKeypress(kbCtrlE);
+        addShortcutKeypress(kbCtrlF);
+        addShortcutKeypress(kbCtrlG);
+        addShortcutKeypress(kbCtrlH);
+        addShortcutKeypress(kbCtrlU);
+        addShortcutKeypress(kbCtrlJ);
+        addShortcutKeypress(kbCtrlK);
+        addShortcutKeypress(kbCtrlL);
+        addShortcutKeypress(kbCtrlM);
+        addShortcutKeypress(kbCtrlN);
+        addShortcutKeypress(kbCtrlO);
+        addShortcutKeypress(kbCtrlP);
+        addShortcutKeypress(kbCtrlQ);
+        addShortcutKeypress(kbCtrlR);
+        addShortcutKeypress(kbCtrlS);
+        addShortcutKeypress(kbCtrlT);
+        addShortcutKeypress(kbCtrlU);
+        addShortcutKeypress(kbCtrlV);
+        addShortcutKeypress(kbCtrlW);
+        addShortcutKeypress(kbCtrlX);
+        addShortcutKeypress(kbCtrlY);
+        addShortcutKeypress(kbCtrlZ);
+        addShortcutKeypress(kbF1);
+        addShortcutKeypress(kbF2);
+        addShortcutKeypress(kbF3);
+        addShortcutKeypress(kbF4);
+        addShortcutKeypress(kbF5);
+        addShortcutKeypress(kbF6);
+        addShortcutKeypress(kbF7);
+        addShortcutKeypress(kbF8);
+        addShortcutKeypress(kbF9);
+        addShortcutKeypress(kbF10);
+        addShortcutKeypress(kbF11);
+        addShortcutKeypress(kbF12);
+        addShortcutKeypress(kbAltA);
+        addShortcutKeypress(kbAltB);
+        addShortcutKeypress(kbAltC);
+        addShortcutKeypress(kbAltD);
+        addShortcutKeypress(kbAltE);
+        addShortcutKeypress(kbAltF);
+        addShortcutKeypress(kbAltG);
+        addShortcutKeypress(kbAltH);
+        addShortcutKeypress(kbAltU);
+        addShortcutKeypress(kbAltJ);
+        addShortcutKeypress(kbAltK);
+        addShortcutKeypress(kbAltL);
+        addShortcutKeypress(kbAltM);
+        addShortcutKeypress(kbAltN);
+        addShortcutKeypress(kbAltO);
+        addShortcutKeypress(kbAltP);
+        addShortcutKeypress(kbAltQ);
+        addShortcutKeypress(kbAltR);
+        addShortcutKeypress(kbAltS);
+        addShortcutKeypress(kbAltT);
+        addShortcutKeypress(kbAltU);
+        addShortcutKeypress(kbAltV);
+        addShortcutKeypress(kbAltW);
+        addShortcutKeypress(kbAltX);
+        addShortcutKeypress(kbAltY);
+        addShortcutKeypress(kbAltZ);
+    }
+
     /**
      * Public constructor spawns a shell.
      *
@@ -88,17 +159,27 @@ public class TTerminalWindow extends TWindow {
 
             // You cannot run a login shell in a bare Process interactively,
             // due to libc's behavior of buffering when stdin/stdout aren't a
-            // tty.  Use 'script' instead to run a shell in a pty.
-            String [] cmdShell = {
+            // tty.  Use 'script' instead to run a shell in a pty.  And
+            // because BSD and GNU differ on the '-f' vs '-F' flags, we need
+            // two different commands.  Lovely.
+            String [] cmdShellGNU = {
                 "script", "-fqe", "/dev/null"
             };
+            String [] cmdShellBSD = {
+                "script", "-q", "-F", "/dev/null"
+            };
             // Spawn a shell and pass its I/O to the other constructor.
 
             ProcessBuilder pb;
             if (System.getProperty("os.name").startsWith("Windows")) {
                 pb = new ProcessBuilder(cmdShellWindows);
+            } else if (System.getProperty("os.name").startsWith("Mac")) {
+                pb = new ProcessBuilder(cmdShellBSD);
+            } else if (System.getProperty("os.name").startsWith("Linux")) {
+                pb = new ProcessBuilder(cmdShellGNU);
             } else {
-                pb = new ProcessBuilder(cmdShell);
+                // When all else fails, assume GNU.
+                pb = new ProcessBuilder(cmdShellGNU);
             }
             Map<String, String> env = pb.environment();
             env.put("TERM", ECMA48.deviceTypeTerm(deviceType));
@@ -116,6 +197,45 @@ public class TTerminalWindow extends TWindow {
         // Setup the scroll bars
         onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, getWidth(),
                 getHeight()));
+
+        // Claim the keystrokes the emulator will need.
+        addShortcutKeys();
+
+        // Add shortcut text
+        newStatusBar("Terminal session executing...");
+    }
+
+    /**
+     * Terminate the child of the 'script' process used on POSIX.  This may
+     * or may not work.
+     */
+    private void terminateShellChildProcess() {
+        int pid = -1;
+        if (shell.getClass().getName().equals("java.lang.UNIXProcess")) {
+            /* get the PID on unix/linux systems */
+            try {
+                Field field = shell.getClass().getDeclaredField("pid");
+                field.setAccessible(true);
+                pid = field.getInt(shell);
+            } catch (Throwable e) {
+                // SQUASH, this didn't work.  Just bail out quietly.
+                return;
+            }
+        }
+        if (pid != -1) {
+            // shell.destroy() works successfully at killing this side of
+            // 'script'.  But we need to make sure the other side (child
+            // process) is also killed.
+            String [] cmdKillIt = {
+                "pkill", "-P", Integer.toString(pid)
+            };
+            try {
+                Runtime.getRuntime().exec(cmdKillIt);
+            } catch (Throwable e) {
+                // SQUASH, this didn't work.  Just bail out quietly.
+                return;
+            }
+        }
     }
 
     /**
@@ -144,6 +264,11 @@ public class TTerminalWindow extends TWindow {
         onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, getWidth(),
                 getHeight()));
 
+        // Claim the keystrokes the emulator will need.
+        addShortcutKeys();
+
+        // Add shortcut text
+        newStatusBar("Terminal session executing...");
     }
 
     /**
@@ -239,12 +364,13 @@ public class TTerminalWindow extends TWindow {
     /**
      * Handle window close.
      */
-    @Override public void onClose() {
+    @Override
+    public void onClose() {
+        emulator.close();
         if (shell != null) {
+            terminateShellChildProcess();
             shell.destroy();
             shell = null;
-        } else {
-            emulator.close();
         }
     }
 
@@ -286,6 +412,9 @@ public class TTerminalWindow extends TWindow {
                             getTitle(), rc));
                     shell = null;
                     emulator.close();
+                    clearShortcutKeypresses();
+                    statusBar.setText("Terminal session completed, exit " +
+                        "code " + rc + ".");
                 } catch (IllegalThreadStateException e) {
                     // The emulator thread has exited, but the shell Process
                     // hasn't figured that out yet.  Do nothing, we will see
@@ -300,6 +429,9 @@ public class TTerminalWindow extends TWindow {
                             getTitle(), rc));
                     shell = null;
                     emulator.close();
+                    clearShortcutKeypresses();
+                    statusBar.setText("Terminal session completed, exit " +
+                        "code " + rc + ".");
                 } catch (IllegalThreadStateException e) {
                     // The shell is still running, do nothing.
                 }