public abstract class InputJPanel {

    protected static Logger logger = Logger.getLogger(InputJPanel.class);
    
	KeyCode_FileBased newKeyMapper = null;

	protected Vector pressedKeys;

	protected static boolean capsLockOn = false;
	protected static boolean numLockOn = false;
	protected static boolean scrollLockOn = false;

	protected static boolean serverAltDown = false;
    protected static boolean altDown = false;
	protected static boolean ctrlDown = false;

    protected static long last_mousemove = 0;
    
	// Using this flag value (0x0001) seems to do nothing, and after running
	// through other possible values, the RIGHT flag does not appear to be
	// implemented
	protected static final int KBD_FLAG_RIGHT = 0x0001;
	protected static final int KBD_FLAG_EXT = 0x0100;

	// QUIET flag is actually as below (not 0x1000 as in rdesktop)
	protected static final int KBD_FLAG_QUIET = 0x200;
	protected static final int KBD_FLAG_DOWN = 0x4000;
	protected static final int KBD_FLAG_UP = 0x8000;

	protected static final int RDP_KEYPRESS = 0;
	protected static final int RDP_KEYRELEASE = KBD_FLAG_DOWN | KBD_FLAG_UP;
	protected static final int MOUSE_FLAG_MOVE = 0x0800;

	protected static final int MOUSE_FLAG_BUTTON1 = 0x1000;
	protected static final int MOUSE_FLAG_BUTTON2 = 0x2000;
	protected static final int MOUSE_FLAG_BUTTON3 = 0x4000;

	protected static final int MOUSE_FLAG_BUTTON4 = 0x0280; // wheel up -
	                                                        // rdesktop 1.2.0
	protected static final int MOUSE_FLAG_BUTTON5 = 0x0380; // wheel down -
															// rdesktop 1.2.0
	protected static final int MOUSE_FLAG_DOWN = 0x8000;

    protected static final int RDP_INPUT_SYNCHRONIZE = 0;
	protected static final int RDP_INPUT_CODEPOINT = 1;
	protected static final int RDP_INPUT_VIRTKEY = 2;
	protected static final int RDP_INPUT_SCANCODE = 4;
	protected static final int RDP_INPUT_MOUSE = 0x8001;

	protected static int time = 0;

	public KeyEvent lastKeyEvent = null;
	public boolean modifiersValid = false;
	public boolean keyDownWindows = false;

	protected JPanel canvas = null;
	protected RdesktopJFrame frame = null;
	protected RdpJPanel rdp = null;
	KeyCode keys = null;
	public static boolean key_Code=true;
	public static int keyCode=0xfffffff;
    /**
     * Create a new Input object with a given keymap object
     * @param c Canvas on which to listen for input events
     * @param r Rdp layer on which to send input messages
     * @param k Key map to use in handling keyboard events
     */
	public InputJPanel(JPanel c, RdpJPanel r, KeyCode_FileBased k) {
		newKeyMapper = k;
		canvas = c;
		Window fm = SwingUtilities.getWindowAncestor(canvas);
		if(fm instanceof RdesktopJFrame)
			this.frame = (RdesktopJFrame)fm;
		rdp = r;
		if (Options.debug_keyboard)
			logger.setLevel(Level.DEBUG);
		addInputListeners();
		pressedKeys = new Vector();
	}

    /**
     * Create a new Input object, using a keymap generated from a specified file
     * @param c Canvas on which to listen for input events
     * @param r Rdp layer on which to send input messages
     * @param keymapFile Path to file containing keymap data
     */
	public InputJPanel(JPanel c, RdpJPanel r, String keymapFile) {
		try {
			newKeyMapper = new KeyCode_FileBased_Localised(keymapFile);
		} catch (KeyMapException kmEx) {
			System.err.println(kmEx.getMessage());
			if(!Common.underApplet) System.exit(-1);
		}

		canvas = c;
		this.frame = (RdesktopJFrame) SwingUtilities.getWindowAncestor(canvas);
		rdp = r;
		if (Options.debug_keyboard)
			logger.setLevel(Level.DEBUG);
		addInputListeners();
		pressedKeys = new Vector();
	}

    /**
     * Add all relevant input listeners to the canvas
    */
	public void addInputListeners() {
		canvas.addMouseListener(new RdesktopMouseAdapter());
		canvas.addMouseMotionListener(new RdesktopMouseMotionAdapter());
		canvas.addKeyListener(new RdesktopKeyAdapter());
	}

	
	/**
     * Send a sequence of key actions to the server
     * @param pressSequence String representing a sequence of key actions.
     *                      Actions are represented as a pair of consecutive characters,
     *                      the first character's value (cast to integer) being the scancode
     *                      to send, the second (cast to integer) of the pair representing the action
     *                      (0 == UP, 1 == DOWN, 2 == QUIET UP, 3 == QUIET DOWN). 
	 */
	public void sendKeyPresses(String pressSequence) {
		try {
			String debugString = "Sending keypresses: "+pressSequence;
			for (int i = 0; i < pressSequence.length(); i += 2) {
				int scancode = (int) pressSequence.charAt(i);
				int action = (int) pressSequence.charAt(i + 1);
				int flags = 0;

				if (action == KeyCode_FileBased.UP)
					flags = RDP_KEYRELEASE;
				else if (action == KeyCode_FileBased.DOWN)
					flags = RDP_KEYPRESS;
				else if (action == KeyCode_FileBased.QUIETUP)
					flags = RDP_KEYRELEASE | KBD_FLAG_QUIET;
				else if (action == KeyCode_FileBased.QUIETDOWN)
					flags = RDP_KEYPRESS | KBD_FLAG_QUIET;

				long t = getTime();

				debugString += "(0x"
						+ Integer.toHexString(scancode)
						+ ", "
						+ ((action == KeyCode_FileBased.UP || action == KeyCode_FileBased.QUIETUP) ? "up"
								: "down")
						+ ((flags & KBD_FLAG_QUIET) != 0 ? " quiet" : "")
						+ " at " + t + ")";
//				System.out.println("debugString);
				sendScancode(t, flags, scancode);
			}
//			System.out.println("debugString");
			if (pressSequence.length() > 0)
				logger.debug(debugString);
		} catch (Exception ex) {
//			System.out.println("debugString");
			return;
		}
	}

    /**
     * Retrieve the next "timestamp", by incrementing previous
     * stamp (up to the maximum value of an integer, at which the
     * timestamp is reverted to 1)
     * @return New timestamp value
     */
	public static int getTime() {
		time++;
		if (time == Integer.MAX_VALUE)
			time = 1;
		return time;
	}

    /**
     * Handle loss of focus to the main canvas.
     * Clears all depressed keys (sending release messages
     * to the server.
     */
	public void lostFocus() {
        clearKeys();
		modifiersValid = false;
	}

    /**
     * Handle the main canvas gaining focus.
     * Check locking key states.
     */
    public void gainedFocus() {
		doLockKeys(); // ensure lock key states are correct
	}

    /**
     * Send a keyboard event to the server
     * @param time Time stamp to identify this event
     * @param flags Flags defining the nature of the event (eg: press/release/quiet/extended)
     * @param scancode Scancode value identifying the key in question
     */
	public void sendScancode(long time, int flags, int scancode) {

        if(scancode == 0x38){ // be careful with alt
            if((flags & RDP_KEYRELEASE) != 0){
                //logger.info("Alt release, serverAltDown = " + serverAltDown);
                serverAltDown = false;
            }
            if((flags == RDP_KEYPRESS)){
                //logger.info("Alt press, serverAltDown = " + serverAltDown);
                serverAltDown = true;
            }
        }
        
		if ((scancode & KeyCode.SCANCODE_EXTENDED) != 0) {
			rdp.sendInput((int) time, RDP_INPUT_SCANCODE, flags | KBD_FLAG_EXT,
					scancode & ~KeyCode.SCANCODE_EXTENDED, 0);
		} else
			rdp.sendInput((int) time, RDP_INPUT_SCANCODE, flags, scancode, 0);
	}

    /**
     * Release any modifier keys that may be depressed.
     */
	public void clearKeys() {
		if (!modifiersValid)
			return;
        
        altDown = false;
        ctrlDown = false;
        
        if(lastKeyEvent == null) return;
        
		if (lastKeyEvent.isShiftDown())
			sendScancode(getTime(), RDP_KEYRELEASE, 0x2a); // shift
		if (lastKeyEvent.isAltDown() || serverAltDown){
            sendScancode(getTime(), RDP_KEYRELEASE, 0x38); // ALT
            sendScancode(getTime(), RDP_KEYPRESS | KBD_FLAG_QUIET, 0x38); // ALT
            sendScancode(getTime(), RDP_KEYRELEASE | KBD_FLAG_QUIET, 0x38); // l.alt
        }
		if (lastKeyEvent.isControlDown()){
            sendScancode(getTime(), RDP_KEYRELEASE, 0x1d); // l.ctrl    
            //sendScancode(getTime(), RDP_KEYPRESS | KBD_FLAG_QUIET, 0x1d); // Ctrl
            //sendScancode(getTime(), RDP_KEYRELEASE | KBD_FLAG_QUIET, 0x1d); // ctrl
        }
		
	}

    /**
     * Send keypress events for any modifier keys that are currently down
     */
	public void setKeys() {
		if (!modifiersValid)
			return;


        if(lastKeyEvent == null) return;
        
		if (lastKeyEvent.isShiftDown())
			sendScancode(getTime(), RDP_KEYPRESS, 0x2a); // shift
		if (lastKeyEvent.isAltDown())
			sendScancode(getTime(), RDP_KEYPRESS, 0x38); // l.alt
		if (lastKeyEvent.isControlDown())
			sendScancode(getTime(), RDP_KEYPRESS, 0x1d); // l.ctrl
	}

	class RdesktopKeyAdapter extends KeyAdapter {

        /**
         * Construct an RdesktopKeyAdapter based on the parent KeyAdapter class
         */
		public RdesktopKeyAdapter() {
			super();
		}

        /**
         * Handle a keyPressed event, sending any relevant keypresses to the server
         */
		public void keyPressed(KeyEvent e) {
//			System.out.println("PRESSED keychar='" + e.getKeyChar() + "' keycode=0x"
//					+ Integer.toHexString(e.getKeyCode()) + " char='"
//					+ ((char) e.getKeyCode()) + "'");
			lastKeyEvent = e;
			modifiersValid = true;
			long time = getTime();

			// Some java versions have keys that don't generate keyPresses -
			// here we add the key so we can later check if it happened
			pressedKeys.addElement(new Integer(e.getKeyCode()));

			logger.debug("PRESSED keychar='" + e.getKeyChar() + "' keycode=0x"
					+ Integer.toHexString(e.getKeyCode()) + " char='"
					+ ((char) e.getKeyCode()) + "'");

			if (rdp != null) {
				if (!handleSpecialKeys(time, e, true)) {
					//send
					sendKeyPresses(newKeyMapper.getKeyStrokes(e));
				}
				// sendScancode(time, RDP_KEYPRESS, keys.getScancode(e));
			}
		}

		private void hideFrameMenu(){
			if(frame != null)
				frame.hideMenu();
		}
		
        /**
         * Handle a keyTyped event, sending any relevant keypresses to the server
         */
		public void keyTyped(KeyEvent e) {
			hideFrameMenu();
//			System.out.println("TYPED keychar='" + e.getKeyChar() + "' keycode=0x"
//					+ Integer.toHexString(e.getKeyCode()) + " char='"
//					+ ((char) e.getKeyCode()) + "'");
			lastKeyEvent = e;
			modifiersValid = true;
			long time = getTime();

			// Some java versions have keys that don't generate keyPresses -
			// here we add the key so we can later check if it happened
			pressedKeys.addElement(new Integer(e.getKeyCode()));

			logger.debug("TYPED keychar='" + e.getKeyChar() + "' keycode=0x"
					+ Integer.toHexString(e.getKeyCode()) + " char='"
					+ ((char) e.getKeyCode()) + "'");

			if (rdp != null) {
				if (!handleSpecialKeys(time, e, true))
					sendKeyPresses(newKeyMapper.getKeyStrokes(e));
				// sendScancode(time, RDP_KEYPRESS, keys.getScancode(e));
			}
		}

        /**
         * Handle a keyReleased event, sending any relevent key events to the server
         */
		public void keyReleased(KeyEvent e) {
			hideFrameMenu();
			Integer keycode = new Integer(e.getKeyCode());
			InputJPanel.keyCode=keycode;

			if (!pressedKeys.contains(keycode)) {
				this.keyPressed(e);
			}

			pressedKeys.removeElement(keycode);

			lastKeyEvent = e;
			modifiersValid = true;
			long time = getTime();

			logger.debug("RELEASED keychar='" + e.getKeyChar() + "' keycode=0x"
					+ Integer.toHexString(e.getKeyCode()) + " char='"
					+ ((char) e.getKeyCode()) + "'");
			if (rdp != null) {
				if (!handleSpecialKeys(time, e, false))
					sendKeyPresses(newKeyMapper.getKeyStrokes(e));
				// sendScancode(time, RDP_KEYRELEASE, keys.getScancode(e));
			}
//			if(e.getY() != 0) 
			hideFrameMenu();
		}

	}
	
