diff options
author | Sebastien Vincent <seb@jitsi.org> | 2010-09-28 07:46:45 +0000 |
---|---|---|
committer | Sebastien Vincent <seb@jitsi.org> | 2010-09-28 07:46:45 +0000 |
commit | 6a67589c86221faedcebd370824274013be64026 (patch) | |
tree | b5a73d1deec90532057f70b801cafaa015a907e0 /src/net/java/sip/communicator/impl | |
parent | a4ddb0361ebc3848e30eefbf0391c755adb3387c (diff) | |
download | jitsi-6a67589c86221faedcebd370824274013be64026.zip jitsi-6a67589c86221faedcebd370824274013be64026.tar.gz jitsi-6a67589c86221faedcebd370824274013be64026.tar.bz2 |
Desktop sharing support for SIP and XMPP. Note that GUI is not ready yet to propose this feature to the users.
Diffstat (limited to 'src/net/java/sip/communicator/impl')
30 files changed, 4107 insertions, 21 deletions
diff --git a/src/net/java/sip/communicator/impl/gui/main/call/CallManager.java b/src/net/java/sip/communicator/impl/gui/main/call/CallManager.java index d9c0daf..ebe3e8d 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/CallManager.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/CallManager.java @@ -31,6 +31,9 @@ import net.java.sip.communicator.util.*; */ public class CallManager { + /** + * Our class logger. + */ private static final Logger logger = Logger.getLogger(CallManager.class); /** @@ -329,7 +332,7 @@ public class CallManager */ public static void transferCall(CallPeer peer, CallPeer target) { - OperationSetAdvancedTelephony telephony + OperationSetAdvancedTelephony<?> telephony = peer.getCall().getProtocolProvider() .getOperationSet(OperationSetAdvancedTelephony.class); @@ -354,7 +357,7 @@ public class CallManager */ public static void transferCall(CallPeer peer, String target) { - OperationSetAdvancedTelephony telephony + OperationSetAdvancedTelephony<?> telephony = peer.getCall().getProtocolProvider() .getOperationSet(OperationSetAdvancedTelephony.class); diff --git a/src/net/java/sip/communicator/impl/gui/main/call/OneToOneCallPeerPanel.java b/src/net/java/sip/communicator/impl/gui/main/call/OneToOneCallPeerPanel.java index d67959d..d57b119 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/OneToOneCallPeerPanel.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/OneToOneCallPeerPanel.java @@ -29,18 +29,28 @@ import net.java.sip.communicator.util.swing.*; * * @author Yana Stamcheva * @author Lubomir Marinov + * @author Sebastien Vincent */ public class OneToOneCallPeerPanel extends TransparentPanel implements CallPeerRenderer { /** + * Serial version UID. + */ + private static final long serialVersionUID = 0L; + + /** * The <tt>Logger</tt> used by the <tt>OneToOneCallPeerPanel</tt> class and * its instances for logging output. */ private static final Logger logger = Logger.getLogger(OneToOneCallPeerPanel.class); + /** + * The <tt>CallPeerAdapter</tt> that implements all common tt>CallPeer</tt> + * related listeners. + */ private CallPeerAdapter callPeerAdapter; /** @@ -129,11 +139,28 @@ public class OneToOneCallPeerPanel private Component localVideo; /** + * The component showing the remote video. + */ + private Component remoteVideo; + + /** * The <tt>CallPeer</tt>, which is rendered in this panel. */ private CallPeer callPeer; /** + * In case of desktop streaming (client-side) if the local peer can control + * remote peer's computer. + */ + private boolean allowRemoteControl = false; + + /** + * Listener for all key and mouse events. It is used for desktop sharing + * purposes. + */ + private MouseAndKeyListener mouseAndKeyListener = null; + + /** * Creates a <tt>CallPeerPanel</tt> for the given call peer. * * @param callRenderer the renderer of the call @@ -201,6 +228,7 @@ public class OneToOneCallPeerPanel this.createSoundLevelIndicators(); addVideoListener(); + addRemoteControlListener(); } /** @@ -382,10 +410,18 @@ public class OneToOneCallPeerPanel }); } + /** + * Listener that will process change related to video such as if local + * streaming has been turned on/off or a video component has been + * added/removed. + */ private class VideoTelephonyListener implements PropertyChangeListener, VideoListener { + /** + * {@inheritDoc} + */ public void propertyChange(PropertyChangeEvent event) { if (OperationSetVideoTelephony.LOCAL_VIDEO_STREAMING @@ -393,16 +429,25 @@ public class OneToOneCallPeerPanel handleLocalVideoStreamingChange(this); } + /** + * {@inheritDoc} + */ public void videoAdded(VideoEvent event) { handleVideoEvent(event); } + /** + * {@inheritDoc} + */ public void videoRemoved(VideoEvent event) { handleVideoEvent(event); } + /** + * {@inheritDoc} + */ public void videoUpdate(VideoEvent event) { handleVideoEvent(event); @@ -410,6 +455,30 @@ public class OneToOneCallPeerPanel } /** + * Sets up listening to remote-control notifications (granted or revoked). + * + * @return reference to <tt>OperationSetDesktopSharingClient</tt> + */ + private OperationSetDesktopSharingClient addRemoteControlListener() + { + final Call call = callPeer.getCall(); + + if (call == null) + return null; + + OperationSetDesktopSharingClient desktopSharingClient = + call.getProtocolProvider() + .getOperationSet(OperationSetDesktopSharingClient.class); + + if(desktopSharingClient != null) + { + mouseAndKeyListener = new MouseAndKeyListener(desktopSharingClient); + desktopSharingClient.addRemoteControlListener(mouseAndKeyListener); + } + return desktopSharingClient; + } + + /** * Sets up listening to notifications about adding or removing video for the * <code>CallPeer</code> this panel depicts and displays the video in * question in the last-known of {@link #videoContainers} (because the video @@ -434,7 +503,7 @@ public class OneToOneCallPeerPanel final VideoTelephonyListener videoTelephonyListener = new VideoTelephonyListener(); - /* + /** * The video is only available while the #callPeer is in a Call * and that call is in progress so only listen to VideoEvents during * that time. @@ -462,10 +531,12 @@ public class OneToOneCallPeerPanel } } - /* + /** * When the #callPeer of this CallPeerPanel gets added * to the Call, starts listening for changes in the video in order * to display it. + * + * @param event the <tt>CallPeerEvent</tt> received */ public synchronized void callPeerAdded( CallPeerEvent event) @@ -482,10 +553,12 @@ public class OneToOneCallPeerPanel } } - /* + /** * When the #callPeer of this CallPeerPanel leaves the * Call, stops listening for changes in the video because it should * no longer be updated anyway. + * + * @param event the <tt>CallPeerEvent</tt> received */ public synchronized void callPeerRemoved( CallPeerEvent event) @@ -498,12 +571,14 @@ public class OneToOneCallPeerPanel } } - /* + /** * When the Call of #callPeer ends, stops tracking the * updates in the video because there should no longer be any video * anyway. When the Call in question starts, starts tracking any * changes to the video because it's negotiated and it should be * displayed in this CallPeerPanel. + * + * @param event the <tt>CallChangeEvent</tt> received */ public synchronized void callStateChanged(CallChangeEvent event) { @@ -519,6 +594,12 @@ public class OneToOneCallPeerPanel if (videoListenerIsAdded) removeVideoListener(); call.removeCallChangeListener(this); + + if(allowRemoteControl) + { + allowRemoteControl = false; + removeMouseAndKeyListeners(); + } } else if (CallState.CALL_IN_PROGRESS.equals(newCallState)) { @@ -571,15 +652,27 @@ public class OneToOneCallPeerPanel synchronized (videoContainers) { if ((event != null) - && !event.isConsumed() - && (event.getOrigin() == VideoEvent.LOCAL)) + && !event.isConsumed()) { Component video = event.getVisualComponent(); switch (event.getType()) { case VideoEvent.VIDEO_ADDED: - this.localVideo = video; + if(event.getOrigin() == VideoEvent.LOCAL) + { + this.localVideo = video; + } + else if(event.getOrigin() == VideoEvent.REMOTE) + { + this.remoteVideo = video; + + if(allowRemoteControl) + { + addMouseAndKeyListeners(); + } + } + /* * Let the creator of the local visual Component know it * shouldn't be disposed of because we're going to use it. @@ -588,8 +681,16 @@ public class OneToOneCallPeerPanel break; case VideoEvent.VIDEO_REMOVED: - if (this.localVideo == video) + if (event.getOrigin() == VideoEvent.LOCAL && + localVideo == video) + { this.localVideo = null; + } + else if(event.getOrigin() == VideoEvent.REMOTE && + remoteVideo == video) + { + this.remoteVideo = video; + } break; } } @@ -727,6 +828,12 @@ public class OneToOneCallPeerPanel videoContainer.repaint(); } + /** + * Handles the change when we turn on/off local video streaming such as + * creating/releasing visual component. + * + * @param listener Listener that will be callbacked + */ private void handleLocalVideoStreamingChange( VideoTelephonyListener listener) { @@ -768,22 +875,33 @@ public class OneToOneCallPeerPanel return peerName; } + /** + * The <tt>TransparentPanel</tt> that will display the peer status. + */ private static class PeerStatusPanel extends TransparentPanel { - /* + /** * Silence the serial warning. Though there isn't a plan to serialize * the instances of the class, there're no fields so the default * serialization routine will work. */ private static final long serialVersionUID = 0L; + /** + * Constructs a new <tt>PeerStatusPanel</tt>. + * + * @param layout the <tt>LayoutManager</tt> to use + */ public PeerStatusPanel(LayoutManager layout) { super(layout); this.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5)); } + /** + * @{inheritDoc} + */ @Override public void paintComponent(Graphics g) { @@ -1030,6 +1148,38 @@ public class OneToOneCallPeerPanel } /** + * Add <tt>KeyListener</tt>, <tt>MouseListener</tt>, + * <tt>MouseWheelListener</tt> and <tt>MouseMotionListener</tt> to remote + * video component. + */ + private void addMouseAndKeyListeners() + { + if(remoteVideo != null) + { + remoteVideo.addKeyListener(mouseAndKeyListener); + remoteVideo.addMouseListener(mouseAndKeyListener); + remoteVideo.addMouseMotionListener(mouseAndKeyListener); + remoteVideo.addMouseWheelListener(mouseAndKeyListener); + } + } + + /** + * Remove <tt>KeyListener</tt>, <tt>MouseListener</tt>, + * <tt>MouseWheelListener</tt> and <tt>MouseMotionListener</tt> to remote + * video component. + */ + private void removeMouseAndKeyListeners() + { + if(remoteVideo != null) + { + remoteVideo.removeKeyListener(mouseAndKeyListener); + remoteVideo.removeMouseListener(mouseAndKeyListener); + remoteVideo.removeMouseMotionListener(mouseAndKeyListener); + remoteVideo.removeMouseWheelListener(mouseAndKeyListener); + } + } + + /** * Sets the reason of a call failure if one occurs. The renderer should * display this reason to the user. * @param reason the reason to display @@ -1070,4 +1220,183 @@ public class OneToOneCallPeerPanel if (isVisible()) errorMessageComponent.repaint(); } + + /** + * Listener for all key and mouse events and will transfer them to + * the <tt>OperationSetDesktopSharingClient</tt>. + * + * @author Sebastien Vincent + */ + private class MouseAndKeyListener + implements RemoteControlListener, + KeyListener, + MouseListener, + MouseMotionListener, + MouseWheelListener + { + /** + * Desktop sharing clien-side <tt>OperationSet</tt>. + */ + private final OperationSetDesktopSharingClient desktopSharingClient; + + /** + * Last time the mouse has moved inside remote video. It is used mainly + * to avoid sending too much <tt>MouseEvent</tt> which can take a lot of + * bandwidth. + */ + private long lastMouseMovedTime = 0; + + /** + * Constructor. + * + * @param opSet <tt>OperationSetDesktopSharingClient</tt> object + */ + public MouseAndKeyListener(OperationSetDesktopSharingClient opSet) + { + desktopSharingClient = opSet; + } + + /** + * {@inheritDoc} + */ + public void mouseMoved(MouseEvent event) + { + if(System.currentTimeMillis() > lastMouseMovedTime + 50) + { + desktopSharingClient.sendMouseEvent(callPeer, event, + remoteVideo.getSize()); + lastMouseMovedTime = System.currentTimeMillis(); + } + } + + /** + * {@inheritDoc} + */ + public void mousePressed(MouseEvent event) + { + desktopSharingClient.sendMouseEvent(callPeer, event); + } + + /** + * {@inheritDoc} + */ + public void mouseReleased(MouseEvent event) + { + desktopSharingClient.sendMouseEvent(callPeer, event); + } + + /** + * {@inheritDoc} + */ + public void mouseClicked(MouseEvent event) + { + /* do nothing */ + } + + /** + * {@inheritDoc} + */ + public void mouseEntered(MouseEvent event) + { + /* do nothing */ + } + + /** + * {@inheritDoc} + */ + public void mouseExited(MouseEvent event) + { + /* do nothing */ + } + + /** + * {@inheritDoc} + */ + public void mouseWheelMoved(MouseWheelEvent event) + { + desktopSharingClient.sendMouseEvent(callPeer, event); + } + + /** + * {@inheritDoc} + */ + public void mouseDragged(MouseEvent event) + { + desktopSharingClient.sendMouseEvent(callPeer, event, + remoteVideo.getSize()); + } + + /** + * {@inheritDoc} + */ + public void keyPressed(KeyEvent event) + { + char key = event.getKeyChar(); + int code = event.getKeyCode(); + + if(key == KeyEvent.CHAR_UNDEFINED || + code == KeyEvent.VK_CLEAR || + code == KeyEvent.VK_DELETE || + code == KeyEvent.VK_BACK_SPACE || + code == KeyEvent.VK_ENTER) + { + desktopSharingClient.sendKeyboardEvent(callPeer, event); + } + } + + /** + * {@inheritDoc} + */ + public void keyReleased(KeyEvent event) + { + char key = event.getKeyChar(); + int code = event.getKeyCode(); + + if(key == KeyEvent.CHAR_UNDEFINED || + code == KeyEvent.VK_CLEAR || + code == KeyEvent.VK_DELETE || + code == KeyEvent.VK_BACK_SPACE || + code == KeyEvent.VK_ENTER) + { + desktopSharingClient.sendKeyboardEvent(callPeer, event); + } + } + + /** + * {@inheritDoc} + */ + public void keyTyped(KeyEvent event) + { + char key = event.getKeyChar(); + + if(key != '\n' && key != '\b') + { + desktopSharingClient.sendKeyboardEvent(callPeer, event); + } + } + + /** + * This method is called when remote control has been granted. + * + * @param event <tt>RemoteControlGrantedEvent</tt> + */ + public void remoteControlGranted(RemoteControlGrantedEvent event) + { + allowRemoteControl = true; + } + + /** + * This method is called when remote control has been revoked. + * + * @param event <tt>RemoteControlRevokedEvent</tt> + */ + public void remoteControlRevoked(RemoteControlRevokedEvent event) + { + if(allowRemoteControl) + { + allowRemoteControl = false; + removeMouseAndKeyListeners(); + } + } + } } diff --git a/src/net/java/sip/communicator/impl/hid/HIDActivator.java b/src/net/java/sip/communicator/impl/hid/HIDActivator.java new file mode 100644 index 0000000..30d127f --- /dev/null +++ b/src/net/java/sip/communicator/impl/hid/HIDActivator.java @@ -0,0 +1,72 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.hid; + +import org.osgi.framework.*; + +import net.java.sip.communicator.service.hid.*; +import net.java.sip.communicator.util.*; + +/** + * OSGi activator for the HID service. + * + * @author Sebastien Vincent + */ +public class HIDActivator + implements BundleActivator +{ + /** + * The <tt>Logger</tt> used by the <tt>HIDActivator</tt> class and its + * instances for logging output. + */ + private final Logger logger = Logger.getLogger(HIDActivator.class); + + /** + * The OSGi <tt>ServiceRegistration</tt> of <tt>HIDServiceImpl</tt>. + */ + private ServiceRegistration serviceRegistration; + + /** + * Starts the execution of the <tt>hid</tt> bundle in the specified context. + * + * @param bundleContext the context in which the <tt>hid</tt> bundle is to + * start executing + * @throws Exception if an error occurs while starting the execution of the + * <tt>hid</tt> bundle in the specified context + */ + public void start(BundleContext bundleContext) + throws Exception + { + if (logger.isDebugEnabled()) + logger.debug("Started."); + + serviceRegistration = + bundleContext.registerService(HIDService.class.getName(), + new HIDServiceImpl(), null); + + if (logger.isDebugEnabled()) + logger.debug("HID Service ... [REGISTERED]"); + } + + /** + * Stops the execution of the <tt>hid</tt> bundle in the specified context. + * + * @param bundleContext the context in which the <tt>hid</tt> bundle is to + * stop executing + * @throws Exception if an error occurs while stopping the execution of the + * <tt>hid</tt> bundle in the specified context + */ + public void stop(BundleContext bundleContext) + throws Exception + { + if (serviceRegistration != null) + { + serviceRegistration.unregister(); + serviceRegistration = null; + } + } +} diff --git a/src/net/java/sip/communicator/impl/hid/HIDServiceImpl.java b/src/net/java/sip/communicator/impl/hid/HIDServiceImpl.java new file mode 100644 index 0000000..08654e9 --- /dev/null +++ b/src/net/java/sip/communicator/impl/hid/HIDServiceImpl.java @@ -0,0 +1,222 @@ +/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license. See terms of license at gnu.org.
+ */
+
+package net.java.sip.communicator.impl.hid;
+
+import java.awt.*;
+import java.awt.event.*;
+
+import net.java.sip.communicator.service.hid.*;
+import net.java.sip.communicator.util.*;
+
+/**
+ * Implementation of the HID service to provide way of regenerate key press
+ * and mouse interactions.
+ *
+ * @author Sebastien Vincent
+ */
+public class HIDServiceImpl implements HIDService
+{
+ /**
+ * The <tt>Logger</tt> used by the <tt>NeomediaActivator</tt> class and its
+ * instances for logging output.
+ */
+ private final Logger logger = Logger.getLogger(HIDServiceImpl.class);
+
+ /**
+ * The robot used to perform some operations (mouse/key interactions).
+ */
+ private Robot robot = null;
+
+ /**
+ * Object to regenerates keys with JNI.
+ */
+ private NativeKeyboard nativeKeyboard = null;
+
+ /**
+ * Constructor.
+ */
+ protected HIDServiceImpl()
+ {
+ try
+ {
+ robot = new Robot();
+ nativeKeyboard = new NativeKeyboard();
+ }
+ catch(Throwable e)
+ {
+ logger.error("Error when creating Robot/NativeKeyboard instance",
+ e);
+ }
+ }
+
+ /**
+ * Press a specific key using its keycode.
+ *
+ * @param keycode the Java keycode, all available keycode can be found
+ * in java.awt.event.KeyEvent class (VK_A, VK_SPACE, ...)
+ * @see java.awt.event.KeyEvent
+ */
+ public void keyPress(int keycode)
+ {
+ if(OSUtils.IS_WINDOWS || OSUtils.IS_MAC)
+ {
+ /* do not allow modifiers for Windows (as
+ * they are handled in native code with
+ * VkScanCode) and Mac OS X
+ */
+ if(keycode == KeyEvent.VK_ALT ||
+ keycode == KeyEvent.VK_SHIFT ||
+ keycode == KeyEvent.VK_ALT_GRAPH)
+ {
+ return;
+ }
+ }
+
+ /* AltGr does not seems to work with robot, handle it via our
+ * JNI code
+ */
+ if(keycode == KeyEvent.VK_ALT_GRAPH)
+ {
+ symbolPress("altgr");
+ }
+ else
+ {
+ robot.keyPress(keycode);
+ }
+ }
+
+ /**
+ * Release a specific key using its keycode.
+ *
+ * @param keycode the Java keycode, all available keycode can be found
+ * in java.awt.event.KeyEvent class (VK_A, VK_SPACE, ...)
+ * @see java.awt.event.KeyEvent
+ */
+ public void keyRelease(int keycode)
+ {
+ /* AltGr does not seems to work with robot, handle it via our
+ * JNI code
+ */
+ if(keycode == KeyEvent.VK_ALT_GRAPH)
+ {
+ symbolRelease("altgr");
+ }
+ else
+ {
+ robot.keyRelease(keycode);
+ }
+ }
+
+ /**
+ * Press a specific key using its char representation.
+ *
+ * @param key char representation of the key
+ */
+ public void keyPress(char key)
+ {
+ /* check for CTRL+X where X is [A-Z]
+ * CTRL+A = 1, A = 65
+ */
+ if(key >= 1 && key <= 0x1A)
+ {
+ key = (char)(key + 64);
+ robot.keyPress(key);
+ return;
+ }
+
+ nativeKeyboard.keyPress(key);
+ }
+
+ /**
+ * Release a specific key using its char representation.
+ *
+ * @param key char representation of the key
+ */
+ public void keyRelease(char key)
+ {
+ /* check for CTRL+X where X is [A-Z]
+ * CTRL+A = 1, A = 65
+ */
+ if(key >= 1 && key <= 0x1A)
+ {
+ key = (char)(key + 64);
+ robot.keyRelease(key);
+ return;
+ }
+
+ if(nativeKeyboard != null)
+ nativeKeyboard.keyRelease(key);
+ }
+
+ /**
+ * Press a specific symbol (such as SHIFT or CTRL).
+ *
+ * @param symbol symbol name
+ */
+ private void symbolPress(String symbol)
+ {
+ if(nativeKeyboard != null)
+ nativeKeyboard.symbolPress(symbol);
+ }
+
+ /**
+ * Release a specific symbol (such as SHIFT or CTRL).
+ *
+ * @param symbol symbol name
+ */
+ private void symbolRelease(String symbol)
+ {
+ if(nativeKeyboard != null)
+ nativeKeyboard.symbolRelease(symbol);
+ }
+
+ /**
+ * Press a mouse button(s).
+ *
+ * @param btns button masks
+ * @see java.awt.Robot#mousePress(int btns)
+ */
+ public void mousePress(int btns)
+ {
+ robot.mousePress(btns);
+ }
+
+ /**
+ * Release a mouse button(s).
+ *
+ * @param btns button masks
+ * @see java.awt.Robot#mouseRelease(int btns)
+ */
+ public void mouseRelease(int btns)
+ {
+ robot.mouseRelease(btns);
+ }
+
+ /**
+ * Move the mouse on the screen.
+ *
+ * @param x x position on the screen
+ * @param y y position on the screen
+ * @see java.awt.Robot#mouseMove(int x, int y)
+ */
+ public void mouseMove(int x, int y)
+ {
+ robot.mouseMove(x, y);
+ }
+
+ /**
+ * Release a mouse button(s).
+ *
+ * @param rotation wheel rotation (could be negative or positive depending
+ * on the direction).
+ * @see java.awt.Robot#mouseWheel(int wheelAmt)
+ */
+ public void mouseWheel(int rotation)
+ {
+ robot.mouseWheel(rotation);
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/hid/NativeKeyboard.java b/src/net/java/sip/communicator/impl/hid/NativeKeyboard.java new file mode 100644 index 0000000..81e18b4 --- /dev/null +++ b/src/net/java/sip/communicator/impl/hid/NativeKeyboard.java @@ -0,0 +1,76 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.hid; + +/** + * Class used to interact with native keyboard. + * + * @author Sebastien Vincent + */ +public class NativeKeyboard +{ + static + { + System.loadLibrary("hid"); + } + + /** + * Simulate a key press. + * + * @param key ascii representation of the key + */ + public void keyPress(char key) + { + doKeyAction(key, true); + } + + /** + * Simulate a key release. + * + * @param key ascii representation of the key + */ + public void keyRelease(char key) + { + doKeyAction(key, false); + } + + /** + * Simulate a symbol key press. + * + * @param symbol symbol name + */ + public void symbolPress(String symbol) + { + doSymbolAction(symbol, true); + } + + /** + * Simulate a symbol key release. + * + * @param symbol symbol name + */ + public void symbolRelease(String symbol) + { + doSymbolAction(symbol, false); + } + + /** + * Native method to press or release a key. + * + * @param key ascii representation of the key + * @param pressed if the key is pressed or not (i.e. released) + */ + private static native void doKeyAction(char key, boolean pressed); + + /** + * Native method to press or release a key. + * + * @param symbol symbol name + * @param pressed if the key is pressed or not (i.e. released) + */ + private static native void doSymbolAction(String symbol, boolean pressed); +} diff --git a/src/net/java/sip/communicator/impl/hid/hid.manifest.mf b/src/net/java/sip/communicator/impl/hid/hid.manifest.mf new file mode 100644 index 0000000..b55a5ce --- /dev/null +++ b/src/net/java/sip/communicator/impl/hid/hid.manifest.mf @@ -0,0 +1,9 @@ +Bundle-Activator: net.java.sip.communicator.impl.hid.HIDActivator +Bundle-Name: HID Service Implementation +Bundle-Description: A bundle that offers Human Interaction features. +Bundle-Vendor: sip-communicator.org +Bundle-Version: 0.0.1 +System-Bundle: yes +Import-Package: org.osgi.framework, + net.java.sip.communicator.util +Export-Package: net.java.sip.communicator.service.hid
\ No newline at end of file diff --git a/src/net/java/sip/communicator/impl/metahistory/MetaHistoryActivator.java b/src/net/java/sip/communicator/impl/metahistory/MetaHistoryActivator.java index bfead79..f0f4d5d 100644 --- a/src/net/java/sip/communicator/impl/metahistory/MetaHistoryActivator.java +++ b/src/net/java/sip/communicator/impl/metahistory/MetaHistoryActivator.java @@ -18,16 +18,23 @@ import net.java.sip.communicator.service.metahistory.*; public class MetaHistoryActivator implements BundleActivator { + /** + * The <tt>Logger</tt> instance used by the + * <tt>MetaHistoryActivator</tt> class and its instances for logging output. + */ private static Logger logger = Logger.getLogger(MetaHistoryActivator.class); + /** + * The <tt>MetaHistoryService</tt> reference. + */ private MetaHistoryServiceImpl metaHistoryService = null; /** * Initialize and start meta history * * @param bundleContext BundleContext - * @throws Exception + * @throws Exception if initializing and starting meta history service fails */ public void start(BundleContext bundleContext) throws Exception { @@ -54,6 +61,12 @@ public class MetaHistoryActivator } + /** + * Stops this bundle. + * + * @param bundleContext the <tt>BundleContext</tt> + * @throws Exception if the stop operation goes wrong + */ public void stop(BundleContext bundleContext) throws Exception { if(metaHistoryService != null) diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java index 9088e91..eaf84af 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java @@ -36,6 +36,12 @@ public class CallJabberImpl extends MediaAwareCall< private final OperationSetBasicTelephonyJabberImpl parentOpSet; /** + * Indicates if the <tt>CallPeer</tt> will support </tt>inputevt</tt> + * extension (i.e. will be able to be remote-controlled). + */ + private boolean localInputEvtAware = false; + + /** * Crates a CallJabberImpl instance belonging to <tt>sourceProvider</tt> and * associated with the jingle session with the specified <tt>jingleSID</tt>. * If this call corresponds to an incoming jingle session then the jingleSID @@ -57,6 +63,26 @@ public class CallJabberImpl extends MediaAwareCall< } /** + * Enable or disable <tt>inputevt</tt> support (remote control). + * + * @param enable new state of inputevt support + */ + public void setLocalInputEvtAware(boolean enable) + { + localInputEvtAware = enable; + } + + /** + * Returns if the call support <tt>inputevt</tt> (remote control). + * + * @return true if the call support <tt>inputevt</tt>, false otherwise + */ + public boolean getLocalInputEvtAware() + { + return localInputEvtAware; + } + + /** * Creates a new call peer and sends a RINGING response. * * @param jingleIQ the {@link JingleIQ} that created the session. @@ -132,6 +158,8 @@ public class CallJabberImpl extends MediaAwareCall< /* enable video if it is a videocall */ callPeer.getMediaHandler().setLocalVideoTransmissionEnabled( localVideoAllowed); + /* enable remote-control if it is a desktop sharing session */ + callPeer.getMediaHandler().setLocalInputEvtAware(localInputEvtAware); //set call state to connecting so that the user interface would start //playing the tones. we do that here because we may be harvesting @@ -151,8 +179,13 @@ public class CallJabberImpl extends MediaAwareCall< * @throws OperationFailedException if problem occurred during message * generation or network problem */ - public void modifyVideoContent(boolean allowed) throws OperationFailedException + public void modifyVideoContent(boolean allowed) + throws OperationFailedException { + if(logger.isInfoEnabled()) + logger.info(allowed ? "Start local video streaming" : + "Stop local video streaming"); + for(CallPeerJabberImpl peer : getCallPeersVector()) { peer.sendModifyVideoContent(allowed); diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerMediaHandlerJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerMediaHandlerJabberImpl.java index e03c4ba..1ab2ae3 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerMediaHandlerJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/CallPeerMediaHandlerJabberImpl.java @@ -61,6 +61,12 @@ public class CallPeerMediaHandlerJabberImpl private boolean remotelyOnHold = false; /** + * Indicates if the <tt>CallPeer</tt> will support </tt>inputevt</tt> + * extension (i.e. will be able to be remote-controlled). + */ + private boolean localInputEvtAware = false; + + /** * Creates a new handler that will be managing media streams for * <tt>peer</tt>. * @@ -97,6 +103,16 @@ public class CallPeerMediaHandlerJabberImpl } /** + * Enable or disable <tt>inputevt</tt> support (remote-control). + * + * @param enable new state of inputevt support + */ + public void setLocalInputEvtAware(boolean enable) + { + localInputEvtAware = enable; + } + + /** * Get the remote content of a specific content type (like audio or video). * * @param contentType content type name @@ -300,6 +316,14 @@ public class CallPeerMediaHandlerJabberImpl } } + // got an content which have inputevt, it means that peer requests + // a desktop sharing session so tell it we support inputevt + if(content.getChildExtensionsOfType( + InputEvtPacketExtension.class) != null) + { + ourContent.addChildExtension(new InputEvtPacketExtension()); + } + answerContentList.add(ourContent); localContentMap.put(content.getName(), ourContent); @@ -395,6 +419,22 @@ public class CallPeerMediaHandlerJabberImpl // create the corresponding stream... initStream(ourContent.getName(), connector, dev, format, target, direction, rtpExtensions); + + // if remote peer requires inputevt, notify UI to capture mouse + // and keyboard events + if(ourContent.getChildExtensionsOfType( + InputEvtPacketExtension.class) != null) + { + OperationSetDesktopSharingClientJabberImpl client = + (OperationSetDesktopSharingClientJabberImpl) + this.getPeer().getProtocolProvider().getOperationSet( + OperationSetDesktopSharingClient.class); + + if(client != null) + { + client.fireRemoteControlGranted(); + } + } } return sessAccept; } @@ -562,6 +602,18 @@ public class CallPeerMediaHandlerJabberImpl } } + /* we request a desktop sharing session so add the inputevt + * extension in the "video" content + */ + RtpDescriptionPacketExtension description + = JingleUtils.getRtpDescription(content); + if(description.getMedia().equals( + MediaType.VIDEO.toString()) && localInputEvtAware) + { + content.addChildExtension( + new InputEvtPacketExtension()); + } + mediaDescs.add(content); } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/JabberActivator.java b/src/net/java/sip/communicator/impl/protocol/jabber/JabberActivator.java index 7e7f723..94181b6 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/JabberActivator.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/JabberActivator.java @@ -10,6 +10,7 @@ import java.util.*; import net.java.sip.communicator.service.configuration.*; import net.java.sip.communicator.service.gui.*; +import net.java.sip.communicator.service.hid.*; import net.java.sip.communicator.service.neomedia.*; import net.java.sip.communicator.service.netaddr.*; import net.java.sip.communicator.service.protocol.*; @@ -58,7 +59,8 @@ public class JabberActivator /** * The Jabber protocol provider factory. */ - private static ProtocolProviderFactoryJabberImpl jabberProviderFactory = null; + private static ProtocolProviderFactoryJabberImpl + jabberProviderFactory = null; /** * The <tt>UriHandler</tt> implementation that we use to handle "xmpp:" URIs @@ -77,6 +79,11 @@ public class JabberActivator private static ResourceManagementService resourcesService = null; /** + * A reference to the currently valid <tt>HIDService</tt> instance. + */ + private static HIDService hidService = null; + + /** * Called when this bundle is started so the Framework can perform the * bundle-specific activities necessary to start this bundle. * @@ -274,4 +281,23 @@ public class JabberActivator } return networkAddressManagerService; } + + /** + * Returns a reference to <tt>HIDService</tt> implementation currently + * registered in the bundle context or null if no such implementation was + * found + * + * @return a currently valid implementation of the <tt>HIDService</tt> + */ + public static HIDService getHIDService() + { + if(hidService == null) + { + ServiceReference hidReference = + bundleContext.getServiceReference( + HIDService.class.getName()); + hidService = (HIDService)bundleContext.getService(hidReference); + } + return hidService; + } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetDesktopSharingClientJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetDesktopSharingClientJabberImpl.java new file mode 100644 index 0000000..190ddc7 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetDesktopSharingClientJabberImpl.java @@ -0,0 +1,187 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.jabber; + +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import java.util.List; // disambiguation + +import org.jivesoftware.smack.packet.*; + +import net.java.sip.communicator.impl.protocol.jabber.extensions.inputevt.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.util.*; + +/** + * Implements all desktop sharing client-side related functions for Jabber + * protocol. + * + * @author Sebastien Vincent + */ +public class OperationSetDesktopSharingClientJabberImpl + implements OperationSetDesktopSharingClient +{ + /** + * Our class logger. + */ + private static final Logger logger = + Logger.getLogger(OperationSetDesktopSharingClientJabberImpl.class); + + /** + * The Jabber <tt>ProtocolProviderService</tt> implementation which created + * this instance and for which telephony conferencing services are being + * provided by this instance. + */ + private final ProtocolProviderServiceJabberImpl parentProvider; + + /** + * List of listeners to be notified when a change occurred in remote control + * access. + */ + private List<RemoteControlListener> listeners = + new ArrayList<RemoteControlListener>(); + + /** + * Initializes a new <tt>OperationSetDesktopSharingClientJabberImpl</tt>. + * + * @param parentProvider the Jabber <tt>ProtocolProviderService</tt> + * implementation which has requested the creation of the new instance and + * for which the new instance is to provide desktop sharing. + */ + public OperationSetDesktopSharingClientJabberImpl( + ProtocolProviderServiceJabberImpl parentProvider) + { + this.parentProvider = parentProvider; + } + + /** + * Fire a <tt>RemoteControlGrantedEvent</tt> to all registered listeners. + */ + public void fireRemoteControlGranted() + { + RemoteControlGrantedEvent event = new RemoteControlGrantedEvent(this); + + for (RemoteControlListener l : listeners) + { + l.remoteControlGranted(event); + } + } + + /** + * Fire a <tt>RemoteControlGrantedEvent</tt> to all registered listeners. + */ + public void fireRemoteControlRevoked() + { + RemoteControlRevokedEvent event = new RemoteControlRevokedEvent(this); + + for (RemoteControlListener l : listeners) + { + l.remoteControlRevoked(event); + } + } + + /** + * Add a <tt>RemoteControlListener</tt> to be notified when remote peer + * accept to give us full control. + * + * @param listener <tt>RemoteControlListener</tt> to add + */ + public void addRemoteControlListener(RemoteControlListener listener) + { + if (logger.isInfoEnabled()) + logger.info("Enable remote control"); + + if (!listeners.contains(listener)) + { + listeners.add(listener); + } + } + + /** + * Remove a <tt>RemoteControlListener</tt> to be notified when remote peer + * accept/revoke to give us full control. + * + * @param listener <tt>RemoteControlListener</tt> to remove + */ + public void removeRemoteControlListener(RemoteControlListener listener) + { + if (logger.isInfoEnabled()) + logger.info("Disable remote control"); + + if (listeners.contains(listener)) + { + listeners.remove(listener); + } + } + + /** + * Send a keyboard notification. + * + * @param callPeer <tt>CallPeer</tt> that will be notified + * @param event <tt>KeyEvent</tt> received and that will be send to remote + * peer + */ + public void sendKeyboardEvent(CallPeer callPeer, KeyEvent event) + { + RemoteControlExtension payload = new RemoteControlExtension(event); + InputEvtIQ inputIQ = new InputEvtIQ(); + + inputIQ.setAction(InputEvtAction.NOTIFY); + inputIQ.setType(IQ.Type.SET); + inputIQ.setFrom(parentProvider.getOurJID()); + inputIQ.setTo(callPeer.getAddress()); + inputIQ.addRemoteControl(payload); + parentProvider.getConnection().sendPacket(inputIQ); + } + + /** + * Send a mouse notification. + * + * @param callPeer <tt>CallPeer</tt> that will be notified + * @param event <tt>MouseEvent</tt> received and that will be send to remote + * peer + */ + public void sendMouseEvent(CallPeer callPeer, MouseEvent event) + { + RemoteControlExtension payload = new RemoteControlExtension(event); + InputEvtIQ inputIQ = new InputEvtIQ(); + + inputIQ.setAction(InputEvtAction.NOTIFY); + inputIQ.setType(IQ.Type.SET); + inputIQ.setFrom(parentProvider.getOurJID()); + inputIQ.setTo(callPeer.getAddress()); + inputIQ.addRemoteControl(payload); + parentProvider.getConnection().sendPacket(inputIQ); + } + + /** + * Send a mouse notification for specific "moved" <tt>MouseEvent</tt>. As + * controller computer could have smaller desktop that controlled ones, we + * should take care to send the percentage of point x and point y. + * + * @param callPeer <tt>CallPeer</tt> that will be notified + * @param event <tt>MouseEvent</tt> received and that will be send to remote + * peer + * @param videoPanelSize size of the panel that contains video + */ + public void sendMouseEvent(CallPeer callPeer, MouseEvent event, + Dimension videoPanelSize) + { + RemoteControlExtension payload = new RemoteControlExtension(event, + videoPanelSize); + InputEvtIQ inputIQ = new InputEvtIQ(); + + inputIQ.setAction(InputEvtAction.NOTIFY); + inputIQ.setType(IQ.Type.SET); + inputIQ.setFrom(parentProvider.getOurJID()); + inputIQ.setTo(callPeer.getAddress()); + inputIQ.addRemoteControl(payload); + parentProvider.getConnection().sendPacket(inputIQ); + } +}
\ No newline at end of file diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetDesktopSharingServerJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetDesktopSharingServerJabberImpl.java new file mode 100644 index 0000000..093c195 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetDesktopSharingServerJabberImpl.java @@ -0,0 +1,473 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.jabber; + +import java.util.*; +import java.util.List; // disambiguation + +import java.awt.*; +import java.awt.event.*; + +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.filter.*; +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smackx.packet.*; + +import net.java.sip.communicator.impl.protocol.jabber.extensions.inputevt.*; +import net.java.sip.communicator.service.hid.*; +import net.java.sip.communicator.service.neomedia.*; +import net.java.sip.communicator.service.neomedia.format.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.util.*; + +/** + * Implements all desktop sharing server-side related functions for Jabber + * protocol. + * + * @author Sebastien Vincent + */ +public class OperationSetDesktopSharingServerJabberImpl + extends OperationSetDesktopStreamingJabberImpl + implements OperationSetDesktopSharingServer, + RegistrationStateChangeListener, + PacketListener, + PacketFilter +{ + /** + * Our class logger. + */ + private static final Logger logger = Logger + .getLogger(OperationSetDesktopSharingServerJabberImpl.class); + + /** + * The <tt>CallPeerListener</tt> which listens to modifications in the + * properties/state of <tt>CallPeer</tt>. + */ + private final CallPeerListener callPeerListener = new CallPeerAdapter() + { + /** + * Indicates that a change has occurred in the status of the source + * <tt>CallPeer</tt>. + * + * @param evt the <tt>CallPeerChangeEvent</tt> instance containing the + * source event as well as its previous and its new status + */ + @Override + public void peerStateChanged(CallPeerChangeEvent evt) + { + CallPeer peer = evt.getSourceCallPeer(); + CallPeerState state = peer.getState(); + + if (remoteControlEnabled && state != null && + (state.equals(CallPeerState.DISCONNECTED) || + state.equals(CallPeerState.FAILED))) + { + disableRemoteControl(evt.getSourceCallPeer()); + } + else if(state != null && state.equals(CallPeerState.CONNECTED)) + { + /* connected peer */ + enableRemoteControl(evt.getSourceCallPeer()); + } + } + }; + + /** + * If the remote control is authorized and thus enabled. + */ + private boolean remoteControlEnabled = false; + + /** + * HID service that will regenerates keyboard and mouse events received in + * Jabber messages. + */ + private HIDService hidService = null; + + /** + * List of callPeers for the desktop sharing session. + */ + private List<String> callPeers = new ArrayList<String>(); + + /** + * Video panel size. + */ + private Dimension size = null; + + /** + * Initializes a new <tt>OperationSetDesktopSharingJabberImpl</tt> instance + * which builds upon the telephony-related functionality of a specific + * <tt>OperationSetBasicTelephonyJabberImpl</tt>. + * + * @param basicTelephony the <tt>OperationSetBasicTelephonyJabberImpl</tt> + * the new extension should build upon + */ + public OperationSetDesktopSharingServerJabberImpl( + OperationSetBasicTelephonyJabberImpl basicTelephony) + { + super(basicTelephony); + + parentProvider.addRegistrationStateChangeListener(this); + hidService = JabberActivator.getHIDService(); + } + + /** + * Create a new video call and invite the specified CallPeer to it. + * + * @param uri the address of the callee that we should invite to a new + * call. + * @return CallPeer the CallPeer that will represented by the + * specified uri. All following state change events will be delivered + * through that call peer. The Call that this peer is a member + * of could be retrieved from the CallParticipatn instance with the use + * of the corresponding method. + * @throws OperationFailedException with the corresponding code if we fail + * to create the video call. + */ + @Override + public Call createVideoCall(String uri) + throws OperationFailedException + { + CallJabberImpl call = (CallJabberImpl)super.createVideoCall(uri); + CallPeerJabberImpl callPeer = call.getCallPeers().next(); + callPeer.addCallPeerListener(callPeerListener); + + size = (((VideoMediaFormat)call.getDefaultDevice( + MediaType.VIDEO).getFormat()).getSize()); + return call; + } + + /** + * Create a new video call and invite the specified CallPeer to it. + * + * @param callee the address of the callee that we should invite to a new + * call. + * @return CallPeer the CallPeer that will represented by the + * specified uri. All following state change events will be delivered + * through that call peer. The Call that this peer is a member + * of could be retrieved from the CallParticipatn instance with the use + * of the corresponding method. + * @throws OperationFailedException with the corresponding code if we fail + * to create the video call. + */ + @Override + public Call createVideoCall(Contact callee) throws OperationFailedException + { + CallJabberImpl call = (CallJabberImpl)super.createVideoCall(callee); + CallPeerJabberImpl callPeer = call.getCallPeers().next(); + callPeer.addCallPeerListener(callPeerListener); + + size = (((VideoMediaFormat)call.getDefaultDevice( + MediaType.VIDEO).getFormat()).getSize()); + return call; + } + + /** + * Check if the remote part supports Jingle video. + * + * @param calleeAddress Contact address + * @return true if contact support Jingle video, false otherwise + * + * @throws OperationFailedException with the corresponding code if we fail + * to create the video call. + */ + protected Call createOutgoingVideoCall(String calleeAddress) + throws OperationFailedException + { + boolean supported = false; + String fullCalleeURI = null; + + if (calleeAddress.indexOf('/') > 0) + { + fullCalleeURI = calleeAddress; + } + else + { + fullCalleeURI = parentProvider.getConnection() + .getRoster().getPresence(calleeAddress).getFrom(); + } + + if (logger.isInfoEnabled()) + logger.info("creating outgoing desktop sharing call..."); + + DiscoverInfo di = null; + try + { + // check if the remote client supports inputevt (remote control) + di = parentProvider.getDiscoveryManager() + .discoverInfo(fullCalleeURI); + + if (di.containsFeature(InputEvtIQ.NAMESPACE)) + { + if (logger.isInfoEnabled()) + logger.info(fullCalleeURI + ": remote-control supported"); + + supported = true; + } + else + { + if (logger.isInfoEnabled()) + logger.info(fullCalleeURI + + ": remote-control not supported!"); + + /* XXX fail or not ? */ + /* + throw new OperationFailedException( + "Failed to create a true desktop sharing.\n" + + fullCalleeURI + " does not support inputevt", + OperationFailedException.INTERNAL_ERROR); + */ + } + } + catch (XMPPException ex) + { + logger.warn("could not retrieve info for " + fullCalleeURI, ex); + } + + if (parentProvider.getConnection() == null) + { + throw new OperationFailedException( + "Failed to create OutgoingJingleSession.\n" + + "we don't have a valid XMPPConnection." + , OperationFailedException.INTERNAL_ERROR); + } + + CallJabberImpl call = new CallJabberImpl(basicTelephony); + /* enable video */ + call.setLocalVideoAllowed(true, getMediaUseCase()); + /* enable remote-control */ + call.setLocalInputEvtAware(supported); + + return basicTelephony.createOutgoingCall(call, calleeAddress); + } + + /** + * Enable desktop remote control. Local desktop can now regenerates keyboard + * and mouse events received from peer. + * + * @param callPeer call peer that will take control on local computer + */ + public void enableRemoteControl(CallPeer callPeer) + { + if(logger.isInfoEnabled()) + logger.info("Enable remote control"); + + CallJabberImpl call = (CallJabberImpl)callPeer.getCall(); + if(call.getLocalInputEvtAware()) + { + remoteControlEnabled = true; + + if(!callPeers.contains(callPeer.getAddress())) + { + callPeers.add(callPeer.getAddress()); + } + } + } + + /** + * Disable desktop remote control. Local desktop stop regenerates keyboard + * and mouse events received from peer. + * + * @param callPeer call peer that will stop controlling on local computer + */ + public void disableRemoteControl(CallPeer callPeer) + { + if(logger.isInfoEnabled()) + logger.info("Disable remote control"); + + remoteControlEnabled = false; + + if(callPeers.contains(callPeer.getAddress())) + { + callPeers.remove(callPeer.getAddress()); + } + } + + /** + * Implementation of method <tt>registrationStateChange</tt> from + * interface RegistrationStateChangeListener for setting up (or down) + * our <tt>InputEvtManager</tt> when an <tt>XMPPConnection</tt> is available + * + * @param evt the event received + */ + public void registrationStateChanged(RegistrationStateChangeEvent evt) + { + if ((evt.getNewState() == RegistrationState.REGISTERING)) + { + /* listen to specific inputevt IQ */ + parentProvider.getConnection().addPacketListener(this, this); + } + } + + /** + * Handles incoming inputevt packets and passes them to the corresponding + * method based on their action. + * + * @param packet the packet to process. + */ + public void processPacket(Packet packet) + { + //this is not supposed to happen because of the filter ... but still + if (!(packet instanceof InputEvtIQ)) + return; + + InputEvtIQ inputIQ = (InputEvtIQ)packet; + + if(inputIQ.getAction() != InputEvtAction.NOTIFY) + { + return; + } + + /* do not waste time to parse packet if remote control is not enabled */ + if(!remoteControlEnabled) + { + return; + } + + //first ack all "set" requests. + if(inputIQ.getType() == IQ.Type.SET) + { + IQ ack = IQ.createResultIQ(inputIQ); + parentProvider.getConnection().sendPacket(ack); + } + + if(!callPeers.contains(inputIQ.getFrom())) + { + return; + } + + for(RemoteControlExtension p : inputIQ.getRemoteControls()) + { + ComponentEvent evt = p.getEvent(); + processComponentEvent(evt); + } + } + + /** + * Tests whether or not the specified packet should be handled by this + * operation set. This method is called by smack prior to packet delivery + * and it would only accept <tt>InputEvtIQ</tt>s. + * + * @param packet the packet to test. + * @return true if and only if <tt>packet</tt> passes the filter. + */ + public boolean accept(Packet packet) + { + //we only handle InputEvtIQ-s + if(!(packet instanceof InputEvtIQ)) + return false; + + return true; + } + + /** + * Process an <tt>ComponentEvent</tt> received from remote peer. + * + * @param event <tt>ComponentEvent</tt> that will be regenerated on local + * computer + */ + public void processComponentEvent(ComponentEvent event) + { + if(event == null) + { + return; + } + + if(event instanceof KeyEvent) + { + processKeyboardEvent((KeyEvent)event); + } + else if(event instanceof MouseEvent) + { + processMouseEvent((MouseEvent)event); + } + } + + /** + * Process keyboard notification received from remote peer. + * + * @param event <tt>KeyboardEvent</tt> that will be regenerated on local + * computer + */ + public void processKeyboardEvent(KeyEvent event) + { + /* ignore command if remote control is not enabled otherwise regenerates + * event on the computer + */ + if (hidService != null) + { + int keycode = 0; + + /* process immediately a "key-typed" event via press/release */ + if(event.getKeyChar() != 0 && event.getID() == KeyEvent.KEY_TYPED) + { + hidService.keyPress(event.getKeyChar()); + hidService.keyRelease(event.getKeyChar()); + return; + } + + keycode = event.getKeyCode(); + + if(keycode == 0) + { + return; + } + + switch(event.getID()) + { + case KeyEvent.KEY_PRESSED: + hidService.keyPress(keycode); + break; + case KeyEvent.KEY_RELEASED: + hidService.keyRelease(keycode); + break; + default: + break; + } + } + } + + /** + * Process mouse notification received from remote peer. + * + * @param event <tt>MouseEvent</tt> that will be regenerated on local + * computer + */ + public void processMouseEvent(MouseEvent event) + { + /* ignore command if remote control is not enabled otherwise regenerates + * event on the computer + */ + if (hidService != null) + { + switch(event.getID()) + { + case MouseEvent.MOUSE_PRESSED: + hidService.mousePress(event.getModifiers()); + break; + case MouseEvent.MOUSE_RELEASED: + hidService.mouseRelease(event.getModifiers()); + break; + case MouseEvent.MOUSE_MOVED: + /* x and y position are sent in percentage but we multiply + * by 1000 in depacketizer because we cannot passed the size + * to the Provider + */ + int x = ((event.getX() * size.width) / 1000); + int y = ((event.getY() * size.height) / 1000); + hidService.mouseMove(x, y); + break; + case MouseEvent.MOUSE_WHEEL: + MouseWheelEvent evt = (MouseWheelEvent)event; + hidService.mouseWheel(evt.getWheelRotation()); + break; + default: + break; + } + } + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetVideoTelephonyJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetVideoTelephonyJabberImpl.java index 0b6fc00..cfb7c0e 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetVideoTelephonyJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetVideoTelephonyJabberImpl.java @@ -110,10 +110,10 @@ public class OperationSetVideoTelephonyJabberImpl * @param calleeAddress Contact address * @return true if contact support Jingle video, false otherwise * - * @exception OperationFailedException with the corresponding code if we fail + * @throws OperationFailedException with the corresponding code if we fail * to create the video call. */ - private Call createOutgoingVideoCall(String calleeAddress) + protected Call createOutgoingVideoCall(String calleeAddress) throws OperationFailedException { if (logger.isInfoEnabled()) diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java index a606c6a..a8f29d4 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java @@ -18,6 +18,7 @@ import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.service.protocol.jabberconstants.*; import net.java.sip.communicator.util.*; import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*; +import net.java.sip.communicator.impl.protocol.jabber.extensions.inputevt.*; import net.java.sip.communicator.impl.protocol.jabber.sasl.*; import net.java.sip.communicator.service.gui.*; @@ -1078,6 +1079,11 @@ public class ProtocolProviderServiceJabberImpl JingleIQ.NAMESPACE, new JingleIQProvider()); + // register our input event provider + providerManager.addIQProvider(InputEvtIQ.ELEMENT_NAME, + InputEvtIQ.NAMESPACE, + new InputEvtIQProvider()); + //initialize the telephony operation set //until we actually finish jingle, we'll have a clumsy way of //enabling it through a system property. @@ -1104,6 +1110,15 @@ public class ProtocolProviderServiceJabberImpl OperationSetDesktopStreaming.class, new OperationSetDesktopStreamingJabberImpl(basicTelephony)); + // initialize desktop sharing OperationSets + addSupportedOperationSet( + OperationSetDesktopSharingServer.class, + new OperationSetDesktopSharingServerJabberImpl( + basicTelephony)); + addSupportedOperationSet( + OperationSetDesktopSharingClient.class, + new OperationSetDesktopSharingClientJabberImpl(this)); + // Add Jingle features to supported features. supportedFeatures.add(URN_XMPP_JINGLE); supportedFeatures.add(URN_XMPP_JINGLE_RTP); @@ -1113,6 +1128,9 @@ public class ProtocolProviderServiceJabberImpl supportedFeatures.add(URN_XMPP_JINGLE_RTP_AUDIO); supportedFeatures.add(URN_XMPP_JINGLE_RTP_VIDEO); supportedFeatures.add(URN_XMPP_JINGLE_RTP_ZRTP); + + /* add extension to support remote control */ + supportedFeatures.add(InputEvtIQ.NAMESPACE); } // OperationSetContactCapabilities diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/inputevt/InputEvtAction.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/inputevt/InputEvtAction.java new file mode 100644 index 0000000..e15f321 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/inputevt/InputEvtAction.java @@ -0,0 +1,70 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.jabber.extensions.inputevt; + +/** + * Enumeration about the possible actions for an InputEvt IQ. + * + * @author Sebastien Vincent + */ +public enum InputEvtAction +{ + /** + * The <tt>notify</tt> action. + */ + NOTIFY("notify"); + + /** + * The name of this direction. + */ + private final String actionName; + + /** + * Creates a <tt>InputEvtAction</tt> instance with the specified name. + * + * @param actionName the name of the <tt>InputEvtAction</tt> we'd like + * to create. + */ + private InputEvtAction(String actionName) + { + this.actionName = actionName; + } + + /** + * Returns the name of this <tt>InputEvtAction</tt>. The name returned by + * this method is meant for use directly in the XMPP XML string. + * + * @return Returns the name of this <tt>InputEvtAction</tt>. + */ + @Override + public String toString() + { + return actionName; + } + + /** + * Returns a <tt>InputEvtAction</tt> value corresponding to the specified + * <tt>inputActionStr</tt>. + * + * @param inputActionStr the action <tt>String</tt> that we'd like to + * parse. + * @return a <tt>InputEvtAction</tt> value corresponding to the specified + * <tt>inputActionStr</tt>. + * @throws IllegalArgumentException in case <tt>inputActionStr</tt> is + * not valid + */ + public static InputEvtAction parseString(String inputActionStr) + throws IllegalArgumentException + { + for (InputEvtAction value : values()) + if (value.toString().equals(inputActionStr)) + return value; + + throw new IllegalArgumentException( + inputActionStr + " is not a valid Input action"); + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/inputevt/InputEvtIQ.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/inputevt/InputEvtIQ.java new file mode 100644 index 0000000..c506401 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/inputevt/InputEvtIQ.java @@ -0,0 +1,142 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.jabber.extensions.inputevt; + +import java.util.*; + +import org.jivesoftware.smack.packet.*; + +/** + * Input event IQ. It is used to transfer key and mouse events through XMPP. + * + * @author Sebastien Vincent + */ +public class InputEvtIQ extends IQ { + + /** + * The namespace that input event belongs to. + */ + public static final String NAMESPACE = + "http://sip-communicator.org/protocol/inputevt"; + + /** + * The name of the element that contains the input event data. + */ + public static final String ELEMENT_NAME = "inputevt"; + + /** + * The name of the argument that contains the input action value. + */ + public static final String ACTION_ATTR_NAME = "action"; + + /** + * Action of this <tt>InputIQ</tt>. + */ + private InputEvtAction action = null; + + /** + * List of remote-control elements. + */ + private List<RemoteControlExtension> remoteControls = + new ArrayList<RemoteControlExtension>(); + + /** + * Constructor. + */ + public InputEvtIQ() + { + } + + /** + * Get the XML representation of the IQ. + * + * @return XML representation of the IQ + */ + @Override + public String getChildElementXML() + { + StringBuilder bldr = new StringBuilder("<" + ELEMENT_NAME); + + bldr.append(" xmlns='" + NAMESPACE + "'"); + + bldr.append(" " + ACTION_ATTR_NAME + "='" + getAction() + "'"); + + if(remoteControls.size() > 0) + { + bldr.append(">"); + + for(RemoteControlExtension p : remoteControls) + bldr.append(p.toXML()); + + bldr.append("</" + ELEMENT_NAME + ">"); + } + else + { + bldr.append("/>"); + } + + return bldr.toString(); + } + + /** + * Sets the value of this element's <tt>action</tt> attribute. The value of + * the 'action' attribute MUST be one of the values enumerated here. If an + * entity receives a value not defined here, it MUST ignore the attribute + * and MUST return a <tt>bad-request</tt> error to the sender. There is no + * default value for the 'action' attribute. + * + * @param action the value of the <tt>action</tt> attribute. + */ + public void setAction(InputEvtAction action) + { + this.action = action; + } + + /** + * Returns the value of this element's <tt>action</tt> attribute. The value + * of the 'action' attribute MUST be one of the values enumerated here. If + * an entity receives a value not defined here, it MUST ignore the attribute + * and MUST return a <tt>bad-request</tt> error to the sender. There is no + * default value for the 'action' attribute. + * + * @return the value of the <tt>action</tt> attribute. + */ + public InputEvtAction getAction() + { + return action; + } + + /** + * Add a remote-control extension. + * + * @param item remote-control extension + */ + public void addRemoteControl(RemoteControlExtension item) + { + remoteControls.add(item); + } + + /** + * Remove a remote-control extension. + * + * @param item remote-control extension + */ + public void removeRemoteControl(RemoteControlExtension item) + { + remoteControls.remove(item); + } + + /** + * Get the <tt>RemoteControlExtension</tt> list of this IQ. + * + * @return list of <tt>RemoteControlExtension</tt> + */ + public List<RemoteControlExtension> getRemoteControls() + { + return remoteControls; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/inputevt/InputEvtIQProvider.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/inputevt/InputEvtIQProvider.java new file mode 100644 index 0000000..c2ea6a5 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/inputevt/InputEvtIQProvider.java @@ -0,0 +1,85 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.jabber.extensions.inputevt; + +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.provider.*; + +import org.xmlpull.v1.*; + +/** + * An implementation of a InputEvt IQ provider that parses incoming Input IQs. + * + * @author Sebastien Vincent + */ +public class InputEvtIQProvider implements IQProvider +{ + /** + * Constructs a new InputEvtIQ provider. + */ + public InputEvtIQProvider() + { +/* + ProviderManager providerManager = ProviderManager.getInstance(); + + providerManager.addExtensionProvider( + InputExtensionProvider.ELEMENT_REMOTE_CONTROL, + InputExtensionProvider.NAMESPACE, + new InputExtensionProvider()); +*/ + } + + /** + * Parse the Input IQ sub-document and returns the corresponding + * <tt>InputEvtIQ</tt>. + * + * @param parser XML parser + * @return <tt>InputEvtIQ</tt> + * @throws Exception if something goes wrong during parsing + */ + public IQ parseIQ(XmlPullParser parser) throws Exception + { + InputEvtIQ inputIQ = new InputEvtIQ(); + boolean done = false; + RemoteControlExtensionProvider provider = new RemoteControlExtensionProvider(); + InputEvtAction action = InputEvtAction.parseString(parser + .getAttributeValue("", InputEvtIQ.ACTION_ATTR_NAME)); + + inputIQ.setAction(action); + + int eventType; + String elementName; + + while (!done) + { + eventType = parser.next(); + elementName = parser.getName(); + + if (eventType == XmlPullParser.START_TAG) + { + // <remote-control/> + if (elementName.equals( + RemoteControlExtensionProvider.ELEMENT_REMOTE_CONTROL)) + { + RemoteControlExtension item = + (RemoteControlExtension)provider.parseExtension(parser); + inputIQ.addRemoteControl(item); + } + } + + if (eventType == XmlPullParser.END_TAG) + { + if (parser.getName().equals(InputEvtIQ.ELEMENT_NAME)) + { + done = true; + } + } + } + + return inputIQ; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/inputevt/RemoteControlExtension.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/inputevt/RemoteControlExtension.java new file mode 100644 index 0000000..9b9f7c5 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/inputevt/RemoteControlExtension.java @@ -0,0 +1,194 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.jabber.extensions.inputevt; + +import java.awt.*; +import java.awt.event.*; + +import org.jivesoftware.smack.packet.*; + +/** + * This class implements input event extension. + * + * @author Sebastien Vincent + */ +public class RemoteControlExtension + implements PacketExtension +{ + /** + * AWT event that represents our <tt>RemoteControlExtension</tt>. + */ + private final ComponentEvent event; + + /** + * Size of the panel that contains video + */ + private final Dimension videoPanelSize; + + /** + * Constructor. + * + */ + public RemoteControlExtension() + { + videoPanelSize = null; + event = null; + } + + /** + * Constructor. + * + * @param videoPanelSize size of the panel that contains video + */ + public RemoteControlExtension(Dimension videoPanelSize) + { + this.videoPanelSize = videoPanelSize; + this.event = null; + } + + /** + * Constructor. + * + * @param event AWT event + */ + public RemoteControlExtension(ComponentEvent event) + { + this.event = event; + this.videoPanelSize = null; + } + + /** + * Constructor. + * + * @param videoPanelSize size of the panel that contains video + * @param event AWT event + */ + public RemoteControlExtension(InputEvent event, + Dimension videoPanelSize) + { + this.videoPanelSize = videoPanelSize; + this.event = event; + } + + /** + * Get <tt>ComponentEvent</tt> that represents our + * <tt>InputExtensionItem</tt>. + * + * @return AWT <tt>ComponentEvent</tt> + */ + public ComponentEvent getEvent() + { + return event; + } + + /** + * Get the element name of the <tt>PacketExtension</tt>. + * + * @return "remote-control" + */ + public String getElementName() + { + return RemoteControlExtensionProvider.ELEMENT_REMOTE_CONTROL; + } + + /** + * Returns the XML namespace of the extension sub-packet root element. + * The namespace is always "http://sip-communicator.org/protocol/inputevt". + * + * @return the XML namespace of the packet extension. + */ + public String getNamespace() + { + return RemoteControlExtensionProvider.NAMESPACE; + } + + /** + * Get the XML representation. + * + * @return XML representation of the item + */ + public String toXML() + { + String ret = null; + + if(event == null) + { + return null; + } + + if(event instanceof MouseEvent) + { + MouseEvent e = (MouseEvent)event; + + switch(e.getID()) + { + case MouseEvent.MOUSE_DRAGGED: + case MouseEvent.MOUSE_MOVED: + if(videoPanelSize != null) + { + Point p = e.getPoint(); + double x = (p.getX() / videoPanelSize.width); + double y = (p.getY() / videoPanelSize.height); + ret = RemoteControlExtensionProvider.getMouseMovedXML(x, y); + } + break; + case MouseEvent.MOUSE_WHEEL: + MouseWheelEvent ew = (MouseWheelEvent)e; + ret = RemoteControlExtensionProvider.getMouseWheelXML( + ew.getWheelRotation()); + break; + case MouseEvent.MOUSE_PRESSED: + ret = RemoteControlExtensionProvider.getMousePressedXML( + e.getModifiers()); + break; + case MouseEvent.MOUSE_RELEASED: + ret = RemoteControlExtensionProvider.getMouseReleasedXML( + e.getModifiers()); + break; + default: + break; + } + } + else if(event instanceof KeyEvent) + { + KeyEvent e = (KeyEvent)event; + int keycode = e.getKeyCode(); + int key = e.getKeyChar(); + + if(key != KeyEvent.CHAR_UNDEFINED) + { + keycode = e.getKeyChar(); + } + else + { + keycode = e.getKeyCode(); + } + + if(keycode == 0) + { + return null; + } + + switch(e.getID()) + { + case KeyEvent.KEY_PRESSED: + ret = RemoteControlExtensionProvider.getKeyPressedXML(keycode); + break; + case KeyEvent.KEY_RELEASED: + ret = RemoteControlExtensionProvider.getKeyReleasedXML(keycode); + break; + case KeyEvent.KEY_TYPED: + ret = RemoteControlExtensionProvider.getKeyTypedXML(keycode); + break; + default: + break; + } + } + + return ret; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/inputevt/RemoteControlExtensionProvider.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/inputevt/RemoteControlExtensionProvider.java new file mode 100644 index 0000000..95394ed --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/inputevt/RemoteControlExtensionProvider.java @@ -0,0 +1,402 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.jabber.extensions.inputevt; + +import java.awt.*; +import java.awt.event.*; + +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.provider.*; +import org.xmlpull.v1.*; + +/** + * This class parses incoming remote-control XML element and extracts + * input events such as keyboard and mouse ones. + * + * @author Sebastien Vincent + */ +public class RemoteControlExtensionProvider + implements PacketExtensionProvider +{ + /** + * The name of the remote-info XML element <tt>remote-control</tt>. + */ + public static final String ELEMENT_REMOTE_CONTROL = "remote-control"; + + /** + * The name of the remote-info XML element <tt>mouse-move</tt>. + */ + public static final String ELEMENT_MOUSE_MOVE = "mouse-move"; + + /** + * The name of the remote-info XML element <tt>mouse-wheel</tt>. + */ + public static final String ELEMENT_MOUSE_WHEEL = "mouse-wheel"; + + /** + * The name of the remote-info XML element <tt>mouse-press</tt>. + */ + public static final String ELEMENT_MOUSE_PRESS = "mouse-press"; + + /** + *The name of the remote-info XML element <tt>mouse-release</tt>. + */ + public static final String ELEMENT_MOUSE_RELEASE = "mouse-release"; + + /** + * The name of the remote-info XML element <tt>key-press</tt>. + */ + public static final String ELEMENT_KEY_PRESS = "key-press"; + + /** + * The name of the remote-info XML element <tt>key-release</tt>. + */ + public static final String ELEMENT_KEY_RELEASE = "key-release"; + + /** + * The name of the remote-info XML element <tt>key-type</tt>. + */ + public static final String ELEMENT_KEY_TYPE = "key-type"; + + /** + * Namespace of this extension. + */ + public static final String NAMESPACE = + "http://sip-communicator.org/protocol/inputevt"; + + /** + * Component to be used in custom generated <tt>MouseEvent</tt> and + * <tt>KeyEvent</tt>. + */ + private static final Component component = new Canvas(); + + /** + * Constructor. + */ + public RemoteControlExtensionProvider() + { + } + + /** + * Parses the extension and returns a <tt>PacketExtension</tt>. + * + * @param parser XML parser + * @return a <tt>PacketExtension</tt> that represents a remote-control + * element. + * @throws Exception if an error occurs during XML parsing + */ + public PacketExtension parseExtension(XmlPullParser parser) + throws Exception + { + RemoteControlExtension result = null; + boolean done = false; + + while (!done) + { + try + { + int eventType = parser.next(); + + if (eventType == XmlPullParser.START_TAG) + { + if(parser.getName().equals(ELEMENT_MOUSE_MOVE)) + { + String attr = parser.getAttributeValue("", "x"); + String attr2 = parser.getAttributeValue("", "y"); + if(attr != null && attr2 != null) + { + int x = (int)(Double. + parseDouble(attr) * 1000); + int y = (int)(Double. + parseDouble(attr2) * 1000); + + MouseEvent me = new MouseEvent(component, + MouseEvent.MOUSE_MOVED, + System.currentTimeMillis(), + 0, x, y, 0, false, 0); + + result = new RemoteControlExtension(me); + continue; + } + } + + if(parser.getName().equals(ELEMENT_MOUSE_WHEEL)) + { + String attr = parser.getAttributeValue("", "notch"); + if(attr != null) + { + MouseWheelEvent me = new MouseWheelEvent( + component, MouseEvent.MOUSE_WHEEL, + System.currentTimeMillis(), + 0, 0, 0, 0, false, 0, 0, + Integer.parseInt(attr)); + + + result = new RemoteControlExtension(me); + continue; + } + } + + if(parser.getName().equals(ELEMENT_MOUSE_PRESS)) + { + String attr = parser.getAttributeValue("", "btns"); + if(attr != null) + { + MouseEvent me = new MouseEvent(component, + MouseEvent.MOUSE_PRESSED, + System.currentTimeMillis(), + Integer.parseInt(attr), + 0, 0, 0, false, 0); + + result = new RemoteControlExtension(me); + continue; + } + } + + if(parser.getName().equals(ELEMENT_MOUSE_RELEASE)) + { + String attr = parser.getAttributeValue("", "btns"); + if(attr != null) + { + MouseEvent me = new MouseEvent(component, + MouseEvent.MOUSE_RELEASED, + System.currentTimeMillis(), + Integer.parseInt(attr), + 0, 0, 0, false, 0); + + result = new RemoteControlExtension(me); + continue; + } + } + + if(parser.getName().equals(ELEMENT_KEY_PRESS)) + { + String attr = parser.getAttributeValue("", "keycode"); + if(attr != null) + { + KeyEvent ke = new KeyEvent(component, + KeyEvent.KEY_PRESSED, + System.currentTimeMillis(), + 0, + Integer.parseInt(attr), + (char)0); + + result = new RemoteControlExtension(ke); + continue; + } + } + + if(parser.getName().equals(ELEMENT_KEY_RELEASE)) + { + String attr = parser.getAttributeValue("", "keycode"); + if(attr != null) + { + KeyEvent ke = new KeyEvent(component, + KeyEvent.KEY_RELEASED, + System.currentTimeMillis(), + 0, + Integer.parseInt(attr), + (char)0); + + result = new RemoteControlExtension(ke); + continue; + } + } + + if(parser.getName().equals(ELEMENT_KEY_TYPE)) + { + String attr = parser.getAttributeValue("", "keychar"); + if(attr != null) + { + KeyEvent ke = new KeyEvent(component, + KeyEvent.KEY_TYPED, + System.currentTimeMillis(), + 0, + 0, + (char)Integer.parseInt(attr)); + + result = new RemoteControlExtension(ke); + continue; + } + } + } + else if (eventType == XmlPullParser.END_TAG) + { + if (parser.getName().equals( + RemoteControlExtensionProvider.ELEMENT_REMOTE_CONTROL)) + { + done = true; + } + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + if(result == null) + { + /* we are not allowed to return null otherwise the parser goes + * crazy + */ + result = new RemoteControlExtension(new ComponentEvent(component, 0)); + } + + return result; + } + + /** + * Appends a specific array of <tt>String</tt>s to a specific + * <tt>StringBuffer</tt>. + * + * @param stringBuffer the <tt>StringBuffer</tt> to append the specified + * <tt>strings</tt> to + * @param strings the <tt>String</tt> values to be appended to the specified + * <tt>stringBuffer</tt> + */ + private static void append(StringBuffer stringBuffer, String... strings) + { + for (String str : strings) + stringBuffer.append(str); + } + + /** + * Build a key-press remote-control XML element. + * + * @param keycode keyboard's code + * @return raw XML bytes + */ + public static String getKeyPressedXML(int keycode) + { + StringBuffer xml = new StringBuffer(); + + append(xml, "<" + ELEMENT_REMOTE_CONTROL + " xmlns=\"" + NAMESPACE + + "\">"); + // <key-press> + append(xml, "<", RemoteControlExtensionProvider.ELEMENT_KEY_PRESS); + append(xml, " keycode=\"", Integer.toString(keycode), "\"/>"); + append(xml, "</" + ELEMENT_REMOTE_CONTROL + ">"); + return xml.toString(); + } + + /** + * Build a key-release remote-control XML element. + * + * @param keycode keyboard's code + * @return raw XML bytes + */ + public static String getKeyReleasedXML(int keycode) + { + StringBuffer xml = new StringBuffer(); + + append(xml, "<" + ELEMENT_REMOTE_CONTROL + " xmlns=\"" + NAMESPACE + + "\">"); + // <key-release> + append(xml, "<", RemoteControlExtensionProvider.ELEMENT_KEY_RELEASE); + append(xml, " keycode=\"", Integer.toString(keycode), "\"/>"); + append(xml, "</" + ELEMENT_REMOTE_CONTROL + ">"); + return xml.toString(); + } + + /** + * Build a key-typed remote-control XML element. + * + * @param keycode keyboard's code + * @return raw XML bytes + */ + public static String getKeyTypedXML(int keycode) + { + StringBuffer xml = new StringBuffer(); + + append(xml, "<" + ELEMENT_REMOTE_CONTROL + " xmlns=\"" + NAMESPACE + + "\">"); + // <key-typed> + append(xml, "<", RemoteControlExtensionProvider.ELEMENT_KEY_TYPE); + append(xml, " keychar=\"", Integer.toString(keycode), "\"/>"); + append(xml, "</" + ELEMENT_REMOTE_CONTROL + ">"); + return xml.toString(); + } + + /** + * Build a mouse-press remote-control XML element. + * + * @param btns button mask + * @return raw XML bytes + */ + public static String getMousePressedXML(int btns) + { + StringBuffer xml = new StringBuffer(); + + append(xml, "<" + ELEMENT_REMOTE_CONTROL + " xmlns=\"" + NAMESPACE + + "\">"); + // <mouse-press> + append(xml, "<", RemoteControlExtensionProvider.ELEMENT_MOUSE_PRESS); + append(xml, " btns=\"", Integer.toString(btns), "\"/>"); + append(xml, "</" + ELEMENT_REMOTE_CONTROL + ">"); + return xml.toString(); + } + + /** + * Build a remote-info mouse-release remote-control XML element. + * + * @param btns button mask + * @return raw XML bytes + */ + public static String getMouseReleasedXML(int btns) + { + StringBuffer xml = new StringBuffer(); + + append(xml, "<" + ELEMENT_REMOTE_CONTROL + " xmlns=\"" + NAMESPACE + + "\">"); + // <mouse-release> + append(xml, "<", RemoteControlExtensionProvider.ELEMENT_MOUSE_RELEASE); + append(xml, " btns=\"", Integer.toString(btns), "\"/>"); + append(xml, "</" + ELEMENT_REMOTE_CONTROL + ">"); + return xml.toString(); + } + + /** + * Build a remote-info mouse-move remote-control XML element. + * + * @param x x position of the mouse + * @param y y position of the mouse + * @return raw XML bytes + */ + public static String getMouseMovedXML(double x, double y) + { + StringBuffer xml = new StringBuffer(); + + append(xml, "<" + ELEMENT_REMOTE_CONTROL + " xmlns=\"" + NAMESPACE + + "\">"); + // <mouse-press> + append(xml, "<", RemoteControlExtensionProvider.ELEMENT_MOUSE_MOVE); + append(xml, " x=\"", Double.toString(x), "\" y=\"", Double.toString(y), + "\"/>"); + append(xml, "</" + ELEMENT_REMOTE_CONTROL + ">"); + return xml.toString(); + } + + /** + * Build a remote-info mouse-wheel remote-control XML element. + * + * @param notch wheel notch + * @return raw XML bytes + */ + public static String getMouseWheelXML(int notch) + { + StringBuffer xml = new StringBuffer(); + + append(xml, "<" + ELEMENT_REMOTE_CONTROL + " xmlns=\"" + NAMESPACE + + "\">"); + // <mouse-wheel> + append(xml, "<", RemoteControlExtensionProvider.ELEMENT_MOUSE_WHEEL); + append(xml, " notch=\"", Integer.toString(notch), "\"/>"); + append(xml, "</" + ELEMENT_REMOTE_CONTROL + ">"); + return xml.toString(); + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/InputEvtPacketExtension.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/InputEvtPacketExtension.java new file mode 100644 index 0000000..7abced8 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/InputEvtPacketExtension.java @@ -0,0 +1,37 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.jabber.extensions.jingle; + +import net.java.sip.communicator.impl.protocol.jabber.extensions.*; + +/** + * Represents the content <tt>inputevt</tt> element that may be find in + * <tt>content</tt> part of a Jingle media negociation. + * + * @author Sebastien Vincent + */ +public class InputEvtPacketExtension extends AbstractPacketExtension +{ + /** + * Name of the XML element representing the extension. + */ + public final static String ELEMENT_NAME = "inputevt"; + + /** + * Namespace.. + */ + public final static String NAMESPACE = + "http://sip-communicator.org/protocol/inputevt"; + + /** + * Constructs a new <tt>inputevt</tt> extension. + */ + public InputEvtPacketExtension() + { + super(NAMESPACE, ELEMENT_NAME); + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQProvider.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQProvider.java index 921b940..910f4e8 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQProvider.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQProvider.java @@ -5,6 +5,7 @@ * See terms of license at gnu.org. */ package net.java.sip.communicator.impl.protocol.jabber.extensions.jingle; + import java.util.logging.*; import net.java.sip.communicator.impl.protocol.jabber.extensions.*; @@ -12,7 +13,6 @@ import net.java.sip.communicator.impl.protocol.jabber.extensions.*; import org.jivesoftware.smack.provider.*; import org.xmlpull.v1.XmlPullParser; - /** * An implementation of a Jingle IQ provider that parses incoming Jingle IQs. * @@ -107,6 +107,13 @@ public class JingleIQProvider implements IQProvider IceUdpTransportPacketExtension.NAMESPACE, new DefaultPacketExtensionProvider<RemoteCandidatePacketExtension>( RemoteCandidatePacketExtension.class)); + + //inputevt <inputevt/> provider + providerManager.addExtensionProvider( + InputEvtPacketExtension.ELEMENT_NAME, + InputEvtPacketExtension.NAMESPACE, + new DefaultPacketExtensionProvider<InputEvtPacketExtension>( + InputEvtPacketExtension.class)); } /** diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/jabber.provider.manifest.mf b/src/net/java/sip/communicator/impl/protocol/jabber/jabber.provider.manifest.mf index 5ff04de..2037109 100755 --- a/src/net/java/sip/communicator/impl/protocol/jabber/jabber.provider.manifest.mf +++ b/src/net/java/sip/communicator/impl/protocol/jabber/jabber.provider.manifest.mf @@ -41,6 +41,7 @@ Import-Package: org.osgi.framework, net.java.sip.communicator.service.neomedia.device, net.java.sip.communicator.service.neomedia.event, net.java.sip.communicator.service.neomedia.format, + net.java.sip.communicator.service.hid, net.java.sip.communicator.service.netaddr, net.java.sip.communicator.service.argdelegation, net.java.sip.communicator.service.gui, diff --git a/src/net/java/sip/communicator/impl/protocol/sip/DesktopSharingProtocolSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/DesktopSharingProtocolSipImpl.java new file mode 100644 index 0000000..b1b7ed9 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/sip/DesktopSharingProtocolSipImpl.java @@ -0,0 +1,434 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.sip; + +import java.util.*; +import java.util.List; // disambiguation +import java.awt.*; +import java.awt.event.*; + +import org.w3c.dom.*; + +/** + * Utility class to provide XML definition for the desktop sharing SIP + * event package. + * + * @author Sebastien Vincent + */ +public class DesktopSharingProtocolSipImpl +{ + /** + * The name of the event package supported by + * <tt>OperationSetDesktopSharingServerSipImpl</tt> in SUBSCRIBE and NOTIFY + * requests. + */ + public static final String EVENT_PACKAGE = "remote-control"; + + /** + * The time in seconds before the expiration of a <tt>Subscription</tt> at + * which the <tt>OperationSetDesktopSharingServerSipImpl</tt> instance + * managing it should refresh it. + */ + public static final int REFRESH_MARGIN = 60; + + /** + * The time in seconds after which a <tt>Subscription</tt> should be expired + * by the <tt>OperationSetDesktopSharingServerSipImpl</tt> instance which + * manages it. + */ + public static final int SUBSCRIPTION_DURATION = 3600; + + /** + * The content sub-type of the content supported in NOTIFY requests handled + * by <tt>OperationSetDesktopSharingSipImpl</tt>. + */ + public static final String CONTENT_SUB_TYPE = "remote-control+xml"; + + /** + * The name of the remote-info XML element <tt>remote-control</tt>. + */ + private static final String ELEMENT_REMOTE_CONTROL = "remote-control"; + + /** + * The name of the remote-info XML element <tt>mouse-move</tt>. + */ + private static final String ELEMENT_MOUSE_MOVE = "mouse-move"; + + /** + * The name of the remote-info XML element <tt>mouse-wheel</tt>. + */ + private static final String ELEMENT_MOUSE_WHEEL = "mouse-wheel"; + + /** + * The name of the remote-info XML element <tt>mouse-press</tt>. + */ + private static final String ELEMENT_MOUSE_PRESS = "mouse-press"; + + /** + *The name of the remote-info XML element <tt>mouse-release</tt>. + */ + private static final String ELEMENT_MOUSE_RELEASE = "mouse-release"; + + /** + * The name of the remote-info XML element <tt>key-press</tt>. + */ + private static final String ELEMENT_KEY_PRESS = "key-press"; + + /** + * The name of the remote-info XML element <tt>key-release</tt>. + */ + private static final String ELEMENT_KEY_RELEASE = "key-release"; + + /** + * The name of the remote-info XML element <tt>key-type</tt>. + */ + private static final String ELEMENT_KEY_TYPE = "key-type"; + + /** + * Component to be used in custom generated <tt>MouseEvent</tt> and + * <tt>KeyEvent</tt>. + */ + private static final Component component = new Canvas(); + + /** + * Appends a specific array of <tt>String</tt>s to a specific + * <tt>StringBuffer</tt>. + * + * @param stringBuffer the <tt>StringBuffer</tt> to append the specified + * <tt>strings</tt> to + * @param strings the <tt>String</tt> values to be appended to the specified + * <tt>stringBuffer</tt> + */ + private static void append(StringBuffer stringBuffer, String... strings) + { + for (String str : strings) + stringBuffer.append(str); + } + + /** + * Build a remote-info key-press SIP NOTIFY message. + * + * @param keycode keyboard's code + * @return raw XML bytes + */ + public static String getKeyPressedXML(int keycode) + { + StringBuffer xml = new StringBuffer(); + + xml.append( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"); + + // <remote-control> + append(xml, "<", ELEMENT_REMOTE_CONTROL, ">"); + // <key-press> + append(xml, "<", ELEMENT_KEY_PRESS); + append(xml, " keycode=\"", Integer.toString(keycode), "\" />"); + append(xml, "</", ELEMENT_REMOTE_CONTROL, ">"); + + return xml.toString(); + } + + /** + * Build a remote-info key-release SIP NOTIFY message. + * + * @param keycode keyboard's code + * @return raw XML bytes + */ + public static String getKeyReleasedXML(int keycode) + { + StringBuffer xml = new StringBuffer(); + + xml.append( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"); + + // <remote-control> + append(xml, "<", ELEMENT_REMOTE_CONTROL, ">"); + // <key-release> + append(xml, "<", ELEMENT_KEY_RELEASE); + append(xml, " keycode=\"", Integer.toString(keycode), "\" />"); + append(xml, "</", ELEMENT_REMOTE_CONTROL, ">"); + + return xml.toString(); + } + + /** + * Build a remote-info key-typed SIP NOTIFY message. + * + * @param keycode keyboard's code + * @return raw XML bytes + */ + public static String getKeyTypedXML(int keycode) + { + StringBuffer xml = new StringBuffer(); + + xml.append( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"); + + // <remote-control> + append(xml, "<", ELEMENT_REMOTE_CONTROL, ">"); + // <key-typed> + append(xml, "<", ELEMENT_KEY_TYPE); + append(xml, " keychar=\"", Integer.toString(keycode), "\" />"); + append(xml, "</", ELEMENT_REMOTE_CONTROL, ">"); + + return xml.toString(); + } + + /** + * Build a remote-info mouse-press SIP NOTIFY message. + * + * @param btns button mask + * @return raw XML bytes + */ + public static String getMousePressedXML(int btns) + { + StringBuffer xml = new StringBuffer(); + + xml.append( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"); + + // <remote-control> + append(xml, "<", ELEMENT_REMOTE_CONTROL, ">"); + // <mouse-press> + append(xml, "<", ELEMENT_MOUSE_PRESS); + append(xml, " btns=\"", Integer.toString(btns), "\" />"); + append(xml, "</", ELEMENT_REMOTE_CONTROL, ">"); + + return xml.toString(); + } + + /** + * Build a remote-info mouse-release SIP NOTIFY message. + * + * @param btns button mask + * @return raw XML bytes + */ + public static String getMouseReleasedXML(int btns) + { + StringBuffer xml = new StringBuffer(); + + xml.append( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"); + + // <remote-control> + append(xml, "<", ELEMENT_REMOTE_CONTROL, ">"); + // <mouse-release> + append(xml, "<", ELEMENT_MOUSE_RELEASE); + append(xml, " btns=\"", Integer.toString(btns), "\" />"); + append(xml, "</", ELEMENT_REMOTE_CONTROL, ">"); + + return xml.toString(); + } + + /** + * Build a remote-info mouse-move SIP NOTIFY message. + * + * @param x x position of the mouse + * @param y y position of the mouse + * @return raw XML bytes + */ + public static String getMouseMovedXML(double x, double y) + { + StringBuffer xml = new StringBuffer(); + + xml.append( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"); + + // <remote-control> + append(xml, "<", ELEMENT_REMOTE_CONTROL, ">"); + // <mouse-press> + append(xml, "<", ELEMENT_MOUSE_MOVE); + append(xml, " x=\"", Double.toString(x), "\" y=\"", Double.toString(y), + "\" />"); + append(xml, "</", ELEMENT_REMOTE_CONTROL, ">"); + + return xml.toString(); + } + + /** + * Build a remote-info mouse-wheel SIP NOTIFY message. + * + * @param notch wheel notch + * @return raw XML bytes + */ + public static String getMouseWheelXML(int notch) + { + StringBuffer xml = new StringBuffer(); + + xml.append( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"); + + // <remote-control> + append(xml, "<", ELEMENT_REMOTE_CONTROL, ">"); + // <mouse-wheel> + append(xml, "<", ELEMENT_MOUSE_WHEEL); + append(xml, " notch=\"", Integer.toString(notch), "\" />"); + append(xml, "</", ELEMENT_REMOTE_CONTROL, ">"); + + return xml.toString(); + } + + /** + * Parses an XML element and returns a list of all <tt>MouseEvent</tt> + * and <tt>KeyEvent</tt> found. + * + * @param root XML root element + * @param size size of the video (used to have right (x,y) for MouseMoved + * and MouseDragged + * @return list of <tt>java.awt.Event</tt> + */ + public static List<ComponentEvent> parse(Element root, Dimension size) + { + List<ComponentEvent> events = new ArrayList<ComponentEvent>(); + NodeList nl = null; + + nl = root.getElementsByTagName(ELEMENT_MOUSE_PRESS); + if(nl != null) + { + for(int i = 0 ; i < nl.getLength() ; i++) + { + Element el = (Element)nl.item(i); + if(el.hasAttribute("btns")) + { + MouseEvent me = new MouseEvent(component, + MouseEvent.MOUSE_PRESSED, + System.currentTimeMillis(), + Integer.parseInt(el.getAttribute("btns")), + 0, 0, 0, false, 0); + + events.add(me); + } + } + } + + nl = root.getElementsByTagName(ELEMENT_MOUSE_RELEASE); + if(nl != null) + { + for(int i = 0 ; i < nl.getLength() ; i++) + { + Element el = (Element)nl.item(i); + if(el.hasAttribute("btns")) + { + MouseEvent me = new MouseEvent(component, + MouseEvent.MOUSE_RELEASED, + System.currentTimeMillis(), + Integer.parseInt(el.getAttribute("btns")), + 0, 0, 0, false, 0); + events.add(me); + } + } + } + + nl = root.getElementsByTagName(ELEMENT_MOUSE_MOVE); + if(nl != null) + { + int x = -1; + int y = -1; + + for(int i = 0 ; i < nl.getLength() ; i++) + { + Element el = (Element)nl.item(i); + + if(el.hasAttribute("x")) + { + x = (int)(Double.parseDouble( + el.getAttribute("x")) * size.width); + } + + if(el.hasAttribute("y")) + { + y = (int)(Double.parseDouble( + el.getAttribute("y")) * size.height); + } + + if(x >= 0 && y >= 0) + { + MouseEvent me = new MouseEvent(component, + MouseEvent.MOUSE_MOVED, + System.currentTimeMillis(), + 0, x, y, 0, false, 0); + + events.add(me); + } + } + } + + nl = root.getElementsByTagName(ELEMENT_MOUSE_WHEEL); + if(nl != null) + { + for(int i = 0 ; i < nl.getLength() ; i++) + { + Element el = (Element)nl.item(i); + if(el.hasAttribute("notch")) + { + MouseWheelEvent me = new MouseWheelEvent( + component, MouseEvent.MOUSE_WHEEL, + System.currentTimeMillis(), + 0, 0, 0, 0, false, 0, 0, + Integer.parseInt(el.getAttribute( + "notch"))); + events.add(me); + } + } + } + + nl = root.getElementsByTagName(ELEMENT_KEY_PRESS); + if(nl != null) + { + for(int i = 0 ; i < nl.getLength() ; i++) + { + Element el = (Element)nl.item(i); + if(el.hasAttribute("keycode")) + { + KeyEvent ke = new KeyEvent(component, + KeyEvent.KEY_PRESSED, + System.currentTimeMillis(), + 0, + Integer.parseInt(el.getAttribute("keycode")), + (char)0); + + events.add(ke); + } + } + } + + nl = root.getElementsByTagName(ELEMENT_KEY_RELEASE); + if(nl != null) + { + for(int i = 0 ; i < nl.getLength() ; i++) + { + Element el = (Element)nl.item(i); + if(el.hasAttribute("keycode")) + { + KeyEvent ke = new KeyEvent(component, + KeyEvent.KEY_RELEASED, + System.currentTimeMillis(), + 0, + Integer.parseInt(el.getAttribute("keycode")), + (char)0); + + events.add(ke); + } + } + } + + nl = root.getElementsByTagName(ELEMENT_KEY_TYPE); + if(nl != null) + { + for(int i = 0 ; i < nl.getLength() ; i++) + { + Element el = (Element)nl.item(i); + if(el.hasAttribute("keychar")) + { + KeyEvent ke = new KeyEvent(component, + KeyEvent.KEY_TYPED, + System.currentTimeMillis(), + 0, + 0, + (char)Integer.parseInt( + el.getAttribute("keychar"))); + + events.add(ke); + } + } + } + return events; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/sip/EventPackageNotifier.java b/src/net/java/sip/communicator/impl/protocol/sip/EventPackageNotifier.java index 471f59b..d7df5ab 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/EventPackageNotifier.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/EventPackageNotifier.java @@ -871,11 +871,11 @@ public abstract class EventPackageNotifier * * @param response a <tt>Response</tt> identifying the <tt>Subscription</tt> * to be removed from the list of subscriptions managed by this instance - * @param eventId + * @param eventId the value of the id tag * @param clientTransaction the <tt>ClientTransaction</tt> through which the * specified <tt>Response</tt> came */ - private void removeSubscription( + protected void removeSubscription( Response response, String eventId, ClientTransaction clientTransaction) diff --git a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicTelephonySipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicTelephonySipImpl.java index 531b15d..c623e6f 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicTelephonySipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicTelephonySipImpl.java @@ -487,6 +487,35 @@ public class OperationSetBasicTelephonySipImpl default: int responseStatusCodeRange = responseStatusCode / 100; + if(responseStatusCode == 500 && responseEvent. + getClientTransaction().getRequest().getMethod(). + equals(Request.NOTIFY)) + { + /* maybe this one comes from desktop sharing session, it is + * possible that keyboard and mouse notifications comes in + * disorder as interval between two events can be very short + * (especially for "mouse moved"). + */ + /* XXX this is not an optimal solution, the ideal will be + * to prevent disordering + */ + byte raw[] = responseEvent.getClientTransaction(). + getRequest().getRawContent(); + String content = new String(raw); + + /* + * we have to bypass SIP specifications in the SIP NOTIFY + * message is desktop sharing specific and thus do not close the + * call. + */ + if(content.startsWith( + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + + "<remote-control>")) + { + return true; + } + } + if ((responseStatusCodeRange == 4) || (responseStatusCodeRange == 5) || (responseStatusCodeRange == 6)) diff --git a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetDesktopSharingClientSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetDesktopSharingClientSipImpl.java new file mode 100644 index 0000000..1d04cea --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetDesktopSharingClientSipImpl.java @@ -0,0 +1,521 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.sip; + +import java.io.*; +import java.text.*; +import java.util.List; // disambiguation +import java.util.*; +import java.awt.*; +import java.awt.event.*; + +import javax.sip.*; +import javax.sip.address.*; +import javax.sip.header.*; +import javax.sip.message.*; +import javax.sip.Dialog; // disambiguation + +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.util.*; + +/** + * Implements all desktop sharing client-side related functions for SIP + * protocol. + * + * @author Sebastien Vincent + */ +public class OperationSetDesktopSharingClientSipImpl + implements OperationSetDesktopSharingClient +{ + /** + * Our class logger. + */ + private static final Logger logger = Logger + .getLogger(OperationSetDesktopSharingClientSipImpl.class); + + /** + * The <tt>CallPeerListener</tt> which listens to modifications in the + * properties/state of <tt>CallPeer</tt>. + */ + private final CallPeerListener callPeerListener = new CallPeerAdapter() + { + /** + * Indicates that a change has occurred in the status of the source + * <tt>CallPeer</tt>. + * + * @param evt the <tt>CallPeerChangeEvent</tt> instance containing the + * source event as well as its previous and its new status + */ + @Override + public void peerStateChanged(CallPeerChangeEvent evt) + { + CallPeer peer = evt.getSourceCallPeer(); + CallPeerState state = peer.getState(); + + if(state != null && (state.equals(CallPeerState.DISCONNECTED) || + state.equals(CallPeerState.FAILED))) + { + /* if the peer is disconnected or call has failed, remove + * corresponding subscription. + */ + try + { + notifier.removeSubscription(parentProvider. + parseAddressString(peer.getAddress())); + } + catch(ParseException ex) + { + } + } + } + }; + + /** + * List of listeners to be notified when a change occurred in remote control + * access. + */ + private List<RemoteControlListener> listeners = + new ArrayList<RemoteControlListener>(); + + /** + * The <tt>EventPackageNotifier</tt> which implements remote-control + * event-package notifier support on behalf of this + * <tt>OperationSetDesktopSharingClient</tt> instance. + */ + private final EventPackageNotifier notifier; + + /** + * The SIP <tt>ProtocolProviderService</tt> implementation which created + * this instance and for which telephony conferencing services are being + * provided by this instance. + */ + private final ProtocolProviderServiceSipImpl parentProvider; + + /** + * The <tt>Timer</tt> which executes delayed tasks scheduled by + * {@link #notifier}. + */ + private final TimerScheduler timer = new TimerScheduler(); + + /** + * List of SIP NOTIFY messages. + */ + private Queue<String> inputEvents = new LinkedList<String>(); + + /** + * Synchronization object for {@link #inputEvents} access. + */ + private final Object inputSync = new Object(); + + /** + * Initializes a new <tt>OperationSetDesktopSharingClientSipImpl</tt>. + * + * @param parentProvider the SIP <tt>ProtocolProviderService</tt> + * implementation which has requested the creation of the new instance and + * for which the new instance is to provide desktop sharing. + */ + public OperationSetDesktopSharingClientSipImpl( + ProtocolProviderServiceSipImpl parentProvider) + { + this.parentProvider = parentProvider; + + this.notifier = new EventPackageNotifier( + this.parentProvider, + DesktopSharingProtocolSipImpl.EVENT_PACKAGE, + DesktopSharingProtocolSipImpl.SUBSCRIPTION_DURATION, + DesktopSharingProtocolSipImpl.CONTENT_SUB_TYPE, + this.timer) + { + protected Subscription createSubscription( + Address fromAddress, + String eventId) + { + /* new subscription received */ + fireRemoteControlGranted(); + + return + new RemoteControlNotifierSubscription( + fromAddress, + eventId); + } + + protected void removeSubscription( + Response response, + String eventId, + ClientTransaction clientTransaction) + { + super.removeSubscription(response, eventId, clientTransaction); + + fireRemoteControlRevoked(); + } + }; + } + + /** + * Notifies all <tt>Subscription</tt>s. + */ + private void notifySubscriptions() + { + EventPackageNotifier.SubscriptionFilter subscriptionFilter + = new EventPackageNotifier.SubscriptionFilter() + { + public boolean accept( + EventPackageNotifier.Subscription subscription) + { + return + (subscription instanceof RemoteControlNotifierSubscription); + /* + && call + .equals( + ((RemoteControlNotifierSubscription) + subscription) + .getCall()); + */ + } + }; + + try + { + notifier.notifyAll(SubscriptionStateHeader.ACTIVE, null, + subscriptionFilter); + } + catch (OperationFailedException ofe) + { + logger.error("Failed to notify the remote-control subscriptions", + ofe); + } + } + + /** + * Send a keyboard notification. + * + * @param callPeer <tt>CallPeer</tt> that will be notified + * @param event <tt>KeyEvent</tt> received and that will be send to + * remote peer + */ + public void sendKeyboardEvent(CallPeer callPeer, KeyEvent event) + { + /* build a SIP NOTIFY with the corresponding keyboard event + * and send it + */ + String msg = null; + int keycode = event.getKeyCode(); + int key = event.getKeyChar(); + + if(key != KeyEvent.CHAR_UNDEFINED) + { + keycode = event.getKeyChar(); + } + else + { + keycode = event.getKeyCode(); + } + + if(keycode == 0) + { + return; + } + + switch(event.getID()) + { + case KeyEvent.KEY_TYPED: + msg = DesktopSharingProtocolSipImpl.getKeyTypedXML(keycode); + break; + case KeyEvent.KEY_PRESSED: + msg = DesktopSharingProtocolSipImpl.getKeyPressedXML(keycode); + break; + case KeyEvent.KEY_RELEASED: + msg = DesktopSharingProtocolSipImpl.getKeyReleasedXML(keycode); + break; + default: + /* ignore */ + return; + } + + synchronized(inputSync) + { + inputEvents.add(msg); + notifySubscriptions(); + } + } + + /** + * Send a mouse notification for specific "moved" <tt>MouseEvent</tt>. As + * controller computer could have smaller desktop that controlled ones, we + * should take care to send the percentage of point x and point y. + * + * @param callPeer <tt>CallPeer</tt> that will be notified + * @param event <tt>MouseEvent</tt> received and that will be send to + * remote peer + * @param videoPanelSize size of the panel that contains video + */ + public void sendMouseEvent(CallPeer callPeer, MouseEvent event, + Dimension videoPanelSize) + { + /* build a SIP NOTIFY with the corresponding mouse event + * and send it + */ + String msg = null; + + if(event.getID() != MouseEvent.MOUSE_MOVED && + event.getID() != MouseEvent.MOUSE_DRAGGED) + { + sendMouseEvent(callPeer, event); + return; + } + + Point p = event.getPoint(); + double x = (p.getX() / videoPanelSize.width); + double y = (p.getY() / videoPanelSize.height); + + msg = DesktopSharingProtocolSipImpl.getMouseMovedXML(x, y); + + synchronized(inputSync) + { + inputEvents.add(msg); + notifySubscriptions(); + } + } + + /** + * Send a mouse notification. + * + * @param callPeer <tt>CallPeer</tt> that will be notified + * @param event <tt>MouseEvent</tt> received and that will be send to + * remote peer + */ + public void sendMouseEvent(CallPeer callPeer, MouseEvent event) + { + /* build a SIP NOTIFY with the corresponding mouse event + * and send it + */ + String msg = null; + + /* note that MOUSE_MOVED and MOUSE_DRAGGED are handled in + * sendMouseEvent(MouseEvent event, Dimension videoPanelSize) + */ + switch(event.getID()) + { + case MouseEvent.MOUSE_PRESSED: + msg = DesktopSharingProtocolSipImpl.getMousePressedXML( + event.getModifiers()); + break; + case MouseEvent.MOUSE_RELEASED: + msg = DesktopSharingProtocolSipImpl.getMouseReleasedXML( + event.getModifiers()); + break; + case MouseEvent.MOUSE_WHEEL: + MouseWheelEvent evt = (MouseWheelEvent)event; + msg = DesktopSharingProtocolSipImpl.getMouseWheelXML( + evt.getWheelRotation()); + break; + default: + /* ignore */ + return; + } + + synchronized(inputSync) + { + inputEvents.add(msg); + notifySubscriptions(); + } + } + + /** + * Fire a <tt>RemoteControlGrantedEvent</tt> to all registered listeners. + */ + public void fireRemoteControlGranted() + { + RemoteControlGrantedEvent event = new RemoteControlGrantedEvent(this); + for(RemoteControlListener l : listeners) + { + l.remoteControlGranted(event); + } + } + + /** + * Fire a <tt>RemoteControlGrantedEvent</tt> to all registered listeners. + */ + public void fireRemoteControlRevoked() + { + RemoteControlRevokedEvent event = new RemoteControlRevokedEvent(this); + for(RemoteControlListener l : listeners) + { + l.remoteControlRevoked(event); + } + } + + /** + * Add a <tt>RemoteControlListener</tt> to be notified when remote peer + * accept to give us full control. + * + * @param listener <tt>RemoteControlListener</tt> to add + */ + public void addRemoteControlListener(RemoteControlListener listener) + { + if(!listeners.contains(listener)) + { + listeners.add(listener); + } + } + + /** + * Remove a <tt>RemoteControlListener</tt> to be notified when remote peer + * accept/revoke to give us full control. + * + * @param listener <tt>RemoteControlListener</tt> to remove + */ + public void removeRemoteControlListener(RemoteControlListener listener) + { + if(listeners.contains(listener)) + { + listeners.remove(listener); + } + } + + /** + * Implements <tt>EventPackageNotifier.Subscription</tt> in order to + * represent a subscription created by a remote <tt>CallPeer</tt> + * to the remote-control event package of a local <tt>Call</tt>. + */ + private class RemoteControlNotifierSubscription + extends EventPackageNotifier.Subscription + { + /** + * The <tt>CallPeer</tt> associated with this notification. + */ + private CallPeerSipImpl callPeer = null; + + /** + * Initializes a new <tt>RemoteControlNotifierSubscription</tt> instance + * with a specific subscription <tt>Address</tt>/Request URI and a + * specific id tag of the associated Event headers. + * + * @param fromAddress the subscription <tt>Address</tt>/Request URI + * which is to be the target of the NOTIFY requests associated with the + * new instance + * @param eventId the value of the id tag to be placed in the Event + * headers of the NOTIFY requests created for the new instance and to be + * present in the received Event headers in order to have the new + * instance associated with them + */ + public RemoteControlNotifierSubscription( + Address fromAddress, + String eventId) + { + super(fromAddress, eventId); + } + + /** + * Creates the content of the NOTIFY request to be sent to the target + * represented by this <tt>Subscription</tt> and having a specific + * subscription state and a specific reason for that subscription state. + * + * @param subscriptionState the subscription state to be notified about + * in the NOTIFY request which is to carry the returned content + * @param reason the reason for the subscription state to be notified + * about in the NOTIFY request which is to carry the returned content + * + * @return an array of <tt>byte</tt>s representing the content of the + * NOTIFY request to be sent to the target represented by this + * <tt>Subscription</tt> + * @see EventPackageNotifier.Subscription#createNotifyContent(String, + * String) + */ + protected byte[] createNotifyContent( + String subscriptionState, + String reason) + { + CallPeerSipImpl callPeer = getCallPeer(); + + if (callPeer == null) + { + logger + .error( + "Failed to find the CallPeer of the remote-control" + + "subscription " + this); + return null; + } + + String xml = null; + byte[] notifyContent = null; + + xml = inputEvents.poll(); + + if(xml == null) + { + xml = new String("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + + "<remote-control />"); + } + + try + { + notifyContent = xml.getBytes("UTF-8"); + } + catch (UnsupportedEncodingException uee) + { + logger + .warn( + "Failed to gets bytes from String for the UTF-8 charset", + uee); + notifyContent = xml.getBytes(); + } + + return notifyContent; + } + + /** + * Gets the <tt>Call</tt> of the <tt>CallPeerSipImpl</tt> subscribed to + * the <tt>EventPackageNotifier</tt> and represented by this + * <tt>Subscription</tt>. + * + * @return the <tt>Call</tt> of the <tt>CallPeerSipImpl</tt> subscribed + * to the <tt>EventPackageNotifier</tt> and represented by this + * <tt>Subscription</tt> + */ + public CallSipImpl getCall() + { + CallPeerSipImpl callPeer = getCallPeer(); + + return (callPeer == null) ? null : callPeer.getCall(); + } + + /** + * Gets the <tt>CallPeerSipImpl</tt> subscribed to the + * <tt>EventPackageNotifier</tt> and represented by this + * <tt>Subscription</tt>. + * + * @return the <tt>CallPeerSipImpl</tt> subscribed to the + * <tt>EventPackageNotifier</tt> and represented by this + * <tt>Subscription</tt> + */ + private CallPeerSipImpl getCallPeer() + { + if(callPeer == null) + { + Dialog dialog = getDialog(); + + if (dialog != null) + { + OperationSetBasicTelephony<?> basicTelephony + = parentProvider.getOperationSet( + OperationSetBasicTelephony.class); + + if (basicTelephony != null) + { + callPeer = + ((OperationSetBasicTelephonySipImpl)basicTelephony) + .getActiveCallsRepository().findCallPeer(dialog); + callPeer.addCallPeerListener(callPeerListener); + } + } + } + return callPeer; + } + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetDesktopSharingServerSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetDesktopSharingServerSipImpl.java new file mode 100644 index 0000000..6df66b8 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetDesktopSharingServerSipImpl.java @@ -0,0 +1,621 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.sip; + +import java.io.*; +import java.util.List; // disambiguation +import java.text.*; +import java.awt.*; +import java.awt.event.*; + +import javax.sip.Dialog; // disambiguation +import javax.sip.*; +import javax.sip.address.*; +import javax.sip.header.*; +import javax.sip.message.*; +import javax.xml.parsers.*; + +import org.w3c.dom.*; +import org.xml.sax.*; + +import net.java.sip.communicator.service.neomedia.MediaType; // disambiguation +import net.java.sip.communicator.service.neomedia.format.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.hid.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.util.*; + +/** + * Implements all desktop sharing server-side related functions for SIP + * protocol. + * + * @author Sebastien Vincent + */ +public class OperationSetDesktopSharingServerSipImpl + extends OperationSetDesktopStreamingSipImpl + implements OperationSetDesktopSharingServer, + MethodProcessorListener +{ + /** + * Our class logger. + */ + private static final Logger logger = Logger + .getLogger(OperationSetDesktopSharingServerSipImpl.class); + + /** + * The <tt>CallPeerListener</tt> which listens to modifications in the + * properties/state of <tt>CallPeer</tt>. + */ + private final CallPeerListener callPeerListener = new CallPeerAdapter() + { + /** + * Indicates that a change has occurred in the status of the source + * <tt>CallPeer</tt>. + * + * @param evt the <tt>CallPeerChangeEvent</tt> instance containing the + * source event as well as its previous and its new status + */ + @Override + public void peerStateChanged(CallPeerChangeEvent evt) + { + CallPeer peer = evt.getSourceCallPeer(); + CallPeerState state = peer.getState(); + + if (remoteControlEnabled && state != null && + (state.equals(CallPeerState.DISCONNECTED) || + state.equals(CallPeerState.FAILED))) + { + /* if the peer is disconnected or call has failed the SIP + * dialog is terminated and sending a SUBSCRIBE (with 0 as + * lifetime) will throw exception + */ + remoteControlEnabled = false; + + try + { + subscriber.removeSubscription(parentProvider. + parseAddressString(peer.getAddress())); + } + catch(ParseException ex) + { + } + } + } + }; + + /** + * If the remote control is authorized and thus enabled. + */ + private boolean remoteControlEnabled = false; + + /** + * The <tt>EventPackageNotifier</tt> which implements remote-control + * event-package subscriber support on behalf of this + * <tt>OperationSetDesktopSharingServer</tt> instance. + */ + private final EventPackageSubscriber subscriber; + + /** + * The SIP <tt>ProtocolProviderService</tt> implementation which created + * this instance and for which telephony conferencing services are being + * provided by this instance. + */ + private final ProtocolProviderServiceSipImpl parentProvider; + + /** + * The <tt>Timer</tt> which executes delayed tasks scheduled by + * {@link #subscriber}. + */ + private final TimerScheduler timer = new TimerScheduler(); + + /** + * HID service that will regenerates keyboard and mouse events received in + * SIP NOTIFY. + */ + private HIDService hidService = null; + + /** + * Dimension of the local desktop streamed. + */ + private Dimension size = null; + + /** + * Initializes a new <tt>OperationSetDesktopSharingSipImpl</tt> instance + * which builds upon the telephony-related functionality of a specific + * <tt>OperationSetBasicTelephonySipImpl</tt>. + * + * @param basicTelephony the <tt>OperationSetBasicTelephonySipImpl</tt> + * the new extension should build upon + */ + public OperationSetDesktopSharingServerSipImpl( + OperationSetBasicTelephonySipImpl basicTelephony) + { + super(basicTelephony); + parentProvider = basicTelephony.getProtocolProvider(); + + hidService = SipActivator.getHIDService(); + + subscriber = new EventPackageSubscriber( + this.parentProvider, + DesktopSharingProtocolSipImpl.EVENT_PACKAGE, + DesktopSharingProtocolSipImpl.SUBSCRIPTION_DURATION, + DesktopSharingProtocolSipImpl.CONTENT_SUB_TYPE, + this.timer, + DesktopSharingProtocolSipImpl.REFRESH_MARGIN); + } + + /** + * Create a new video call and invite the specified CallPeer to it. + * + * @param uri the address of the callee that we should invite to a new + * call. + * @return CallPeer the CallPeer that will represented by the + * specified uri. All following state change events will be delivered + * through that call peer. The Call that this peer is a member + * of could be retrieved from the CallParticipatn instance with the use + * of the corresponding method. + * @throws OperationFailedException with the corresponding code if we fail + * to create the video call. + * @throws ParseException if <tt>callee</tt> is not a valid sip address + * string. + */ + @Override + public Call createVideoCall(String uri) + throws OperationFailedException, ParseException + { + CallSipImpl call = (CallSipImpl)super.createVideoCall(uri); + CallPeerSipImpl callPeer = call.getCallPeers().next(); + callPeer.addMethodProcessorListener(this); + callPeer.addCallPeerListener(callPeerListener); + + /* TODO change to MediaType.DESKTOP */ + size = (((VideoMediaFormat)call.getDefaultDevice(MediaType.VIDEO). + getFormat()).getSize()); + return call; + } + + /** + * Create a new video call and invite the specified CallPeer to it. + * + * @param callee the address of the callee that we should invite to a new + * call. + * @return CallPeer the CallPeer that will represented by the + * specified uri. All following state change events will be delivered + * through that call peer. The Call that this peer is a member + * of could be retrieved from the CallParticipatn instance with the use + * of the corresponding method. + * @throws OperationFailedException with the corresponding code if we fail + * to create the video call. + */ + @Override + public Call createVideoCall(Contact callee) throws OperationFailedException + { + CallSipImpl call = (CallSipImpl)super.createVideoCall(callee); + CallPeerSipImpl callPeer = call.getCallPeers().next(); + callPeer.addMethodProcessorListener(this); + callPeer.addCallPeerListener(callPeerListener); + + /* TODO change to MediaType.DESKTOP */ + size = (((VideoMediaFormat)call.getDefaultDevice(MediaType.VIDEO). + getFormat()).getSize()); + return call; + } + + /** + * Enable desktop remote control. Local desktop can now regenerates keyboard + * and mouse events received from peer. + * + * @param callPeer call peer that will take control on local computer + */ + public void enableRemoteControl(CallPeer callPeer) + { + RemoteControlSubscriberSubscription subscription + = new RemoteControlSubscriberSubscription( + (CallPeerSipImpl)callPeer); + + try + { + subscriber.subscribe(subscription); + } + catch (OperationFailedException ofe) + { + logger.error( + "Failed to create or send a remote-control subscription", + ofe); + return; + } + } + + /** + * Disable desktop remote control. Local desktop stop regenerates keyboard + * and mouse events received from peer. + * + * @param callPeer call peer that will stop controlling on local computer + */ + public void disableRemoteControl(CallPeer callPeer) + { + /* unsubscribe */ + try + { + Address addr = parentProvider.parseAddressString( + callPeer.getAddress()); + subscriber.unsubscribe(addr, false); + } + catch(ParseException ex) + { + logger.error("Failed to parse address", ex); + } + catch (OperationFailedException ofe) + { + logger.error( + "Failed to create or send a remote-control unsubscription", + ofe); + return; + } + + remoteControlEnabled = false; + } + + /** + * Notifies this <tt>MethodProcessorListener</tt> that a specific + * <tt>CallPeer</tt> has processed a specific SIP <tt>Request</tt> and has + * replied to it with a specific SIP <tt>Response</tt>. + * + * @param sourceCallPeer the <tt>CallPeer</tt> which has processed the + * specified SIP <tt>Request</tt> + * @param request the SIP <tt>Request</tt> which has been processed by + * <tt>sourceCallPeer</tt> + * @param response the SIP <tt>Response</tt> sent by <tt>sourceCallPeer</tt> + * as a reply to the specified SIP <tt>request</tt> + * @see MethodProcessorListener#requestProcessed(CallPeerSipImpl, Request, + * Response) + */ + public void requestProcessed( + CallPeerSipImpl sourceCallPeer, + Request request, + Response response) + { + } + + /** + * Notifies this <tt>MethodProcessorListener</tt> that a specific + * <tt>CallPeer</tt> has processed a specific SIP <tt>Response</tt> and has + * replied to it with a specific SIP <tt>Request</tt>. + * + * @param sourceCallPeer the <tt>CallPeer</tt> which has processed the + * specified SIP <tt>Response</tt> + * @param response the SIP <tt>Response</tt> which has been processed by + * <tt>sourceCallPeer</tt> + * @param request the SIP <tt>Request</tt> sent by <tt>sourceCallPeer</tt> + * as a reply to the specified SIP <tt>response</tt> + * @see MethodProcessorListener#responseProcessed(CallPeerSipImpl, Response, + * Request) + */ + public void responseProcessed( + CallPeerSipImpl sourceCallPeer, + Response response, + Request request) + { + if (Response.OK == response.getStatusCode()) + { + CSeqHeader cseqHeader + = (CSeqHeader) response.getHeader(CSeqHeader.NAME); + + if ((cseqHeader != null) + && Request.INVITE.equalsIgnoreCase(cseqHeader.getMethod())) + { + /* if we have successfully established a SIP session, launch + * remote control + */ + enableRemoteControl(sourceCallPeer); + } + } + } + + /** + * Process keyboard notification received from remote peer. + * + * @param event <tt>KeyboardEvent</tt> that will be regenerated on + * local computer + */ + public void processKeyboardEvent(KeyEvent event) + { + /* ignore command if remote control is not enabled otherwise regenerates + * event on the computer + */ + if (remoteControlEnabled && hidService != null) + { + int keycode = 0; + + /* process immediately a "key-typed" event via press/release */ + if(event.getKeyChar() != 0 && event.getID() == KeyEvent.KEY_TYPED) + { + hidService.keyPress(event.getKeyChar()); + hidService.keyRelease(event.getKeyChar()); + return; + } + + keycode = event.getKeyCode(); + + if(keycode == 0) + { + return; + } + + switch(event.getID()) + { + case KeyEvent.KEY_PRESSED: + hidService.keyPress(keycode); + break; + case KeyEvent.KEY_RELEASED: + hidService.keyRelease(keycode); + break; + default: + break; + } + } + } + + /** + * Process mouse notification received from remote peer. + * + * @param event <tt>MouseEvent</tt> that will be regenerated on local + * computer + */ + public void processMouseEvent(MouseEvent event) + { + /* ignore command if remote control is not enabled otherwise regenerates + * event on the computer + */ + if (remoteControlEnabled && hidService != null) + { + switch(event.getID()) + { + case MouseEvent.MOUSE_PRESSED: + hidService.mousePress(event.getModifiers()); + break; + case MouseEvent.MOUSE_RELEASED: + hidService.mouseRelease(event.getModifiers()); + break; + case MouseEvent.MOUSE_MOVED: + hidService.mouseMove(event.getX(), event.getY()); + break; + case MouseEvent.MOUSE_WHEEL: + MouseWheelEvent evt = (MouseWheelEvent)event; + hidService.mouseWheel(evt.getWheelRotation()); + break; + default: + break; + } + } + } + + /** + * Implements <tt>EventPackageSubscriber.Subscription</tt> in order to + * represent the subscription of the local peer to the remote-control event + * package of a specific remote <tt>CallPeer</tt> acting as a desktop + * sharing server. + */ + private class RemoteControlSubscriberSubscription + extends EventPackageSubscriber.Subscription + { + /** + * The <tt>CallPeer</tt> which is acting as a remote-control focus in + * its <tt>Call</tt> with the local peer. + */ + private final CallPeerSipImpl callPeer; + + /** + * Initializes a new <tt>RemoteControlSubscriberSubscription</tt> + * instance which is to represent the subscription of the local peer to + * the remote-control event package of a specific <tt>CallPeer</tt> + * acting as a desktop sharing server. + * + * @param callPeer + * the <tt>CallPeer</tt> acting as a desktop sharing server + * which the new instance is to subscribe to + */ + public RemoteControlSubscriberSubscription(CallPeerSipImpl callPeer) + { + super(callPeer.getPeerAddress()); + + this.callPeer = callPeer; + } + + /** + * Gets the <tt>Dialog</tt> which was created by the SUBSCRIBE request + * associated with this <tt>Subscription</tt> or which was used to send + * that request in. + * + * @return the <tt>Dialog</tt> which was created by the SUBSCRIBE + * request associated with this <tt>Subscription</tt> or which + * was used to send that request in; <tt>null</tt> if the + * success of the SUBSCRIBE request has not been confirmed yet + * or this <tt>Subscription</tt> was removed from the list of + * the <tt>EventPackageSupport</tt> it used to be in + * @see EventPackageSubscriber.Subscription#getDialog() + */ + @Override + protected Dialog getDialog() + { + Dialog dialog = super.getDialog(); + + if ((dialog == null) + || DialogState.TERMINATED.equals(dialog.getState())) + dialog = callPeer.getDialog(); + return dialog; + } + + /** + * Notifies this <tt>Subscription</tt> that an active NOTIFY + * <tt>Request</tt> has been received and it may process the specified + * raw content carried in it. + * + * @param requestEvent + * the <tt>RequestEvent</tt> carrying the full details of the + * received NOTIFY <tt>Request</tt> including the raw content + * which may be processed by this <tt>Subscription</tt> + * @param rawContent + * an array of bytes which represents the raw content carried + * in the body of the received NOTIFY <tt>Request</tt> and + * extracted from the specified <tt>RequestEvent</tt> for the + * convenience of the implementers + * @see EventPackageSubscriber.Subscription#processActiveRequest( + * RequestEvent, byte[]) + */ + protected void processActiveRequest(RequestEvent requestEvent, + byte[] rawContent) + { + if(requestEvent.getDialog() != callPeer.getDialog()) + { + return; + } + + if (rawContent != null) + { + /* parse rawContent */ + Document document = null; + Throwable exception = null; + + try + { + document + = DocumentBuilderFactory.newInstance() + .newDocumentBuilder() + .parse(new ByteArrayInputStream(rawContent)); + } + catch (IOException ioe) + { + exception = ioe; + } + catch (ParserConfigurationException pce) + { + exception = pce; + } + catch (SAXException saxe) + { + exception = saxe; + } + + if (exception != null) + { + logger.error("Failed to parse remote-info XML", exception); + } + else + { + Element root = document.getDocumentElement(); + List<ComponentEvent> events = null; + + events = DesktopSharingProtocolSipImpl.parse(root, size); + + for(ComponentEvent evt : events) + { + if(evt instanceof MouseEvent) + { + processMouseEvent((MouseEvent)evt); + } + else if(evt instanceof KeyEvent) + { + processKeyboardEvent((KeyEvent)evt); + } + } + } + } + } + + /** + * Notifies this <tt>Subscription</tt> that a <tt>Response</tt> to a + * previous SUBSCRIBE <tt>Request</tt> has been received with a status + * code in the failure range and it may process the status code carried + * in it. + * + * @param responseEvent + * the <tt>ResponseEvent</tt> carrying the full details of + * the received <tt>Response</tt> including the status code + * which may be processed by this <tt>Subscription</tt> + * @param statusCode + * the status code carried in the <tt>Response</tt> and + * extracted from the specified <tt>ResponseEvent</tt> for + * the convenience of the implementers + * @see EventPackageSubscriber.Subscription#processFailureResponse( + * ResponseEvent, int) + */ + protected void processFailureResponse(ResponseEvent responseEvent, + int statusCode) + { + /* we have not managed to subscribe to remote peer so it is better + * to disable remote control feature + */ + remoteControlEnabled = false; + } + + /** + * Notifies this <tt>Subscription</tt> that a <tt>Response</tt> to a + * previous SUBSCRIBE <tt>Request</tt> has been received with a status + * code in the success range and it may process the status code carried + * in it. + * + * @param responseEvent + * the <tt>ResponseEvent</tt> carrying the full details of + * the received <tt>Response</tt> including the status code + * which may be processed by this <tt>Subscription</tt> + * @param statusCode + * the status code carried in the <tt>Response</tt> and + * extracted from the specified <tt>ResponseEvent</tt> for + * the convenience of the implementers + * @see EventPackageSubscriber.Subscription#processSuccessResponse( + * ResponseEvent, int) + */ + protected void processSuccessResponse(ResponseEvent responseEvent, + int statusCode) + { + switch (statusCode) + { + case Response.OK: + case Response.ACCEPTED: + /* we have succeeded to subscribe to remote peer */ + remoteControlEnabled = true; + break; + } + } + + /** + * Notifies this <tt>Subscription</tt> that a terminating NOTIFY + * <tt>Request</tt> has been received and it may process the reason code + * carried in it. + * + * @param requestEvent + * the <tt>RequestEvent</tt> carrying the full details of the + * received NOTIFY <tt>Request</tt> including the reason code + * which may be processed by this <tt>Subscription</tt> + * @param reasonCode + * the code of the reason for the termination carried in the + * NOTIFY <tt>Request</tt> and extracted from the specified + * <tt>RequestEvent</tt> for the convenience of the + * implementers + * @see EventPackageSubscriber.Subscription#processTerminatedRequest( + * RequestEvent, String) + */ + protected void processTerminatedRequest(RequestEvent requestEvent, + String reasonCode) + { + if (SubscriptionStateHeader.DEACTIVATED.equals(reasonCode)) + { + try + { + subscriber.poll(this); + } + catch (OperationFailedException ofe) + { + logger.error( + "Failed to renew the remote-control subscription " + + this, ofe); + } + } + } + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/sip/ProtocolProviderServiceSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/ProtocolProviderServiceSipImpl.java index 70fdff2..05ba883 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/ProtocolProviderServiceSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/ProtocolProviderServiceSipImpl.java @@ -101,7 +101,6 @@ public class ProtocolProviderServiceSipImpl private static final String PREFERRED_SIP_PORT = "net.java.sip.communicator.service.protocol.sip.PREFERRED_SIP_PORT"; - /** * The name of the property under which the user may specify the number of * seconds that registrations take to expire. @@ -390,7 +389,6 @@ public class ProtocolProviderServiceSipImpl * @throws OperationFailedException with the corresponding code it the * registration fails for some reason (e.g. a networking error or an * implementation problem). - */ public void register(SecurityAuthority authority) throws OperationFailedException @@ -627,6 +625,17 @@ public class ProtocolProviderServiceSipImpl new OperationSetDesktopStreamingSipImpl( opSetBasicTelephonySipImpl)); + // OperationSetDesktopSharingServer + addSupportedOperationSet( + OperationSetDesktopSharingServer.class, + new OperationSetDesktopSharingServerSipImpl( + opSetBasicTelephonySipImpl)); + + // OperationSetDesktopSharingClient + addSupportedOperationSet( + OperationSetDesktopSharingClient.class, + new OperationSetDesktopSharingClientSipImpl(this)); + // init DTMF (from JM Heitz) addSupportedOperationSet( OperationSetDTMF.class, diff --git a/src/net/java/sip/communicator/impl/protocol/sip/SipActivator.java b/src/net/java/sip/communicator/impl/protocol/sip/SipActivator.java index 897c4da..0531718 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/SipActivator.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/SipActivator.java @@ -13,6 +13,7 @@ import org.osgi.framework.*; import net.java.sip.communicator.service.configuration.*; import net.java.sip.communicator.service.gui.*; import net.java.sip.communicator.service.neomedia.*; +import net.java.sip.communicator.service.hid.*; import net.java.sip.communicator.service.netaddr.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.version.*; @@ -35,6 +36,7 @@ public class SipActivator private static MediaService mediaService = null; private static VersionService versionService = null; private static UIService uiService = null; + private static HIDService hidService = null; private static ProtocolProviderFactorySipImpl sipProviderFactory = null; @@ -119,6 +121,24 @@ public class SipActivator return networkAddressManagerService; } + /** + * Returns a reference to <tt>HIDService</tt> implementation currently + * registered in the bundle context or null if no such implementation was + * found + * + * @return a currently valid implementation of the <tt>HIDService</tt> + */ + public static HIDService getHIDService() + { + if(hidService == null) + { + ServiceReference hidReference = + bundleContext.getServiceReference( + HIDService.class.getName()); + hidService = (HIDService)bundleContext.getService(hidReference); + } + return hidService; + } /** * Returns a reference to the bundle context that we were started with. diff --git a/src/net/java/sip/communicator/impl/protocol/sip/sip.provider.manifest.mf b/src/net/java/sip/communicator/impl/protocol/sip/sip.provider.manifest.mf index 7db8617..a1cc7da 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/sip.provider.manifest.mf +++ b/src/net/java/sip/communicator/impl/protocol/sip/sip.provider.manifest.mf @@ -43,6 +43,7 @@ Import-Package: org.apache.log4j, net.java.sip.communicator.service.neomedia.device, net.java.sip.communicator.service.neomedia.event, net.java.sip.communicator.service.neomedia.format, + net.java.sip.communicator.service.hid, net.java.sip.communicator.service.netaddr, net.java.sip.communicator.service.protocol, net.java.sip.communicator.service.protocol.event, |