Home | History | Annotate | Download | only in service
      1 /*
      2  * ConnectBot: simple, powerful, open-source SSH client for Android
      3  * Copyright 2007 Kenny Root, Jeffrey Sharkey
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package org.connectbot.service;
     19 
     20 import android.content.Context;
     21 import android.content.SharedPreferences;
     22 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
     23 import android.graphics.Bitmap;
     24 import android.graphics.Canvas;
     25 import android.graphics.Color;
     26 import android.graphics.Paint;
     27 import android.graphics.Typeface;
     28 import android.graphics.Bitmap.Config;
     29 import android.graphics.Paint.FontMetrics;
     30 import android.text.ClipboardManager;
     31 import android.view.ContextMenu;
     32 import android.view.Menu;
     33 import android.view.View;
     34 import android.view.ContextMenu.ContextMenuInfo;
     35 
     36 import com.googlecode.android_scripting.Log;
     37 import com.googlecode.android_scripting.R;
     38 import com.googlecode.android_scripting.facade.ui.UiFacade;
     39 import com.googlecode.android_scripting.interpreter.InterpreterProcess;
     40 import com.googlecode.android_scripting.jsonrpc.RpcReceiverManager;
     41 import com.googlecode.android_scripting.jsonrpc.RpcReceiverManagerFactory;
     42 
     43 import de.mud.terminal.VDUBuffer;
     44 import de.mud.terminal.VDUDisplay;
     45 import de.mud.terminal.vt320;
     46 
     47 import java.io.IOException;
     48 import java.nio.charset.Charset;
     49 import java.util.LinkedList;
     50 import java.util.List;
     51 
     52 import org.connectbot.TerminalView;
     53 import org.connectbot.transport.AbsTransport;
     54 import org.connectbot.util.Colors;
     55 import org.connectbot.util.PreferenceConstants;
     56 import org.connectbot.util.SelectionArea;
     57 
     58 /**
     59  * Provides a bridge between a MUD terminal buffer and a possible TerminalView. This separation
     60  * allows us to keep the TerminalBridge running in a background service. A TerminalView shares down
     61  * a bitmap that we can use for rendering when available.
     62  *
     63  * @author ConnectBot Dev Team
     64  * @author raaar
     65  *
     66  */
     67 public class TerminalBridge implements VDUDisplay, OnSharedPreferenceChangeListener {
     68 
     69   private final static int FONT_SIZE_STEP = 2;
     70 
     71   private final int[] color = new int[Colors.defaults.length];
     72 
     73   private final TerminalManager manager;
     74 
     75   private final InterpreterProcess mProcess;
     76 
     77   private int mDefaultFgColor;
     78   private int mDefaultBgColor;
     79 
     80   private int scrollback;
     81 
     82   private String delKey;
     83   private String encoding;
     84 
     85   private AbsTransport transport;
     86 
     87   private final Paint defaultPaint;
     88 
     89   private Relay relay;
     90 
     91   private Bitmap bitmap = null;
     92   private final VDUBuffer buffer;
     93 
     94   private TerminalView parent = null;
     95   private final Canvas canvas = new Canvas();
     96 
     97   private boolean forcedSize = false;
     98   private int columns;
     99   private int rows;
    100 
    101   private final TerminalKeyListener keyListener;
    102 
    103   private boolean selectingForCopy = false;
    104   private final SelectionArea selectionArea;
    105   private ClipboardManager clipboard;
    106 
    107   public int charWidth = -1;
    108   public int charHeight = -1;
    109   private int charTop = -1;
    110 
    111   private float fontSize = -1;
    112 
    113   private final List<FontSizeChangedListener> fontSizeChangedListeners;
    114 
    115   /**
    116    * Flag indicating if we should perform a full-screen redraw during our next rendering pass.
    117    */
    118   private boolean fullRedraw = false;
    119 
    120   private final PromptHelper promptHelper;
    121 
    122   /**
    123    * Create a new terminal bridge suitable for unit testing.
    124    */
    125   public TerminalBridge() {
    126     buffer = new vt320() {
    127       @Override
    128       public void write(byte[] b) {
    129       }
    130 
    131       @Override
    132       public void write(int b) {
    133       }
    134 
    135       @Override
    136       public void sendTelnetCommand(byte cmd) {
    137       }
    138 
    139       @Override
    140       public void setWindowSize(int c, int r) {
    141       }
    142 
    143       @Override
    144       public void debug(String s) {
    145       }
    146     };
    147 
    148     manager = null;
    149 
    150     defaultPaint = new Paint();
    151 
    152     selectionArea = new SelectionArea();
    153     scrollback = 1;
    154 
    155     fontSizeChangedListeners = new LinkedList<FontSizeChangedListener>();
    156 
    157     transport = null;
    158 
    159     keyListener = new TerminalKeyListener(manager, this, buffer, null);
    160 
    161     mProcess = null;
    162 
    163     mDefaultFgColor = 0;
    164     mDefaultBgColor = 0;
    165     promptHelper = null;
    166 
    167     updateCharset();
    168   }
    169 
    170   /**
    171    * Create new terminal bridge with following parameters.
    172    */
    173   public TerminalBridge(final TerminalManager manager, InterpreterProcess process, AbsTransport t)
    174       throws IOException {
    175     this.manager = manager;
    176     transport = t;
    177     mProcess = process;
    178 
    179     String string = manager.getStringParameter(PreferenceConstants.SCROLLBACK, null);
    180     if (string != null) {
    181       scrollback = Integer.parseInt(string);
    182     } else {
    183       scrollback = PreferenceConstants.DEFAULT_SCROLLBACK;
    184     }
    185 
    186     string = manager.getStringParameter(PreferenceConstants.FONTSIZE, null);
    187     if (string != null) {
    188       fontSize = Float.parseFloat(string);
    189     } else {
    190       fontSize = PreferenceConstants.DEFAULT_FONT_SIZE;
    191     }
    192 
    193     mDefaultFgColor =
    194         manager.getIntParameter(PreferenceConstants.COLOR_FG, PreferenceConstants.DEFAULT_FG_COLOR);
    195     mDefaultBgColor =
    196         manager.getIntParameter(PreferenceConstants.COLOR_BG, PreferenceConstants.DEFAULT_BG_COLOR);
    197 
    198     delKey = manager.getStringParameter(PreferenceConstants.DELKEY, PreferenceConstants.DELKEY_DEL);
    199 
    200     // create prompt helper to relay password and hostkey requests up to gui
    201     promptHelper = new PromptHelper(this);
    202 
    203     // create our default paint
    204     defaultPaint = new Paint();
    205     defaultPaint.setAntiAlias(true);
    206     defaultPaint.setTypeface(Typeface.MONOSPACE);
    207     defaultPaint.setFakeBoldText(true); // more readable?
    208 
    209     fontSizeChangedListeners = new LinkedList<FontSizeChangedListener>();
    210 
    211     setFontSize(fontSize);
    212 
    213     // create terminal buffer and handle outgoing data
    214     // this is probably status reply information
    215     buffer = new vt320() {
    216       @Override
    217       public void debug(String s) {
    218         Log.d(s);
    219       }
    220 
    221       @Override
    222       public void write(byte[] b) {
    223         try {
    224           if (b != null && transport != null) {
    225             transport.write(b);
    226           }
    227         } catch (IOException e) {
    228           Log.e("Problem writing outgoing data in vt320() thread", e);
    229         }
    230       }
    231 
    232       @Override
    233       public void write(int b) {
    234         try {
    235           if (transport != null) {
    236             transport.write(b);
    237           }
    238         } catch (IOException e) {
    239           Log.e("Problem writing outgoing data in vt320() thread", e);
    240         }
    241       }
    242 
    243       // We don't use telnet sequences.
    244       @Override
    245       public void sendTelnetCommand(byte cmd) {
    246       }
    247 
    248       // We don't want remote to resize our window.
    249       @Override
    250       public void setWindowSize(int c, int r) {
    251       }
    252 
    253       @Override
    254       public void beep() {
    255         if (parent.isShown()) {
    256           manager.playBeep();
    257         }
    258       }
    259     };
    260 
    261     // Don't keep any scrollback if a session is not being opened.
    262 
    263     buffer.setBufferSize(scrollback);
    264 
    265     resetColors();
    266     buffer.setDisplay(this);
    267 
    268     selectionArea = new SelectionArea();
    269 
    270     keyListener = new TerminalKeyListener(manager, this, buffer, encoding);
    271 
    272     updateCharset();
    273 
    274     manager.registerOnSharedPreferenceChangeListener(this);
    275 
    276   }
    277 
    278   /**
    279    * Spawn thread to open connection and start login process.
    280    */
    281   protected void connect() {
    282     transport.setBridge(this);
    283     transport.setManager(manager);
    284     transport.connect();
    285 
    286     ((vt320) buffer).reset();
    287 
    288     // previously tried vt100 and xterm for emulation modes
    289     // "screen" works the best for color and escape codes
    290     ((vt320) buffer).setAnswerBack("screen");
    291 
    292     if (PreferenceConstants.DELKEY_BACKSPACE.equals(delKey)) {
    293       ((vt320) buffer).setBackspace(vt320.DELETE_IS_BACKSPACE);
    294     } else {
    295       ((vt320) buffer).setBackspace(vt320.DELETE_IS_DEL);
    296     }
    297 
    298     // create thread to relay incoming connection data to buffer
    299     relay = new Relay(this, transport, (vt320) buffer, encoding);
    300     Thread relayThread = new Thread(relay);
    301     relayThread.setDaemon(true);
    302     relayThread.setName("Relay");
    303     relayThread.start();
    304 
    305     // force font-size to make sure we resizePTY as needed
    306     setFontSize(fontSize);
    307 
    308   }
    309 
    310   private void updateCharset() {
    311     encoding =
    312         manager.getStringParameter(PreferenceConstants.ENCODING, Charset.defaultCharset().name());
    313     if (relay != null) {
    314       relay.setCharset(encoding);
    315     }
    316     keyListener.setCharset(encoding);
    317   }
    318 
    319   /**
    320    * Inject a specific string into this terminal. Used for post-login strings and pasting clipboard.
    321    */
    322   public void injectString(final String string) {
    323     if (string == null || string.length() == 0) {
    324       return;
    325     }
    326 
    327     Thread injectStringThread = new Thread(new Runnable() {
    328       public void run() {
    329         try {
    330           transport.write(string.getBytes(encoding));
    331         } catch (Exception e) {
    332           Log.e("Couldn't inject string to remote host: ", e);
    333         }
    334       }
    335     });
    336     injectStringThread.setName("InjectString");
    337     injectStringThread.start();
    338   }
    339 
    340   /**
    341    * @return whether a session is open or not
    342    */
    343   public boolean isSessionOpen() {
    344     if (transport != null) {
    345       return transport.isSessionOpen();
    346     }
    347     return false;
    348   }
    349 
    350   /**
    351    * Force disconnection of this terminal bridge.
    352    */
    353   public void dispatchDisconnect(boolean immediate) {
    354 
    355     // Cancel any pending prompts.
    356     promptHelper.cancelPrompt();
    357 
    358     if (immediate) {
    359       manager.closeConnection(TerminalBridge.this, true);
    360     } else {
    361       Thread disconnectPromptThread = new Thread(new Runnable() {
    362         public void run() {
    363           String prompt = null;
    364           if (transport != null && transport.isConnected()) {
    365             prompt = manager.getResources().getString(R.string.prompt_confirm_exit);
    366           } else {
    367             prompt = manager.getResources().getString(R.string.prompt_process_exited);
    368           }
    369           Boolean result = promptHelper.requestBooleanPrompt(null, prompt);
    370 
    371           if (transport != null && transport.isConnected()) {
    372             manager.closeConnection(TerminalBridge.this, result != null && result.booleanValue());
    373           } else if (result != null && result.booleanValue()) {
    374             manager.closeConnection(TerminalBridge.this, false);
    375           }
    376         }
    377       });
    378       disconnectPromptThread.setName("DisconnectPrompt");
    379       disconnectPromptThread.setDaemon(true);
    380       disconnectPromptThread.start();
    381     }
    382   }
    383 
    384   public void setSelectingForCopy(boolean selectingForCopy) {
    385     this.selectingForCopy = selectingForCopy;
    386   }
    387 
    388   public boolean isSelectingForCopy() {
    389     return selectingForCopy;
    390   }
    391 
    392   public SelectionArea getSelectionArea() {
    393     return selectionArea;
    394   }
    395 
    396   public synchronized void tryKeyVibrate() {
    397     manager.tryKeyVibrate();
    398   }
    399 
    400   /**
    401    * Request a different font size. Will make call to parentChanged() to make sure we resize PTY if
    402    * needed.
    403    */
    404   /* package */final void setFontSize(float size) {
    405     if (size <= 0.0) {
    406       return;
    407     }
    408 
    409     defaultPaint.setTextSize(size);
    410     fontSize = size;
    411 
    412     // read new metrics to get exact pixel dimensions
    413     FontMetrics fm = defaultPaint.getFontMetrics();
    414     charTop = (int) Math.ceil(fm.top);
    415 
    416     float[] widths = new float[1];
    417     defaultPaint.getTextWidths("X", widths);
    418     charWidth = (int) Math.ceil(widths[0]);
    419     charHeight = (int) Math.ceil(fm.descent - fm.top);
    420 
    421     // refresh any bitmap with new font size
    422     if (parent != null) {
    423       parentChanged(parent);
    424     }
    425 
    426     for (FontSizeChangedListener ofscl : fontSizeChangedListeners) {
    427       ofscl.onFontSizeChanged(size);
    428     }
    429     forcedSize = false;
    430   }
    431 
    432   /**
    433    * Add an {@link FontSizeChangedListener} to the list of listeners for this bridge.
    434    *
    435    * @param listener
    436    *          listener to add
    437    */
    438   public void addFontSizeChangedListener(FontSizeChangedListener listener) {
    439     fontSizeChangedListeners.add(listener);
    440   }
    441 
    442   /**
    443    * Remove an {@link FontSizeChangedListener} from the list of listeners for this bridge.
    444    *
    445    * @param listener
    446    */
    447   public void removeFontSizeChangedListener(FontSizeChangedListener listener) {
    448     fontSizeChangedListeners.remove(listener);
    449   }
    450 
    451   /**
    452    * Something changed in our parent {@link TerminalView}, maybe it's a new parent, or maybe it's an
    453    * updated font size. We should recalculate terminal size information and request a PTY resize.
    454    */
    455   public final synchronized void parentChanged(TerminalView parent) {
    456     if (manager != null && !manager.isResizeAllowed()) {
    457       Log.d("Resize is not allowed now");
    458       return;
    459     }
    460 
    461     this.parent = parent;
    462     final int width = parent.getWidth();
    463     final int height = parent.getHeight();
    464 
    465     // Something has gone wrong with our layout; we're 0 width or height!
    466     if (width <= 0 || height <= 0) {
    467       return;
    468     }
    469 
    470     clipboard = (ClipboardManager) parent.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
    471     keyListener.setClipboardManager(clipboard);
    472 
    473     if (!forcedSize) {
    474       // recalculate buffer size
    475       int newColumns, newRows;
    476 
    477       newColumns = width / charWidth;
    478       newRows = height / charHeight;
    479 
    480       // If nothing has changed in the terminal dimensions and not an intial
    481       // draw then don't blow away scroll regions and such.
    482       if (newColumns == columns && newRows == rows) {
    483         return;
    484       }
    485 
    486       columns = newColumns;
    487       rows = newRows;
    488     }
    489 
    490     // reallocate new bitmap if needed
    491     boolean newBitmap = (bitmap == null);
    492     if (bitmap != null) {
    493       newBitmap = (bitmap.getWidth() != width || bitmap.getHeight() != height);
    494     }
    495 
    496     if (newBitmap) {
    497       discardBitmap();
    498       bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
    499       canvas.setBitmap(bitmap);
    500     }
    501 
    502     // clear out any old buffer information
    503     defaultPaint.setColor(Color.BLACK);
    504     canvas.drawPaint(defaultPaint);
    505 
    506     // Stroke the border of the terminal if the size is being forced;
    507     if (forcedSize) {
    508       int borderX = (columns * charWidth) + 1;
    509       int borderY = (rows * charHeight) + 1;
    510 
    511       defaultPaint.setColor(Color.GRAY);
    512       defaultPaint.setStrokeWidth(0.0f);
    513       if (width >= borderX) {
    514         canvas.drawLine(borderX, 0, borderX, borderY + 1, defaultPaint);
    515       }
    516       if (height >= borderY) {
    517         canvas.drawLine(0, borderY, borderX + 1, borderY, defaultPaint);
    518       }
    519     }
    520 
    521     try {
    522       // request a terminal pty resize
    523       synchronized (buffer) {
    524         buffer.setScreenSize(columns, rows, true);
    525       }
    526 
    527       if (transport != null) {
    528         transport.setDimensions(columns, rows, width, height);
    529       }
    530     } catch (Exception e) {
    531       Log.e("Problem while trying to resize screen or PTY", e);
    532     }
    533 
    534     // force full redraw with new buffer size
    535     fullRedraw = true;
    536     redraw();
    537 
    538     parent.notifyUser(String.format("%d x %d", columns, rows));
    539 
    540     Log.i(String.format("parentChanged() now width=%d, height=%d", columns, rows));
    541   }
    542 
    543   /**
    544    * Somehow our parent {@link TerminalView} was destroyed. Now we don't need to redraw anywhere,
    545    * and we can recycle our internal bitmap.
    546    */
    547   public synchronized void parentDestroyed() {
    548     parent = null;
    549     discardBitmap();
    550   }
    551 
    552   private void discardBitmap() {
    553     if (bitmap != null) {
    554       bitmap.recycle();
    555     }
    556     bitmap = null;
    557   }
    558 
    559   public void onDraw() {
    560     int fg, bg;
    561     synchronized (buffer) {
    562       boolean entireDirty = buffer.update[0] || fullRedraw;
    563       boolean isWideCharacter = false;
    564 
    565       // walk through all lines in the buffer
    566       for (int l = 0; l < buffer.height; l++) {
    567 
    568         // check if this line is dirty and needs to be repainted
    569         // also check for entire-buffer dirty flags
    570         if (!entireDirty && !buffer.update[l + 1]) {
    571           continue;
    572         }
    573 
    574         // reset dirty flag for this line
    575         buffer.update[l + 1] = false;
    576 
    577         // walk through all characters in this line
    578         for (int c = 0; c < buffer.width; c++) {
    579           int addr = 0;
    580           int currAttr = buffer.charAttributes[buffer.windowBase + l][c];
    581           // check if foreground color attribute is set
    582           if ((currAttr & VDUBuffer.COLOR_FG) != 0) {
    583             int fgcolor = ((currAttr & VDUBuffer.COLOR_FG) >> VDUBuffer.COLOR_FG_SHIFT) - 1;
    584             if (fgcolor < 8 && (currAttr & VDUBuffer.BOLD) != 0) {
    585               fg = color[fgcolor + 8];
    586             } else {
    587               fg = color[fgcolor];
    588             }
    589           } else {
    590             fg = mDefaultFgColor;
    591           }
    592 
    593           // check if background color attribute is set
    594           if ((currAttr & VDUBuffer.COLOR_BG) != 0) {
    595             bg = color[((currAttr & VDUBuffer.COLOR_BG) >> VDUBuffer.COLOR_BG_SHIFT) - 1];
    596           } else {
    597             bg = mDefaultBgColor;
    598           }
    599 
    600           // support character inversion by swapping background and foreground color
    601           if ((currAttr & VDUBuffer.INVERT) != 0) {
    602             int swapc = bg;
    603             bg = fg;
    604             fg = swapc;
    605           }
    606 
    607           // set underlined attributes if requested
    608           defaultPaint.setUnderlineText((currAttr & VDUBuffer.UNDERLINE) != 0);
    609 
    610           isWideCharacter = (currAttr & VDUBuffer.FULLWIDTH) != 0;
    611 
    612           if (isWideCharacter) {
    613             addr++;
    614           } else {
    615             // determine the amount of continuous characters with the same settings and print them
    616             // all at once
    617             while (c + addr < buffer.width
    618                 && buffer.charAttributes[buffer.windowBase + l][c + addr] == currAttr) {
    619               addr++;
    620             }
    621           }
    622 
    623           // Save the current clip region
    624           canvas.save(Canvas.CLIP_SAVE_FLAG);
    625 
    626           // clear this dirty area with background color
    627           defaultPaint.setColor(bg);
    628           if (isWideCharacter) {
    629             canvas.clipRect(c * charWidth, l * charHeight, (c + 2) * charWidth, (l + 1)
    630                 * charHeight);
    631           } else {
    632             canvas.clipRect(c * charWidth, l * charHeight, (c + addr) * charWidth, (l + 1)
    633                 * charHeight);
    634           }
    635           canvas.drawPaint(defaultPaint);
    636 
    637           // write the text string starting at 'c' for 'addr' number of characters
    638           defaultPaint.setColor(fg);
    639           if ((currAttr & VDUBuffer.INVISIBLE) == 0) {
    640             canvas.drawText(buffer.charArray[buffer.windowBase + l], c, addr, c * charWidth,
    641                 (l * charHeight) - charTop, defaultPaint);
    642           }
    643 
    644           // Restore the previous clip region
    645           canvas.restore();
    646 
    647           // advance to the next text block with different characteristics
    648           c += addr - 1;
    649           if (isWideCharacter) {
    650             c++;
    651           }
    652         }
    653       }
    654 
    655       // reset entire-buffer flags
    656       buffer.update[0] = false;
    657     }
    658     fullRedraw = false;
    659   }
    660 
    661   public void redraw() {
    662     if (parent != null) {
    663       parent.postInvalidate();
    664     }
    665   }
    666 
    667   // We don't have a scroll bar.
    668   public void updateScrollBar() {
    669   }
    670 
    671   /**
    672    * Resize terminal to fit [rows]x[cols] in screen of size [width]x[height]
    673    *
    674    * @param rows
    675    * @param cols
    676    * @param width
    677    * @param height
    678    */
    679   public synchronized void resizeComputed(int cols, int rows, int width, int height) {
    680     float size = 8.0f;
    681     float step = 8.0f;
    682     float limit = 0.125f;
    683 
    684     int direction;
    685 
    686     while ((direction = fontSizeCompare(size, cols, rows, width, height)) < 0) {
    687       size += step;
    688     }
    689 
    690     if (direction == 0) {
    691       Log.d(String.format("Fontsize: found match at %f", size));
    692       return;
    693     }
    694 
    695     step /= 2.0f;
    696     size -= step;
    697 
    698     while ((direction = fontSizeCompare(size, cols, rows, width, height)) != 0 && step >= limit) {
    699       step /= 2.0f;
    700       if (direction > 0) {
    701         size -= step;
    702       } else {
    703         size += step;
    704       }
    705     }
    706 
    707     if (direction > 0) {
    708       size -= step;
    709     }
    710 
    711     columns = cols;
    712     this.rows = rows;
    713     setFontSize(size);
    714     forcedSize = true;
    715   }
    716 
    717   private int fontSizeCompare(float size, int cols, int rows, int width, int height) {
    718     // read new metrics to get exact pixel dimensions
    719     defaultPaint.setTextSize(size);
    720     FontMetrics fm = defaultPaint.getFontMetrics();
    721 
    722     float[] widths = new float[1];
    723     defaultPaint.getTextWidths("X", widths);
    724     int termWidth = (int) widths[0] * cols;
    725     int termHeight = (int) Math.ceil(fm.descent - fm.top) * rows;
    726 
    727     Log.d(String.format("Fontsize: font size %f resulted in %d x %d", size, termWidth, termHeight));
    728 
    729     // Check to see if it fits in resolution specified.
    730     if (termWidth > width || termHeight > height) {
    731       return 1;
    732     }
    733 
    734     if (termWidth == width || termHeight == height) {
    735       return 0;
    736     }
    737 
    738     return -1;
    739   }
    740 
    741   /*
    742    * (non-Javadoc)
    743    *
    744    * @see de.mud.terminal.VDUDisplay#setVDUBuffer(de.mud.terminal.VDUBuffer)
    745    */
    746   @Override
    747   public void setVDUBuffer(VDUBuffer buffer) {
    748   }
    749 
    750   /*
    751    * (non-Javadoc)
    752    *
    753    * @see de.mud.terminal.VDUDisplay#setColor(byte, byte, byte, byte)
    754    */
    755   public void setColor(int index, int red, int green, int blue) {
    756     // Don't allow the system colors to be overwritten for now. May violate specs.
    757     if (index < color.length && index >= 16) {
    758       color[index] = 0xff000000 | red << 16 | green << 8 | blue;
    759     }
    760   }
    761 
    762   public final void resetColors() {
    763     System.arraycopy(Colors.defaults, 0, color, 0, Colors.defaults.length);
    764   }
    765 
    766   public TerminalKeyListener getKeyHandler() {
    767     return keyListener;
    768   }
    769 
    770   public void resetScrollPosition() {
    771     // if we're in scrollback, scroll to bottom of window on input
    772     if (buffer.windowBase != buffer.screenBase) {
    773       buffer.setWindowBase(buffer.screenBase);
    774     }
    775   }
    776 
    777   public void increaseFontSize() {
    778     setFontSize(fontSize + FONT_SIZE_STEP);
    779   }
    780 
    781   public void decreaseFontSize() {
    782     setFontSize(fontSize - FONT_SIZE_STEP);
    783   }
    784 
    785   public int getId() {
    786     return mProcess.getPort();
    787   }
    788 
    789   public String getName() {
    790     return mProcess.getName();
    791   }
    792 
    793   public InterpreterProcess getProcess() {
    794     return mProcess;
    795   }
    796 
    797   public int getForegroundColor() {
    798     return mDefaultFgColor;
    799   }
    800 
    801   public int getBackgroundColor() {
    802     return mDefaultBgColor;
    803   }
    804 
    805   public VDUBuffer getVDUBuffer() {
    806     return buffer;
    807   }
    808 
    809   public PromptHelper getPromptHelper() {
    810     return promptHelper;
    811   }
    812 
    813   public Bitmap getBitmap() {
    814     return bitmap;
    815   }
    816 
    817   public AbsTransport getTransport() {
    818     return transport;
    819   }
    820 
    821   public Paint getPaint() {
    822     return defaultPaint;
    823   }
    824 
    825   public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
    826     if (mProcess.isAlive()) {
    827       RpcReceiverManagerFactory rpcReceiverManagerFactory = mProcess.getRpcReceiverManagerFactory();
    828       for (RpcReceiverManager manager : rpcReceiverManagerFactory.getRpcReceiverManagers().values()) {
    829         UiFacade facade = manager.getReceiver(UiFacade.class);
    830         facade.onCreateContextMenu(menu, v, menuInfo);
    831       }
    832     }
    833   }
    834 
    835   public boolean onPrepareOptionsMenu(Menu menu) {
    836     boolean returnValue = false;
    837     if (mProcess.isAlive()) {
    838       RpcReceiverManagerFactory rpcReceiverManagerFactory = mProcess.getRpcReceiverManagerFactory();
    839       for (RpcReceiverManager manager : rpcReceiverManagerFactory.getRpcReceiverManagers().values()) {
    840         UiFacade facade = manager.getReceiver(UiFacade.class);
    841         returnValue = returnValue || facade.onPrepareOptionsMenu(menu);
    842       }
    843       return returnValue;
    844     }
    845     return false;
    846   }
    847 
    848   public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
    849     if (PreferenceConstants.ENCODING.equals(key)) {
    850       updateCharset();
    851     } else if (PreferenceConstants.FONTSIZE.equals(key)) {
    852       String string = manager.getStringParameter(PreferenceConstants.FONTSIZE, null);
    853       if (string != null) {
    854         fontSize = Float.parseFloat(string);
    855       } else {
    856         fontSize = PreferenceConstants.DEFAULT_FONT_SIZE;
    857       }
    858       setFontSize(fontSize);
    859       fullRedraw = true;
    860     } else if (PreferenceConstants.SCROLLBACK.equals(key)) {
    861       String string = manager.getStringParameter(PreferenceConstants.SCROLLBACK, null);
    862       if (string != null) {
    863         scrollback = Integer.parseInt(string);
    864       } else {
    865         scrollback = PreferenceConstants.DEFAULT_SCROLLBACK;
    866       }
    867       buffer.setBufferSize(scrollback);
    868     } else if (PreferenceConstants.COLOR_FG.equals(key)) {
    869       mDefaultFgColor =
    870           manager.getIntParameter(PreferenceConstants.COLOR_FG,
    871               PreferenceConstants.DEFAULT_FG_COLOR);
    872       fullRedraw = true;
    873     } else if (PreferenceConstants.COLOR_BG.equals(key)) {
    874       mDefaultBgColor =
    875           manager.getIntParameter(PreferenceConstants.COLOR_BG,
    876               PreferenceConstants.DEFAULT_BG_COLOR);
    877       fullRedraw = true;
    878     }
    879     if (PreferenceConstants.DELKEY.equals(key)) {
    880       delKey =
    881           manager.getStringParameter(PreferenceConstants.DELKEY, PreferenceConstants.DELKEY_DEL);
    882       if (PreferenceConstants.DELKEY_BACKSPACE.equals(delKey)) {
    883         ((vt320) buffer).setBackspace(vt320.DELETE_IS_BACKSPACE);
    884       } else {
    885         ((vt320) buffer).setBackspace(vt320.DELETE_IS_DEL);
    886       }
    887     }
    888   }
    889 }
    890