blob: ecfc0d6f82dd68932b44d62a5018389b9457997e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License") + you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openmeetings.screen.webstart;
import static java.awt.Toolkit.getDefaultToolkit;
import static java.awt.datatransfer.DataFlavor.stringFlavor;
import static java.lang.Boolean.TRUE;
import static org.apache.openmeetings.screen.webstart.gui.ScreenDimensions.resizeX;
import static org.apache.openmeetings.screen.webstart.gui.ScreenDimensions.resizeY;
import static org.apache.openmeetings.screen.webstart.gui.ScreenDimensions.spinnerHeight;
import static org.apache.openmeetings.screen.webstart.gui.ScreenDimensions.spinnerWidth;
import static org.apache.openmeetings.screen.webstart.gui.ScreenDimensions.spinnerX;
import static org.apache.openmeetings.screen.webstart.gui.ScreenDimensions.spinnerY;
import static org.slf4j.LoggerFactory.getLogger;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.Robot;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.util.HashMap;
import java.util.Map;
import org.apache.openmeetings.screen.webstart.gui.ScreenSharerFrame;
import org.red5.client.net.rtmp.INetStreamEventHandler;
import org.red5.io.utils.ObjectMap;
import org.red5.server.api.event.IEvent;
import org.red5.server.api.service.IPendingServiceCall;
import org.red5.server.api.service.IPendingServiceCallback;
import org.red5.server.net.rtmp.Channel;
import org.red5.server.net.rtmp.RTMPConnection;
import org.red5.server.net.rtmp.codec.RTMP;
import org.red5.server.net.rtmp.event.Notify;
import org.red5.server.net.rtmp.message.Header;
import org.red5.server.net.rtmp.status.StatusCodes;
import org.slf4j.Logger;
public class CoreScreenShare implements IPendingServiceCallback, INetStreamEventHandler {
private static final Logger log = getLogger(CoreScreenShare.class);
private IScreenShare instance = null;
private String host;
private String app;
private int port;
public String publishName;
private CaptureScreen capture = null;
private RTMPClientPublish publishClient = null;
private ScreenSharerFrame frame;
public int defaultQuality = 1;
public Long organization_id = 0L;
public Long user_id = null;
private boolean allowRecording = true;
private boolean allowPublishing = true;
private boolean startStreaming = false;
private boolean startRecording = false;
private boolean startPublishing = false;
public float Ampl_factor = 1f;
public boolean isConnected = false;
private boolean readyToRecord = false;
public Map<Integer, Boolean> currentPressedKeys = new HashMap<Integer, Boolean>();
private CaptureScreen getCapture() {
if (capture == null) {
capture = new CaptureScreen(this, instance, host, app, port);
}
return capture;
}
// ------------------------------------------------------------------------
//
// Main
//
// ------------------------------------------------------------------------
public CoreScreenShare(IScreenShare instance, String[] args) {
this.instance = instance;
try {
for (String arg : args) {
log.debug("arg: " + arg);
}
String[] textArray = null;
if (args.length > 8) {
host = args[0];
app = args[1];
port = Integer.parseInt(args[2]);
publishName = args[3];
String labelTexts = args[4];
organization_id = Long.parseLong(args[5]);
defaultQuality = Integer.parseInt(args[6]);
user_id = Long.parseLong(args[7]);
allowRecording = bool(args[8]);
allowPublishing = bool(args[9]);
if (labelTexts.length() > 0) {
textArray = labelTexts.split(";");
log.debug("labelTexts :: " + labelTexts);
log.debug("textArray Length " + textArray.length);
for (int i = 0; i < textArray.length; i++) {
log.debug(i + " :: " + textArray[i]);
}
}
log.debug("host: " + host + ", app: "
+ app + ", port: " + port + ", publish: "
+ publishName);
} else {
System.exit(0);
}
createWindow(textArray);
} catch (Exception err) {
log.error("", err);
}
}
// ------------------------------------------------------------------------
//
// GUI
//
// ------------------------------------------------------------------------
public void createWindow(String[] textArray) {
try {
frame = new ScreenSharerFrame(this, textArray);
frame.setVisible(true);
frame.setRecordingTabEnabled(allowRecording);
frame.setPublishingTabEnabled(allowPublishing);
log.debug("initialized");
} catch (Exception err) {
log.error("createWindow Exception: ", err);
}
}
synchronized public void sendCursorStatus() {
try {
Point mouseP = MouseInfo.getPointerInfo().getLocation();
float scaleFactor = (1.0f * resizeX) / spinnerWidth;
// Real size: Real mouse position = Resize : X
int x = (int)(Ampl_factor * (mouseP.getX() - spinnerX) * scaleFactor);
int y = (int)(Ampl_factor * (mouseP.getY() - spinnerY) * scaleFactor);
Map<String, Object> cursorPosition = new HashMap<String, Object>();
cursorPosition.put("publicSID", publishName);
cursorPosition.put("cursor_x", x);
cursorPosition.put("cursor_y", y);
if (instance.getConnection() != null) {
instance.invoke("setNewCursorPosition", new Object[] { cursorPosition }, this);
}
} catch (NullPointerException npe) {
//noop
} catch (Exception err) {
frame.setStatus("Exception: " + err);
log.error("[sendCursorStatus]", err);
}
}
synchronized public void setConnectionAsSharingClient() {
try {
log.debug("########## setConnectionAsSharingClient");
Map<Object, Object> map = new HashMap<Object, Object>();
map.put("screenX", spinnerX);
map.put("screenY", spinnerY);
int scaledWidth = (int)(Ampl_factor * resizeX);
int scaledHeight = (int)(Ampl_factor * resizeY);
map.put("screenWidth", scaledWidth);
map.put("screenHeight", scaledHeight);
map.put("publishName", publishName);
map.put("startRecording", startRecording);
map.put("startStreaming", startStreaming);
map.put("startPublishing", startPublishing);
map.put("publishingHost", frame.getPublishHost());
map.put("publishingApp", frame.getPublishApp());
map.put("publishingId", frame.getPublishId());
map.put("organization_id", organization_id);
map.put("user_id", user_id);
instance.invoke("setConnectionAsSharingClient", new Object[] { map }, this);
} catch (Exception err) {
log.error("setConnectionAsSharingClient Exception: ", err);
frame.setStatus("Error: " + err.getLocalizedMessage());
log.error("[setConnectionAsSharingClient]", err);
}
}
/**
* @param startStreaming flag denoting the streaming is started
* @param startRecording flag denoting the recording is started
*/
public void captureScreenStart(boolean startStreaming, boolean startRecording) {
captureScreenStart(startStreaming, startRecording, false);
}
public void captureScreenStart(boolean startStreaming, boolean startRecording, boolean startPublishing) {
try {
log.debug("captureScreenStart");
this.startStreaming = startStreaming;
this.startRecording= startRecording;
this.startPublishing = startPublishing;
if (!isConnected) {
instance.connect(host, port, app, this);
} else {
setConnectionAsSharingClient();
}
} catch (Exception err) {
log.error("captureScreenStart Exception: ", err);
frame.setStatus("Exception: " + err);
}
}
public void sendCaptureScreenStop(boolean stopStreaming, boolean stopRecording) {
try {
log.debug("INVOKE screenSharerAction" );
Map<String, Object> map = new HashMap<String, Object>();
map.put("stopStreaming", stopStreaming);
map.put("stopRecording", stopRecording);
instance.invoke("screenSharerAction", new Object[] { map }, this);
} catch (Exception err) {
log.error("captureScreenStop Exception: ", err);
frame.setStatus("Exception: " + err);
}
}
public void sendStopPublishing() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("stopPublishing", true);
instance.invoke("screenSharerAction", new Object[] { map }, this);
}
public void stopStreaming() {
startStreaming = false;
frame.setSharingStatus(false, !startPublishing && !startRecording && !startStreaming);
}
public void stopRecording() {
startRecording = false;
frame.setRecordingStatus(false, !startPublishing && !startRecording && !startStreaming);
}
public void stopPublishing() {
startPublishing = false;
frame.setPublishingStatus(false, !startPublishing && !startRecording && !startStreaming);
if (publishClient != null) {
publishClient.disconnect();
publishClient = null;
}
}
synchronized public boolean isReadyToRecord() {
return readyToRecord;
}
synchronized private void setReadyToRecord(boolean readyToRecord) {
this.readyToRecord = readyToRecord;
}
protected void onInvoke(RTMPConnection conn, Channel channel, Header source, org.red5.server.net.rtmp.event.Notify invoke, RTMP rtmp) {
if (invoke.getType() == IEvent.Type.STREAM_DATA) {
return;
}
String method = invoke.getCall().getServiceMethodName();
if ("stopStream".equals(method)) {
stopStream();
} else if ("sendRemoteCursorEvent".equals(method)) {
sendRemoteCursorEvent(invoke.getCall().getArguments()[0]);
} else if ("screenSharerAction".equals(method)) {
Object[] args = invoke.getCall().getArguments();
if (args != null && args.length > 0) {
@SuppressWarnings("unchecked")
Map<String, Object> params = (Map<String, Object>)args[0];
if (bool(params.get("stopPublishing"))) {
stopPublishing();
}
if (params.containsKey("error")) {
frame.setStatus("" + params.get("error"));
}
}
}
}
public void stopStream() {
try {
log.debug("ScreenShare stopStream");
stopStreaming();
stopRecording();
stopPublishing();
isConnected = false;
instance.disconnect();
setReadyToRecord(false);
getCapture().setStartPublish(false);
getCapture().release();
capture = null;
} catch (Exception e) {
log.error("ScreenShare stopStream exception " + e);
}
}
public void onStreamEvent(Notify notify) {
log.debug( "onStreamEvent " + notify );
@SuppressWarnings("rawtypes")
ObjectMap map = (ObjectMap) notify.getCall().getArguments()[0];
String code = (String) map.get("code");
if (StatusCodes.NS_PUBLISH_START.equals(code)) {
log.debug( "onStreamEvent Publish start" );
getCapture().setStartPublish(true);
setReadyToRecord(true);
}
}
private boolean bool(Object b) {
return TRUE.equals(Boolean.valueOf("" + b));
}
private int getInt(Map<String, Object> returnMap, String key) {
return Integer.valueOf(returnMap.get(key).toString()).intValue();
}
private float getFloat(Map<String, Object> returnMap, String key) {
return Float.valueOf(returnMap.get(key).toString()).floatValue();
}
private Point getCoordinates(Map<String, Object> returnMap) {
float scaleFactorX = spinnerWidth / (Ampl_factor * resizeX);
float scaleFactorY = spinnerHeight / (Ampl_factor * resizeY);
int x = Math.round(scaleFactorX * getFloat(returnMap, "x") + spinnerX);
int y = Math.round(scaleFactorY * getFloat(returnMap, "y") + spinnerY);
return new Point(x, y);
}
public void sendRemoteCursorEvent(Object obj) {
try {
log.trace("#### sendRemoteCursorEvent ");
log.trace("Result Map Type "+obj.getClass().getName());
@SuppressWarnings("unchecked")
Map<String, Object> returnMap = (Map<String, Object>)obj;
String action = "" + returnMap.get("action");
if (action.equals("onmouseup")) {
Robot robot = new Robot();
Point p = getCoordinates(returnMap);
robot.mouseMove(p.x, p.y);
robot.mouseRelease(InputEvent.BUTTON1_MASK);
} else if (action.equals("onmousedown")) {
Robot robot = new Robot();
Point p = getCoordinates(returnMap);
robot.mouseMove(p.x, p.y);
robot.mousePress(InputEvent.BUTTON1_MASK);
} else if (action.equals("mousePos")) {
Robot robot = new Robot();
Point p = getCoordinates(returnMap);
robot.mouseMove(p.x, p.y);
} else if (action.equals("onkeydown")) {
Robot robot = new Robot();
int key = getInt(returnMap, "k");
// logger.debug("key onkeydown -1 "+key);
boolean doAction = true;
if (key == 221) {
key = 61;
} else if (key == -1) {
String charValue = returnMap.get("c").toString();
// key = KeyEvent.VK_ADD;
doAction = false;
for (Integer storedKey : currentPressedKeys.keySet()) {
robot.keyRelease(storedKey);
}
currentPressedKeys = new HashMap<Integer, Boolean>();
pressSpecialSign(charValue, robot);
} else if (key == 188) {
key = 44;
} else if (key == 189) {
key = 109;
} else if (key == 190) {
key = 46;
} else if (key == 191) {
key = 47;
} else if (key == 13) {
key = KeyEvent.VK_ENTER;
}
if (doAction) {
currentPressedKeys.put(key, true);
robot.keyPress(key);
}
} else if (action.equals("onkeyup")) {
Robot robot = new Robot();
int key = getInt(returnMap, "k");
boolean doAction = true;
if (key == 221) {
key = 61;
} else if (key == -1) {
doAction = false;
} else if (key == 188) {
key = 44;
} else if (key == 189) {
key = 109;
} else if (key == 190) {
key = 46;
} else if (key == 191) {
key = 47;
} else if (key == 13) {
key = KeyEvent.VK_ENTER;
}
// logger.debug("key onkeyup 2- "+key);
if (doAction) {
if (currentPressedKeys.containsKey(key)) {
currentPressedKeys.remove(key);
robot.keyRelease(key);
}
}
} else if (action.equals("paste")) {
Robot robot = new Robot();
String paste = returnMap.get("paste").toString();
pressSpecialSign(paste, robot);
} else if (action.equals("copy")) {
Robot robot = new Robot();
String paste = this.getHighlightedText(robot);
Map<Integer, String> map = new HashMap<Integer, String>();
map.put(0, "copiedText");
map.put(1, paste);
String clientId = returnMap.get("clientId").toString();
instance.invoke("sendMessageWithClientById", new Object[]{map, clientId}, this);
} else if (action.equals("show")) {
String paste = getClipboardText();
Map<Integer, String> map = new HashMap<Integer, String>();
map.put(0, "copiedText");
map.put(1, paste);
String clientId = returnMap.get("clientId").toString();
// public synchronized int sendMessageWithClientById(Object
// newMessage, String clientId)
instance.invoke("sendMessageWithClientById", new Object[]{map, clientId}, this);
}
} catch (Exception err) {
log.error("[sendRemoteCursorEvent]", err);
}
}
public String getClipboardText() {
try {
// get the system clipboard
Clipboard systemClipboard = getDefaultToolkit().getSystemClipboard();
// get the contents on the clipboard in a
// transferable object
Transferable clipboardContents = systemClipboard.getContents(null);
// check if clipboard is empty
if (clipboardContents == null) {
// Clipboard is empty!!!
return ("");
// see if DataFlavor of
// DataFlavor.stringFlavor is supported
} else if (clipboardContents.isDataFlavorSupported(stringFlavor)) {
// return text content
String returnText = (String) clipboardContents.getTransferData(stringFlavor);
return returnText;
}
return "";
} catch (Exception e) {
log.error("Unexpected exception while getting clipboard text", e);
}
return "";
}
private void pressSequence(Robot robot, long delay, int... codes) throws InterruptedException {
for (int code : codes) {
robot.keyPress(code);
Thread.sleep(delay);
}
}
private String getHighlightedText(Robot instance) {
try {
if (System.getProperty("os.name").toUpperCase().indexOf("WINDOWS") >= 0) {
// pressing STRG+C == copy
pressSequence(instance, 200, KeyEvent.VK_CONTROL, KeyEvent.VK_C, KeyEvent.VK_C, KeyEvent.VK_CONTROL);
} else {
// Macintosh simulate Copy
pressSequence(instance, 200, 157, 67, 67, 157);
}
return getClipboardText();
} catch (Exception e) {
log.error("Unexpected exception while getting highlighted text", e);
}
return "";
}
private void pressSpecialSign(String charValue, Robot instance) {
Clipboard clippy = getDefaultToolkit().getSystemClipboard();
try {
Transferable transferableText = new StringSelection(charValue);
clippy.setContents(transferableText, null);
if (System.getProperty("os.name").toUpperCase().indexOf("WINDOWS") >= 0) {
// pressing STRG+V == insert-mode
pressSequence(instance, 100, KeyEvent.VK_CONTROL, KeyEvent.VK_V, KeyEvent.VK_V, KeyEvent.VK_CONTROL);
} else {
// Macintosh simulate Insert
pressSequence(instance, 100, 157, 86, 86, 157);
}
} catch (Exception e) {
log.error("Unexpected exception while pressSpecialSign", e);
}
}
public void resultReceived(IPendingServiceCall call) {
try {
log.trace( "service call result: " + call );
String method = call == null ? null : call.getServiceMethodName();
log.trace("call ### get Method Name " + method);
if ("connect".equals(method)) {
isConnected = true;
setConnectionAsSharingClient();
} else if ("setConnectionAsSharingClient".equals(method)) {
Object o = call.getResult();
@SuppressWarnings("unchecked")
Map<String, Object> returnMap = (Map<String, Object>) o;
if (o == null || !bool(returnMap.get("alreadyPublished"))) {
log.trace("Stream not yet started - do it ");
instance.createStream(this);
} else {
getCapture().resetBuffer();
log.trace("The Stream was already started ");
}
if (returnMap != null) {
Object modus = returnMap.get("modus");
if ("startStreaming".equals(modus)) {
frame.setSharingStatus(true, false);
}
if ("startRecording".equals(modus)) {
frame.setRecordingStatus(true, false);
}
if ("startPublishing".equals(modus)) {
frame.setPublishingStatus(true, false);
publishClient = new RTMPClientPublish(
this
, frame.getPublishHost()
, frame.getPublishApp()
, frame.getPublishId());
publishClient.connect();
}
} else {
throw new Exception("Could not aquire modus for event setConnectionAsSharingClient");
}
} else if ("createStream".equals(method)) {
if (startRecording || startStreaming) {
if (call.getResult() != null) {
getCapture().setStreamId((Integer)call.getResult());
}
log.debug("createPublishStream result stream id: " + getCapture().getStreamId());
log.debug("publishing video by name: " + publishName);
instance.publish(getCapture().getStreamId(), publishName, "live", this);
log.debug("setup capture thread");
log.debug("setup capture thread vScreenSpinnerWidth " + spinnerWidth);
log.debug("setup capture thread vScreenSpinnerHeight " + spinnerHeight);
getCapture().setSendCursor(true);
getCapture().start();
}
} else if ("screenSharerAction".equals(method)) {
Object o = call.getResult();
log.trace("Result Map Type " + o.getClass().getName());
@SuppressWarnings("unchecked")
Map<String, Object> returnMap = (Map<String, Object>)o;
Object result = returnMap.get("result");
if ("stopAll".equals(result)) {
log.trace("Stopping to stream, there is neither a Desktop Sharing nor Recording anymore");
stopStream();
} else if ("stopSharingOnly".equals(result)) {
stopStreaming();
} else if ("stopRecordingOnly".equals(result)) {
stopRecording();
} else if ("stopPublishingOnly".equals(result)) {
stopPublishing();
}
} else if ("setNewCursorPosition".equals(method)) {
// Do not do anything
} else {
log.debug("Unknown method " + method);
}
} catch (Exception err) {
log.error("[resultReceived]", err);
}
}
}