    /**
     * Act on any keyboard shortcuts that a specified KeyEvent may describe
     * @param time Time stamp for event to send to server
     * @param e Keyboard event to be checked for shortcut keys
     * @param pressed True if key was pressed, false if released
     * @return True if a shortcut key combination was detected and acted upon, false otherwise
     */
	public boolean handleShortcutKeys(long time, KeyEvent e, boolean pressed) {
		if(!e.isAltDown()) return false;
        
        if (!altDown) return false; // all of the below have ALT on

		switch (e.getKeyCode()) {

		/* case KeyEvent.VK_M:
			if(pressed) ((RdesktopFrame_Localised) canvas.getParent()).toggleMenu();
			break; */
		
		case KeyEvent.VK_ENTER:
            sendScancode(time, RDP_KEYRELEASE, 0x38);
            altDown = false;
            //@deprecated ((RdesktopJPanel_Localised) canvas.getParent()).toggleFullScreen();
			break;

			/*
			 * The below case block handles "real" ALT+TAB events. Once the TAB in
			 * an ALT+TAB combination has been pressed, the TAB is sent to the
			 * server with the quiet flag on, as is the subsequent ALT-up.
			 * 
			 * This ensures that the initial ALT press is "undone" by the server.
			 * 
			 * --- Tom Elliott, 7/04/05
			 */
			
		case KeyEvent.VK_TAB: // Alt+Tab received, quiet combination

			sendScancode(time, (pressed ? RDP_KEYPRESS : RDP_KEYRELEASE)
					| KBD_FLAG_QUIET, 0x0f);
			if (!pressed) {
				sendScancode(time, RDP_KEYRELEASE | KBD_FLAG_QUIET, 0x38); // Release
																			// Alt
			}

			if (pressed)
				logger.debug("Alt + Tab pressed, ignoring, releasing tab");
			break;
		case KeyEvent.VK_PAGE_UP: // Alt + PgUp = Alt-Tab
			sendScancode(time, pressed ? RDP_KEYPRESS : RDP_KEYRELEASE, 0x0f); // TAB
			if (pressed)
				logger.debug("shortcut pressed: sent ALT+TAB");
			break;
		case KeyEvent.VK_PAGE_DOWN: // Alt + PgDown = Alt-Shift-Tab
			if (pressed) {
				sendScancode(time, RDP_KEYPRESS, 0x2a); // Shift
				sendScancode(time, RDP_KEYPRESS, 0x0f); // TAB
				logger.debug("shortcut pressed: sent ALT+SHIFT+TAB");
			} else {
				sendScancode(time, RDP_KEYRELEASE, 0x0f); // TAB
				sendScancode(time, RDP_KEYRELEASE, 0x2a); // Shift
			}

			break;
		case KeyEvent.VK_INSERT: // Alt + Insert = Alt + Esc
			sendScancode(time, pressed ? RDP_KEYPRESS : RDP_KEYRELEASE, 0x01); // ESC
			if (pressed)
				logger.debug("shortcut pressed: sent ALT+ESC");
			break;
		case KeyEvent.VK_HOME: // Alt + Home = Ctrl + Esc (Start)
			if (pressed) {
				sendScancode(time, RDP_KEYRELEASE, 0x38); // ALT
				sendScancode(time, RDP_KEYPRESS, 0x1d); // left Ctrl
				sendScancode(time, RDP_KEYPRESS, 0x01); // Esc
				logger.debug("shortcut pressed: sent CTRL+ESC (Start)");

			} else {
				sendScancode(time, RDP_KEYRELEASE, 0x01); // escape
				sendScancode(time, RDP_KEYRELEASE, 0x1d); // left ctrl
				// sendScancode(time,RDP_KEYPRESS,0x38); // ALT
			}

			break;
		case KeyEvent.VK_END: // Ctrl+Alt+End = Ctrl+Alt+Del
			if (ctrlDown) {
				sendScancode(time, pressed ? RDP_KEYPRESS : RDP_KEYRELEASE,
						0x53 | KeyCode.SCANCODE_EXTENDED); // DEL
				if (pressed)
					logger.debug("shortcut pressed: sent CTRL+ALT+DEL");
			}
			break;
		case KeyEvent.VK_DELETE: // Alt + Delete = Menu
			if (pressed) {
				sendScancode(time, RDP_KEYRELEASE, 0x38); // ALT
				// need to do another press and release to shift focus from
				// to/from menu bar
				sendScancode(time, RDP_KEYPRESS, 0x38); // ALT
				sendScancode(time, RDP_KEYRELEASE, 0x38); // ALT
				sendScancode(time, RDP_KEYPRESS,
						0x5d | KeyCode.SCANCODE_EXTENDED); // Menu
				logger.debug("shortcut pressed: sent MENU");
			} else {
				sendScancode(time, RDP_KEYRELEASE,
						0x5d | KeyCode.SCANCODE_EXTENDED); // Menu
				// sendScancode(time,RDP_KEYPRESS,0x38); // ALT
			}
			break;
		case KeyEvent.VK_SUBTRACT: // Ctrl + Alt + Minus (on NUM KEYPAD) =
									// Alt+PrtSc
			if (ctrlDown) {
				if (pressed) {
					sendScancode(time, RDP_KEYRELEASE, 0x1d); // Ctrl
					sendScancode(time, RDP_KEYPRESS,
							0x37 | KeyCode.SCANCODE_EXTENDED); // PrtSc
					logger.debug("shortcut pressed: sent ALT+PRTSC");
				} else {
					sendScancode(time, RDP_KEYRELEASE,
							0x37 | KeyCode.SCANCODE_EXTENDED); // PrtSc
					sendScancode(time, RDP_KEYPRESS, 0x1d); // Ctrl
				}
			}
			break;
		case KeyEvent.VK_ADD: // Ctrl + ALt + Plus (on NUM KEYPAD) = PrtSc
		case KeyEvent.VK_EQUALS: // for laptops that can't do Ctrl-Alt+Plus
			if (ctrlDown) {
				if (pressed) {
					sendScancode(time, RDP_KEYRELEASE, 0x38); // Alt
					sendScancode(time, RDP_KEYRELEASE, 0x1d); // Ctrl
					sendScancode(time, RDP_KEYPRESS,
							0x37 | KeyCode.SCANCODE_EXTENDED); // PrtSc
					logger.debug("shortcut pressed: sent PRTSC");
				} else {
					sendScancode(time, RDP_KEYRELEASE,
							0x37 | KeyCode.SCANCODE_EXTENDED); // PrtSc
					sendScancode(time, RDP_KEYPRESS, 0x1d); // Ctrl
					sendScancode(time, RDP_KEYPRESS, 0x38); // Alt
				}
			}
			break;
		default:
			return false;
		}
		return true;
	}

    /**
     * Deal with modifier keys as control, alt or caps lock
     * @param time Time stamp for key event
     * @param e Key event to check for special keys
     * @param pressed True if key was pressed, false if released
     * @return
     */
	public boolean handleSpecialKeys(long time, KeyEvent e, boolean pressed) {
		if (handleShortcutKeys(time, e, pressed))
			return true;

		switch (e.getKeyCode()) {
		case KeyEvent.VK_CONTROL:
			ctrlDown = pressed;
			return false;
		case KeyEvent.VK_ALT:
			altDown = pressed;
			return false;
		case KeyEvent.VK_CAPS_LOCK:
			if (pressed && Options.caps_sends_up_and_down)
				capsLockOn = !capsLockOn;
			if (!Options.caps_sends_up_and_down) {
				if (pressed)
					capsLockOn = true;
				else
					capsLockOn = false;
			}
			return false;
		case KeyEvent.VK_NUM_LOCK:
			if (pressed)
				numLockOn = !numLockOn;
			return false;
		case KeyEvent.VK_SCROLL_LOCK:
			if (pressed)
				scrollLockOn = !scrollLockOn;
			return false;
		case KeyEvent.VK_PAUSE: // untested
			if (pressed) { // E1 1D 45 E1 9D C5
				rdp.sendInput((int) time, RDP_INPUT_SCANCODE, RDP_KEYPRESS,
						0xe1, 0);
				rdp.sendInput((int) time, RDP_INPUT_SCANCODE, RDP_KEYPRESS,
						0x1d, 0);
				rdp.sendInput((int) time, RDP_INPUT_SCANCODE, RDP_KEYPRESS,
						0x45, 0);
				rdp.sendInput((int) time, RDP_INPUT_SCANCODE, RDP_KEYPRESS,
						0xe1, 0);
				rdp.sendInput((int) time, RDP_INPUT_SCANCODE, RDP_KEYPRESS,
						0x9d, 0);
				rdp.sendInput((int) time, RDP_INPUT_SCANCODE, RDP_KEYPRESS,
						0xc5, 0);
			} else { // release left ctrl
				rdp.sendInput((int) time, RDP_INPUT_SCANCODE, RDP_KEYRELEASE,
						0x1d, 0);
			}
			break;

		// Removed, as java on MacOS send the option key as VK_META
		/*
		 * case KeyEvent.VK_META: // Windows key logger.debug("Windows key
		 * received"); if(pressed){ sendScancode(time, RDP_KEYPRESS, 0x1d); //
		 * left ctrl sendScancode(time, RDP_KEYPRESS, 0x01); // escape } else{
		 * sendScancode(time, RDP_KEYRELEASE, 0x01); // escape
		 * sendScancode(time, RDP_KEYRELEASE, 0x1d); // left ctrl } break;
		 */

		// haven't found a way to detect BREAK key in java - VK_BREAK doesn't
		// exist
		/*
		 * case KeyEvent.VK_BREAK: if(pressed){
		 * sendScancode(time,RDP_KEYPRESS,(KeyCode.SCANCODE_EXTENDED | 0x46));
		 * sendScancode(time,RDP_KEYPRESS,(KeyCode.SCANCODE_EXTENDED | 0xc6)); } //
		 * do nothing on release break;
		 */
		default:
			return false; // not handled - use sendScancode instead
		}
		return true; // handled - no need to use sendScancode
	}

    /**
     * Turn off any locking key, check states if available
     */
	public void triggerReadyToSend() {
		capsLockOn = false;
		numLockOn = false;
		scrollLockOn = false;
		doLockKeys(); // ensure lock key states are correct
	}

	protected void doLockKeys() {
	}

	/**
	 * Send CTRL-ALT-DEL combination.
	 */
	public void sendCtrlAltDel()
	{
		sendScancode(getTime(), RDP_KEYPRESS, 0x1d);	// CTRL
		sendScancode(getTime(), RDP_KEYPRESS, 0x38);	// ALT
		sendScancode(getTime(), RDP_KEYPRESS,
			     0x53 | KeyCode.SCANCODE_EXTENDED);	// DEL

		sendScancode(getTime(), RDP_KEYRELEASE,
			     0x53 | KeyCode.SCANCODE_EXTENDED);	// DEL
		sendScancode(getTime(), RDP_KEYRELEASE, 0x38);	// ALT
		sendScancode(getTime(), RDP_KEYRELEASE, 0x1d);	// CTRL
	}
	/**
	 * Send key��press
	 */
	public void sendKeyByPress(int key)
	{
		sendScancode(getTime(), RDP_KEYPRESS, key);	
	}
	/**
	 * Send key��release
	 */
	public void sendKeyByRelease(int key)
	{
		sendScancode(getTime(), RDP_KEYRELEASE, key);	// CTRL
	}
	
