diff options
author | Sebastien Vincent <seb@jitsi.org> | 2010-09-28 07:46:45 +0000 |
---|---|---|
committer | Sebastien Vincent <seb@jitsi.org> | 2010-09-28 07:46:45 +0000 |
commit | 6a67589c86221faedcebd370824274013be64026 (patch) | |
tree | b5a73d1deec90532057f70b801cafaa015a907e0 /src | |
parent | a4ddb0361ebc3848e30eefbf0391c755adb3387c (diff) | |
download | jitsi-6a67589c86221faedcebd370824274013be64026.zip jitsi-6a67589c86221faedcebd370824274013be64026.tar.gz jitsi-6a67589c86221faedcebd370824274013be64026.tar.bz2 |
Desktop sharing support for SIP and XMPP. Note that GUI is not ready yet to propose this feature to the users.
Diffstat (limited to 'src')
38 files changed, 4934 insertions, 21 deletions
diff --git a/src/native/build.xml b/src/native/build.xml index fe5d08d..6ffa648 100644 --- a/src/native/build.xml +++ b/src/native/build.xml @@ -148,6 +148,63 @@ </cc> </target> + <!-- compile hid library --> + <target name="hid" description="Build hid shared library" + depends="init-native"> + <cc outtype="shared" name="gcc" outfile="${native_install_dir}/hid" objdir="${obj}"> + <!-- common compiler flags --> + <compilerarg value="-Wall" /> + <compilerarg value="-Wextra" /> + + <!-- Linux specific flags --> + <compilerarg value="-m32" if="cross_32" unless="is.running.macos" /> + <compilerarg value="-m64" if="cross_64" unless="is.running.macos" /> + <compilerarg value="-I${system.JAVA_HOME}/include" if="is.running.linux" /> + <compilerarg value="-I${system.JAVA_HOME}/include/linux" if="is.running.linux" /> + + <linkerarg value="-m32" if="cross_32" unless="is.running.macos" /> + <linkerarg value="-m64" if="cross_64" unless="is.running.macos" /> + <linkerarg value="-lX11" location="end" if="is.running.linux" /> + + <fileset dir="${src}/native/hid" includes="net*.c KeyboardUtil_unix.c" if="is.running.linux" /> + + <!-- Mac OS X specific flags --> + <compilerarg value="-arch" if="is.running.macos" /> + <compilerarg value="x86_64" if="is.running.macos" /> + <compilerarg value="-arch" if="is.running.macos" /> + <compilerarg value="i386" if="is.running.macos" /> + <compilerarg value="-arch" if="is.running.macos" /> + <compilerarg value="ppc" if="is.running.macos" /> + <compilerarg value="-I/System/Library/Frameworks/ApplicationServices.framework/Headers" if="is.running.macos" /> + <compilerarg value="-I/System/Library/Frameworks/JavaVM.framework/Headers" if="is.running.macos" /> + + <linkerarg value="-arch" if="is.running.macos" /> + <linkerarg value="x86_64" if="is.running.macos" /> + <linkerarg value="-arch" if="is.running.macos" /> + <linkerarg value="i386" if="is.running.macos" /> + <linkerarg value="-arch" if="is.running.macos" /> + <linkerarg value="ppc" if="is.running.macos" /> + <linkerarg value="-framework" if="is.running.macos" /> + <linkerarg value="ApplicationServices" if="is.running.macos" /> + <linkerarg value="-framework" if="is.running.macos" /> + <linkerarg value="Carbon" if="is.running.macos" /> + <linkerarg value="-o" location="end" if="is.running.macos" /> + <linkerarg value="libhid.jnilib" location="end" if="is.running.macos" /> + + <fileset dir="${src}/native/hid" includes="net*.c KeyboardUtil_mac.c" if="is.running.macos" /> + + <!-- Windows specific flags --> + <compilerarg value="-I${system.JAVA_HOME}/include" if="is.running.windows" /> + <compilerarg value="-I${system.JAVA_HOME}/include/win32" if="is.running.windows" /> + + <linkerarg value="-lgdi32" location="end" if="is.running.windows" /> + <linkerarg value="-ohid.dll" if="is.running.windows" /> + <linkerarg value="-Wl,--kill-at" if="is.running.windows" /> + + <fileset dir="${src}/native/hid" includes="net*.c KeyboardUtil_windows.c" if="is.running.windows" /> + </cc> + </target> + <!-- compile jawtrenderer library --> <target name="jawtrenderer" description="Build jawtrenderer shared library" depends="init-native,jawtrenderer-windows" unless="is.running.windows"> @@ -708,6 +765,7 @@ <echo message="'ant ffmpeg' to compile ffmpeg shared library" /> <echo message="'ant portaudio' to compile jportaudio shared library" /> <echo message="'ant speex' to compile jspeex shared library" /> + <echo message="'ant hid' to compile hid shared library" /> <echo message="'ant hwaddressretriever' to compile hwaddressretriever shared library" /> <echo message="'ant video4linux2 (Linux only)' to compile jvideo4linux2 shared library" /> <echo message="'ant galagonotification (Linux only)' to compile galagonotification shared library" /> diff --git a/src/native/hid/KeyboardUtil.h b/src/native/hid/KeyboardUtil.h new file mode 100644 index 0000000..6282540 --- /dev/null +++ b/src/native/hid/KeyboardUtil.h @@ -0,0 +1,34 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ + +/** + * \file KeyboardUtil.h + * \brief Prototypes of the function to press/release keys. + * \author Sebastien Vincent + */ + +#ifndef KEYBOARD_UTIL_H +#define KEYBOARD_UTIL_H + +#include <jni.h> + +/** + * \brief Press or release a key. + * \param key ascii code of the key + * \param pressed if the key have to be pressed or released + */ +void generateKey(jchar key, jboolean pressed); + +/** + * \brief Press or release a symbol. + * \param symbol symbol name + * \param pressed if the key have to be pressed or released + */ +void generateSymbol(const char* symbol, jboolean pressed); + +#endif /* KEYBOARD_UTIL_H */ + diff --git a/src/native/hid/KeyboardUtil_mac.c b/src/native/hid/KeyboardUtil_mac.c new file mode 100644 index 0000000..b514be3 --- /dev/null +++ b/src/native/hid/KeyboardUtil_mac.c @@ -0,0 +1,228 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ + +/** + * \file KeyboardUtil_unix.c + * \brief Mac OS X specific code to press/release keys. + * \author Sebastien Vincent + */ + +#include <ApplicationServices/ApplicationServices.h> +#include <Carbon/Carbon.h> + +#include "KeyboardUtil.h" + +/** + * \brief Get the keycode from a specific keyboard layout. + * \param c ascii character + * \param uchrHeader keyboard layout + * \param pshift if the character need shift modifier, value will have 1 otherwise 0 + * \param palt if the character need alt modifier, value will have 1 otherwise 0 + */ +void checkModifiers(const char c, const UCKeyboardLayout* uchrHeader, CGKeyCode keycode, int* pshift, int* palt) +{ + int alt = (optionKey >> 8) & 0xff; + int shift = (shiftKey >> 8) & 0xff; + int altShift = ((optionKey | shiftKey) >> 8) & 0xff; + UInt32 deadKeyState = 0; + UniCharCount count; + char character; + + if(UCKeyTranslate(uchrHeader, keycode, kUCKeyActionDown, alt, LMGetKbdType(), 0, + &deadKeyState, 1, &count, &character) == 0 && character == c) + { + *palt = 1; + } + else if(UCKeyTranslate(uchrHeader, keycode, kUCKeyActionDown, shift, LMGetKbdType(), 0, + &deadKeyState, 1, &count, &character) == 0 && character == c) + { + *pshift = 1; + } + else if(UCKeyTranslate(uchrHeader, keycode, kUCKeyActionDown, altShift, LMGetKbdType(), 0, + &deadKeyState, 1, &count, &character) == 0 && character == c) + { + *pshift = 1; + *palt = 1; + } +} + +/* following two functions has been taken from + * http://stackoverflow.com/questions/1918841/how-to-convert-ascii-character-to-cgkeycode + */ + +/** + * \brief Get the keycode from a specific keyboard layout. + * \param c ascii character + * \param uchrHeader keyboard layout + * \param shift if the character needs shift modifier, value will have 1 otherwise 0 + * \param alt if the character needs alt modifier, value will have 1 otherwise 0 + * \return keycode or UINT16_MAX if not found + * \note Function taken from http://stackoverflow.com/questions/1918841/how-to-convert-ascii-character-to-cgkeycode + */ +CGKeyCode keyCodeForCharWithLayout(const char c, + const UCKeyboardLayout *uchrHeader, int* shift, int* alt) +{ + uint8_t *uchrData = (uint8_t *)uchrHeader; + UCKeyboardTypeHeader *uchrKeyboardList = (UCKeyboardTypeHeader*)uchrHeader->keyboardTypeList; + + /* Loop through the keyboard type list. */ + ItemCount i, j; + for (i = 0; i < uchrHeader->keyboardTypeCount; ++i) + { + /* Get a pointer to the keyToCharTable structure. */ + UCKeyToCharTableIndex *uchrKeyIX = (UCKeyToCharTableIndex *) + (uchrData + (uchrKeyboardList[i].keyToCharTableIndexOffset)); + + /* Not sure what this is for but it appears to be a safeguard... */ + UCKeyStateRecordsIndex *stateRecordsIndex; + if(uchrKeyboardList[i].keyStateRecordsIndexOffset != 0) + { + stateRecordsIndex = (UCKeyStateRecordsIndex *) + (uchrData + (uchrKeyboardList[i].keyStateRecordsIndexOffset)); + + if((stateRecordsIndex->keyStateRecordsIndexFormat) != + kUCKeyStateRecordsIndexFormat) + { + stateRecordsIndex = NULL; + } + } + else + { + stateRecordsIndex = NULL; + } + + /* Make sure structure is a table that can be searched. */ + if((uchrKeyIX->keyToCharTableIndexFormat) != kUCKeyToCharTableIndexFormat) + { + continue; + } + + /* Check the table of each keyboard for character */ + for (j = 0; j < uchrKeyIX->keyToCharTableCount; ++j) + { + UCKeyOutput *keyToCharData = + (UCKeyOutput *)(uchrData + (uchrKeyIX->keyToCharTableOffsets[j])); + + /* Check THIS table of the keyboard for the character. */ + UInt16 k; + for (k = 0; k < uchrKeyIX->keyToCharTableSize; ++k) + { + /* Here's the strange safeguard again... */ + if((keyToCharData[k] & kUCKeyOutputTestForIndexMask) == + kUCKeyOutputStateIndexMask) + { + long keyIndex = (keyToCharData[k] & kUCKeyOutputGetIndexMask); + if(stateRecordsIndex != NULL && + keyIndex <= (stateRecordsIndex->keyStateRecordCount)) + { + UCKeyStateRecord *stateRecord = (UCKeyStateRecord *) + (uchrData + + (stateRecordsIndex->keyStateRecordOffsets[keyIndex])); + + if((stateRecord->stateZeroCharData) == c) + { + checkModifiers(c, uchrHeader, k, shift, alt); + return (CGKeyCode)k; + } + } + else if(keyToCharData[k] == c) + { + checkModifiers(c, uchrHeader, k, shift, alt); + return (CGKeyCode)k; + } + } + else if(((keyToCharData[k] & kUCKeyOutputTestForIndexMask) + != kUCKeyOutputSequenceIndexMask) && + keyToCharData[k] != 0xFFFE && + keyToCharData[k] != 0xFFFF && + keyToCharData[k] == c) + { + /* try to see if character is obtained with modifiers such as + * Option (alt) or shift + */ + checkModifiers(c, uchrHeader, k, shift, alt); + return (CGKeyCode)k; + } + } + } + } + + return UINT16_MAX; +} + +/** + * \brief Get the keycode from a specific keyboard layout. + * \param c ascii character + * \param shift if the character need shift modifier, value will have 1 otherwise 0 + * \param alt if the character need shift modifier, value will have 1 otherwise 0 + * \return keycode or UINT16_MAX if not found + * \note Function taken from http://stackoverflow.com/questions/1918841/how-to-convert-ascii-character-to-cgkeycode + */ +CGKeyCode keyCodeForChar(const char c, int* shift, int* alt) +{ + CFDataRef currentLayoutData; + TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); + + if(currentKeyboard == NULL) + { + printf("Could not find keyboard layout\n"); + return UINT16_MAX; + } + + currentLayoutData = TISGetInputSourceProperty(currentKeyboard, + kTISPropertyUnicodeKeyLayoutData); + CFRelease(currentKeyboard); + if(currentLayoutData == NULL) + { + printf("Could not find layout data\n"); + return UINT16_MAX; + } + + return keyCodeForCharWithLayout(c, + (const UCKeyboardLayout *)CFDataGetBytePtr(currentLayoutData), shift, alt); +} + +void generateSymbol(const char* symbol, jboolean pressed) +{ + /* avoid warnings */ + symbol = symbol; + pressed = pressed; +} + +void generateKey(jchar key, jboolean pressed) +{ + int keycode = -1; + int shift = 0; + int alt = 0; + CGEventRef e = NULL; + int flags = 0; + + keycode = keyCodeForChar((char)key, &shift, &alt); + + if(keycode == UINT16_MAX) + { + return; + } + + e = CGEventCreateKeyboardEvent(NULL, keycode, pressed ? 1 : 0); + + if(pressed && shift) + { + flags |= kCGEventFlagMaskShift; + } + + if(pressed && alt) + { + flags |= kCGEventFlagMaskAlternate; + } + + CGEventSetFlags(e, flags); + + CGEventPost(kCGSessionEventTap, e); + CFRelease(e); +} + diff --git a/src/native/hid/KeyboardUtil_unix.c b/src/native/hid/KeyboardUtil_unix.c new file mode 100644 index 0000000..8d8673b --- /dev/null +++ b/src/native/hid/KeyboardUtil_unix.c @@ -0,0 +1,203 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ + +/** + * \file KeyboardUtil_unix.c + * \brief Unix specific code to press/release keys. + * \author Sebastien Vincent + */ + +#include "KeyboardUtil.h" + +#include <stdio.h> +#include <string.h> + +#include <X11/X.h> +#include <X11/Xlib.h> +#include <X11/extensions/XTest.h> + +/** + * \struct keysymcharmap + * \brief Map structure between the string representation of the keysym and + * the ascii character. + */ +typedef struct keysymcharmap +{ + char *keysym; /**< String representation of the keysym */ + char key; /**< Ascii character */ +} keysymcharmap_t; + +/** + * \var g_symbolmap + * \brief Map between symbol name and X11 string representation of the keysym. + */ +static char *g_symbolmap[] = +{ + "alt", "Alt_L", + "altgr", "ISO_Level3_Shift", + "ctrl", "Control_L", + "control", "Control_L", + "meta", "Meta_L", + "super", "Super_L", + "shift", "Shift_L", + NULL, NULL, +}; + +/** + * \var g_specialcharmap + * \brief Map of the special characters. + */ +static keysymcharmap_t g_specialcharmap[] = +{ + {"Return", '\n'}, + {"ampersand", '&'}, + {"apostrophe", '\''}, + {"asciicircum", '^'}, + {"asciitilde", '~'}, + {"asterisk", '*'}, + {"at", '@'}, + {"backslash", '\\'}, + {"bar", '|'}, + {"braceleft", '{'}, + {"braceright", '}'}, + {"bracketleft", '['}, + {"bracketright", ']'}, + {"colon", ':'}, + {"comma", ','}, + {"dollar", '$'}, + {"equal", '='}, + {"exclam", '!'}, + {"grave", '`'}, + {"greater", '>'}, + {"less", '<'}, + {"minus", '-'}, + {"numbersign", '#'}, + {"parenleft", '('}, + {"parenright", ')'}, + {"percent", '%'}, + {"period", '.'}, + {"plus", '+'}, + {"question", '?'}, + {"quotedbl", '"'}, + {"semicolon", ';'}, + {"slash", '/'}, + {"space", ' '}, + {"tab", '\t'}, + {"underscore", '_'}, + {NULL, 0}, +}; + +/** + * \brief Find X11 string representation of a symbol. + * \param k human string representation of the symbol + * \return X11 string representation of the symbol + */ +static char* find_symbol(const char* k) +{ + size_t i = 0; + + while(g_symbolmap[i]) + { + if(!strcmp(g_symbolmap[i], k)) + { + return g_symbolmap[i + 1]; + } + + i += 2; + } + + return NULL; +} + +/** + * \brief Find X11 string representation of a special character. + * \param k ascii representation of the special character + * \return X11 string representation of the special character + */ +static char* find_keysym(char k) +{ + size_t i = 0; + + while(g_specialcharmap[i].key) + { + keysymcharmap_t ks = g_specialcharmap[i]; + + if(ks.key == k) + { + return ks.keysym; + } + + i++; + } + + return NULL; +} + +void generateSymbol(const char* symbol, jboolean pressed) +{ + Display *dpy = NULL; + char* s = NULL; + + dpy = XOpenDisplay(NULL); + + if(!dpy) + { + return; + } + + s = find_symbol(symbol); + + if(!s) + { + /* printf("no symbol %s\n", s); */ + XCloseDisplay(dpy); + return; + } + + XTestFakeKeyEvent(dpy, XKeysymToKeycode(dpy, XStringToKeysym(s)), pressed ? True : False, 1); + + XFlush(dpy); + XCloseDisplay(dpy); +} + +void generateKey(jchar key, jboolean pressed) +{ + Display *dpy = NULL; + + dpy = XOpenDisplay(NULL); + + if(!dpy) + { + return; + } + + KeySym sym = XStringToKeysym((const char*)&key); + KeyCode code; + + if(sym == NoSymbol) + { + char* special = find_keysym(key); + + if(special) + { + sym = XStringToKeysym(special); + } + else + { + XCloseDisplay(dpy); + return; + } + } + + code = XKeysymToKeycode(dpy, sym); + + XTestFakeKeyEvent(dpy, code, pressed ? True : False, 1); + + XFlush(dpy); + XCloseDisplay(dpy); +} + diff --git a/src/native/hid/KeyboardUtil_windows.c b/src/native/hid/KeyboardUtil_windows.c new file mode 100644 index 0000000..ce5993a --- /dev/null +++ b/src/native/hid/KeyboardUtil_windows.c @@ -0,0 +1,132 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ + +/** + * \file KeyboardUtil_unix.c + * \brief Windows specific code to press/release keys. + * \author Sebastien Vincent + */ + +#include <windows.h> + +#include "KeyboardUtil.h" + +void generateSymbol(const char* symbol, jboolean pressed) +{ + /* on Windows AltGr correspond to CTRL-ALT */ + if(!strcmp(symbol, "altgr")) + { + int scancode = MapVirtualKey(VK_CONTROL, 0); + keybd_event(VK_CONTROL, scancode, pressed ? 0 : KEYEVENTF_KEYUP, 0); + scancode = MapVirtualKey(VK_MENU, 0); + keybd_event(VK_MENU, scancode, pressed ? 0 : KEYEVENTF_KEYUP, 0); + } + else if(!strcmp(symbol, "shift")) + { + int scancode = MapVirtualKey(VK_SHIFT, 0); + keybd_event(VK_SHIFT, scancode, pressed ? 0 : KEYEVENTF_KEYUP, 0); + } + else if(!strcmp(symbol, "ctrl")) + { + int scancode = MapVirtualKey(VK_CONTROL, 0); + keybd_event(VK_CONTROL, scancode, pressed ? 0 : KEYEVENTF_KEYUP, 0); + } + else if(!strcmp(symbol, "alt")) + { + int scancode = MapVirtualKey(VK_MENU, 0); + keybd_event(VK_MENU, scancode, pressed ? 0 : KEYEVENTF_KEYUP, 0); + } + else if(!strcmp(symbol, "hankaku")) + { + /* XXX constant name for HANKAKU ? */ + /* + int scancode = MapVirtualKey(VK_HANKAKU, 0); + keybd_event(VK_HANKAKU, scancode, pressed ? 0 : KEYEVENTF_KEYUP, 0); + */ + } +} + +void generateKey(jchar key, jboolean pressed) +{ + SHORT letter = 0; + TCHAR ch = key; + UINT scancode = 0; + SHORT modifiers = 0; + + letter = VkKeyScan(ch); + + if(letter == -1) + { + /* printf("No key found\n"); */ + return; + } + + modifiers = HIBYTE(letter); + letter = LOBYTE(letter); + + if(pressed) + { + /* shift */ + if(modifiers & 1) + { + generateSymbol("shift", JNI_TRUE); + } + + /* ctrl */ + if(modifiers & 2) + { + generateSymbol("ctrl", JNI_TRUE); + } + + /* alt */ + if(modifiers & 4) + { + generateSymbol("alt", JNI_TRUE); + } + + /* hankaku */ + if(modifiers & 8) + { + generateSymbol("hankaku", JNI_TRUE); + } + } + + /* find scancode */ + scancode = MapVirtualKey(letter, 0); + + /* press and release key as well as modifiers */ + keybd_event(letter, scancode, pressed ? 0 : KEYEVENTF_KEYUP, 0); + + if(!pressed) + { + /* shift */ + if(modifiers & 1) + { + generateSymbol("shift", JNI_FALSE); + } + + /* ctrl */ + if(modifiers & 2) + { + generateSymbol("ctrl", JNI_FALSE); + } + + /* alt */ + if(modifiers & 4) + { + generateSymbol("alt", JNI_FALSE); + } + + /* hankaku */ + if(modifiers & 8) + { + generateSymbol("hankaku", JNI_FALSE); + } + } +} + + diff --git a/src/native/hid/net_java_sip_communicator_impl_hid_NativeKeyboard.c b/src/native/hid/net_java_sip_communicator_impl_hid_NativeKeyboard.c new file mode 100644 index 0000000..1233e32 --- /dev/null +++ b/src/native/hid/net_java_sip_communicator_impl_hid_NativeKeyboard.c @@ -0,0 +1,59 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ + +/** + * \file net_java_sip_communicator_util_KeyboardUtil.c + * \brief Native method to get keycode from ascii. + * \author Sebastien Vincent + */ + +#include "net_java_sip_communicator_impl_hid_NativeKeyboard.h" + +#include "KeyboardUtil.h" + +/** + * \brief Press or release a key. + * \param env JNI environment + * \param clazz class + * \param key string representation of the key + * \param pressed true if the key has to be pressed, false if the key has to be released + */ +JNIEXPORT void JNICALL Java_net_java_sip_communicator_impl_hid_NativeKeyboard_doKeyAction + (JNIEnv *env, jclass clazz, jchar key, jboolean pressed) +{ + /* avoid warnings */ + env = env; + clazz = clazz; + + generateKey(key, pressed); +} + +/** + * \brief Press or release a symbol (i.e. CTRL, ALT, ...). + * \param env JNI environment + * \param clazz class + * \param symbol symbol string representation + * \param pressed true if the key has to be pressed, false if the key has to be released + */ +JNIEXPORT void JNICALL Java_net_java_sip_communicator_impl_hid_NativeKeyboard_doSymbolAction + (JNIEnv *env, jclass clazz, jstring symbol, jboolean pressed) +{ + const char* s; + + /* avoid warnings */ + env = env; + clazz = clazz; + + s = (*env)->GetStringUTFChars(env, symbol, 0); + + if(s) + { + generateSymbol(s, pressed); + (*env)->ReleaseStringUTFChars(env, symbol, s); + } +} + diff --git a/src/native/hid/net_java_sip_communicator_impl_hid_NativeKeyboard.h b/src/native/hid/net_java_sip_communicator_impl_hid_NativeKeyboard.h new file mode 100644 index 0000000..a59b9f9 --- /dev/null +++ b/src/native/hid/net_java_sip_communicator_impl_hid_NativeKeyboard.h @@ -0,0 +1,29 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include <jni.h> +/* Header for class net_java_sip_communicator_impl_hid_NativeKeyboard */ + +#ifndef _Included_net_java_sip_communicator_impl_hid_NativeKeyboard +#define _Included_net_java_sip_communicator_impl_hid_NativeKeyboard +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: net_java_sip_communicator_impl_hid_NativeKeyboard + * Method: doKeyAction + * Signature: (CZ)V + */ +JNIEXPORT void JNICALL Java_net_java_sip_communicator_impl_hid_NativeKeyboard_doKeyAction + (JNIEnv *, jclass, jchar, jboolean); + +/* + * Class: net_java_sip_communicator_impl_hid_NativeKeyboard + * Method: doSymbolAction + * Signature: (Ljava/lang/String;Z)V + */ +JNIEXPORT void JNICALL Java_net_java_sip_communicator_impl_hid_NativeKeyboard_doSymbolAction + (JNIEnv *, jclass, jstring, jboolean); + +#ifdef __cplusplus +} +#endif +#endif 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, diff --git a/src/net/java/sip/communicator/service/hid/HIDService.java b/src/net/java/sip/communicator/service/hid/HIDService.java new file mode 100644 index 0000000..bf60c26 --- /dev/null +++ b/src/net/java/sip/communicator/service/hid/HIDService.java @@ -0,0 +1,84 @@ +/* + * 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.service.hid; + +/** + * Human Interface Device service. + * This service is used to regenerates key and mouse events on the local + * computer. It is typically used in case of remote control features. + * + * @author Sebastien Vincent + */ +public interface HIDService +{ + /** + * 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 + * @see java.awt.Robot#keyRelease(int keycode) + */ + public void keyPress(int 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 + * @see java.awt.Robot#keyRelease(int keycode) + */ + public void keyRelease(int keycode); + + /** + * Press a specific key using its char representation. + * + * @param key char representation of the key + */ + public void keyPress(char key); + + /** + * Release a specific key using its char representation. + * + * @param key char representation of the key + */ + public void keyRelease(char key); + + /** + * Press a mouse button(s). + * + * @param btns button masks + * @see java.awt.Robot#mousePress(int btns) + */ + public void mousePress(int btns); + + /** + * Release a mouse button(s). + * + * @param btns button masks + * @see java.awt.Robot#mouseRelease(int btns) + */ + public void mouseRelease(int 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); + + /** + * 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); +} |