Added: openmeetings/application/trunk/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/job/EncodeJob.java URL: http://svn.apache.org/viewvc/openmeetings/application/trunk/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/job/EncodeJob.java?rev=1760631&view=auto ============================================================================== --- openmeetings/application/trunk/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/job/EncodeJob.java (added) +++ openmeetings/application/trunk/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/job/EncodeJob.java Wed Sep 14 05:24:18 2016 @@ -0,0 +1,83 @@ +/* + * 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.screenshare.job; + +import static org.apache.openmeetings.screenshare.gui.ScreenDimensions.spinnerHeight; +import static org.apache.openmeetings.screenshare.gui.ScreenDimensions.spinnerWidth; +import static org.apache.openmeetings.screenshare.gui.ScreenDimensions.spinnerX; +import static org.apache.openmeetings.screenshare.gui.ScreenDimensions.spinnerY; +import static org.slf4j.LoggerFactory.getLogger; + +import java.awt.AWTException; +import java.awt.Rectangle; +import java.awt.Robot; + +import org.apache.openmeetings.screenshare.CaptureScreen; +import org.apache.openmeetings.screenshare.ScreenV1Encoder; +import org.quartz.DisallowConcurrentExecution; +import org.quartz.Job; +import org.quartz.JobDataMap; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.red5.server.net.rtmp.event.VideoData; +import org.slf4j.Logger; + +@DisallowConcurrentExecution +public class EncodeJob implements Job { + private static final Logger log = getLogger(EncodeJob.class); + public static final String CAPTURE_KEY = "capture"; + Robot robot; + Rectangle screen = new Rectangle(spinnerX, spinnerY, spinnerWidth, spinnerHeight); + int[][] image = null; + + public EncodeJob() { + try { + robot = new Robot(); + } catch (AWTException e) { + log.error("encode: Unexpected Error while creating robot", e); + } + } + + @Override + public void execute(JobExecutionContext context) throws JobExecutionException { + JobDataMap data = context.getJobDetail().getJobDataMap(); + CaptureScreen capture = (CaptureScreen)data.get(CAPTURE_KEY); + + long start = 0; + if (log.isTraceEnabled()) { + start = System.currentTimeMillis(); + } + image = ScreenV1Encoder.getImage(screen, robot); + if (log.isTraceEnabled()) { + log.trace(String.format("encode: Image was captured in %s ms, size %sk", System.currentTimeMillis() - start, 4 * image.length * image[0].length / 1024)); + start = System.currentTimeMillis(); + } + try { + VideoData vData = capture.getEncoder().encode(image); + if (log.isTraceEnabled()) { + long now = System.currentTimeMillis(); + log.trace(String.format("encode: Image was encoded in %s ms, timestamp is %s", now - start, now - capture.getStartTime())); + } + capture.getFrames().offer(vData); + capture.getEncoder().createUnalteredFrame(); + } catch (Exception e) { + log.error("Error while encoding: ", e); + } + } +}
Added: openmeetings/application/trunk/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/job/OmKeyEvent.java URL: http://svn.apache.org/viewvc/openmeetings/application/trunk/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/job/OmKeyEvent.java?rev=1760631&view=auto ============================================================================== --- openmeetings/application/trunk/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/job/OmKeyEvent.java (added) +++ openmeetings/application/trunk/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/job/OmKeyEvent.java Wed Sep 14 05:24:18 2016 @@ -0,0 +1,128 @@ +/* + * 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.screenshare.job; + +import static java.lang.Boolean.TRUE; +import java.awt.event.KeyEvent; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; + +import static org.apache.openmeetings.screenshare.util.Util.getInt; +import static org.slf4j.LoggerFactory.getLogger; + +public class OmKeyEvent { + private static final Logger log = getLogger(OmKeyEvent.class); + private static final Map<Integer, Integer> KEY_MAP = new HashMap<>(); + static { + KEY_MAP.put(13, KeyEvent.VK_ENTER); + KEY_MAP.put(16, 0); + KEY_MAP.put(20, KeyEvent.VK_CAPS_LOCK); + KEY_MAP.put(46, KeyEvent.VK_DELETE); + KEY_MAP.put(110, KeyEvent.VK_DECIMAL); + KEY_MAP.put(186, KeyEvent.VK_SEMICOLON); + KEY_MAP.put(187, KeyEvent.VK_EQUALS); + KEY_MAP.put(188, KeyEvent.VK_COMMA); + KEY_MAP.put(189, KeyEvent.VK_MINUS); + KEY_MAP.put(190, KeyEvent.VK_PERIOD); + KEY_MAP.put(191, KeyEvent.VK_SLASH); + KEY_MAP.put(219, KeyEvent.VK_OPEN_BRACKET); + KEY_MAP.put(220, KeyEvent.VK_BACK_SLASH); + KEY_MAP.put(221, KeyEvent.VK_CLOSE_BRACKET); + KEY_MAP.put(222, KeyEvent.VK_QUOTE); + } + private boolean alt = false; + private boolean ctrl = false; + private boolean shift = false; + private int key = 0; + private char ch = 0; + + public OmKeyEvent(int key) { + this(key, false); + } + + public OmKeyEvent(int key, boolean shift) { + this.key = key; + this.shift = shift; + } + + public OmKeyEvent(Map<String, Object> obj) { + alt = TRUE.equals(obj.get("alt")); + ctrl = TRUE.equals(obj.get("ctrl")); + shift = TRUE.equals(obj.get("shift")); + ch = (char)getInt(obj, "char"); + Integer _key = null; + int key = getInt(obj, "key"); + if (key == 0) { + if (ch == 61) { + this.key = KeyEvent.VK_EQUALS; + } + } else { + _key = KEY_MAP.get(key); + this.key = _key == null ? key : _key; + } + log.debug("sequence:: shift {}, orig {} -> key {}, _key {}", shift, key, this.key, _key); + } + + public int[] sequence() { + List<Integer> list = new ArrayList<>(); + if (alt) { + list.add(KeyEvent.VK_ALT); + } + if (ctrl) { + list.add(KeyEvent.VK_CONTROL); + } + if (shift) { + list.add(KeyEvent.VK_SHIFT); + } + if (key != 0) { + list.add(key); + } + return list.stream().mapToInt(Integer::intValue).toArray(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + key; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof OmKeyEvent)) { + return false; + } + OmKeyEvent other = (OmKeyEvent) obj; + if (key != other.key) { + return false; + } + return true; + } +} Added: openmeetings/application/trunk/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/job/RemoteJob.java URL: http://svn.apache.org/viewvc/openmeetings/application/trunk/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/job/RemoteJob.java?rev=1760631&view=auto ============================================================================== --- openmeetings/application/trunk/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/job/RemoteJob.java (added) +++ openmeetings/application/trunk/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/job/RemoteJob.java Wed Sep 14 05:24:18 2016 @@ -0,0 +1,194 @@ +/* + * 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.screenshare.job; + +import static java.awt.Toolkit.getDefaultToolkit; +import static java.awt.datatransfer.DataFlavor.stringFlavor; +import static org.apache.openmeetings.screenshare.Core.Ampl_factor; +import static org.apache.openmeetings.screenshare.gui.ScreenDimensions.resizeX; +import static org.apache.openmeetings.screenshare.gui.ScreenDimensions.resizeY; +import static org.apache.openmeetings.screenshare.gui.ScreenDimensions.spinnerHeight; +import static org.apache.openmeetings.screenshare.gui.ScreenDimensions.spinnerWidth; +import static org.apache.openmeetings.screenshare.gui.ScreenDimensions.spinnerX; +import static org.apache.openmeetings.screenshare.gui.ScreenDimensions.spinnerY; +import static org.apache.openmeetings.screenshare.util.Util.getFloat; +import static org.slf4j.LoggerFactory.getLogger; + +import java.awt.AWTException; +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 java.util.concurrent.TimeUnit; + +import org.apache.openmeetings.screenshare.Core; +import org.quartz.DisallowConcurrentExecution; +import org.quartz.Job; +import org.quartz.JobDataMap; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.slf4j.Logger; + +@DisallowConcurrentExecution +public class RemoteJob implements Job { + private static final Logger log = getLogger(RemoteJob.class); + private static final boolean isWindows = System.getProperty("os.name").toUpperCase().indexOf("WINDOWS") > -1; + public static final String CORE_KEY = "core"; + private Robot robot = null; + + public RemoteJob() { + try { + robot = new Robot(); + robot.setAutoDelay(5); + } catch (AWTException e) { + log.error("Unexpected error while creating Robot", e); + } + } + + @Override + public void execute(JobExecutionContext context) throws JobExecutionException { + JobDataMap data = context.getJobDetail().getJobDataMap(); + Core core = (Core)data.get(CORE_KEY); + try { + Map<String, Object> obj = null; + while ((obj = core.getRemoteEvents().poll(1, TimeUnit.MILLISECONDS)) != null) { + String action = "" + obj.get("action"); + log.trace("Action polled:: {}, count: {}", action, core.getRemoteEvents().size()); + + if (action.equals("onmouseup")) { + Point p = getCoordinates(obj); + robot.mouseMove(p.x, p.y); + robot.mouseRelease(InputEvent.BUTTON1_MASK); + } else if (action.equals("onmousedown")) { + Point p = getCoordinates(obj); + robot.mouseMove(p.x, p.y); + robot.mousePress(InputEvent.BUTTON1_MASK); + } else if (action.equals("mousePos")) { + Point p = getCoordinates(obj); + robot.mouseMove(p.x, p.y); + } else if (action.equals("keyDown")) { + pressSequence(new OmKeyEvent(obj).sequence()); + } else if (action.equals("paste")) { + String paste = obj.get("paste").toString(); + pressSpecialSign(paste); + } else if (action.equals("copy")) { + String paste = getHighlightedText(); + + Map<Integer, String> map = new HashMap<Integer, String>(); + map.put(0, "copiedText"); + map.put(1, paste); + + String clientId = obj.get("clientId").toString(); + + core.getInstance().invoke("sendMessageWithClientById", new Object[]{map, clientId}, core); + } 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 = obj.get("clientId").toString(); + + core.getInstance().invoke("sendMessageWithClientById", new Object[]{map, clientId}, core); + } + } + } catch (Exception err) { + log.error("[sendRemoteCursorEvent]", err); + } + } + + private void pressSequence(int... codes) throws InterruptedException { + for (int i = 0; i < codes.length; ++i) { + robot.keyPress(codes[i]); + } + for (int i = codes.length - 1; i > -1; --i) { + robot.keyRelease(codes[i]); + } + } + + private String getHighlightedText() { + try { + if (isWindows) { + // pressing STRG+C == copy + pressSequence(KeyEvent.VK_CONTROL, KeyEvent.VK_C); + } else { + // Macintosh simulate Copy + pressSequence(157, 67); + } + return getClipboardText(); + } catch (Exception e) { + log.error("Unexpected exception while getting highlighted text", e); + } + return ""; + } + + 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!!! + } else if (clipboardContents.isDataFlavorSupported(stringFlavor)) { + // see if DataFlavor of DataFlavor.stringFlavor is supported + // return text content + String returnText = (String) clipboardContents.getTransferData(stringFlavor); + return returnText; + } + } catch (Exception e) { + log.error("Unexpected exception while getting clipboard text", e); + } + return ""; + } + + private void pressSpecialSign(String charValue) { + Clipboard clippy = getDefaultToolkit().getSystemClipboard(); + try { + Transferable transferableText = new StringSelection(charValue); + clippy.setContents(transferableText, null); + + if (isWindows) { + // pressing STRG+V == insert-mode + pressSequence(KeyEvent.VK_CONTROL, KeyEvent.VK_V); + } else { + // Macintosh simulate Insert + pressSequence(157, 86); + } + } catch (Exception e) { + log.error("Unexpected exception while pressSpecialSign", e); + } + } + + private Point getCoordinates(Map<String, Object> obj) { + float scaleFactorX = spinnerWidth / (Ampl_factor * resizeX); + float scaleFactorY = spinnerHeight / (Ampl_factor * resizeY); + + int x = Math.round(scaleFactorX * getFloat(obj, "x") + spinnerX); + int y = Math.round(scaleFactorY * getFloat(obj, "y") + spinnerY); + return new Point(x, y); + } +} Added: openmeetings/application/trunk/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/job/SendJob.java URL: http://svn.apache.org/viewvc/openmeetings/application/trunk/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/job/SendJob.java?rev=1760631&view=auto ============================================================================== --- openmeetings/application/trunk/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/job/SendJob.java (added) +++ openmeetings/application/trunk/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/job/SendJob.java Wed Sep 14 05:24:18 2016 @@ -0,0 +1,71 @@ +/* + * 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.screenshare.job; + +import static org.slf4j.LoggerFactory.getLogger; + +import java.io.IOException; + +import org.apache.openmeetings.screenshare.CaptureScreen; +import org.quartz.Job; +import org.quartz.JobDataMap; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.red5.server.net.rtmp.event.VideoData; +import org.slf4j.Logger; + +public class SendJob implements Job { + private static final Logger log = getLogger(SendJob.class); + public static final String CAPTURE_KEY = "capture"; + public SendJob() {} + + @Override + public void execute(JobExecutionContext context) throws JobExecutionException { + JobDataMap data = context.getJobDetail().getJobDataMap(); + CaptureScreen capture = (CaptureScreen)data.get(CAPTURE_KEY); + capture.setSendFrameGuard(true); + if (log.isTraceEnabled()) { + long real = System.currentTimeMillis() - capture.getStartTime(); + log.trace(String.format("send: Enter method, timestamp: %s, real: %s, diff: %s", capture.getTimestamp(), real, real - capture.getTimestamp().get())); + } + VideoData f = capture.getFrames().poll(); + if (log.isTraceEnabled()) { + log.trace(String.format("send: Getting %s image", f == null ? "DUMMY" : "CAPTURED")); + } + f = f == null ? capture.getEncoder().getUnalteredFrame() : f; + if (f != null) { + try { + capture.pushVideo(f, capture.getTimestamp().get()); + if (log.isTraceEnabled()) { + long real = System.currentTimeMillis() - capture.getStartTime(); + log.trace(String.format("send: Sending video %sk, timestamp: %s, real: %s, diff: %s", f.getData().capacity() / 1024, capture.getTimestamp(), real, real - capture.getTimestamp().get())); + } + capture.getTimestamp().addAndGet(capture.getTimestampDelta()); + if (log.isTraceEnabled()) { + log.trace(String.format("send: new timestamp: %s", capture.getTimestamp())); + } + } catch (IOException e) { + log.error("Error while sending: ", e); + } + } else if (log.isTraceEnabled()) { + log.trace(String.format("send: nothing to send")); + } + capture.setSendFrameGuard(false); + } +} Added: openmeetings/application/trunk/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/util/Util.java URL: http://svn.apache.org/viewvc/openmeetings/application/trunk/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/util/Util.java?rev=1760631&view=auto ============================================================================== --- openmeetings/application/trunk/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/util/Util.java (added) +++ openmeetings/application/trunk/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/util/Util.java Wed Sep 14 05:24:18 2016 @@ -0,0 +1,52 @@ +/* + * 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.screenshare.util; + +import static org.quartz.impl.StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME; +import static org.quartz.impl.StdSchedulerFactory.PROP_SCHED_SKIP_UPDATE_CHECK; + +import java.util.Map; +import java.util.Properties; + +public class Util { + public static Properties getQurtzProps(String name) { + final Properties p = new Properties(); + p.put(PROP_SCHED_SKIP_UPDATE_CHECK, "true"); + p.put(PROP_SCHED_INSTANCE_NAME, name); + p.put("org.quartz.threadPool.threadCount", "10"); + return p; + } + + public static String getString(Map<String, Object> map, String key) { + return String.valueOf(map.get(key)); + } + + public static Double getDouble(Map<String, Object> map, String key) { + return Double.valueOf(getString(map, key)); + } + + public static int getInt(Map<String, Object> map, String key) { + return getDouble(map, key).intValue(); + } + + public static float getFloat(Map<String, Object> map, String key) { + return getDouble(map, key).floatValue(); + } + +} Modified: openmeetings/application/trunk/openmeetings-screenshare/src/main/jnlp/templates/template.jnlp URL: http://svn.apache.org/viewvc/openmeetings/application/trunk/openmeetings-screenshare/src/main/jnlp/templates/template.jnlp?rev=1760631&r1=1760630&r2=1760631&view=diff ============================================================================== --- openmeetings/application/trunk/openmeetings-screenshare/src/main/jnlp/templates/template.jnlp (original) +++ openmeetings/application/trunk/openmeetings-screenshare/src/main/jnlp/templates/template.jnlp Wed Sep 14 05:24:18 2016 @@ -36,7 +36,7 @@ <jar href="bcprov-jdk15on-1.55.jar" main="true"/> <jar href="openmeetings-screenshare-${project.version}-full.jar" main="true"/> </resources> - <application-desc main-class='org.apache.openmeetings.screen.webstart.CoreScreenShare'> + <application-desc main-class='org.apache.openmeetings.screenshare.Core'> <argument>$url</argument> <argument>$publicSid</argument> <argument>$labels</argument>
