aboutsummaryrefslogtreecommitdiffstats
path: root/src/net/java/sip/communicator/impl
diff options
context:
space:
mode:
authorSebastien Vincent <seb@jitsi.org>2010-09-28 07:46:45 +0000
committerSebastien Vincent <seb@jitsi.org>2010-09-28 07:46:45 +0000
commit6a67589c86221faedcebd370824274013be64026 (patch)
treeb5a73d1deec90532057f70b801cafaa015a907e0 /src/net/java/sip/communicator/impl
parenta4ddb0361ebc3848e30eefbf0391c755adb3387c (diff)
downloadjitsi-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')
-rw-r--r--src/net/java/sip/communicator/impl/gui/main/call/CallManager.java7
-rw-r--r--src/net/java/sip/communicator/impl/gui/main/call/OneToOneCallPeerPanel.java347
-rw-r--r--src/net/java/sip/communicator/impl/hid/HIDActivator.java72
-rw-r--r--src/net/java/sip/communicator/impl/hid/HIDServiceImpl.java222
-rw-r--r--src/net/java/sip/communicator/impl/hid/NativeKeyboard.java76
-rw-r--r--src/net/java/sip/communicator/impl/hid/hid.manifest.mf9
-rw-r--r--src/net/java/sip/communicator/impl/metahistory/MetaHistoryActivator.java15
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java35
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/CallPeerMediaHandlerJabberImpl.java52
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/JabberActivator.java28
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/OperationSetDesktopSharingClientJabberImpl.java187
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/OperationSetDesktopSharingServerJabberImpl.java473
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/OperationSetVideoTelephonyJabberImpl.java4
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java18
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/extensions/inputevt/InputEvtAction.java70
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/extensions/inputevt/InputEvtIQ.java142
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/extensions/inputevt/InputEvtIQProvider.java85
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/extensions/inputevt/RemoteControlExtension.java194
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/extensions/inputevt/RemoteControlExtensionProvider.java402
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/InputEvtPacketExtension.java37
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/JingleIQProvider.java9
-rwxr-xr-xsrc/net/java/sip/communicator/impl/protocol/jabber/jabber.provider.manifest.mf1
-rw-r--r--src/net/java/sip/communicator/impl/protocol/sip/DesktopSharingProtocolSipImpl.java434
-rw-r--r--src/net/java/sip/communicator/impl/protocol/sip/EventPackageNotifier.java4
-rw-r--r--src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicTelephonySipImpl.java29
-rw-r--r--src/net/java/sip/communicator/impl/protocol/sip/OperationSetDesktopSharingClientSipImpl.java521
-rw-r--r--src/net/java/sip/communicator/impl/protocol/sip/OperationSetDesktopSharingServerSipImpl.java621
-rw-r--r--src/net/java/sip/communicator/impl/protocol/sip/ProtocolProviderServiceSipImpl.java13
-rw-r--r--src/net/java/sip/communicator/impl/protocol/sip/SipActivator.java20
-rw-r--r--src/net/java/sip/communicator/impl/protocol/sip/sip.provider.manifest.mf1
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,