	/**
	 * Send CTRL-Shift combination.
	 */
	public void sendCtrlShift()
	{
		sendScancode(getTime(), RDP_KEYPRESS, 0x1d);	// CTRL
		sendScancode(getTime(), RDP_KEYPRESS, 0x2a);	// SHIFT
		sendScancode(getTime(), RDP_KEYRELEASE, 0x2a);	// SHIFT
		sendScancode(getTime(), RDP_KEYRELEASE, 0x1d);	// CTRL
	}
	/**
	 * Send CTRL-Blank combination
	 */
	public void sendCtrlBlank()
	{
		sendScancode(getTime(), RDP_KEYPRESS, 0x1d);
		sendScancode(getTime(), RDP_KEYPRESS, 0x39);
		sendScancode(getTime(), RDP_KEYRELEASE, 0x39);
		sendScancode(getTime(), RDP_KEYRELEASE, 0x1d);
		
	}
	/**
	 * Send One by split info
	 */
	public void sendOneByKeysRdp5()
	{		//"*	"	"
		if(InputJPanel.key_Code)
		{
			sendKeyPresses("78");//*	 
			InputJPanel.key_Code=false;
		}
		else
		{
			sendKeyPresses("58");//*	
			InputJPanel.key_Code=true;
		}
//		Integer keycode = new Integer(106);
//		pressedKeys.removeElement(keycode);
//		sendKeyPresses("");
	}
    /**
     * Handle pressing of the middle mouse button, sending relevent event data to the server
     * @param e MouseEvent detailing circumstances under which middle button was pressed
     */
	protected void middleButtonPressed(MouseEvent e) {
		/*
		 * if (Options.paste_hack && ctrlDown){ try{ canvas.setBusyCursor();
		 * }catch (RdesktopException ex){ logger.warn(ex.getMessage()); } if
		 * (capsLockOn){ logger.debug("Turning caps lock off for paste"); //
		 * turn caps lock off sendScancode(getTime(), RDP_KEYPRESS, 0x3a); //
		 * caps lock sendScancode(getTime(), RDP_KEYRELEASE, 0x3a); // caps lock }
		 * paste(); if (capsLockOn){ // turn caps lock back on
		 * logger.debug("Turning caps lock back on after paste");
		 * sendScancode(getTime(), RDP_KEYPRESS, 0x3a); // caps lock
		 * sendScancode(getTime(), RDP_KEYRELEASE, 0x3a); // caps lock }
		 * canvas.unsetBusyCursor(); } else
		 */
		rdp.sendInput(time, RDP_INPUT_MOUSE, MOUSE_FLAG_BUTTON3
				| MOUSE_FLAG_DOWN, e.getX(), e.getY());
	}

    /**
     * Handle release of the middle mouse button, sending relevent event data to the server
     * @param e MouseEvent detailing circumstances under which middle button was released
     */
	protected void middleButtonReleased(MouseEvent e) {
		/* if (!Options.paste_hack || !ctrlDown) */
		rdp.sendInput(time, RDP_INPUT_MOUSE, MOUSE_FLAG_BUTTON3, e.getX(), e
				.getY());
	}

	private int o_x=0, o_y=0;
	private boolean drag_flag = false;
	
	class RdesktopMouseAdapter extends MouseAdapter {

		public RdesktopMouseAdapter() {
			super();
		}

		public void mousePressed(MouseEvent e) {
			if(e.getY() != 0 && frame!=null) frame.hideMenu();
			/*
			if(Position.isFrameArea(e.getX(), e.getY())){
				o_x = e.getX();
				o_y = e.getY();
				drag_flag = true;
			}
			else{
				drag_flag = false;
			}
			*/
			int time = getTime();
			if (rdp != null) {
				if ((e.getModifiers() & InputEvent.BUTTON1_MASK) == InputEvent.BUTTON1_MASK) {
//					logger.debug("Mouse Button 1 Pressed.");
					rdp.sendInput(time, RDP_INPUT_MOUSE, MOUSE_FLAG_BUTTON1
							| MOUSE_FLAG_DOWN, e.getX(), e.getY());
				} else if ((e.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK) {
//					logger.debug("Mouse Button 3 Pressed.");
					rdp.sendInput(time, RDP_INPUT_MOUSE, MOUSE_FLAG_BUTTON2
							| MOUSE_FLAG_DOWN, e.getX(), e.getY());
				} else if ((e.getModifiers() & InputEvent.BUTTON2_MASK) == InputEvent.BUTTON2_MASK) {
//					logger.debug("Middle Mouse Button Pressed.");
					middleButtonPressed(e);
				}
			}
		}

		public void mouseReleased(MouseEvent e) {
			int time = getTime();
			if (rdp != null) {
				if ((e.getModifiers() & InputEvent.BUTTON1_MASK) == InputEvent.BUTTON1_MASK) {
					rdp.sendInput(time, RDP_INPUT_MOUSE, MOUSE_FLAG_BUTTON1, e
							.getX(), e.getY());
				} else if ((e.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK) {
					rdp.sendInput(time, RDP_INPUT_MOUSE, MOUSE_FLAG_BUTTON2, e
							.getX(), e.getY());
				} else if ((e.getModifiers() & InputEvent.BUTTON2_MASK) == InputEvent.BUTTON2_MASK) {
					middleButtonReleased(e);
				}
			}
		}
	}

	class RdesktopMouseMotionAdapter extends MouseMotionAdapter {

		public RdesktopMouseMotionAdapter() {
			super();
		}

		public void mouseMoved(MouseEvent e) {
			int time = getTime();
           
			if (rdp != null) {
				rdp.sendInput(time, RDP_INPUT_MOUSE, MOUSE_FLAG_MOVE, e.getX(),
						e.getY());
			}
		}

		public void mouseDragged(MouseEvent e) {
			int time = getTime();
			// if(logger.isInfoEnabled()) logger.info("mouseMoved to
			// "+e.getX()+", "+e.getY()+" at "+time);				
			if (rdp != null) {
				rdp.sendInput(time, RDP_INPUT_MOUSE, MOUSE_FLAG_MOVE, e.getX(),
						e.getY());
			}
		}
	}

}
 public class Common {

    public static boolean underApplet = false;
	public static Rdp5JPanel rdp;
	public static Secure secure;
	public static MCS mcs;
	public static RdesktopJFrame frame;
	
    /**
     * Quit the application
     */
	public static void exit(){
		RdesktopSwing.exit(0,rdp,frame,true);
	}
}
 public class Options {
    
    public static final int DIRECT_BITMAP_DECOMPRESSION = 0;
    public static final int BUFFEREDIMAGE_BITMAP_DECOMPRESSION = 1;
    public static final int INTEGER_BITMAP_DECOMPRESSION = 2;
    
    public static int bitmap_decompression_store = INTEGER_BITMAP_DECOMPRESSION;
    
    
	public static boolean low_latency = true; // disables bandwidth saving tcp packets
	public static int keylayout = 0x809; // UK by default
	public static String username = ""; // -u username
	public static String domain = ""; // -d domain
	public static String password = ""; // -p password
	public static String hostname = ""; // -n hostname
	public static String command = "";  // -s command
	public static String directory = ""; // -d directory
	public static String windowTitle = ""; // -T windowTitle
	public static int width = 800; // -g widthxheight
	public static int height = 600; // -g widthxheight
	public static int port = 3389; // -t port
	public static boolean fullscreen = false;
	public static boolean built_in_licence = false;
    
	//setting
	public static boolean  autologin = false;
	public static boolean  bulk_compression = false;
	public static boolean  console_audio = false;
	
	public static boolean load_licence = false;
	public static boolean save_licence = false;

    public static String licence_path = "./";
    
    public static boolean debug_keyboard = false;
	public static boolean debug_hexdump = false;
    
	public static boolean enable_menu = false;
	//public static boolean paste_hack = true;
	public static boolean no_loginProgress = false;
	
	public static boolean seamless_active = false;
	public static boolean http_mode = false;
	public static String http_server = ""; //192.168.0.115:8080/WSService/RDPSocket";
	
	public static int screenInsets_title = 14;
	
	public static boolean altkey_quiet = false;
	public static boolean caps_sends_up_and_down = true;
	public static boolean remap_hash = true;
	public static boolean useLockingKeyState = true;
	
	public static boolean use_rdp5 = true;
	public static int server_bpp = 24;							// Bits per pixel
	public static int Bpp = (server_bpp + 7) / 8;				// Bytes per pixel
	
	public static int bpp_mask =  0xFFFFFF >> 8 * (3 - Bpp);	// Correction value to ensure only the relevant
			                                                    // number of bytes are used for a pixel
    
    public static int imgCount = 0;
    
    
    public static DirectColorModel colour_model = new DirectColorModel(24,0xFF0000,0x00FF00,0x0000FF);
    
    /**
     * Set a new value for the server's bits per pixel
     * @param server_bpp New bpp value
     */
	public static void set_bpp(int server_bpp){
		Options.server_bpp = server_bpp;
		Options.Bpp = (server_bpp + 7) / 8;
//		if(server_bpp == 8) bpp_mask = 0xFF;
//        else bpp_mask = 0xFFFFFF;
		bpp_mask =  0xFFFFFF >> 8 * (3 - Bpp);
        colour_model = new DirectColorModel(24,0xFF0000,0x00FF00,0x0000FF);
	}
	
	
	public static int server_rdp_version;
	
	public static int win_button_size = 0;	/* If zero, disable single app mode */
	public static boolean bitmap_compression = true;
    public static boolean persistent_bitmap_caching = false;
    public static boolean bitmap_caching = true;
    public static boolean precache_bitmaps = true;
    public static boolean polygon_ellipse_orders = false;
	public static boolean sendmotion = true;
	public static boolean orders = true;
	public static boolean encryption = true;
	public static boolean packet_encryption = true;
	public static boolean desktop_save = true;
	public static boolean grab_keyboard = true;
	public static boolean hide_decorations = false;
	public static boolean console_session = false;
	public static boolean owncolmap;
	
	public static boolean use_ssl = false;
	public static boolean map_clipboard = true;
    public static int rdp5_performanceflags =   RdpJPanel.RDP5_NO_CURSOR_SHADOW | RdpJPanel.RDP5_NO_CURSORSETTINGS |
    											RdpJPanel.RDP5_NO_FULLWINDOWDRAG | RdpJPanel.RDP5_NO_MENUANIMATIONS |
    											RdpJPanel.RDP5_NO_THEMING | RdpJPanel.RDP5_NO_WALLPAPER;
    public static boolean save_graphics = false;
	
    public static boolean keys_register=true;
    public static boolean is_debug = false;
}
 public class RdesktopException extends Exception {

    public RdesktopException() {
	super();
    }

    public RdesktopException(String s) {
	super(s);
    }
}
 public abstract class RdpPacket {
	static Logger logger = Logger.getLogger(RdpPacket.class);
	
	/* constants for Packet */
		public static final int MCS_HEADER = 1;
		public static final int SECURE_HEADER = 2;
		public static final int RDP_HEADER = 3;
		public static final int CHANNEL_HEADER = 4;
		
	protected int mcs = -1;
	protected int secure = -1;
	protected int rdp = -1;
	protected int channel = -1;
	protected int start = -1;
	protected int end=-1;
	
    /**
     * Read an 8-bit integer value from the packet (at current read/write position)
     * @return Value read from packet
     */
	public abstract int get8() ;
    
    /**
     * Read an 8-bit integer value from a specified offset in the packet
     * @param where Offset to read location
     * @return Value read from packet
     */
	public abstract int get8(int where); 
    
    /**
     * Write 8-bit value to packet at current read/write position
     * @param what Value to write to packet
     */
	public abstract void set8(int what);
    
    /**
     * Write 8-bit value to packet at specified offset
     * @param where Offset in packet to write location
     * @param what Value to write to packet
     */
	public abstract void set8(int where, int what);
	
    /**
     * Read a 2-byte, little-endian integer value from the packet (at current read/write position)
     * @return Value read from packet
     */
	public abstract int getLittleEndian16();
    
    /**
     * Read a 2-byte, little-endian integer value from a specified offset in the packet
     * @param where Offset to read location
     * @return Value read from packet
     */
	public abstract int getLittleEndian16(int where);
    
    /**
     * Write a 2-byte, little-endian integer value to packet at current read/write position
     * @param what Value to write to packet
     */
	public abstract void setLittleEndian16(int what) ;
    
    /**
     * Write a 2-byte, little-endian integer value to packet at specified offset
     * @param where Offset in packet to write location
     * @param what Value to write to packet
     */
	public abstract void setLittleEndian16(int where, int what) ;
	
    /**
     * Read a 2-byte, big-endian integer value from the packet (at current read/write position)
     * @return Value read from packet
     */
	public abstract int getBigEndian16();
    
    /**
     * Read a 2-byte, big-endian integer value from a specified offset in the packet
     * @param where Offset to read location
     * @return Value read from packet
     */
	public abstract int getBigEndian16(int where);
    
    /**
     * Write a 2-byte, big-endian integer value to packet at current read/write position
     * @param what Value to write to packet
     */
	public abstract void setBigEndian16(int what); 
    
    /**
     * Write a 2-byte, big-endian integer value to packet at specified offset
     * @param where Offset in packet to write location
     * @param what Value to write to packet
     */
	public abstract void setBigEndian16(int where, int what) ;
	
    /**
     * Read a 3-byte, little-endian integer value from the packet (at current read position)
     * @return Value read from packet
     */
    public abstract int getLittleEndian32();
    
    /**
     * Read a 3-byte, little-endian integer value from a specified offset in the packet
     * @param where Offset to read location
     * @return Value read from packet
     */
    public abstract int getLittleEndian32(int where);
    
