/* * 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.text.*; 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.device.*; 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 CallPeerListener which listens to modifications in the * properties/state of CallPeer. */ private final CallPeerListener callPeerListener = new CallPeerAdapter() { /** * Indicates that a change has occurred in the status of the source * CallPeer. * * @param evt the CallPeerChangeEvent 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 callPeers = new ArrayList(); /** * Video panel size. */ private Dimension size = null; /** * Initializes a new OperationSetDesktopSharingJabberImpl instance * which builds upon the telephony-related functionality of a specific * OperationSetBasicTelephonyJabberImpl. * * @param basicTelephony the OperationSetBasicTelephonyJabberImpl * 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. * @param device video device that will be used to stream desktop. * @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, MediaDevice device) throws OperationFailedException, ParseException { CallJabberImpl call = (CallJabberImpl)super.createVideoCall(uri, device); 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. * @param device video device that will be used to stream desktop. * @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, MediaDevice device) throws OperationFailedException { CallJabberImpl call = (CallJabberImpl)super.createVideoCall(callee, device); 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 registrationStateChange from * interface RegistrationStateChangeListener for setting up (or down) * our InputEvtManager when an XMPPConnection 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 InputEvtIQs. * * @param packet the packet to test. * @return true if and only if packet passes the filter. */ public boolean accept(Packet packet) { //we only handle InputEvtIQ-s if(!(packet instanceof InputEvtIQ)) return false; return true; } /** * Process an ComponentEvent received from remote peer. * * @param event ComponentEvent 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 KeyboardEvent 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 MouseEvent 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; } } } }