From 6a67589c86221faedcebd370824274013be64026 Mon Sep 17 00:00:00 2001 From: Sebastien Vincent Date: Tue, 28 Sep 2010 07:46:45 +0000 Subject: Desktop sharing support for SIP and XMPP. Note that GUI is not ready yet to propose this feature to the users. --- build.xml | 14 +- lib/felix.client.run.properties | 1 + lib/native/freebsd-64/libhid.so | Bin 0 -> 11625 bytes lib/native/freebsd/libhid.so | Bin 0 -> 8153 bytes lib/native/linux-64/libhid.so | Bin 0 -> 12821 bytes lib/native/linux/libhid.so | Bin 0 -> 9701 bytes lib/native/mac/libhid.jnilib | Bin 0 -> 46272 bytes lib/native/windows-64/hid.dll | Bin 0 -> 39341 bytes lib/native/windows/hid.dll | Bin 0 -> 36015 bytes src/native/build.xml | 58 ++ src/native/hid/KeyboardUtil.h | 34 ++ src/native/hid/KeyboardUtil_mac.c | 228 ++++++++ src/native/hid/KeyboardUtil_unix.c | 203 +++++++ src/native/hid/KeyboardUtil_windows.c | 132 +++++ ...java_sip_communicator_impl_hid_NativeKeyboard.c | 59 ++ ...java_sip_communicator_impl_hid_NativeKeyboard.h | 29 + .../impl/gui/main/call/CallManager.java | 7 +- .../impl/gui/main/call/OneToOneCallPeerPanel.java | 347 +++++++++++- .../sip/communicator/impl/hid/HIDActivator.java | 72 +++ .../sip/communicator/impl/hid/HIDServiceImpl.java | 222 ++++++++ .../sip/communicator/impl/hid/NativeKeyboard.java | 76 +++ .../java/sip/communicator/impl/hid/hid.manifest.mf | 9 + .../impl/metahistory/MetaHistoryActivator.java | 15 +- .../impl/protocol/jabber/CallJabberImpl.java | 35 +- .../jabber/CallPeerMediaHandlerJabberImpl.java | 52 ++ .../impl/protocol/jabber/JabberActivator.java | 28 +- ...OperationSetDesktopSharingClientJabberImpl.java | 187 +++++++ ...OperationSetDesktopSharingServerJabberImpl.java | 473 ++++++++++++++++ .../OperationSetVideoTelephonyJabberImpl.java | 4 +- .../jabber/ProtocolProviderServiceJabberImpl.java | 18 + .../jabber/extensions/inputevt/InputEvtAction.java | 70 +++ .../jabber/extensions/inputevt/InputEvtIQ.java | 142 +++++ .../extensions/inputevt/InputEvtIQProvider.java | 85 +++ .../inputevt/RemoteControlExtension.java | 194 +++++++ .../inputevt/RemoteControlExtensionProvider.java | 402 +++++++++++++ .../extensions/jingle/InputEvtPacketExtension.java | 37 ++ .../jabber/extensions/jingle/JingleIQProvider.java | 9 +- .../protocol/jabber/jabber.provider.manifest.mf | 1 + .../sip/DesktopSharingProtocolSipImpl.java | 434 ++++++++++++++ .../impl/protocol/sip/EventPackageNotifier.java | 4 +- .../sip/OperationSetBasicTelephonySipImpl.java | 29 + .../OperationSetDesktopSharingClientSipImpl.java | 521 +++++++++++++++++ .../OperationSetDesktopSharingServerSipImpl.java | 621 +++++++++++++++++++++ .../sip/ProtocolProviderServiceSipImpl.java | 13 +- .../impl/protocol/sip/SipActivator.java | 20 + .../impl/protocol/sip/sip.provider.manifest.mf | 1 + .../sip/communicator/service/hid/HIDService.java | 84 +++ 47 files changed, 4948 insertions(+), 22 deletions(-) create mode 100755 lib/native/freebsd-64/libhid.so create mode 100755 lib/native/freebsd/libhid.so create mode 100755 lib/native/linux-64/libhid.so create mode 100755 lib/native/linux/libhid.so create mode 100755 lib/native/mac/libhid.jnilib create mode 100755 lib/native/windows-64/hid.dll create mode 100644 lib/native/windows/hid.dll create mode 100644 src/native/hid/KeyboardUtil.h create mode 100644 src/native/hid/KeyboardUtil_mac.c create mode 100644 src/native/hid/KeyboardUtil_unix.c create mode 100644 src/native/hid/KeyboardUtil_windows.c create mode 100644 src/native/hid/net_java_sip_communicator_impl_hid_NativeKeyboard.c create mode 100644 src/native/hid/net_java_sip_communicator_impl_hid_NativeKeyboard.h create mode 100644 src/net/java/sip/communicator/impl/hid/HIDActivator.java create mode 100644 src/net/java/sip/communicator/impl/hid/HIDServiceImpl.java create mode 100644 src/net/java/sip/communicator/impl/hid/NativeKeyboard.java create mode 100644 src/net/java/sip/communicator/impl/hid/hid.manifest.mf create mode 100644 src/net/java/sip/communicator/impl/protocol/jabber/OperationSetDesktopSharingClientJabberImpl.java create mode 100644 src/net/java/sip/communicator/impl/protocol/jabber/OperationSetDesktopSharingServerJabberImpl.java create mode 100644 src/net/java/sip/communicator/impl/protocol/jabber/extensions/inputevt/InputEvtAction.java create mode 100644 src/net/java/sip/communicator/impl/protocol/jabber/extensions/inputevt/InputEvtIQ.java create mode 100644 src/net/java/sip/communicator/impl/protocol/jabber/extensions/inputevt/InputEvtIQProvider.java create mode 100644 src/net/java/sip/communicator/impl/protocol/jabber/extensions/inputevt/RemoteControlExtension.java create mode 100644 src/net/java/sip/communicator/impl/protocol/jabber/extensions/inputevt/RemoteControlExtensionProvider.java create mode 100644 src/net/java/sip/communicator/impl/protocol/jabber/extensions/jingle/InputEvtPacketExtension.java create mode 100644 src/net/java/sip/communicator/impl/protocol/sip/DesktopSharingProtocolSipImpl.java create mode 100644 src/net/java/sip/communicator/impl/protocol/sip/OperationSetDesktopSharingClientSipImpl.java create mode 100644 src/net/java/sip/communicator/impl/protocol/sip/OperationSetDesktopSharingServerSipImpl.java create mode 100644 src/net/java/sip/communicator/service/hid/HIDService.java diff --git a/build.xml b/build.xml index 8e4570f..1562f02 100644 --- a/build.xml +++ b/build.xml @@ -892,7 +892,7 @@ bundle-callhistory, bundle-callhistory-slick, bundle-popupmessagehandler-slick, bundle-netaddr,bundle-netaddr-slick,bundle-slickless, bundle-slick-runner,bundle-sip,bundle-sip-slick,bundle-fileaccess, - bundle-fileaccess-slick,bundle-neomedia, + bundle-fileaccess-slick,bundle-neomedia,bundle-hid, bundle-resource-manager,bundle-resources-defaultpack, bundle-protocol,bundle-protocol-media,bundle-icq, bundle-icq-slick,bundle-mock,bundle-smacklib, @@ -1182,6 +1182,18 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -708,6 +765,7 @@ + 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 + +/** + * \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 +#include + +#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 +#include + +#include +#include +#include + +/** + * \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 + +#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 +/* 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 Logger used by the OneToOneCallPeerPanel class and * its instances for logging output. */ private static final Logger logger = Logger.getLogger(OneToOneCallPeerPanel.class); + /** + * The CallPeerAdapter that implements all common tt>CallPeer + * 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 CallPeer, 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 CallPeerPanel 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 OperationSetDesktopSharingClient + */ + 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 * CallPeer 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 CallPeerEvent 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 CallPeerEvent 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 CallChangeEvent 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 TransparentPanel 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 PeerStatusPanel. + * + * @param layout the LayoutManager 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 KeyListener, MouseListener, + * MouseWheelListener and MouseMotionListener to remote + * video component. + */ + private void addMouseAndKeyListeners() + { + if(remoteVideo != null) + { + remoteVideo.addKeyListener(mouseAndKeyListener); + remoteVideo.addMouseListener(mouseAndKeyListener); + remoteVideo.addMouseMotionListener(mouseAndKeyListener); + remoteVideo.addMouseWheelListener(mouseAndKeyListener); + } + } + + /** + * Remove KeyListener, MouseListener, + * MouseWheelListener and MouseMotionListener 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 OperationSetDesktopSharingClient. + * + * @author Sebastien Vincent + */ + private class MouseAndKeyListener + implements RemoteControlListener, + KeyListener, + MouseListener, + MouseMotionListener, + MouseWheelListener + { + /** + * Desktop sharing clien-side OperationSet. + */ + private final OperationSetDesktopSharingClient desktopSharingClient; + + /** + * Last time the mouse has moved inside remote video. It is used mainly + * to avoid sending too much MouseEvent which can take a lot of + * bandwidth. + */ + private long lastMouseMovedTime = 0; + + /** + * Constructor. + * + * @param opSet OperationSetDesktopSharingClient 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 RemoteControlGrantedEvent + */ + public void remoteControlGranted(RemoteControlGrantedEvent event) + { + allowRemoteControl = true; + } + + /** + * This method is called when remote control has been revoked. + * + * @param event RemoteControlRevokedEvent + */ + 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 Logger used by the HIDActivator class and its + * instances for logging output. + */ + private final Logger logger = Logger.getLogger(HIDActivator.class); + + /** + * The OSGi ServiceRegistration of HIDServiceImpl. + */ + private ServiceRegistration serviceRegistration; + + /** + * Starts the execution of the hid bundle in the specified context. + * + * @param bundleContext the context in which the hid bundle is to + * start executing + * @throws Exception if an error occurs while starting the execution of the + * hid 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 hid bundle in the specified context. + * + * @param bundleContext the context in which the hid bundle is to + * stop executing + * @throws Exception if an error occurs while stopping the execution of the + * hid 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 Logger used by the NeomediaActivator 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 Logger instance used by the + * MetaHistoryActivator class and its instances for logging output. + */ private static Logger logger = Logger.getLogger(MetaHistoryActivator.class); + /** + * The MetaHistoryService 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 BundleContext + * @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 CallPeer will support inputevt + * extension (i.e. will be able to be remote-controlled). + */ + private boolean localInputEvtAware = false; + + /** * Crates a CallJabberImpl instance belonging to sourceProvider and * associated with the jingle session with the specified jingleSID. * If this call corresponds to an incoming jingle session then the jingleSID @@ -57,6 +63,26 @@ public class CallJabberImpl extends MediaAwareCall< } /** + * Enable or disable inputevt support (remote control). + * + * @param enable new state of inputevt support + */ + public void setLocalInputEvtAware(boolean enable) + { + localInputEvtAware = enable; + } + + /** + * Returns if the call support inputevt (remote control). + * + * @return true if the call support inputevt, 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 CallPeer will support inputevt + * 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 * peer. * @@ -97,6 +103,16 @@ public class CallPeerMediaHandlerJabberImpl } /** + * Enable or disable inputevt 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 UriHandler 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 HIDService 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 HIDService implementation currently + * registered in the bundle context or null if no such implementation was + * found + * + * @return a currently valid implementation of the HIDService + */ + 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 ProtocolProviderService 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 listeners = + new ArrayList(); + + /** + * Initializes a new OperationSetDesktopSharingClientJabberImpl. + * + * @param parentProvider the Jabber ProtocolProviderService + * 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 RemoteControlGrantedEvent to all registered listeners. + */ + public void fireRemoteControlGranted() + { + RemoteControlGrantedEvent event = new RemoteControlGrantedEvent(this); + + for (RemoteControlListener l : listeners) + { + l.remoteControlGranted(event); + } + } + + /** + * Fire a RemoteControlGrantedEvent to all registered listeners. + */ + public void fireRemoteControlRevoked() + { + RemoteControlRevokedEvent event = new RemoteControlRevokedEvent(this); + + for (RemoteControlListener l : listeners) + { + l.remoteControlRevoked(event); + } + } + + /** + * Add a RemoteControlListener to be notified when remote peer + * accept to give us full control. + * + * @param listener RemoteControlListener to add + */ + public void addRemoteControlListener(RemoteControlListener listener) + { + if (logger.isInfoEnabled()) + logger.info("Enable remote control"); + + if (!listeners.contains(listener)) + { + listeners.add(listener); + } + } + + /** + * Remove a RemoteControlListener to be notified when remote peer + * accept/revoke to give us full control. + * + * @param listener RemoteControlListener 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 CallPeer that will be notified + * @param event KeyEvent 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 CallPeer that will be notified + * @param event MouseEvent 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" MouseEvent. 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 CallPeer that will be notified + * @param event MouseEvent 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 CallPeerListener which listens to modifications in the + * properties/state of CallPeer. + */ + private final CallPeerListener callPeerListener = new CallPeerAdapter() + { + /** + * Indicates that a change has occurred in the status of the source + * CallPeer. + * + * @param evt the CallPeerChangeEvent instance containing the + * source event as well as its previous and its new status + */ + @Override + public void peerStateChanged(CallPeerChangeEvent evt) + { + CallPeer peer = evt.getSourceCallPeer(); + CallPeerState state = peer.getState(); + + if (remoteControlEnabled && state != null && + (state.equals(CallPeerState.DISCONNECTED) || + state.equals(CallPeerState.FAILED))) + { + disableRemoteControl(evt.getSourceCallPeer()); + } + else if(state != null && state.equals(CallPeerState.CONNECTED)) + { + /* connected peer */ + enableRemoteControl(evt.getSourceCallPeer()); + } + } + }; + + /** + * If the remote control is authorized and thus enabled. + */ + private boolean remoteControlEnabled = false; + + /** + * HID service that will regenerates keyboard and mouse events received in + * Jabber messages. + */ + private HIDService hidService = null; + + /** + * List of callPeers for the desktop sharing session. + */ + private List callPeers = new ArrayList(); + + /** + * Video panel size. + */ + private Dimension size = null; + + /** + * Initializes a new OperationSetDesktopSharingJabberImpl instance + * which builds upon the telephony-related functionality of a specific + * OperationSetBasicTelephonyJabberImpl. + * + * @param basicTelephony the OperationSetBasicTelephonyJabberImpl + * the new extension should build upon + */ + public OperationSetDesktopSharingServerJabberImpl( + OperationSetBasicTelephonyJabberImpl basicTelephony) + { + super(basicTelephony); + + parentProvider.addRegistrationStateChangeListener(this); + hidService = JabberActivator.getHIDService(); + } + + /** + * Create a new video call and invite the specified CallPeer to it. + * + * @param uri the address of the callee that we should invite to a new + * call. + * @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 registrationStateChange from + * interface RegistrationStateChangeListener for setting up (or down) + * our InputEvtManager when an XMPPConnection is available + * + * @param evt the event received + */ + public void registrationStateChanged(RegistrationStateChangeEvent evt) + { + if ((evt.getNewState() == RegistrationState.REGISTERING)) + { + /* listen to specific inputevt IQ */ + parentProvider.getConnection().addPacketListener(this, this); + } + } + + /** + * Handles incoming inputevt packets and passes them to the corresponding + * method based on their action. + * + * @param packet the packet to process. + */ + public void processPacket(Packet packet) + { + //this is not supposed to happen because of the filter ... but still + if (!(packet instanceof InputEvtIQ)) + return; + + InputEvtIQ inputIQ = (InputEvtIQ)packet; + + if(inputIQ.getAction() != InputEvtAction.NOTIFY) + { + return; + } + + /* do not waste time to parse packet if remote control is not enabled */ + if(!remoteControlEnabled) + { + return; + } + + //first ack all "set" requests. + if(inputIQ.getType() == IQ.Type.SET) + { + IQ ack = IQ.createResultIQ(inputIQ); + parentProvider.getConnection().sendPacket(ack); + } + + if(!callPeers.contains(inputIQ.getFrom())) + { + return; + } + + for(RemoteControlExtension p : inputIQ.getRemoteControls()) + { + ComponentEvent evt = p.getEvent(); + processComponentEvent(evt); + } + } + + /** + * Tests whether or not the specified packet should be handled by this + * operation set. This method is called by smack prior to packet delivery + * and it would only accept InputEvtIQs. + * + * @param packet the packet to test. + * @return true if and only if packet passes the filter. + */ + public boolean accept(Packet packet) + { + //we only handle InputEvtIQ-s + if(!(packet instanceof InputEvtIQ)) + return false; + + return true; + } + + /** + * Process an ComponentEvent received from remote peer. + * + * @param event ComponentEvent that will be regenerated on local + * computer + */ + public void processComponentEvent(ComponentEvent event) + { + if(event == null) + { + return; + } + + if(event instanceof KeyEvent) + { + processKeyboardEvent((KeyEvent)event); + } + else if(event instanceof MouseEvent) + { + processMouseEvent((MouseEvent)event); + } + } + + /** + * Process keyboard notification received from remote peer. + * + * @param event KeyboardEvent that will be regenerated on local + * computer + */ + public void processKeyboardEvent(KeyEvent event) + { + /* ignore command if remote control is not enabled otherwise regenerates + * event on the computer + */ + if (hidService != null) + { + int keycode = 0; + + /* process immediately a "key-typed" event via press/release */ + if(event.getKeyChar() != 0 && event.getID() == KeyEvent.KEY_TYPED) + { + hidService.keyPress(event.getKeyChar()); + hidService.keyRelease(event.getKeyChar()); + return; + } + + keycode = event.getKeyCode(); + + if(keycode == 0) + { + return; + } + + switch(event.getID()) + { + case KeyEvent.KEY_PRESSED: + hidService.keyPress(keycode); + break; + case KeyEvent.KEY_RELEASED: + hidService.keyRelease(keycode); + break; + default: + break; + } + } + } + + /** + * Process mouse notification received from remote peer. + * + * @param event MouseEvent that will be regenerated on local + * computer + */ + public void processMouseEvent(MouseEvent event) + { + /* ignore command if remote control is not enabled otherwise regenerates + * event on the computer + */ + if (hidService != null) + { + switch(event.getID()) + { + case MouseEvent.MOUSE_PRESSED: + hidService.mousePress(event.getModifiers()); + break; + case MouseEvent.MOUSE_RELEASED: + hidService.mouseRelease(event.getModifiers()); + break; + case MouseEvent.MOUSE_MOVED: + /* x and y position are sent in percentage but we multiply + * by 1000 in depacketizer because we cannot passed the size + * to the Provider + */ + int x = ((event.getX() * size.width) / 1000); + int y = ((event.getY() * size.height) / 1000); + hidService.mouseMove(x, y); + break; + case MouseEvent.MOUSE_WHEEL: + MouseWheelEvent evt = (MouseWheelEvent)event; + hidService.mouseWheel(evt.getWheelRotation()); + break; + default: + break; + } + } + } +} 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 notify action. + */ + NOTIFY("notify"); + + /** + * The name of this direction. + */ + private final String actionName; + + /** + * Creates a InputEvtAction instance with the specified name. + * + * @param actionName the name of the InputEvtAction we'd like + * to create. + */ + private InputEvtAction(String actionName) + { + this.actionName = actionName; + } + + /** + * Returns the name of this InputEvtAction. The name returned by + * this method is meant for use directly in the XMPP XML string. + * + * @return Returns the name of this InputEvtAction. + */ + @Override + public String toString() + { + return actionName; + } + + /** + * Returns a InputEvtAction value corresponding to the specified + * inputActionStr. + * + * @param inputActionStr the action String that we'd like to + * parse. + * @return a InputEvtAction value corresponding to the specified + * inputActionStr. + * @throws IllegalArgumentException in case inputActionStr 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 InputIQ. + */ + private InputEvtAction action = null; + + /** + * List of remote-control elements. + */ + private List remoteControls = + new ArrayList(); + + /** + * 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(""); + } + else + { + bldr.append("/>"); + } + + return bldr.toString(); + } + + /** + * Sets the value of this element's action 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 bad-request error to the sender. There is no + * default value for the 'action' attribute. + * + * @param action the value of the action attribute. + */ + public void setAction(InputEvtAction action) + { + this.action = action; + } + + /** + * Returns the value of this element's action 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 bad-request error to the sender. There is no + * default value for the 'action' attribute. + * + * @return the value of the action 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 RemoteControlExtension list of this IQ. + * + * @return list of RemoteControlExtension + */ + public List 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 + * InputEvtIQ. + * + * @param parser XML parser + * @return InputEvtIQ + * @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) + { + // + 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 RemoteControlExtension. + */ + 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 ComponentEvent that represents our + * InputExtensionItem. + * + * @return AWT ComponentEvent + */ + public ComponentEvent getEvent() + { + return event; + } + + /** + * Get the element name of the PacketExtension. + * + * @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 remote-control. + */ + public static final String ELEMENT_REMOTE_CONTROL = "remote-control"; + + /** + * The name of the remote-info XML element mouse-move. + */ + public static final String ELEMENT_MOUSE_MOVE = "mouse-move"; + + /** + * The name of the remote-info XML element mouse-wheel. + */ + public static final String ELEMENT_MOUSE_WHEEL = "mouse-wheel"; + + /** + * The name of the remote-info XML element mouse-press. + */ + public static final String ELEMENT_MOUSE_PRESS = "mouse-press"; + + /** + *The name of the remote-info XML element mouse-release. + */ + public static final String ELEMENT_MOUSE_RELEASE = "mouse-release"; + + /** + * The name of the remote-info XML element key-press. + */ + public static final String ELEMENT_KEY_PRESS = "key-press"; + + /** + * The name of the remote-info XML element key-release. + */ + public static final String ELEMENT_KEY_RELEASE = "key-release"; + + /** + * The name of the remote-info XML element key-type. + */ + 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 MouseEvent and + * KeyEvent. + */ + private static final Component component = new Canvas(); + + /** + * Constructor. + */ + public RemoteControlExtensionProvider() + { + } + + /** + * Parses the extension and returns a PacketExtension. + * + * @param parser XML parser + * @return a PacketExtension 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 Strings to a specific + * StringBuffer. + * + * @param stringBuffer the StringBuffer to append the specified + * strings to + * @param strings the String values to be appended to the specified + * stringBuffer + */ + 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 + + "\">"); + // + append(xml, "<", RemoteControlExtensionProvider.ELEMENT_KEY_PRESS); + append(xml, " keycode=\"", Integer.toString(keycode), "\"/>"); + append(xml, ""); + 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 + + "\">"); + // + append(xml, "<", RemoteControlExtensionProvider.ELEMENT_KEY_RELEASE); + append(xml, " keycode=\"", Integer.toString(keycode), "\"/>"); + append(xml, ""); + 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 + + "\">"); + // + append(xml, "<", RemoteControlExtensionProvider.ELEMENT_KEY_TYPE); + append(xml, " keychar=\"", Integer.toString(keycode), "\"/>"); + append(xml, ""); + 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 + + "\">"); + // + append(xml, "<", RemoteControlExtensionProvider.ELEMENT_MOUSE_PRESS); + append(xml, " btns=\"", Integer.toString(btns), "\"/>"); + append(xml, ""); + 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 + + "\">"); + // + append(xml, "<", RemoteControlExtensionProvider.ELEMENT_MOUSE_RELEASE); + append(xml, " btns=\"", Integer.toString(btns), "\"/>"); + append(xml, ""); + 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 + + "\">"); + // + append(xml, "<", RemoteControlExtensionProvider.ELEMENT_MOUSE_MOVE); + append(xml, " x=\"", Double.toString(x), "\" y=\"", Double.toString(y), + "\"/>"); + append(xml, ""); + 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 + + "\">"); + // + append(xml, "<", RemoteControlExtensionProvider.ELEMENT_MOUSE_WHEEL); + append(xml, " notch=\"", Integer.toString(notch), "\"/>"); + append(xml, ""); + 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 inputevt element that may be find in + * content 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 inputevt 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.class)); + + //inputevt provider + providerManager.addExtensionProvider( + InputEvtPacketExtension.ELEMENT_NAME, + InputEvtPacketExtension.NAMESPACE, + new DefaultPacketExtensionProvider( + 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 + * OperationSetDesktopSharingServerSipImpl in SUBSCRIBE and NOTIFY + * requests. + */ + public static final String EVENT_PACKAGE = "remote-control"; + + /** + * The time in seconds before the expiration of a Subscription at + * which the OperationSetDesktopSharingServerSipImpl instance + * managing it should refresh it. + */ + public static final int REFRESH_MARGIN = 60; + + /** + * The time in seconds after which a Subscription should be expired + * by the OperationSetDesktopSharingServerSipImpl instance which + * manages it. + */ + public static final int SUBSCRIPTION_DURATION = 3600; + + /** + * The content sub-type of the content supported in NOTIFY requests handled + * by OperationSetDesktopSharingSipImpl. + */ + public static final String CONTENT_SUB_TYPE = "remote-control+xml"; + + /** + * The name of the remote-info XML element remote-control. + */ + private static final String ELEMENT_REMOTE_CONTROL = "remote-control"; + + /** + * The name of the remote-info XML element mouse-move. + */ + private static final String ELEMENT_MOUSE_MOVE = "mouse-move"; + + /** + * The name of the remote-info XML element mouse-wheel. + */ + private static final String ELEMENT_MOUSE_WHEEL = "mouse-wheel"; + + /** + * The name of the remote-info XML element mouse-press. + */ + private static final String ELEMENT_MOUSE_PRESS = "mouse-press"; + + /** + *The name of the remote-info XML element mouse-release. + */ + private static final String ELEMENT_MOUSE_RELEASE = "mouse-release"; + + /** + * The name of the remote-info XML element key-press. + */ + private static final String ELEMENT_KEY_PRESS = "key-press"; + + /** + * The name of the remote-info XML element key-release. + */ + private static final String ELEMENT_KEY_RELEASE = "key-release"; + + /** + * The name of the remote-info XML element key-type. + */ + private static final String ELEMENT_KEY_TYPE = "key-type"; + + /** + * Component to be used in custom generated MouseEvent and + * KeyEvent. + */ + private static final Component component = new Canvas(); + + /** + * Appends a specific array of Strings to a specific + * StringBuffer. + * + * @param stringBuffer the StringBuffer to append the specified + * strings to + * @param strings the String values to be appended to the specified + * stringBuffer + */ + 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( "\r\n"); + + // + append(xml, "<", ELEMENT_REMOTE_CONTROL, ">"); + // + append(xml, "<", ELEMENT_KEY_PRESS); + append(xml, " keycode=\"", Integer.toString(keycode), "\" />"); + append(xml, ""); + + 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( "\r\n"); + + // + append(xml, "<", ELEMENT_REMOTE_CONTROL, ">"); + // + append(xml, "<", ELEMENT_KEY_RELEASE); + append(xml, " keycode=\"", Integer.toString(keycode), "\" />"); + append(xml, ""); + + 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( "\r\n"); + + // + append(xml, "<", ELEMENT_REMOTE_CONTROL, ">"); + // + append(xml, "<", ELEMENT_KEY_TYPE); + append(xml, " keychar=\"", Integer.toString(keycode), "\" />"); + append(xml, ""); + + 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( "\r\n"); + + // + append(xml, "<", ELEMENT_REMOTE_CONTROL, ">"); + // + append(xml, "<", ELEMENT_MOUSE_PRESS); + append(xml, " btns=\"", Integer.toString(btns), "\" />"); + append(xml, ""); + + 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( "\r\n"); + + // + append(xml, "<", ELEMENT_REMOTE_CONTROL, ">"); + // + append(xml, "<", ELEMENT_MOUSE_RELEASE); + append(xml, " btns=\"", Integer.toString(btns), "\" />"); + append(xml, ""); + + 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( "\r\n"); + + // + append(xml, "<", ELEMENT_REMOTE_CONTROL, ">"); + // + append(xml, "<", ELEMENT_MOUSE_MOVE); + append(xml, " x=\"", Double.toString(x), "\" y=\"", Double.toString(y), + "\" />"); + append(xml, ""); + + 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( "\r\n"); + + // + append(xml, "<", ELEMENT_REMOTE_CONTROL, ">"); + // + append(xml, "<", ELEMENT_MOUSE_WHEEL); + append(xml, " notch=\"", Integer.toString(notch), "\" />"); + append(xml, ""); + + return xml.toString(); + } + + /** + * Parses an XML element and returns a list of all MouseEvent + * and KeyEvent 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 java.awt.Event + */ + public static List parse(Element root, Dimension size) + { + List events = new ArrayList(); + 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 Response identifying the Subscription * 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 ClientTransaction through which the * specified Response 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( + "\r\n" + + "")) + { + 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 CallPeerListener which listens to modifications in the + * properties/state of CallPeer. + */ + private final CallPeerListener callPeerListener = new CallPeerAdapter() + { + /** + * Indicates that a change has occurred in the status of the source + * CallPeer. + * + * @param evt the CallPeerChangeEvent instance containing the + * source event as well as its previous and its new status + */ + @Override + public void peerStateChanged(CallPeerChangeEvent evt) + { + CallPeer peer = evt.getSourceCallPeer(); + CallPeerState state = peer.getState(); + + if(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 listeners = + new ArrayList(); + + /** + * The EventPackageNotifier which implements remote-control + * event-package notifier support on behalf of this + * OperationSetDesktopSharingClient instance. + */ + private final EventPackageNotifier notifier; + + /** + * The SIP ProtocolProviderService implementation which created + * this instance and for which telephony conferencing services are being + * provided by this instance. + */ + private final ProtocolProviderServiceSipImpl parentProvider; + + /** + * The Timer which executes delayed tasks scheduled by + * {@link #notifier}. + */ + private final TimerScheduler timer = new TimerScheduler(); + + /** + * List of SIP NOTIFY messages. + */ + private Queue inputEvents = new LinkedList(); + + /** + * Synchronization object for {@link #inputEvents} access. + */ + private final Object inputSync = new Object(); + + /** + * Initializes a new OperationSetDesktopSharingClientSipImpl. + * + * @param parentProvider the SIP ProtocolProviderService + * 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 Subscriptions. + */ + 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 CallPeer that will be notified + * @param event KeyEvent 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" MouseEvent. 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 CallPeer that will be notified + * @param event MouseEvent 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 CallPeer that will be notified + * @param event MouseEvent 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 RemoteControlGrantedEvent to all registered listeners. + */ + public void fireRemoteControlGranted() + { + RemoteControlGrantedEvent event = new RemoteControlGrantedEvent(this); + for(RemoteControlListener l : listeners) + { + l.remoteControlGranted(event); + } + } + + /** + * Fire a RemoteControlGrantedEvent to all registered listeners. + */ + public void fireRemoteControlRevoked() + { + RemoteControlRevokedEvent event = new RemoteControlRevokedEvent(this); + for(RemoteControlListener l : listeners) + { + l.remoteControlRevoked(event); + } + } + + /** + * Add a RemoteControlListener to be notified when remote peer + * accept to give us full control. + * + * @param listener RemoteControlListener to add + */ + public void addRemoteControlListener(RemoteControlListener listener) + { + if(!listeners.contains(listener)) + { + listeners.add(listener); + } + } + + /** + * Remove a RemoteControlListener to be notified when remote peer + * accept/revoke to give us full control. + * + * @param listener RemoteControlListener to remove + */ + public void removeRemoteControlListener(RemoteControlListener listener) + { + if(listeners.contains(listener)) + { + listeners.remove(listener); + } + } + + /** + * Implements EventPackageNotifier.Subscription in order to + * represent a subscription created by a remote CallPeer + * to the remote-control event package of a local Call. + */ + private class RemoteControlNotifierSubscription + extends EventPackageNotifier.Subscription + { + /** + * The CallPeer associated with this notification. + */ + private CallPeerSipImpl callPeer = null; + + /** + * Initializes a new RemoteControlNotifierSubscription instance + * with a specific subscription Address/Request URI and a + * specific id tag of the associated Event headers. + * + * @param fromAddress the subscription Address/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 Subscription 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 bytes representing the content of the + * NOTIFY request to be sent to the target represented by this + * Subscription + * @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("" + + ""); + } + + 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 Call of the CallPeerSipImpl subscribed to + * the EventPackageNotifier and represented by this + * Subscription. + * + * @return the Call of the CallPeerSipImpl subscribed + * to the EventPackageNotifier and represented by this + * Subscription + */ + public CallSipImpl getCall() + { + CallPeerSipImpl callPeer = getCallPeer(); + + return (callPeer == null) ? null : callPeer.getCall(); + } + + /** + * Gets the CallPeerSipImpl subscribed to the + * EventPackageNotifier and represented by this + * Subscription. + * + * @return the CallPeerSipImpl subscribed to the + * EventPackageNotifier and represented by this + * Subscription + */ + 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 CallPeerListener which listens to modifications in the + * properties/state of CallPeer. + */ + private final CallPeerListener callPeerListener = new CallPeerAdapter() + { + /** + * Indicates that a change has occurred in the status of the source + * CallPeer. + * + * @param evt the CallPeerChangeEvent instance containing the + * source event as well as its previous and its new status + */ + @Override + public void peerStateChanged(CallPeerChangeEvent evt) + { + CallPeer peer = evt.getSourceCallPeer(); + CallPeerState state = peer.getState(); + + if (remoteControlEnabled && state != null && + (state.equals(CallPeerState.DISCONNECTED) || + state.equals(CallPeerState.FAILED))) + { + /* 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 EventPackageNotifier which implements remote-control + * event-package subscriber support on behalf of this + * OperationSetDesktopSharingServer instance. + */ + private final EventPackageSubscriber subscriber; + + /** + * The SIP ProtocolProviderService implementation which created + * this instance and for which telephony conferencing services are being + * provided by this instance. + */ + private final ProtocolProviderServiceSipImpl parentProvider; + + /** + * The Timer 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 OperationSetDesktopSharingSipImpl instance + * which builds upon the telephony-related functionality of a specific + * OperationSetBasicTelephonySipImpl. + * + * @param basicTelephony the OperationSetBasicTelephonySipImpl + * 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 callee 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 MethodProcessorListener that a specific + * CallPeer has processed a specific SIP Request and has + * replied to it with a specific SIP Response. + * + * @param sourceCallPeer the CallPeer which has processed the + * specified SIP Request + * @param request the SIP Request which has been processed by + * sourceCallPeer + * @param response the SIP Response sent by sourceCallPeer + * as a reply to the specified SIP request + * @see MethodProcessorListener#requestProcessed(CallPeerSipImpl, Request, + * Response) + */ + public void requestProcessed( + CallPeerSipImpl sourceCallPeer, + Request request, + Response response) + { + } + + /** + * Notifies this MethodProcessorListener that a specific + * CallPeer has processed a specific SIP Response and has + * replied to it with a specific SIP Request. + * + * @param sourceCallPeer the CallPeer which has processed the + * specified SIP Response + * @param response the SIP Response which has been processed by + * sourceCallPeer + * @param request the SIP Request sent by sourceCallPeer + * as a reply to the specified SIP response + * @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 KeyboardEvent that will be regenerated on + * local computer + */ + public void processKeyboardEvent(KeyEvent event) + { + /* ignore command if remote control is not enabled otherwise regenerates + * event on the computer + */ + if (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 MouseEvent that will be regenerated on local + * computer + */ + public void processMouseEvent(MouseEvent event) + { + /* ignore command if remote control is not enabled otherwise regenerates + * event on the computer + */ + if (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 EventPackageSubscriber.Subscription in order to + * represent the subscription of the local peer to the remote-control event + * package of a specific remote CallPeer acting as a desktop + * sharing server. + */ + private class RemoteControlSubscriberSubscription + extends EventPackageSubscriber.Subscription + { + /** + * The CallPeer which is acting as a remote-control focus in + * its Call with the local peer. + */ + private final CallPeerSipImpl callPeer; + + /** + * Initializes a new RemoteControlSubscriberSubscription + * instance which is to represent the subscription of the local peer to + * the remote-control event package of a specific CallPeer + * acting as a desktop sharing server. + * + * @param callPeer + * the CallPeer 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 Dialog which was created by the SUBSCRIBE request + * associated with this Subscription or which was used to send + * that request in. + * + * @return the Dialog which was created by the SUBSCRIBE + * request associated with this Subscription or which + * was used to send that request in; null if the + * success of the SUBSCRIBE request has not been confirmed yet + * or this Subscription was removed from the list of + * the EventPackageSupport 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 Subscription that an active NOTIFY + * Request has been received and it may process the specified + * raw content carried in it. + * + * @param requestEvent + * the RequestEvent carrying the full details of the + * received NOTIFY Request including the raw content + * which may be processed by this Subscription + * @param rawContent + * an array of bytes which represents the raw content carried + * in the body of the received NOTIFY Request and + * extracted from the specified RequestEvent 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 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 Subscription that a Response to a + * previous SUBSCRIBE Request has been received with a status + * code in the failure range and it may process the status code carried + * in it. + * + * @param responseEvent + * the ResponseEvent carrying the full details of + * the received Response including the status code + * which may be processed by this Subscription + * @param statusCode + * the status code carried in the Response and + * extracted from the specified ResponseEvent 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 Subscription that a Response to a + * previous SUBSCRIBE Request has been received with a status + * code in the success range and it may process the status code carried + * in it. + * + * @param responseEvent + * the ResponseEvent carrying the full details of + * the received Response including the status code + * which may be processed by this Subscription + * @param statusCode + * the status code carried in the Response and + * extracted from the specified ResponseEvent 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 Subscription that a terminating NOTIFY + * Request has been received and it may process the reason code + * carried in it. + * + * @param requestEvent + * the RequestEvent carrying the full details of the + * received NOTIFY Request including the reason code + * which may be processed by this Subscription + * @param reasonCode + * the code of the reason for the termination carried in the + * NOTIFY Request and extracted from the specified + * RequestEvent 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 HIDService implementation currently + * registered in the bundle context or null if no such implementation was + * found + * + * @return a currently valid implementation of the HIDService + */ + 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); +} -- cgit v1.1