    /**
     * Write a 3-byte, little-endian integer value to packet at current read/write position
     * @param what Value to write to packet
     */
    public abstract void setLittleEndian32(int what) ;
    
    /**
     * Write a 3-byte, little-endian integer value to packet at specified offset
     * @param where Offset in packet to write location
     * @param what Value to write to packet
     */
    public abstract void setLittleEndian32(int where, int what) ;
    
    /**
     * Read a 3-byte, big-endian integer value from the packet (at current read/write position)
     * @return Value read from packet
     */
    public abstract int getBigEndian32();
    
    /**
     * Read a 3-byte, big-endian integer value from a specified offset in the packet
     * @param where Offset to read location
     * @return Value read from packet
     */
    public abstract int getBigEndian32(int where);
    
    /**
     * Write a 3-byte, big-endian integer value to packet at current read/write position
     * @param what Value to write to packet
     */
    public abstract void setBigEndian32(int what); 
    
    /**
     * Write a 3-byte, big-endian integer value to packet at specified offset
     * @param where Offset in packet to write location
     * @param what Value to write to packet
     */
    public abstract void setBigEndian32(int where, int what) ;
   	
    /**
     * Copy data from this packet to an array of bytes
     * @param array Array of bytes to which data should be copied
     * @param array_offset Offset into array for start of data
     * @param mem_offset Offset into packet for start of data
     * @param len Length of data to be copied
     */
	public abstract void copyToByteArray(byte[] array, int array_offset, int mem_offset, int len);
    /**
     * Copy data to this packet from an array of bytes
     * @param array Array of bytes containing source data
     * @param array_offset Offset into array for start of data
     * @param mem_offset Offset into packet for start of data
     * @param len Length of data to be copied
     */
	public abstract void copyFromByteArray(byte[] array, int array_offset, int mem_offset, int len);
	
    /**
     * Copy data from this packet to another packet
     * @param dst Destination packet
     * @param srcOffset Offset into this packet (source) for start of data
     * @param dstOffset Offset into destination packet for start of data
     * @param len Length of data to be copied
     */
	public abstract void copyToPacket(RdpPacket_Localised dst, int srcOffset, int dstOffset, int len);
    
    /**
     * Copy data to this packet from another packet
     * @param src Source packet
     * @param srcOffset Offset into source packet for start of data
     * @param dstOffset Offset into this packet (destination) for start of data
     * @param len Length of data to be copied
     */
	public abstract void copyFromPacket(RdpPacket_Localised src, int srcOffset, int dstOffset, int len) ;
	
    /**
     * Retrieve size of this packet
     * @return Packet size
     */
	public abstract int size();
	
    /**
     * Retrieve offset to current read/write position
     * @return Current read/write position (as byte offset from start)
     */
	public abstract int getPosition();
    
    /**
     * Set current read/write position
     * @param position New read/write position (as byte offset from start)
     */
	public abstract void setPosition(int position) ;
	
    /**
     * Advance the read/write position
     * @param length Number of bytes to advance read position by
     */
	public abstract void incrementPosition(int length);

    /**
     * Mark current read/write position as end of packet
     */
	public void markEnd() {
	this.end = getPosition();
	}
    
    /**
     * Retrieve capacity of this packet
     * @return Packet capacity (in bytes)
     */
    public abstract int capacity();

    /**
     * Mark specified position as end of packet
     * @param position New end position (as byte offset from start)
     */
	public void markEnd(int position) {
	if (position > capacity()) {
		throw new ArrayIndexOutOfBoundsException("Mark > size!");
	}
	this.end = position;
	}
	
    /**
     * Retrieve location of packet end
     * @return Position of packet end (as byte offset from start)
     */
	public int getEnd() {
	return this.end;
	}

    /**
     * Reserve space within this packet for writing of headers for a specific communications layer.
     * Move read/write position ready for adding data for a higher communications layer.
     * @param header ID of header type
     * @param increment Required size to be reserved for header
     * @throws RdesktopException
     */
	public void pushLayer(int header, int increment) throws RdesktopException{
		this.setHeader(header);
		this.incrementPosition(increment);
		//this.setStart(this.getPosition());
	}
	
    /**
     * Get location of the header for a specific communications layer
     * @param header ID of header type
     * @return Location of header, as byte offset from start of packet
     * @throws RdesktopException
     */
	public int getHeader(int header) throws RdesktopException {
	switch(header) {
	case RdpPacket_Localised.MCS_HEADER:
		return this.mcs;
	case RdpPacket_Localised.SECURE_HEADER:
		return this.secure;
	case RdpPacket_Localised.RDP_HEADER:
		return this.rdp;
	case RdpPacket_Localised.CHANNEL_HEADER:
		return this.channel;
	default:
		throw new RdesktopException("Wrong Header!");
	}
	}
    
	/**
     * Set current read/write position as the start of a layer header
     * @param header ID of header type
     * @throws RdesktopException
	 */
	public void setHeader(int header) throws RdesktopException{
	switch(header) {
	case RdpPacket_Localised.MCS_HEADER:
		this.mcs = this.getPosition();
		break;
	case RdpPacket_Localised.SECURE_HEADER:
		this.secure = this.getPosition();
		break;
	case RdpPacket_Localised.RDP_HEADER:
		this.rdp = this.getPosition();
		break;
		case RdpPacket_Localised.CHANNEL_HEADER:
		this.channel = this.getPosition();
		break;
	default:
		throw new RdesktopException("Wrong Header!");
	}
	}

    /**
     * Retrieve start location of this packet
     * @return Start location of packet (as byte offset from location 0)
     */
	public int getStart() {
	return this.start;
	}
	
    /**
     * Set start position of this packet
     * @param position New start position (as byte offset from location 0)
     */
	public void setStart(int position) {
		this.start=position;
	}
    
    /**
     * Add a unicode string to this packet at the current read/write position
     * @param str String to write as unicode to packet
     * @param len Desired length of output unicode string
     */
    public void outUnicodeString(String str, int len) {
        int i = 0, j = 0;

        if (str.length() != 0) {
            char[] name = str.toCharArray();
            while (i < len) {
                this.setLittleEndian16((short) name[j++]);
                i += 2;
            }
            this.setLittleEndian16(0); // Terminating Null Character
        } else {
            this.setLittleEndian16(0);
        }
    }
    
    /**
     * Write an ASCII string to this packet at current read/write position
     * @param str String to be written
     * @param length Length in bytes to be occupied by string (may be longer than string itself) 
     */
    public void out_uint8p(String str, int length){
        byte[] bStr = str.getBytes();
        this.copyFromByteArray(bStr,0,this.getPosition(),bStr.length);
        this.incrementPosition(length);
    }
}
 public class RdpPacket_Localised extends RdpPacket {

	private ByteBuffer bb = null;
	private int size = 0;

	public RdpPacket_Localised(int capacity) {
		bb = ByteBuffer.allocateDirect(capacity);
		size = capacity;
	}

	public void reset(int length) {
		// logger.info("RdpPacket_Localised.reset(" + length + "), capacity = "
		// + bb.capacity());
		this.end = 0;
		this.start = 0;
		if (bb.capacity() < length)
			bb = ByteBuffer.allocateDirect(length);
		size = length;
		bb.clear();
	}

	public void set8(int where, int what) {
		if (where < 0 || where >= bb.capacity()) {
			throw new ArrayIndexOutOfBoundsException(
					"memory accessed out of Range!");
		}
		bb.put(where, (byte) what);
	}

	public void set8(int what) {
		if (bb.position() >= bb.capacity()) {
			throw new ArrayIndexOutOfBoundsException(
					"memory accessed out of Range!");
		}
		bb.put((byte) what);
	}

	// where is a 8-bit offset
	public int get8(int where) {
		if (where < 0 || where >= bb.capacity()) {
			throw new ArrayIndexOutOfBoundsException(
					"memory accessed out of Range!");
		}
		return bb.get(where) & 0xff; // treat as unsigned byte
	}

	// where is a 8-bit offset
	public int get8() {
		if (bb.position() >= bb.capacity()) {
			throw new ArrayIndexOutOfBoundsException(
					"memory accessed out of Range!");
		}
		return bb.get() & 0xff; // treat as unsigned byte
	}

	public void copyFromByteArray(byte[] array, int array_offset,
			int mem_offset, int len) {
		if ((array_offset >= array.length)
				|| (array_offset + len > array.length)
				|| (mem_offset + len > bb.capacity())) {
			throw new ArrayIndexOutOfBoundsException(
					"memory accessed out of Range!");
		}
		// store position
		int oldpos = getPosition();

		setPosition(mem_offset);
		bb.put(array, array_offset, len);

		// restore position
		setPosition(oldpos);
	}

	public void copyToByteArray(byte[] array, int array_offset, int mem_offset,
			int len) {
		if ((array_offset >= array.length))
			throw new ArrayIndexOutOfBoundsException(
					"Array offset beyond end of array!");
		if (array_offset + len > array.length)
			throw new ArrayIndexOutOfBoundsException(
					"Not enough bytes in array to copy!");
		if (mem_offset + len > bb.capacity())
			throw new ArrayIndexOutOfBoundsException(
					"Memory accessed out of Range!");

		int oldpos = getPosition();
		setPosition(mem_offset);
		bb.get(array, array_offset, len);
		setPosition(oldpos);
//		System.out
//				.println("********************************************************");
//		System.out.println(bb);
////		System.out.println(array);
//		System.out
//				.println("********************************************************");
		
//		for (int i = 0; i < array.length; i++) {
//			System.out.println("array" + i + ":" + array[i]);
//		}
		
	}

	public void copyToPacket(RdpPacket_Localised dst, int srcOffset,
			int dstOffset, int len) {
		int olddstpos = dst.getPosition();
		int oldpos = getPosition();
		dst.setPosition(dstOffset);
		setPosition(srcOffset);
		for (int i = 0; i < len; i++)
			dst.set8(bb.get());
		dst.setPosition(olddstpos);
		setPosition(oldpos);

//		System.out
//				.println("copyToPacket is there....................................");
	}

	public void copyFromPacket(RdpPacket_Localised src, int srcOffset,
			int dstOffset, int len) {
		int oldsrcpos = src.getPosition();
		int oldpos = getPosition();
		src.setPosition(srcOffset);
		setPosition(dstOffset);
		for (int i = 0; i < len; i++)
			bb.put((byte) src.get8());
		src.setPosition(oldsrcpos);
		setPosition(oldpos);

//		System.out
//				.println("copyFromPacket is there..............................");
	}

	public int capacity() {
		return bb.capacity();
	}

	// return size in bytes
	public int size() {
		return size;
		// return bb.capacity(); //this.end - this.start;
	}

	public int getPosition() {
		return bb.position();
	}

	public int getLittleEndian16(int where) {
		bb.order(ByteOrder.LITTLE_ENDIAN);
		return bb.getShort(where);
	}

	public int getLittleEndian16() {
		bb.order(ByteOrder.LITTLE_ENDIAN);
		return bb.getShort();
	}

	public int getBigEndian16(int where) {
		bb.order(ByteOrder.BIG_ENDIAN);
		return bb.getShort(where);
	}

	public int getBigEndian16() {
		bb.order(ByteOrder.BIG_ENDIAN);
		return bb.getShort();
	}

	public void setLittleEndian16(int where, int what) {
		bb.order(ByteOrder.LITTLE_ENDIAN);
		bb.putShort(where, (short) what);
	}

	public void setLittleEndian16(int what) {
		bb.order(ByteOrder.LITTLE_ENDIAN);
		bb.putShort((short) what);
	}

	public void setBigEndian16(int where, int what) {
		bb.order(ByteOrder.BIG_ENDIAN);
		bb.putShort(where, (short) what);
	}

	public void setBigEndian16(int what) {
		bb.order(ByteOrder.BIG_ENDIAN);
		bb.putShort((short) what);
	}

	public int getLittleEndian32(int where) {
		bb.order(ByteOrder.LITTLE_ENDIAN);
		return bb.getInt(where);
	}

	public int getLittleEndian32() {
		bb.order(ByteOrder.LITTLE_ENDIAN);
		return bb.getInt();
	}

	public int getBigEndian32(int where) {
		bb.order(ByteOrder.BIG_ENDIAN);
		return bb.getInt(where);
	}

	public int getBigEndian32() {
		bb.order(ByteOrder.BIG_ENDIAN);
		return bb.getInt();
	}

	public void setLittleEndian32(int where, int what) {
		bb.order(ByteOrder.LITTLE_ENDIAN);
		bb.putInt(where, what);
	}

	public void setLittleEndian32(int what) {
		bb.order(ByteOrder.LITTLE_ENDIAN);
		bb.putInt(what);
	}

