/* * Copyright 2010 jOpenRay, ILM Informatique * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or (at * your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/> */ package org.jopenray.adapter; import java.awt.Color; import java.awt.Cursor; import java.awt.Graphics; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.InetAddress; import java.net.SocketException; import java.net.UnknownHostException; import java.util.HashMap; import java.util.Map; import javax.imageio.ImageIO; import org.apache.log4j.BasicConfigurator; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.jopenray.operation.CopyOperation; import org.jopenray.operation.FillOperation; import org.jopenray.operation.SetMouseCursorOperation; import org.jopenray.rdp.Common; import org.jopenray.rdp.ConnectionException; import org.jopenray.rdp.Constants; import org.jopenray.rdp.Input; import org.jopenray.rdp.Options; import org.jopenray.rdp.RDPCanvas; import org.jopenray.rdp.RDPError; import org.jopenray.rdp.RDPKeymap; import org.jopenray.rdp.RdesktopException; import org.jopenray.rdp.Rdp; import org.jopenray.rdp.keymapping.KeyCodeFileBased; import org.jopenray.rdp.orders.BoundsOrder; import org.jopenray.rdp.rdp5.Rdp5; import org.jopenray.rdp.rdp5.VChannels; import org.jopenray.rdp.rdp5.cliprdr.ClipChannel; import org.jopenray.server.event.Event; import org.jopenray.server.event.EventManager; import org.jopenray.server.session.Session; import org.jopenray.server.thinclient.DisplayMessage; import org.jopenray.server.thinclient.InputListener; import org.jopenray.server.thinclient.ThinClient; import org.jopenray.util.Util; public class RDPAdapter extends RDPCanvas implements InputListener { 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 - protected static final int MOUSE_FLAG_BUTTON5 = 0x0380; // wheel down - protected static final int MOUSE_FLAG_DOWN = 0x8000; 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_INPUT_MOUSE = 0x8001; protected static final int RDP_KEYPRESS = 0; protected static final int RDP_KEYRELEASE = KBD_FLAG_DOWN | KBD_FLAG_UP; static Logger logger = Logger.getLogger("org.jopenray"); static final String keyMapPath = "Keymaps/"; private static final boolean DEBUG_REPAINT = false; static String mapFile = "en-us"; private boolean readytosend; private ThinClient client; private Session session; Rdp5 rdp = null; Input input; private boolean stopped; public RDPAdapter() { super(); } public void start(ThinClient displayClient, Session session) { init(displayClient.getScreenWidth(), displayClient.getScreenHeight()); this.stopped = false; this.client = displayClient; this.session = session; // Logger configuration BasicConfigurator.configure(); logger.setLevel(Level.INFO); String server = session.getServer(); int logonflags = Rdp.RDP_LOGON_NORMAL; VChannels channels = new VChannels(); ClipChannel clipChannel = new ClipChannel(); // Initialise all RDP5 channels if (Options.use_rdp5) { // TODO: implement all relevant channels if (Options.map_clipboard) try { channels.register(clipChannel); } catch (RdesktopException e) { // TODO Auto-generated catch block e.printStackTrace(); } } // Now do the startup... String os = System.getProperty("os.name"); String osvers = System.getProperty("os.version"); if (os.equals("Windows 2000") || os.equals("Windows XP")) Options.built_in_licence = true; logger.info("Operating System is " + os + " version " + osvers); if (os.startsWith("Linux")) Constants.OS = Constants.LINUX; else if (os.startsWith("Windows")) Constants.OS = Constants.WINDOWS; else if (os.startsWith("Mac")) Constants.OS = Constants.MAC; if (Constants.OS == Constants.MAC) Options.caps_sends_up_and_down = false; Options.width = displayClient.getScreenWidth(); Options.height = displayClient.getScreenHeight(); Options.username = session.getLogin(); final String password = session.getPassword(); if (password != null && password.trim().length() > 0) { logonflags |= Rdp.RDP_LOGON_AUTO; Options.password = password; } Options.fullscreen = false; Common.rdp = rdp; // Configure a keyboard layout KeyCodeFileBased keyMap = null; try { // logger.info("looking for: " + "/" + keyMapPath + mapFile); InputStream istr = RDPError.class.getResourceAsStream("/" + keyMapPath + mapFile); // logger.info("istr = " + istr); if (istr == null) { logger.info("Loading keymap from filename"); keyMap = new KeyCodeFileBased(keyMapPath + mapFile); } else { logger.info("Loading keymap from InputStream"); keyMap = new KeyCodeFileBased(istr); } if (istr != null) istr.close(); Options.keylayout = keyMap.getMapCode(); } catch (Exception kmEx) { EventManager.getInstance().add( new Event("Keymap not loaded", kmEx.getMessage(), Event.TYPE_ERROR)); kmEx.printStackTrace(); } boolean[] deactivated = new boolean[1]; int[] ext_disc_reason = new int[1]; boolean keep_running = true; logger.debug("keep_running = " + keep_running); { logger.debug("Initialising RDP layer..."); rdp = new Rdp5(channels); Common.rdp = rdp; logger.debug("Registering drawing surface..."); rdp.registerSurface(this); logger.debug("Registering comms layer..."); // // window.registerCommLayer(rdp); input = new Input(this, rdp, keyMap); readytosend = false; logger .info("Connecting to " + server + ":" + Options.port + " ..."); if (server.equalsIgnoreCase("localhost")) server = "127.0.0.1"; if (rdp != null) { // Attempt to connect to server on port Options.port try { rdp.connect(Options.username, InetAddress.getByName(server), logonflags, Options.domain, Options.password, Options.command, Options.directory); if (keep_running) { /* * By setting encryption to False here, we have an * encrypted login packet but unencrypted transfer of * other packets */ if (!Options.packet_encryption) Options.encryption = false; logger.info("Connection successful"); // now show window after licence negotiation EventManager.getInstance().add( new Event("RDP Connection successfull to " + server, displayClient.getName() + "connected to " + server, Event.TYPE_INFO)); rdp.mainLoop(deactivated, ext_disc_reason, this); if (deactivated[0]) { /* clean disconnect */ rdp.disconnect(); } else { if (ext_disc_reason[0] == RDPError.exDiscReasonAPIInitiatedDisconnect || ext_disc_reason[0] == RDPError.exDiscReasonAPIInitiatedLogoff) { rdp.disconnect(); } if (ext_disc_reason[0] >= 2) { String reason = RDPError .getMessageFromDisconnectError(ext_disc_reason[0]); EventManager.getInstance().add( new Event("Connection terminated", reason, Event.TYPE_WARNING)); rdp.disconnect(); } } keep_running = false; // exited main loop if (!readytosend) { // maybe the licence server was having a comms // problem, retry? String msg1 = "The RDP server (" + server + ") disconnected before licence negotiation completed."; String msg2 = "Possible cause: terminal server could not issue a licence."; logger.warn(msg1); logger.warn(msg2); EventManager.getInstance().add( new Event("RDP server disconnected", msg1, Event.TYPE_WARNING)); } } } catch (ConnectionException e) { EventManager.getInstance().add( new Event("RDP connection failed to " + server, e .getMessage(), Event.TYPE_ERROR)); e.printStackTrace(); rdp.disconnect(); } catch (UnknownHostException e) { // //error(e, rdp, window, true); } catch (SocketException s) { if (rdp.isConnected()) { logger.fatal(s.getClass().getName() + " " + s.getMessage()); rdp.disconnect(); } } catch (RdesktopException e) { String msg1 = e.getClass().getName(); String msg2 = e.getMessage(); logger.fatal(msg1 + ": " + msg2); e.printStackTrace(); if (!readytosend) { // maybe the licence server was having a communication // issue EventManager .getInstance() .add( new Event( "RDP connection failed", "RDP server reset connection before licence negotiation", Event.TYPE_WARNING)); rdp.disconnect(); } else { EventManager.getInstance().add( new Event("RDP connection failed to " + server, e.getMessage(), Event.TYPE_WARNING)); rdp.disconnect(); } } catch (Exception e) { logger.warn(e.getClass().getName() + " " + e.getMessage()); e.printStackTrace(); rdp.disconnect(); } } else { logger .fatal("The communications layer could not be initiated!"); } } rdp.disconnect(); } public void stop() { this.stopped = true; } // @Override public void drawLineVerticalHorizontal(int x1, int y1, int x2, int y2, int color, int opcode) { // TODO Auto-generated method stub super.drawLineVerticalHorizontal(x1, y1, x2, y2, color, opcode); } @Override public void backFill(int x, int y, int cx, int cy, int color) { // TODO Auto-generated method stub DisplayMessage m = new DisplayMessage(client.getWriter()); m.addOperation(new FillOperation(x, y, cx, cy, new Color(color))); this.client.getWriter().addMessage(m); // super.fillRectangle(x, y, cx, cy, color); } @Override public void update(Graphics g) { System.out.println("RDPAdapter.update()"); // super.update(g); } @Override public void paint(Graphics g) { System.out.println("RDPAdapter.paint()"); super.paint(g); } int counter = 0; private long last_mousemove; private Map<Integer, SetMouseCursorOperation> cursorMap = new HashMap<Integer, SetMouseCursorOperation>(); @Override public void repaint(int x, int y, int width, int height) { // System.out.println("RDPAdapter.repaint()" + " " + x + "," + y + " " // + width + "x" + height); if (x < 0) x = 0; if (y < 0) y = 0; if (x > this.width || y > this.height) { return; } if (x + width > this.width) { width = this.width - x; } if (y + height > this.height) { height = this.height - y; } if (height <= 0 || width <= 0) { return; } counter++; try { BufferedImage image = backstore.getSubimage(x, y, width, height); this.client.getWriter().sendImage(image, x, y); if (DEBUG_REPAINT) { try { ImageIO.write(image, "png", new File("out/" + counter + ".png")); } catch (IOException e) { e.printStackTrace(); } } } catch (Exception e) { System.out.println("RDPAdapter.repaint():" + x + "," + y + " " + width + "x" + height); e.printStackTrace(); } // this.backstore.getRGB() // super.repaint(x, y, width, height); } @Override public void setClip(BoundsOrder bounds) { this.top = bounds.getTop(); this.left = bounds.getLeft(); this.right = bounds.getRight(); this.bottom = bounds.getBottom(); } @Override public void resetClip() { this.top = 0; this.left = 0; this.right = this.width - 1; // changed this.bottom = this.height - 1; // changed } @Override public synchronized void addMouseListener(MouseListener l) { System.out.println("RDPAdapter.addMouseListener()" + l); } @Override public void mouseMoved(int mouseX, int mouseY) { // processMouseEvent(new MouseEvent(this, MouseEvent.MOUSE_MOVED, System // .currentTimeMillis(), 0, mouseX, mouseY, 0, false)); // limits to 25 events/s long mTime = System.currentTimeMillis(); if ((mTime - last_mousemove) < (1000 / 25)) return; last_mousemove = mTime; if (rdp != null) { rdp.sendInput((int) System.currentTimeMillis(), RDP_INPUT_MOUSE, MOUSE_FLAG_MOVE, mouseX, mouseY); } } @Override public void mousePressed(int button, int mouseX, int mouseY) { // System.err.println("RDPAdapter.mousePressed()"); if (rdp != null) { if (button == MouseEvent.BUTTON1) { logger.debug("Mouse Button 1 Pressed."); rdp.sendInput((int) System.currentTimeMillis(), RDP_INPUT_MOUSE, MOUSE_FLAG_BUTTON1 | MOUSE_FLAG_DOWN, mouseX, mouseY); } else if (button == MouseEvent.BUTTON2) { logger.debug("Mouse Button 2 Pressed."); rdp.sendInput((int) System.currentTimeMillis(), RDP_INPUT_MOUSE, MOUSE_FLAG_BUTTON3 | MOUSE_FLAG_DOWN, mouseX, mouseY); } else if (button == MouseEvent.BUTTON3) { logger.debug("Middle Mouse Button Pressed."); rdp.sendInput((int) System.currentTimeMillis(), RDP_INPUT_MOUSE, MOUSE_FLAG_BUTTON2 | MOUSE_FLAG_DOWN, mouseX, mouseY); } } } @Override public void mouseReleased(int button, int mouseX, int mouseY) { if (rdp != null) { int time = (int) System.currentTimeMillis(); if (button == MouseEvent.BUTTON1) { logger.debug("Mouse Button 1 released."); rdp.sendInput(time, RDP_INPUT_MOUSE, MOUSE_FLAG_BUTTON1, mouseX, mouseY); } else if (button == MouseEvent.BUTTON2) { logger.debug("Mouse Button 2 released."); rdp.sendInput(time, RDP_INPUT_MOUSE, MOUSE_FLAG_BUTTON3, mouseX, mouseY); } else if (button == MouseEvent.BUTTON3) { logger.debug("Mouse Button 31 released."); rdp.sendInput(time, RDP_INPUT_MOUSE, MOUSE_FLAG_BUTTON2, mouseX, mouseY); } } } @Override public void keyPressed(int key, boolean shift, boolean ctrl, boolean alt, boolean meta, boolean altGr) { KeyEvent e = new KeyEvent(this, KeyEvent.KEY_PRESSED, System .currentTimeMillis(), 0, key); input.lastKeyEvent = e; input.modifiersValid = true; long time = input.getTime(); // Some java versions have keys that don't generate keyPresses - // here we add the key so we can later check if it happened input.pressedKeys.addElement(Integer.valueOf(e.getKeyCode())); logger.info("PRESSED keychar='" + e.getKeyChar() + "' keycode=0x" + Integer.toHexString(e.getKeyCode()) + " char='" + ((char) e.getKeyCode()) + "'"); if (rdp != null) { long t = input.getTime(); input.sendScancode(t, RDP_KEYPRESS, RDPKeymap.getScancode(key)); } } @Override public void keyReleased(int key) { KeyEvent e = new KeyEvent(this, KeyEvent.KEY_RELEASED, System .currentTimeMillis(), 0, key); // Some java versions have keys that don't generate keyPresses - // we added the key to the vector in keyPressed so here we check for // it Integer keycode = new Integer(e.getKeyCode()); if (!input.pressedKeys.contains(keycode)) { keyPressed(key, false, false, false, false, false); } input.pressedKeys.removeElement(keycode); input.lastKeyEvent = e; input.modifiersValid = true; long time = input.getTime(); logger.info("RELEASED keychar='" + e.getKeyChar() + "' keycode=0x" + Integer.toHexString(e.getKeyCode()) + " char='" + ((char) e.getKeyCode()) + "'"); if (rdp != null) { long t = input.getTime(); input.sendScancode(t, RDP_KEYRELEASE, RDPKeymap.getScancode(key)); } } @Override public void blit(int x, int y, int w, int h, int srcx, int srcy) { // System.out.println("Blit:" + x + "," + y + " " + w + "x" + h + // " from " // + srcx + "," + srcy); DisplayMessage m = new DisplayMessage(client.getWriter()); m.addOperation(new CopyOperation(x, y, w, h, srcx, srcy)); this.client.getWriter().addMessage(m); } @Override public void displayImage(int[] data, int w, int h, int x, int y, int cx, int cy) throws RdesktopException { // TODO Auto-generated method stub super.displayImage(data, w, h, x, y, cx, cy); // System.err.println(data.length + " ->" + w + "x" + h + " " + x + "," // + y + " : " + cx + "," + cy); if (w != cx) { int[] newData = new int[cx * cy]; for (int yy = 0; yy < cy; yy++) { System.arraycopy(data, yy * w, newData, yy * cx, cx); } data = newData; } this.client.getWriter().encoder.encode(x, y, cx, cy, data); if (cy != h) { Thread.dumpStack(); } } public boolean isStopped() { return this.stopped; } @Override public void mouseWheelDown(int mouseX, int mouseY) { // System.err.println("RDPAdapter.mousePressed()"); if (rdp != null) { rdp.sendInput((int) System.currentTimeMillis(), RDP_INPUT_MOUSE, MOUSE_FLAG_BUTTON5 | MOUSE_FLAG_DOWN, mouseX, mouseY); } } @Override public void mouseWheelUp(int mouseX, int mouseY) { if (rdp != null) { rdp.sendInput((int) System.currentTimeMillis(), RDP_INPUT_MOUSE, MOUSE_FLAG_BUTTON4 | MOUSE_FLAG_DOWN, mouseX, mouseY); } } public int[] getCursor(int w, int h, byte[] andmask, byte[] xormask) { int pxormask = 0; int pandmask = 0; int size = w * h; int scanline = w / 8; int offset = 0; byte[] mask = new byte[size]; int[] cursor = new int[size]; int pcursor = 0, pmask = 0; offset = size; for (int i = 0; i < h; i++) { offset -= w; pmask = offset; for (int j = 0; j < scanline; j++) { for (int bit = 0x80; bit > 0; bit >>= 1) { if ((andmask[pandmask] & bit) != 0) { mask[pmask] = 0; } else { mask[pmask] = 1; } pmask++; } pandmask++; } } offset = size; pcursor = 0; for (int i = 0; i < h; i++) { offset -= w; pcursor = offset; for (int j = 0; j < w; j++) { cursor[pcursor] = ((xormask[pxormask + 2] << 16) & 0x00ff0000) | ((xormask[pxormask + 1] << 8) & 0x0000ff00) | (xormask[pxormask] & 0x000000ff); pxormask += 3; pcursor++; } } offset = size; pmask = 0; pcursor = 0; pxormask = 0; for (int i = 0; i < h; i++) { for (int j = 0; j < w; j++) { if ((mask[pmask] == 0) && (cursor[pcursor] != 0)) { cursor[pcursor] = ~(cursor[pcursor]); cursor[pcursor] |= 0xff000000; } else if ((mask[pmask] == 1) || (cursor[pcursor] != 0)) { cursor[pcursor] |= 0xff000000; } pcursor++; pmask++; } } return cursor; } @Override public Cursor createCursor(int x, int y, int w, int h, byte[] andmask, byte[] xormask, int cache_idx) { Cursor c = super.createCursor(x, y, w, h, andmask, xormask, cache_idx); DisplayMessage m = new DisplayMessage(client.getWriter()); int[] cursor = getCursor(w, h, andmask, xormask); int bytesPerLine = w / 8; int length = h * bytesPerLine; byte[] bitmap2 = new byte[length]; byte[] bitmap = new byte[length]; byte[] mask2 = new byte[length]; byte[] mask = new byte[length]; andmask = Util.invert(andmask); for (int i = 0; i < h; i++) { for (int j = 0; j < w; j++) { final int offsetFrom = i * w + j; final int offsetTo = i * w + w - j - 1; int b = cursor[offsetFrom]; if (b != 0) { Util.setBit(bitmap2, offsetTo, b != -1); Util.setBit(mask2, offsetTo, true); } else { Util.setBit(mask2, offsetTo, false); } } } int offset = 0; for (int i = h - 1; i >= 0; i--) { for (int j = 0; j < bytesPerLine; j++) { final int o = i * bytesPerLine + j; bitmap[o] = bitmap2[offset]; mask[o] = mask2[offset]; offset++; } } final SetMouseCursorOperation op = new SetMouseCursorOperation(x, y, w, h, Color.WHITE, Color.BLACK, bitmap, mask); cursorMap.put(cache_idx, op); m.addOperation(op); if (session.isHardwareCursorUsed()) { this.client.getWriter().addMessage(m); } return c; } public void setCursorFromIdx(int cache_idx) { SetMouseCursorOperation op = cursorMap.get(cache_idx); if (op == null) { op = new SetMouseCursorOperation( SetMouseCursorOperation.UNIX_CURSOR); } DisplayMessage m = new DisplayMessage(client.getWriter()); m.addOperation(op); this.client.getWriter().addMessage(m); } }