	public void setBigEndian32(int where, int what) {
		bb.order(ByteOrder.BIG_ENDIAN);
		bb.putInt(where, what);
	}

	public void setBigEndian32(int what) {
		bb.order(ByteOrder.BIG_ENDIAN);
		bb.putInt(what);
	}

	public void incrementPosition(int length) {

		if (length > bb.capacity() || length + bb.position() > bb.capacity()
				|| length < 0) {
			throw new ArrayIndexOutOfBoundsException();
		}
		bb.position(bb.position() + length);
	}

	public void setPosition(int position) {
		if (position > bb.capacity() || position < 0) {
			logger.warn("stream position =" + getPosition() + " end ="
					+ getEnd() + " capacity =" + capacity());
			logger.warn("setPosition(" + position + ") failed");
			throw new ArrayIndexOutOfBoundsException();
		}
		bb.position(position);
	}

}
 public class Secure {
	boolean readCert = false;
	static Logger logger = Logger.getLogger("Secure");
	private Licence licence = new Licence(this);
    /* constants for the secure layer */
    public static final int SEC_ENCRYPT = 0x0008;
    public static final int SEC_INFO_PKT = 0x0040;

    static final int SEC_RANDOM_SIZE = 32;
    static final int SEC_MODULUS_SIZE = 64;
    static final int SEC_MAX_MODULUS_SIZE = 256;
    static final int SEC_PADDING_SIZE = 8;
    private static final int SEC_EXPONENT_SIZE = 4;

    private static final int SEC_CLIENT_RANDOM = 0x0001;
    static final int SEC_LICENCE_NEG = 0x0080;

    private static final int SEC_TAG_SRV_INFO = 0x0c01;
    private static final int SEC_TAG_SRV_CRYPT = 0x0c02;
    private static final int SEC_TAG_SRV_3 = 0x0c03;
    private static final int SEC_TAG_SRV_CHANNELS = 0x0c03;

    private static final int SEC_TAG_CLI_INFO = 0xc001;
    private static final int SEC_TAG_CLI_CRYPT = 0xc002;
    private static final int SEC_TAG_CLI_CHANNELS = 0xc003;
    private static final int SEC_TAG_CLI_4 = 0xc004;

    private static final int SEC_TAG_PUBKEY = 0x0006;
    private static final int SEC_TAG_KEYSIG = 0x0008;

    private static final int SEC_RSA_MAGIC = 0x31415352; /* RSA1 */
 
    private MCS McsLayer=null;
 //   private String hostname=null;
   // private String username=null;
    boolean licenceIssued = false;
    private RC4 rc4_enc = null;
    private RC4 rc4_dec = null;
    private RC4 rc4_update = null;
    private BlockMessageDigest sha1 = null;
    private BlockMessageDigest md5 = null;
    private int keylength = 0;
    private int enc_count = 0;
    private int dec_count = 0;
    
    private byte[] sec_sign_key = null;

    private byte[] sec_decrypt_key = null;

    private byte[] sec_encrypt_key = null;

    private byte[] sec_decrypt_update_key = null;

    private byte[] sec_encrypt_update_key = null;
    private byte[] sec_crypted_random=null;

    private byte[] exponent = null;
    private byte[] modulus = null;
    private byte[] server_random = null;
    private byte[] client_random = new byte[SEC_RANDOM_SIZE];

    private static final byte[] pad_54 = {
	54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54,
	54, 54, 54,
	54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54,
	54, 54, 54
    };
    
    private static final byte[] pad_92 = {
	92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92,
	92, 92, 92, 92, 92, 92, 92,
	92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92,
	92, 92, 92, 92, 92, 92, 92
    };
    
    private VChannels channels;
    
    public int server_public_key_len = SEC_MODULUS_SIZE;
    /**
     * Initialise Secure layer of communications
     * @param channels Virtual channels for this connection
     */
    public Secure(VChannels channels) {
        this.channels = channels;
	McsLayer = new MCS(channels);
	Common.mcs = McsLayer;
	rc4_dec = new RC4();
	rc4_enc = new RC4();
	rc4_update = new RC4();
	sha1 = new SHA1();
	md5 = new MD5();
	sec_sign_key = new byte[16]; // changed from 8 - rdesktop 1.2.0
	sec_decrypt_key = new byte[16];
	sec_encrypt_key = new byte[16];
	sec_decrypt_update_key = new byte[16]; // changed from 8 - rdesktop 1.2.0
	sec_encrypt_update_key = new byte[16]; // changed from 8 - rdesktop 1.2.0
	sec_crypted_random = new byte[64];
	
    }
    
    /**
     * Connect to server
     * @param host Address of server to connect to
     * @param port Port to connect to
     * @throws UnknownHostException
     * @throws IOException
     * @throws RdesktopException
     * @throws SocketException
     * @throws CryptoException
     * @throws OrderException
     */
    public void connect(InetAddress host, int port) throws UnknownHostException, IOException, RdesktopException, SocketException, CryptoException, OrderException {
	if(Options.hostname==""){
		InetAddress localhost = InetAddress.getLocalHost();
		String name = localhost.getHostName();
		StringTokenizer tok = new StringTokenizer(name, ".");
		Options.hostname = tok.nextToken();
		Options.hostname.trim();
	}

	RdpPacket_Localised mcs_data = this.sendMcsData();
	McsLayer.connect(host, port, mcs_data);
	
	this.processMcsData(mcs_data);

	if (Constants.encryption) {
	    this.establishKey();  
	}
    }
    
    /**
     * Connect to server on default port
     * @param host Server to connect to
     * @throws IOException
     * @throws RdesktopException
     * @throws OrderException
     * @throws CryptoException
     */
    public void connect(InetAddress host) throws IOException, RdesktopException, OrderException, CryptoException {
	this.connect(host, Options.port);
    }
    
    /**
     * Close connection
     */
    public void disconnect() {
	McsLayer.disconnect();
	}  

    /**
     * Construct MCS data, including channel, encryption and display options
     * @return Packet populated with MCS data
     */
    public RdpPacket_Localised sendMcsData() {
    	logger.debug("Secure.sendMcsData");
    
    RdpPacket_Localised buffer= new RdpPacket_Localised(512);
        
    int hostlen = 2 * (Options.hostname==null ? 0 : Options.hostname.length());
        
    if (hostlen > 30) { hostlen=30;}
            
	int length = 158;
	if(Options.use_rdp5) length += 76 + 12 + 4;

	if (Options.use_rdp5 && (channels.num_channels() > 0))
		length += channels.num_channels() * 12 + 8;

	buffer.setBigEndian16(5);	/* unknown */
	buffer.setBigEndian16(0x14);
	buffer.set8(0x7c);
	buffer.setBigEndian16(1);

	buffer.setBigEndian16(length | 0x8000);	// remaining length

	buffer.setBigEndian16(8);	// length?
	buffer.setBigEndian16(16);
	buffer.set8(0);
	buffer.setLittleEndian16(0xc001);
	buffer.set8(0);

	buffer.setLittleEndian32(0x61637544);	// "Duca" ?!
	buffer.setBigEndian16(length-14 | 0x8000);	// remaining length

	// Client information
	buffer.setLittleEndian16(SEC_TAG_CLI_INFO);
	buffer.setLittleEndian16(Options.use_rdp5 ? 212 : 136);	// length
	buffer.setLittleEndian16(Options.use_rdp5 ? 4 : 1);
	buffer.setLittleEndian16(8);
	buffer.setLittleEndian16(Options.width); 
	buffer.setLittleEndian16(Options.height); 
	buffer.setLittleEndian16(0xca01);
	buffer.setLittleEndian16(0xaa03);
	buffer.setLittleEndian32(Options.keylayout);
	buffer.setLittleEndian32(Options.use_rdp5 ? 2600 : 419); // or 0ece	// client build? we are 2600 compatible :-)

	/* Unicode name of client, padded to 32 bytes */
    buffer.outUnicodeString(Options.hostname.toUpperCase(), hostlen);
	buffer.incrementPosition(30 - hostlen);

	buffer.setLittleEndian32(4);
	buffer.setLittleEndian32(0);
	buffer.setLittleEndian32(12);
	buffer.incrementPosition(64);	/* reserved? 4 + 12 doublewords */

    buffer.setLittleEndian16(0xca01); // out_uint16_le(s, 0xca01);
	buffer.setLittleEndian16(Options.use_rdp5 ? 1 : 0);
    
	if(Options.use_rdp5){
	buffer.setLittleEndian32(0); // out_uint32(s, 0);
	buffer.set8(Options.server_bpp); // out_uint8(s, g_server_bpp);
	buffer.setLittleEndian16(0x0700); // out_uint16_le(s, 0x0700);
	buffer.set8(0); // out_uint8(s, 0);
	buffer.setLittleEndian32(1); // out_uint32_le(s, 1);
	
	buffer.incrementPosition(64);

	buffer.setLittleEndian16(SEC_TAG_CLI_4); // out_uint16_le(s, SEC_TAG_CLI_4);
	buffer.setLittleEndian16(12); // out_uint16_le(s, 12);
	buffer.setLittleEndian32(Options.console_session ? 0xb : 0xd); // out_uint32_le(s, g_console_session ? 0xb : 9);
	buffer.setLittleEndian32(0); // out_uint32(s, 0);
    }
    
	// Client encryption settings //
	buffer.setLittleEndian16(SEC_TAG_CLI_CRYPT);
	buffer.setLittleEndian16(Options.use_rdp5 ? 12 : 8);	// length
    
    //if(Options.use_rdp5) buffer.setLittleEndian32(Options.encryption ? 0x1b : 0);	// 128-bit encryption supported
    //else
    buffer.setLittleEndian32(Options.encryption ? (Options.console_session ? 0xb : 0x3) : 0);
    
	if(Options.use_rdp5) buffer.setLittleEndian32(0); // unknown
	
	if (Options.use_rdp5 && (channels.num_channels() > 0))
	{
	    logger.debug(("num_channels is " + channels.num_channels()));
		buffer.setLittleEndian16(SEC_TAG_CLI_CHANNELS); // out_uint16_le(s, SEC_TAG_CLI_CHANNELS);
		buffer.setLittleEndian16(channels.num_channels() * 12 + 8); // out_uint16_le(s, g_num_channels * 12 + 8);	// length 
		buffer.setLittleEndian32(channels.num_channels()); // out_uint32_le(s, g_num_channels);	// number of virtual channels 
		for (int i = 0; i < channels.num_channels(); i++)
		{
			logger.debug(("Requesting channel " + channels.channel(i).name()));
			buffer.out_uint8p(channels.channel(i).name(), 8); // out_uint8a(s, g_channels[i].name, 8);
			buffer.setBigEndian32(channels.channel(i).flags()); // out_uint32_be(s, g_channels[i].flags);
		}
	}
	
	buffer.markEnd();
	return buffer;
    }

    /**
     * Handle MCS info from server (server info, encryption info and channel information)
     * @param mcs_data Data received from server
     */    
    public void processMcsData(RdpPacket_Localised mcs_data) throws RdesktopException, CryptoException {
	logger.debug("Secure.processMcsData");
	int tag=0, len=0, length=0, nexttag=0;
	
	mcs_data.incrementPosition(21); // header (T.124 stuff, probably)
	len=mcs_data.get8();

	if ((len&0x00000080)!=0) {
	    len=mcs_data.get8();
	}

	while (mcs_data.getPosition() < mcs_data.getEnd()) {
	    tag=mcs_data.getLittleEndian16();
	    length=mcs_data.getLittleEndian16();

	    if (length <=4) return;
	    
	    nexttag=mcs_data.getPosition() + length -4;
	    
	    switch(tag) {
	    case (Secure.SEC_TAG_SRV_INFO): 
	    processSrvInfo(mcs_data);
	    break;
	    case (Secure.SEC_TAG_SRV_CRYPT):
		this.processCryptInfo(mcs_data);
		break;
		case (Secure.SEC_TAG_SRV_CHANNELS):
			/* FIXME: We should parse this information and
			   use it to map RDP5 channels to MCS 
			   channels */
			break;

	    default: throw new RdesktopException("Not implemented! Tag:"+tag+"not recognized!");
	    }

	    mcs_data.setPosition(nexttag);
	}
    }

    /**
     * Read server info from packet, specifically the RDP version of the server
     * @param mcs_data Packet to read
     */
	private void processSrvInfo(RdpPacket_Localised mcs_data) {
		Options.server_rdp_version = mcs_data.getLittleEndian16(); // in_uint16_le(s, g_server_rdp_version);
		logger.debug(("Server RDP version is " +  Options.server_rdp_version));
		if (1 == Options.server_rdp_version)
			Options.use_rdp5 = false;
	}

    public void establishKey()throws RdesktopException, IOException, CryptoException {
	int length = server_public_key_len + SEC_PADDING_SIZE;
	int flags = SEC_CLIENT_RANDOM;
	RdpPacket_Localised buffer = this.init(flags, length+4);

	buffer.setLittleEndian32(length);

	buffer.copyFromByteArray(this.sec_crypted_random, 0, buffer.getPosition(), server_public_key_len);
	buffer.incrementPosition(server_public_key_len);
	buffer.incrementPosition(SEC_PADDING_SIZE);
	buffer.markEnd();
	this.send(buffer, flags);
	
    }

    public void processCryptInfo(RdpPacket_Localised data) throws RdesktopException, CryptoException {
	int rc4_key_size=0;
	    
	rc4_key_size = this.parseCryptInfo(data);
	if (rc4_key_size == 0) {
	    return;
	}
	
	//this.client_random = this.generateRandom(SEC_RANDOM_SIZE);
	logger.debug("readCert = " + readCert);
	if (readCert)
	{			/* Which means we should use 
				   RDP5-style encryption */
		
		
		// *** reverse the client random
		//this.reverse(this.client_random);
		
		// *** load the server public key into the stored data for encryption
		/* this.exponent = this.server_public_key.getPublicExponent().toByteArray();
		this.modulus = this.server_public_key.getModulus().toByteArray();
		
		System.out.println("Exponent: " + server_public_key.getPublicExponent());
		System.out.println("Modulus: " + server_public_key.getModulus());
		*/
		
		// *** perform encryption
		//this.sec_crypted_random = RSA_public_encrypt(this.client_random, this.server_public_key);
		//this.RSAEncrypt(SEC_RANDOM_SIZE);
		
		//this.RSAEncrypt(SEC_RANDOM_SIZE);
		
		// *** reverse the random data back
		//this.reverse(this.sec_crypted_random);

	}
	else{
		this.generateRandom();
		this.RSAEncrypt(SEC_RANDOM_SIZE);
	}
	this.generate_keys(rc4_key_size);
    }

    /**
     * Intialise a packet at the Secure layer
     * @param flags Encryption flags
     * @param length Length of packet
     * @return Intialised packet
     * @throws RdesktopException
     */
    public RdpPacket_Localised init(int flags, int length) throws RdesktopException {
	int headerlength=0;
	RdpPacket_Localised buffer;
 
	if (!this.licenceIssued) 
	    headerlength = ((flags & SEC_ENCRYPT)!=0) ? 12 : 4;
	else 
	     headerlength = ((flags & SEC_ENCRYPT)!=0) ? 12 : 0;
	
	buffer=McsLayer.init(length+headerlength);
	buffer.pushLayer(RdpPacket_Localised.SECURE_HEADER,headerlength);
	//buffer.setHeader(RdpPacket_Localised.SECURE_HEADER);
	//buffer.incrementPosition(headerlength);
	//buffer.setStart(buffer.getPosition());
	return buffer;
    }

    /**
     * Send secure data on the global channel
     * @param sec_data Data to send
     * @param flags Encryption flags
     * @throws RdesktopException
     * @throws IOException
     * @throws CryptoException
     */
	public void send(RdpPacket_Localised sec_data, int flags) throws RdesktopException, IOException, CryptoException {
		send_to_channel(sec_data,flags,MCS.MCS_GLOBAL_CHANNEL);
	}
    
    /**
     * Prepare data as a Secure PDU and pass down to the MCS layer
     * @param sec_data Data to send
     * @param flags Encryption flags
     * @param channel Channel over which to send data
     * @throws RdesktopException
     * @throws IOException
     * @throws CryptoException
     */
    public void send_to_channel(RdpPacket_Localised sec_data, int flags, int channel) throws RdesktopException, IOException, CryptoException {
	int datalength=0;
	byte[] signature = null;
	byte[] data;
	byte[] buffer;

	sec_data.setPosition(sec_data.getHeader(RdpPacket_Localised.SECURE_HEADER));
	
	if (this.licenceIssued == false || (flags & SEC_ENCRYPT) !=0) {
	    sec_data.setLittleEndian32(flags);
	}
	if ((flags & SEC_ENCRYPT) !=0) {
	    flags &= ~SEC_ENCRYPT;
	    datalength = sec_data.getEnd() - sec_data.getPosition() - 8;
	    data = new byte[datalength];
	    buffer = null;
	    sec_data.copyToByteArray(data, 0, sec_data.getPosition()+8, datalength); 
	    signature = this.sign(this.sec_sign_key, 8, this.keylength, data, datalength);
	   
	    buffer = this.encrypt(data, datalength);
	    
	    sec_data.copyFromByteArray(signature, 0, sec_data.getPosition(), 8);
	    sec_data.copyFromByteArray(buffer, 0, sec_data.getPosition()+8, datalength);
	   
	}
	//McsLayer.send(sec_data);
    McsLayer.send_to_channel(sec_data,channel);
    }

    /**
     * Generate MD5 signature
     * @param session_key Key with which to sign data
     * @param length Length of signature
     * @param keylen Length of key
     * @param data Data to sign
     * @param datalength Length of data to sign
     * @return Signature for data
     * @throws CryptoException
     */
    public byte[] sign(byte[] session_key, int length, int keylen, byte[] data, int datalength) throws CryptoException {
	byte[] shasig = new byte[20];
	byte[] md5sig = new byte[16];
	byte[] lenhdr = new byte[4];
	byte[] signature = new byte[length];

	this.setLittleEndian32(lenhdr, datalength);
	
	sha1.engineReset();
	sha1.engineUpdate(session_key, 0, keylen/*length*/);
	sha1.engineUpdate(pad_54, 0, 40);
	sha1.engineUpdate(lenhdr, 0, 4);
	sha1.engineUpdate(data, 0, datalength);
	shasig = sha1.engineDigest();
	sha1.engineReset();

	md5.engineReset();
	md5.engineUpdate(session_key, 0, keylen/*length*/);
	md5.engineUpdate(pad_92, 0, 48);
	md5.engineUpdate(shasig, 0, 20);
	md5sig = md5.engineDigest();
	md5.engineReset();

	System.arraycopy(md5sig, 0, signature, 0, length);
	return signature;
    }

    /**
     * Encrypt specified number of bytes from provided data using RC4 algorithm
     * @param data Data to encrypt
     * @param length Number of bytes to encrypt (from start of array)
     * @return Encrypted data
     * @throws CryptoException
     */
    public byte[] encrypt(byte[] data, int length) throws CryptoException {
	byte[] buffer = null;
	if (this.enc_count==4096) {
	    sec_encrypt_key = this.update(this.sec_encrypt_key, this.sec_encrypt_update_key);
	    byte[] key = new byte[this.keylength];
	    System.arraycopy(this.sec_encrypt_key, 0, key, 0, this.keylength);
	    this.rc4_enc.engineInitEncrypt(key);
//		logger.debug("Packet enc_count="+enc_count);
		 this.enc_count=0;
	}
	//this.rc4.engineInitEncrypt(this.rc4_encrypt_key);
	buffer = this.rc4_enc.crypt(data, 0, length);
	this.enc_count++;
	return buffer;
    }
    
    /**
     * Encrypt provided data using the RC4 algorithm
     * @param data Data to encrypt
     * @return Encrypted data
     * @throws CryptoException
     */
    public byte[] encrypt(byte[] data) throws CryptoException {
	byte[] buffer = null;
	if (this.enc_count==4096) {
	    sec_encrypt_key = this.update(this.sec_encrypt_key, this.sec_encrypt_update_key);
	    byte[] key = new byte[this.keylength];
	    System.arraycopy(this.sec_encrypt_key, 0, key, 0, this.keylength);
	    this.rc4_enc.engineInitEncrypt(key);
	//	logger.debug("Packet enc_count="+enc_count);
		this.enc_count=0;
	}
//	this.rc4.engineInitEncrypt(this.rc4_encrypt_key);
	
	buffer = this.rc4_enc.crypt(data);
	this.enc_count++;
	return buffer;
    }

    /**
     * Decrypt specified number of bytes from provided data using RC4 algorithm
     * @param data Data to decrypt
     * @param length Number of bytes to decrypt (from start of array)
     * @return Decrypted data
     * @throws CryptoException
     */
    public byte[] decrypt(byte[] data, int length) throws CryptoException {
	byte[] buffer = null;
	if (this.dec_count==4096) {
	    sec_decrypt_key = this.update(this.sec_decrypt_key, this.sec_decrypt_update_key);
	    byte[] key = new byte[this.keylength];
	    System.arraycopy(this.sec_decrypt_key, 0, key, 0, this.keylength);
	    this.rc4_dec.engineInitDecrypt(key);
//		logger.debug("Packet dec_count="+dec_count);
		this.dec_count=0;
	}
	//this.rc4.engineInitDecrypt(this.rc4_decrypt_key);
	buffer = this.rc4_dec.crypt(data, 0, length);
	this.dec_count++;
	return buffer;
    }
    
    /**
     * Decrypt provided data using RC4 algorithm
     * @param data Data to decrypt
     * @return Decrypted data
     * @throws CryptoException
     */
    public byte[] decrypt(byte[] data) throws CryptoException {
	byte[] buffer = null;
	if (this.dec_count==4096) {
	    sec_decrypt_key = this.update(this.sec_decrypt_key, this.sec_decrypt_update_key);
	    byte[] key = new byte[this.keylength];
	    System.arraycopy(this.sec_decrypt_key, 0, key, 0, this.keylength);
	    this.rc4_dec.engineInitDecrypt(key);
//		logger.debug("Packet dec_count="+dec_count);
		this.dec_count=0;
	}
	//this.rc4.engineInitDecrypt(this.rc4_decrypt_key);
	
	buffer = this.rc4_dec.crypt(data);
	this.dec_count++;
	return buffer;
    }
    
    /**
     * Read encryption information from a Secure layer PDU, obtaining and storing
     * level of encryption and any keys received
     * @param data Packet to read encryption information from
     * @return Size of RC4 key
     * @throws RdesktopException
     */
    public int parseCryptInfo(RdpPacket_Localised data) throws RdesktopException {
	logger.debug("Secure.parseCryptInfo");
	int encryption_level=0, random_length=0, RSA_info_length=0;
	int tag=0, length=0;
	int next_tag=0, end=0; 
	int rc4_key_size=0;

	rc4_key_size = data.getLittleEndian32(); // 1 = 40-Bit 2 = 128 Bit
	encryption_level = data.getLittleEndian32(); // 1 = low, 2 = medium, 3 = high
	if (encryption_level==0) { // no encryption
	    return 0;
	}
	random_length = data.getLittleEndian32();
	RSA_info_length = data.getLittleEndian32();

	if ( random_length != SEC_RANDOM_SIZE) {
	    throw new RdesktopException("Wrong Size of Random! Got" + random_length + "expected" + SEC_RANDOM_SIZE);
	}
	this.server_random = new byte[random_length];
	data.copyToByteArray(this.server_random, 0, data.getPosition(), random_length);
	data.incrementPosition(random_length);

	end = data.getPosition() + RSA_info_length;

	if (end > data.getEnd()) {
	   logger.debug("Reached end of crypt info prematurely ");
	    return 0;
	}

	//data.incrementPosition(12); // unknown bytes
	int flags = data.getLittleEndian32(); // in_uint32_le(s, flags);	// 1 = RDP4-style, 0x80000002 = X.509
	logger.debug("Flags = 0x" + Integer.toHexString(flags));
	if ((flags & 1) != 0)
	{
		logger.debug(("We're going for the RDP4-style encryption"));
		data.incrementPosition(8); //in_uint8s(s, 8);	// unknown

	while (data.getPosition() < data.getEnd()) {
	    tag=data.getLittleEndian16();
	    length=data.getLittleEndian16();

	    next_tag = data.getPosition() + length;

	    switch (tag) {

	    case (Secure.SEC_TAG_PUBKEY):
		
		if (!parsePublicKey(data)) {
		    return 0;
		}
		
		break;
	    case (Secure.SEC_TAG_KEYSIG):
		// Microsoft issued a key but we don't care
		break;

	    default:
		throw new RdesktopException("Unimplemented decrypt tag "+tag);
	    }
	    data.setPosition(next_tag);
	}

	if (data.getPosition() == data.getEnd()) {
	    return rc4_key_size;
	} else {
	    logger.warn("End not reached!");
	    return 0;
	}
	
	}else{
		//data.incrementPosition(4); // number of certificates
		int num_certs = data.getLittleEndian32();
		
		int cacert_len = data.getLittleEndian32();
		data.incrementPosition(cacert_len);
		int cert_len = data.getLittleEndian32();
		data.incrementPosition(cert_len);
		
		readCert = true;
		
		return rc4_key_size;
	}
	
	
    }
    /*
    public X509Certificate readCert(int length, RdpPacket_Localised data){  	
    	byte[] buf = new byte[length];
    	
    	data.copyToByteArray(buf,0,data.getPosition(),buf.length);
    	data.incrementPosition(length);	
    	
    	for(int i = 0; i < buf.length; i++){
    		buf[i] = (byte) (buf[i] & 0xFF);
    	}
    	
    	ByteArrayInputStream bIn = new ByteArrayInputStream(buf);
    	X509Certificate cert = null;
    	CertificateFactory cf = null;
		try {
			cf = CertificateFactory.getInstance("X.509");
			cert = (X509Certificate)cf.generateCertificate(bIn);
		} catch (CertificateException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
    	
    	bIn.reset();
    	return cert;
    }
*/
    public void generateRandom() {
	/*try{
		SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
		random.nextBytes(this.client_random);
    } catch(NoSuchAlgorithmException e){logger.warn("No Such Random Algorithm");}*/
    }

    public void RSAEncrypt(int length) throws RdesktopException {
	byte[] inr = new byte[length];
	//int outlength = 0;
	BigInteger mod = null;
	BigInteger exp = null;
	BigInteger x   = null;

	this.reverse(this.exponent);
	this.reverse(this.modulus);
	System.arraycopy(this.client_random, 0, inr, 0, length);
	this.reverse(inr);

	if ((this.modulus[0] & 0x80) != 0) {
	    byte[] temp = new byte [this.modulus.length+1];
	    System.arraycopy(this.modulus, 0, temp, 1, this.modulus.length);
	    temp[0]=0;
	    mod = new BigInteger(temp);
	} else {
	    mod = new BigInteger(this.modulus);
	}
	if ((this.exponent[0] & 0x80) != 0) {
	    byte[] temp = new byte [this.exponent.length+1];
	    System.arraycopy(this.exponent, 0, temp, 1, this.exponent.length);
	    temp[0]=0;
	    exp = new BigInteger(temp);
	} else {
	    exp = new BigInteger(this.exponent);
	}
	if ((inr[0] & 0x80) != 0) {
	    byte[] temp = new byte [inr.length+1];
	    System.arraycopy(inr, 0, temp, 1, inr.length);
	    temp[0]=0;
	    x = new BigInteger(temp);
	} else {
	    x   = new BigInteger(inr);
	}
	
	BigInteger y = x.modPow(exp, mod);
	this.sec_crypted_random = y.toByteArray();

	if ((this.sec_crypted_random[0] & 0x80) != 0) {
	    throw new RdesktopException("Wrong Sign! Expected positive Integer!");
	}
	
	if (this.sec_crypted_random.length > this.server_public_key_len) {
	    logger.warn("sec_crypted_random too big!"); /* FIXME */
	}
	this.reverse(this.sec_crypted_random);

	byte[] temp = new byte[server_public_key_len];

	if (this.sec_crypted_random.length < server_public_key_len) {
	    System.arraycopy(this.sec_crypted_random, 0, temp, 0, this.sec_crypted_random.length);
	    for (int i = this.sec_crypted_random.length; i < temp.length; i++) {
		temp[i] = 0;
	    }
	    this.sec_crypted_random = temp;
	    
	}

    }

    

    /**
     * Read in a public key from a provided Secure layer PDU, and store
     * in this.exponent and this.modulus
     * @param data Secure layer PDU containing key data
     * @return True if key successfully read
     * @throws RdesktopException
     */
    public boolean parsePublicKey(RdpPacket_Localised data) throws RdesktopException {
	int magic=0, modulus_length=0;

	magic = data.getLittleEndian32();

	if (magic != SEC_RSA_MAGIC) {
	    throw new RdesktopException("Wrong magic! Expected" + SEC_RSA_MAGIC + "got:" + magic);
	}

	modulus_length = data.getLittleEndian32()-SEC_PADDING_SIZE;
	
	if ((modulus_length < SEC_MODULUS_SIZE) || (modulus_length > SEC_MAX_MODULUS_SIZE)){
	    throw new RdesktopException("Wrong modulus size! Expected" + SEC_MODULUS_SIZE + "+" + SEC_PADDING_SIZE + "got:" + modulus_length);
	}
	
	data.incrementPosition(8); //unknown modulus bits
	this.exponent = new byte[SEC_EXPONENT_SIZE];
	data.copyToByteArray(this.exponent, 0, data.getPosition(), SEC_EXPONENT_SIZE);	
	data.incrementPosition(SEC_EXPONENT_SIZE);
	this.modulus = new byte[modulus_length];
	data.copyToByteArray(this.modulus, 0, data.getPosition(), modulus_length);
	data.incrementPosition(modulus_length);
	data.incrementPosition(SEC_PADDING_SIZE);
	this.server_public_key_len = modulus_length;

	if (data.getPosition() <= data.getEnd()) {
	    return true;
	} else {
	    return false;
	}
    }
    
    /**
     * Reverse the values in the provided array
     * @param data Array as passed reversed on return
     */
    public void reverse(byte[] data) {
	int i=0, j=0;
	byte temp = 0;

	for(i=0, j=data.length-1; i < j; i++, j--) {
	    temp = data[i];
	    data[i] = data[j];
	    data[j] = temp;
	}
    }
       
     public void reverse(byte[] data, int length) {
	 int i=0, j=0;
	byte temp = 0;
	
	for(i=0, j=length-1; i < j; i++, j--) {
	    temp = data[i];
	    data[i] = data[j];
	    data[j] = temp;
	}
     }

    public byte[] hash48(byte[] in, byte[] salt1, byte[] salt2, int salt)throws CryptoException {
	byte[] shasig = new byte[20];
	byte[] pad = new byte[4];
	byte[] out = new byte[48];
	int i = 0;
	
	for(i=0; i< 3; i++) {
	    for(int j=0; j <=i; j++) {
		pad[j]=(byte)(salt+i);
	    }
	    sha1.engineUpdate(pad, 0, i + 1);
	    sha1.engineUpdate(in, 0, 48);
	    sha1.engineUpdate(salt1, 0, 32);
	    sha1.engineUpdate(salt2, 0, 32);
	    shasig = sha1.engineDigest();
	    sha1.engineReset();

	    md5.engineUpdate(in, 0, 48);
	    md5.engineUpdate(shasig, 0, 20);
	    System.arraycopy(md5.engineDigest(), 0, out, i*16, 16);
	}

	return out;
    }
	
    public byte[] hash16(byte[] in, byte[] salt1, byte[] salt2, int in_position) throws CryptoException {
	
	md5.engineUpdate(in, in_position, 16);
	md5.engineUpdate(salt1, 0, 32);
	md5.engineUpdate(salt2, 0, 32);
	return md5.engineDigest();
    }

    /**
     * Generate a 40-bit key and store in the parameter key.
     * @param key
     */
    public void make40bit(byte[] key) {
	key[0] = (byte)0xd1;
	key[1] = (byte)0x26;
	key[2] = (byte)0x9e;
    }

    /**
     * 
     * @param key
     * @param update_key
     * @return
     * @throws CryptoException
     */
    public byte[] update(byte[] key, byte[] update_key) throws CryptoException {
	byte[] shasig = new byte[20];
	byte[] update = new byte[this.keylength]; // changed from 8 - rdesktop 1.2.0
	byte[] thekey = new byte[key.length];

	sha1.engineReset();
    sha1.engineUpdate(update_key, 0, keylength); 
	sha1.engineUpdate(pad_54, 0, 40);
	sha1.engineUpdate(key,0 , keylength); // changed from 8 - rdesktop 1.2.0
	shasig = sha1.engineDigest();
	sha1.engineReset();

	md5.engineReset();
	md5.engineUpdate(update_key,0 ,keylength); // changed from 8 - rdesktop 1.2.0
	md5.engineUpdate(pad_92, 0, 48);
	md5.engineUpdate(shasig, 0, 20);
	thekey = md5.engineDigest();
	md5.engineReset();

	System.arraycopy(thekey, 0, update, 0, this.keylength);
	rc4_update.engineInitDecrypt(update);
	// added
	thekey = rc4_update.crypt(thekey, 0, this.keylength);

	if( this.keylength == 8) {
	    this.make40bit(thekey);
	}
	
	return thekey;
    }

    /**
     * Write a 32-bit integer value to an array of bytes, length 4
     * @param data Modified by method to be a 4-byte array representing the parameter value
     * @param value Integer value to return as a little-endian 32-bit value
     */
    public void setLittleEndian32(byte[] data, int value) {

	data[3] = (byte)((value >>> 24) & 0xff); 
	data[2] = (byte)((value >>> 16) & 0xff); 
	data[1] = (byte)((value >>> 8) & 0xff); 
	data[0] = (byte)(value & 0xff); 
    }

    /**
     * Receive a Secure layer PDU from the MCS layer
     * 
     * @return Packet representing received Secure PDU
     * @throws RdesktopException
     * @throws IOException
     * @throws CryptoException
     * @throws OrderException
     */
    public RdpPacket_Localised receive() throws RdesktopException, IOException, CryptoException, OrderException {
	int sec_flags=0;
	RdpPacket_Localised buffer=null;
	while(true) {
    	int[] channel = new int[1];
    	buffer=McsLayer.receive(channel);
    	if(buffer==null) return null;
	    buffer.setHeader(RdpPacket_Localised.SECURE_HEADER);
	    if (Constants.encryption || (! this.licenceIssued)) {
	
		sec_flags=buffer.getLittleEndian32();

		if ((sec_flags & SEC_LICENCE_NEG) != 0) {
		   licence.process(buffer);
		    continue;
		}
		if ((sec_flags & SEC_ENCRYPT) != 0) {
		    buffer.incrementPosition(8); //signature
		    byte[] data = new byte[buffer.size() - buffer.getPosition()];
		    buffer.copyToByteArray(data, 0, buffer.getPosition(), data.length);
		    byte[] packet = this.decrypt(data);
		    
		    buffer.copyFromByteArray(packet, 0, buffer.getPosition(), packet.length);

		    //buffer.setStart(buffer.getPosition());
		    //return buffer;
		}
	    }
	    
    		if (channel[0] != MCS.MCS_GLOBAL_CHANNEL)
    		{
    			channels.channel_process(buffer, channel[0]);
    			continue;
    		}
    		
    		buffer.setStart(buffer.getPosition());
    		return buffer;
    	}
    }

    /**
     * Generate encryption keys of applicable size for connection
     * @param rc4_key_size Size of keys to generate (1 if 40-bit encryption, otherwise 128-bit)
     * @throws CryptoException
     */
	public void generate_keys(int rc4_key_size) throws CryptoException {
			byte[] session_key = new byte[48];
			byte[] temp_hash = new byte[48];
			byte[] input = new byte[48];
	
			System.arraycopy(this.client_random, 0, input, 0, 24);
			System.arraycopy(this.server_random, 0, input, 24, 24);

			temp_hash = this.hash48(input, this.client_random, this.server_random, 65);
			session_key = this.hash48(temp_hash, this.client_random, this.server_random, 88);

			System.arraycopy(session_key, 0, this.sec_sign_key, 0, 16); // changed from 8 - rdesktop 1.2.0
	
			this.sec_decrypt_key = this.hash16(session_key, this.client_random, this.server_random, 16);
			this.sec_encrypt_key = this.hash16(session_key, this.client_random, this.server_random, 32);
	
			if (rc4_key_size == 1) {
				logger.info("40 Bit Encryption enabled");
				this.make40bit(this.sec_sign_key);
				this.make40bit(this.sec_decrypt_key);
				this.make40bit(this.sec_encrypt_key);
				this.keylength = 8;
			} else {
				logger.info("128 Bit Encryption enabled");
				this.keylength = 16;
			}
	
			System.arraycopy(this.sec_decrypt_key, 0, this.sec_decrypt_update_key, 0, 16); // changed from 8 - rdesktop 1.2.0
			System.arraycopy(this.sec_encrypt_key, 0, this.sec_encrypt_update_key, 0, 16); // changed from 8 - rdesktop 1.2.0

	
			byte[] key = new byte[this.keylength];
			System.arraycopy(this.sec_encrypt_key, 0, key, 0, this.keylength);
			rc4_enc.engineInitEncrypt(key);
			System.arraycopy(this.sec_decrypt_key, 0, key, 0, this.keylength);
			rc4_dec.engineInitDecrypt(key);
			}

    /**
     * @return MCS user ID
     */
    public int getUserID() {
	return McsLayer.getUserID();
    }
}
 public class CryptoException
extends Exception
{
    public CryptoException() { super(); }
    /** @param reason  the reason why the exception was thrown. */
    public CryptoException(String reason) { super(reason); }       
}
 public abstract class VChannel {

	protected static Logger logger = Logger.getLogger(InputJPanel.class);
	
	private int mcs_id = 0;
	
    /**
     * Provide the name of this channel
     * @return Channel name as string
     */
	public abstract String name();
	
    /**
     * Provide the set of flags specifying working options for this channel
     * @return Option flags
     */
    public abstract int flags();
    
    /**
     * Process a packet sent on this channel
     * @param data Packet sent to this channel
     * @throws RdesktopException
     * @throws IOException
     * @throws CryptoException
     */
	public abstract void process(RdpPacket data) throws RdesktopException, IOException, CryptoException;
	public int mcs_id(){
		return mcs_id;
	}
	
    /**
     * Set the MCS ID for this channel
     * @param mcs_id New MCS ID
     */
	public void set_mcs_id(int mcs_id){
		this.mcs_id = mcs_id;
	}
	
    /**
     * Initialise a packet for transmission over this virtual channel
     * @param length Desired length of packet
     * @return Packet prepared for this channel
     * @throws RdesktopException
     */
	public RdpPacket_Localised init(int length) throws RdesktopException{
		RdpPacket_Localised s;
		
		s = Common.secure.init(Options.encryption ? Secure.SEC_ENCRYPT : 0,length + 8);
		s.setHeader(RdpPacket.CHANNEL_HEADER);
		s.incrementPosition(8);
				
		return s;
	}
		
    /**
     * Send a packet over this virtual channel
     * @param data Packet to be sent
     * @throws RdesktopException
     * @throws IOException
     * @throws CryptoException
     */
	public void send_packet(RdpPacket_Localised data) throws RdesktopException, IOException, CryptoException
	{
		if(Common.secure == null) return;
		int length = data.size();
		
		int data_offset = 0;
		int packets_sent = 0;
		int num_packets = (length/VChannels.CHANNEL_CHUNK_LENGTH);
		num_packets += length - (VChannels.CHANNEL_CHUNK_LENGTH)*num_packets;
		
		while(data_offset < length){
		
			int thisLength = Math.min(VChannels.CHANNEL_CHUNK_LENGTH, length - data_offset);
			
			RdpPacket_Localised s = Common.secure.init(Constants.encryption ? Secure.SEC_ENCRYPT : 0, 8 + thisLength);
			s.setLittleEndian32(length);
		
			int flags = ((data_offset == 0) ? VChannels.CHANNEL_FLAG_FIRST : 0);
			if(data_offset + thisLength >= length) flags |= VChannels.CHANNEL_FLAG_LAST;
			
			if ((this.flags() & VChannels.CHANNEL_OPTION_SHOW_PROTOCOL) != 0) flags |= VChannels.CHANNEL_FLAG_SHOW_PROTOCOL;
			//System.out.printf("Sending %d bytes with flags %d\n", thisLength, flags);
			s.setLittleEndian32(flags);
			s.copyFromPacket(data,data_offset,s.getPosition(),thisLength);
			s.incrementPosition(thisLength);
			s.markEnd();
			
			data_offset += thisLength;		
			
			if(Common.secure != null) Common.secure.send_to_channel(s, Constants.encryption ? Secure.SEC_ENCRYPT : 0, this.mcs_id());
			packets_sent++;
		}
	}
	
}
 public class VChannels {

    protected static Logger logger = Logger.getLogger("InputJPanel");

    /* Sound format constants */
    public static final int WAVE_FORMAT_PCM = 1;

    public static final int WAVE_FORMAT_ADPCM = 2;

    public static final int WAVE_FORMAT_ALAW = 6;

    public static final int WAVE_FORMAT_MULAW = 7;

    /* Virtual channel options */
    public static final int CHANNEL_OPTION_INITIALIZED = 0x80000000;

    public static final int CHANNEL_OPTION_ENCRYPT_RDP = 0x40000000;

    public static final int CHANNEL_OPTION_COMPRESS_RDP = 0x00800000;

    public static final int CHANNEL_OPTION_SHOW_PROTOCOL = 0x00200000;

    /* NT status codes for RDPDR */
    public static final int STATUS_SUCCESS = 0x00000000;

    public static final int STATUS_INVALID_PARAMETER = 0xc000000d;

    public static final int STATUS_INVALID_DEVICE_REQUEST = 0xc0000010;

    public static final int STATUS_ACCESS_DENIED = 0xc0000022;

    public static final int MAX_CHANNELS = 6;

    public static final int CHANNEL_CHUNK_LENGTH = 1600;//the value is 1600,don't change by lijanaan 2009.12.14

    public static final int CHANNEL_FLAG_FIRST = 0x01;

    public static final int CHANNEL_FLAG_LAST = 0x02;

    public static final int CHANNEL_FLAG_SHOW_PROTOCOL = 0x10;

    private VChannel channels[] = new VChannel[MAX_CHANNELS];

    private int num_channels;

    public int num_channels() {
        return num_channels;
    }

    private byte[] fragment_buffer = null;

    /**
     * Obtain the MCS ID for a specific numbered channel
     * 
     * @param c
     *            Channel number for which to obtain MCS ID
     * @return MCS ID associated with the supplied channel number
     */
    public int mcs_id(int c) {
        return MCS.MCS_GLOBAL_CHANNEL + 1 + c;
    }

    /**
     * Initialise the maximum number of Virtual Channels
     */
    public VChannels() {
        channels = new VChannel[MAX_CHANNELS];
    }

    /**
     * Retrieve the VChannel object for the numbered channel
     * 
     * @param c
     *            Channel number
     * @return The requested Virtual Channel
     */
    public VChannel channel(int c) {
        if (c < num_channels)
            return channels[c];
        else
            return null;
    }

    /**
     * Retrieve the VChannel object for the specified MCS channel ID
     * 
     * @param channelno
     *            MCS ID for the required channel
     * @return Virtual Channel associated with the supplied MCS ID
     */
    public VChannel find_channel_by_channelno(int channelno) {
        if (channelno > MCS.MCS_GLOBAL_CHANNEL + num_channels) {
            logger.warn("Channel " + channelno
                    + " not defined. Highest channel defined is "
                    + MCS.MCS_GLOBAL_CHANNEL + num_channels);
            return null;
        } else
            return channels[channelno - MCS.MCS_GLOBAL_CHANNEL - 1];
    }

    /**
     * Remove all registered virtual channels
     */
    public void clear() {
        channels = new VChannel[MAX_CHANNELS];
        num_channels = 0;
    }

    /**
     * Register a new virtual channel
     * @param v Virtual channel to be registered
     * @return True if successful
     * @throws RdesktopException
     */
    public boolean register(VChannel v) throws RdesktopException {
        if (!Options.use_rdp5) {
            return false;
        }

        if (num_channels >= MAX_CHANNELS)
            throw new RdesktopException(
                    "Channel table full. Could not register channel.");

        channels[num_channels] = v;
        v.set_mcs_id(MCS.MCS_GLOBAL_CHANNEL + 1 + num_channels);
        num_channels++;

        return true;
    }

    /**
     * Process a packet sent on a numbered channel
     * @param data Packet sent to channel
     * @param mcsChannel Number specified for channel
     * @throws RdesktopException
     * @throws IOException
     * @throws CryptoException
     */
    public void channel_process(RdpPacket_Localised data, int mcsChannel)
            throws RdesktopException, IOException, CryptoException {

        int length, flags;
        int thislength = 0;
        VChannel channel = null;

        int i;

        for (i = 0; i < num_channels; i++) {
            if (mcs_id(i) == mcsChannel) {
                channel = channels[i];
                break;
            }
        }

        if (i >= num_channels)
            return;

        length = data.getLittleEndian32();
        flags = data.getLittleEndian32();
        if (((flags & CHANNEL_FLAG_FIRST) != 0)
                && ((flags & CHANNEL_FLAG_LAST) != 0)) {
            // single fragment - pass straight up
            channel.process(data);
        } else {
            // append to the defragmentation buffer
            byte[] content = new byte[data.getEnd() - data.getPosition()];
            data
                    .copyToByteArray(content, 0, data.getPosition(),
                            content.length);
            fragment_buffer = append(fragment_buffer, content);

            if ((flags & CHANNEL_FLAG_LAST) != 0) {
                RdpPacket_Localised fullpacket = new RdpPacket_Localised(
                        fragment_buffer.length);
                fullpacket.copyFromByteArray(fragment_buffer, 0, 0,
                        fragment_buffer.length);
                // process the entire reconstructed packet
                channel.process(fullpacket);
                fragment_buffer = null;
            }

        }
    }

    /**
     * Increase the size of an array
     * @param a Array to expand
     * @param amount Number of elements to add to the array
     * @return Expanded array
     */
    static Object arrayExpand(Object a, int amount) {
        Class cl = a.getClass();
        if (!cl.isArray())
            return null;
        int length = Array.getLength(a);
        int newLength = length + amount; // 50% more

        Class componentType = a.getClass().getComponentType();
        Object newArray = Array.newInstance(componentType, newLength);
        System.arraycopy(a, 0, newArray, 0, length);
        return newArray;
    }

    /**
     * Concatenate two byte arrays
     * @param target Contains initial bytes in output
     * @param source Appended to target array
     * @return Concatenation of arrays, target+source
     */
    static byte[] append(byte[] target, byte[] source) {
        if (target == null || target.length <= 0)
            return source;
        else if (source == null || source.length <= 0)
            return target;
        else {
            byte[] out = (byte[]) arrayExpand(target, source.length);
            System.arraycopy(source, 0, out, target.length, source.length);
            return out;
        }
    }
}

import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.io.IOException;
import java.util.Iterator;
import org.apache.log4j.Logger;
import com.lixia.rdp.CommunicationMonitor;
import com.lixia.rdp.Constants;
import com.lixia.rdp.InputJPanel;
import com.lixia.rdp.Common;
import com.lixia.rdp.Options;
import com.lixia.rdp.RdesktopException;
import com.lixia.rdp.RdpPacket;
import com.lixia.rdp.RdpPacket_Localised;
import com.lixia.rdp.Secure;
import com.lixia.rdp.crypto.CryptoException;
import com.lixia.rdp.rdp5.VChannel;
import com.lixia.rdp.rdp5.VChannels;

/* ClipChannel.java
 * Component: ProperJavaRDP
 * 
 * Revision: $Revision: 1.1.1.1 $
 * Author: $Author: suvarov $
 * Date: $Date: 2007/03/08 00:26:41 $
 *
 * Copyright (c) 2005 Propero Limited
 *
 * Purpose: 
 */
package com.lixia.rdp.rdp5.cliprdr;




public class ClipChannel extends VChannel implements ClipInterface, ClipboardOwner, FocusListener {

	String[] types = {
			"unused",
			"CF_TEXT",
			"CF_BITMAP",
			"CF_METAFILEPICT",
			"CF_SYLK",
			"CF_DIF",
			"CF_TIFF",
			"CF_OEMTEXT",
			"CF_DIB",
			"CF_PALETTE",
			"CF_PENDATA",
			"CF_RIFF",
			"CF_WAVE",
			"CF_UNICODETEXT",
			"CF_ENHMETAFILE",
			"CF_HDROP",
			"CF_LOCALE",
			"CF_MAX"
	};
	
	protected static Logger logger = Logger.getLogger(InputJPanel.class);
		
	// Message types
	public static final int CLIPRDR_CONNECT = 1;
	public static final int CLIPRDR_FORMAT_ANNOUNCE = 2;
	public static final int CLIPRDR_FORMAT_ACK = 3;
	public static final int CLIPRDR_DATA_REQUEST = 4;
	public static final int CLIPRDR_DATA_RESPONSE = 5;

	// Message status codes
	public static final int CLIPRDR_REQUEST = 0;
	public static final int CLIPRDR_RESPONSE = 1;
	public static final int CLIPRDR_ERROR = 2;

	Clipboard clipboard;

	// TypeHandler for data currently being awaited
	TypeHandler currentHandler = null;
	// All type handlers available
	TypeHandlerList allHandlers = null;
	byte[] localClipData = null;
	
	public ClipChannel(){
		this.clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
		
		// initialise all clipboard format handlers
		allHandlers = new TypeHandlerList();
		allHandlers.add(new UnicodeHandler());
		allHandlers.add(new TextHandler());
		allHandlers.add(new DIBHandler());
		//allHandlers.add(new MetafilepictHandler());
	}
	
	/* 
	 * VChannel inherited abstract methods
	 */
	public String name() {
		return "cliprdr";
	}

	public int flags() {
		return VChannels.CHANNEL_OPTION_INITIALIZED | VChannels.CHANNEL_OPTION_ENCRYPT_RDP |
		 VChannels.CHANNEL_OPTION_COMPRESS_RDP | VChannels.CHANNEL_OPTION_SHOW_PROTOCOL;
	}
	
	/*
	 * Data processing methods
	 */	

	public void process(RdpPacket data) throws RdesktopException, IOException, CryptoException {
